面向对象的三大特征是:封装,继承和多态
java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围) :
访问级别 | 访问控制修饰符 | 同类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | × |
默认 | 无需修饰符 | √ | √ | × | × |
私有 | private | √ | × | × | × |
封装是把抽象出的数据 (属性) 和对数据操纵的 方法 封装再一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(方法)才能对数据进行操作
封装实现的步骤:
当我们使用构造器进行初始化时,属性的赋值有一定要求(要求在set方法中体现),为了减少冗余的的代码,可以使用set方法在构造器内部初始化对象
当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。 继承可以解决代码复用的问题。
class 子类 extends 父类{
}
使用super可以访问父类的属性,方法和构造器
super.属性名,super.方法名(参数列表),super不能访问父类的私有属性和私有方法,当子类和父类的属性和方法重名时,如果想要访问父类的成员,必须使用super关键字,没有重名时,使用super,this和直接访问的作用是一样的。
使用super调用父类的构造器能给编程带来便利:父类的属性调用父类构造器进行初始化,而子类的属性交给子类构造器初始化。
区别点 | this | super |
---|---|---|
访问属性和方法 | 从本类开始向上查找 | 从父类开始向上查找 |
调用构造器 | 调用本类的构造器 | 调用父类的构造器,子类都会隐式的调用super |
意义 | 表示当前对象 | 表示父类对象 |
当我们要实现子类的一个方法,它与父类的某个方法有类似的作用,我们让这个方法与父类的方法具有相同的函数名,返回类型和参数列表,于是子类的这个方法覆盖了父类的方法,或者说我们在子类中重写了父类的这个复方
注意:
多态是指方法和对象具有多种状态,多态建立在继承和封装的基础上
方法重载和方法覆盖体现了方法的多态,对于方法重载,同一个方法名,我们传入不同的参数,它调用不同的方法。对于方法重写,同一个方法名,当对象是父类对象时调用父类方法,子类会调用子类的方法
对象的多态体现一个父类对象变量既可以引用父类对象也可以引用子类对象,所以多态的前提是存在继承关系,深入理解对象的多态要先理解以下几句话。
Animal animal = new Cat();//编译类型为animal,运行类型Cat animal = new Dog()//运行类型发生变化,为Dog
当父类的引用指向了子类的对象就发生了向上转型,使用向上转型可以减少冗余的代码。当多个类具有相同的方法,可以在父类中写一个方法,参数设置为父类对象,当调用这个方法时可以使用向上转型,那么它在运行时实际还是把这个对象看出子类对象。
语法:父类类型 对象名 = new 子类类型()
public class Test { public static void main(String[] args) { Car car1 = new Benz() show(car1); Car car2 = new BMW() show(car2); } } class Car { public void run() { System.out.println("这是父类run()方法"); } public void speed() { System.out.println("speed:0"); } public void show(Car car) {//父类实例作为参数 car.run(); car.speed(); } } class BMW extends Car { public void run() { System.out.println("这是BMW的run()方法"); } public void speed() { System.out.println("speed:80"); } } class Benz extends Car { public void run() { System.out.println("这是Benz的run()方法"); } public void speed() { System.out.println("speed:100"); } public void price() { System.out.println("prict:18w"); } }
如上面的示例,如果没有使用向上转型,那么如果想要输出每个子类的信息就需要在每个子类中写对应的show()方法。但同时向上转型存在一个问题:不能调用子类的特有方法,这是在编译过程中,这个对象被看作父类对象,而父类对象没有这个方法,当调用父类和子类共同的方法时,看对象的运行类型。
如前面向上转型提到的问题:不能调用子类特有方法,于是出现了向下转型,向下转型是强制类型转换,子类对象引用了父类类型。进行向下转型只改变了这个对象的引用而没有改变对象,父类的引用必须指向当前目标类型的对象,进行向下转型后就可以使用子类的全部成员
语法:子类类型 引用名 = (子类类型) 父类引用
//接上面的java类 public class Test { public static void main(String[] args) { Car car1 = new Benz(); show(car1); Benz benz = (Benz) car1; benz.price()//向下转型调用子类特有方法 // BMW benz = (BMW) car1; 这一句会报错,因为当前这个对象指向的Benz类型的对象 } }
Java的动态绑定机制可以由以下两句话总结
看一个例子
public class DynamicBinding { public static void main(String[] args) { //a 的编译类型 A, 运行类型 B A a = new B();//向上转型 System.out.println(a.sum());//输出30 System.out.println(a.sum1());//输出20 } } class A {//父类 public int i = 10; //动态绑定机制:绑定B类的getI()方法 public int sum() { return getI() + 10;//20 + 10 } public int sum1() { return i + 10;//使用A类的 i = 10,10 + 10 } public int getI() { return i; } } class B extends A {//子类 public int i = 20; public int getI() {//子类 getI() return i;//使用B类的 i = 20 } }
多态的应用由多态数组和多态参数,它们能够很好的解决代码复用的问题
语法:对象变量 instanceof 对象类型
instanceof 关键字可以用来判断某个对象的运行类型是否是目标类型,使用instanceof关键字和向下转型还可以方便的调用子类的特有方法。
数组定义为父类,但是里面的元素可以是父类对象也可以是子类对象,这样可以方便的遍历数组,对不同的对象做相似的处理。
类定义:
public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String say(){ return name + "\t" + age; } public String getName() { return name; } } class Student extends Person{ private int score; public Student(String name, int age, int score) { super(name, age); this.score = score; } @Override public String say(){ return super.say() + "成绩是:" + score; } //特有方法 public void examine(){ System.out.println(super.getName() + "正在考试ing~~~"); } } class Teacher extends Person{ private String major; public Teacher(String name, int age, String major) { super(name, age); this.major = major; } @Override public String say() { return super.say() + "教授学科是:" + major; } //特有方法 public void teach() { System.out.println(super.getName() + "正在上课ing~~~"); }
测试类:
public class PersonTest { public static void main(String[] args) { Person[] person = new Person[3]; person[0] = new Person("jack",23); person[1] = new Student("Tom",23,80); person[2] = new Teacher("Milan",23,"Math"); //遍历数组调用say方法,并调用特有方法 for (int i = 0; i < person.length; i++) { System.out.println(person[i].say()); if(person[i] instanceof Student){//判断运行类型 ((Student) person[i]).examine();//向下转型 }else if(person[i] instanceof Teacher){ ((Teacher) person[i]).teach(); } } } }
方法定义的形参类型是父类类型,实参可以是子类类型,在前面向上转型中其实可以直接把子类实参(BMW对象,Benz对象)传给形参(Car对象)。多态参数的使用非常广泛(在jdk的源码中),它能够大大的提高代码的复用性
对于基本数据类型,我们可以用 == 运算符来判断值是否相等,但是对于引用类型即对象,== 运算符只能用来判断对象变量是否指向了同一个对象,而如果要判断对象本身即属性的值是否相等就需要用到 equals() 方法。如果类中没有重写 equals() 方法,调用的就是Object类中的方法,它只能判断是否指向同一个引用
@Override public boolean equals(Object o) { if (this == o)//指向同一个对象则一定相等 return true; if (o == null || getClass() != o.getClass())//对象为空或者不是同一个类,则一定不相等 return false; Employee employee = (Employee) o;//向下转型 return salary == employee.salary && age == employee.age && name.equals(employee.name); }
返回该对象的哈希码值,由object类定义的hashcode()能根据不同的对象返回不同的整数值,这是通过对象的内部地址经过转换成一个整数实现的重写该方法能够提高哈希表的性能。在学习集合部分后再对该方法作补充。
默认返回:全类名+@+哈希值的十六进制。
子类往往重写 toString 方法,用于返回对象的属性信息
public String toString() { return "Employee[name=" + name +", salary=" + salary +", age=" + age +"]" }
学习总结来源于韩顺平老师循序渐进学Java