一、向上转型
含义:父类的一个引用,指向子类。
class Person {
public String name;
public Person(String name) {
this.name = name;
}
public static void age(int age) {
System.out.println(age);
}
}
class Dad extends Person {
public Dad(String name) {
super(name);
}
public static void main(String[] args) {
Dad dad = new Dad("父亲");
Dad.age(36);
}
}
在上述继承中,main 函数中可以有其他的实例引用方法。
//第一种写法
Dad dad = new Dad("父亲");
Person dad1 = dad;
//第二种写法
Person dad1 = new Dad("父亲");
这两种写法都属于向上转型,属于直接赋值。
还有方法传参、方法返回时也可以使用,使用方法相同。
方法传参:
Dad.age(36);//上述代码中
// =>
Person.age(36);
方法返回:
public static void main(String[] args) {
Person person = findPerson();
}
public static Person findPerson() {
Dad dad = new Dad("父亲");
return dad;
}
此时方法 findPerson 返回的是一个 Person 类型的引用, 但是实际上对应到 Dad 的实例。
二、向下转型
向上转型是子类对象转父类对象,向下转型则是父类对象转子类对象。
向上转型的缺点:
class Person {
protected String name;
public Person(String name) {
this.name = name;
}
protected void age(int age) {
System.out.println(this.name + "的年龄是" + age);
System.out.println(age);
}
public static void main(String[] args) {
Person person = new Son("儿子");
person.age(18);
//person.subject("数学");
}
}
class Son extends Person {
public Son(String name) {
super(name);
}
protected void age(int age) {
System.out.println(this.name + "的年龄是" + age);
System.out.println(age);
}
public void subject(String subject) {
System.out.println(subject);
}
}
结果正常:
但如果调用 person.subject("数学");
后,编译出错:
可见:
1.编译器检查有哪些方法存在, 看的是 Person 这个类型
2.执行时究竟执行父类的方法还是子类的方法, 看的是 Son 这个类型.
要弥补上述代码的不足,就需要向下转型。
public static void main(String[] args) {
Person person = new Son("儿子");
Son son = (Son) person;
son.age(18);
son.subject("数学");
}
此时,代码可正常运行:
但也容易出错,例如:
public static void main(String[] args) {
Person person = new Dad("儿子");
Son son = (Son) person;
son.age(18);
son.subject("数学");
}
会出现异常:
为避免这种问题,需要先判断能否转换。
可以写成:
public static void main(String[] args) {
Person person = new Son("儿子");
if(person instanceof Son) {
Son son = (Son) person;
son.age(18);
son.subject("数学");
}
}
三、动态绑定
对向上转型的代码稍做改动:
class Person {
protected String name;
public Person(String name) {
this.name = name;
}
public void age(int age) {
System.out.println(this.name + "的年龄是" + age);
System.out.println(age);
}
}
class Dad extends Person {
public Dad(String name) {
super(name);
}
public void age(int age) {
System.out.println(this.name + "的年龄是" + age);
System.out.println(age);
}
public static void main(String[] args) {
Person person1 = new Person("父亲");
person1.age(36);
Person person2 = new Dad("爹");
person2.age(42);
}
}
输出结果:
由此例可总结:
1.person1 指向 Person 类型的实例, person2 指向 Dad 类型的实例。
2. person1.age() 实际调用了父类的方法, 而 personl2.age() 实际调用了子类的方法。
可见, 在 Java 中调用某个类的方法, 究竟执行父类方法的代码还是子类方法的代码, 要看这个引用指向的是父类对象还是子类对象。这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定。
四、方法重写
上述代码,子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).
注意事项:
1.与重载区分开
2.普通方法可以重写, static 修饰的静态方法不能重写
3.重写中子类的方法的访问权限不能低于父类的方法访问权限(public > protected > private).
4.重写的方法返回值类型不一定和父类的方法相同(建议写成相同, 特殊情况除外).
5.建议在代码中进行重写方法时显式加上 @Override 注解(能进行合法性校验).(alt + insert)
区别重载和重写:
五、多态
将向上转型、向下转型、动态绑定、方法重写综合运用到一个代码中,就是多态了。
简单来说,就是“一个引用,就能表现出多种不同形态”。
优点:
1.多态可以理解成是封装的更进一步,使类调用者对类的使用成本进一步降低:
a.封装是让类的调用者不需要知道类的实现细节.
b.多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
2.能够降低代码的 “圈复杂度”, 避免使用大量的 if - else.
3. 可扩展能力更强:
只需要创造一个新的实例就可以,不需要用 if - else 进行修改.