多态性使得Java语言有很强的灵活性
很多设计模式都是基于多态的特点发展出来的
多态
父类(抽象的)引用指向子类(具体的)对象
Animal a = new Person();
编译时,编译器检查左边(Animal a)
a.getAge(); //编译通过,Animal中有getAge()
a.speakLanguage(); //编译失败,Animal中没有子类的特有方法speakLanguage()
多态编译是否通过,就看父类中是否存在被调用的方法
a.getAge(); //运行时,如果子类有覆盖,则调用子类的getAge(),否则调用父类的getAge()
a.speakLanguage(); //无法运行,编译没通过;只能通过对象向下转型才能调用特有方法
多态的好处
1.可以提高代码的扩展性,父类引用可以接收多种子类对象
向上转型--->限定功能使用,提高扩展性,实现统一操作
Animal a = new Person();
getAnimalAge(a); //凡是Animal对象都可以使用getAnimalAge()方法,实现统一操作
多态的弊端
1.由于子类对象类型的向上提升,导致父类引用只能调用父类中定义好的方法/规则,会丢失对子类特有方法的调用;
调用子类对象特有方法的解决方案:
向下转型--->使用对象的特有方法
Person person = (Person)Amimal;
person.speakLanguage();
风险:类型转换异常
通过类型判断: A instanceof AType ?
多态使用的前提
1.必须有继承或者实现关系--->父类通过引用来接收子类对象
2.要想在运行时父类引用调用到子类的方法,这些方法必须是覆盖父类的方法,子类特有方法在多态下无法调用!
多态中的成员变量
编译和运行都参考左边的父类;
成员变量没有覆盖的功能,子类与父类可以存在相同类型相同名称的变量;
记住:多态时,类型发生了提升,所有调用都要看父类中的定义,
对于成员变量而言,是不存在覆盖的,所以始终都是使用的父类的成员变量,但是子类可以 改变这个变量值!
对于方法而言,如果子类有覆盖,则调用子类的,否则调用父类的;
示例1
public class Animal {
int num;
public Animal() {
num = 1;
}
private void say() {
System.out.println("Animal.say()");
}
public void hello() {
say();
System.out.println("Animal.hello():"+num);
}
}
public class Person extends Animal{
//子类可以定义与父类完全相同的成员变量,这样,子类与父类各自持有自己的一份成员变量
//注意:成员变量没有覆盖的功能
//int num;
public Person() {
//如果子类自己也定义了与父类相同的成员变量,则这里就是对子类的成员变量进行赋值
//否则,就是对继承自父类的成员变量进行赋值;
num = 3;
}
}
public class Test {
public static void main(String[] args) {
Animal p = new Person();
p.hello();
}
}
运行结果说明:
Animal.say()
Animal.hello():3
子类没有覆盖父类中的方法,将调用父类的hello(),所以父类中的私有方法也被调用到,但是,子类改变了父类的成员变量,所以num从1变为了3。
示例2
子类定义了与父类完全相同的成员变量
public class Animal {
int num;
public Animal() {
num = 1;
}
private void say() {
System.out.println("Animal.say()");
}
public void hello() {
say();
System.out.println("Animal.hello():"+num);
}
}
public class Person extends Animal{
//子类定义了一个与父类相同的成员变量num
int num;
public Person() {
//子类中有成员变量num,则这里就是对子类自己的成员变量num进行赋值
num = 3;
}
}
public class Test {
public static void main(String[] args) {
Animal p = new Person();
p.hello();
}
}
运行结果:
Animal.say()
Animal.hello():1
子类没有覆盖父类的hello(),所以,调用父类的hello();
由于子类定义了一个与父类完全相同的成员变量num,并在构造函数中初始化,但是这是对子类自己的num成员变量进行赋值,并不会修改父类的成员变量num,所以,执行结果中的num=1;
示例3
子类定义了与父类完全相同的成员变量,并覆盖父类的方法
public class Animal {
int num;
public Animal() {
num = 1;
}
private void say() {
System.out.println("Animal.say()");
}
public void hello() {
say();
System.out.println("Animal.hello():"+num);
}
}
public class Person extends Animal{
//子类定义了一个与父类相同的成员变量num
int num;
public Person() {
//子类中有成员变量num,则这里就是对子类自己的成员变量num进行赋值
num = 3;
}
public void hello() {
System.out.println("Person.hello():"+num);
}
}
public class Test {
public static void main(String[] args) {
Animal p = new Person();
p.hello();
}
}
运行结果:
Person.hello():3
子类定义了与父类完全相同的成员变量,则子类构造函数中赋值操作是针对子类自己的成员变量num进行的;
子类对父类方法进行了覆盖,调用时,将执行子类的方法;
由于执行的是子类的方法,方法中使用的变量就是子类中定义的变量
所以,num=3。
多态中的成员函数---成员函数绑定到对象上执行
非静态方法的执行都是绑定到对象上进行的
如果子类有覆盖父类的方法,则子类对象直接调用自己的这个覆盖方法;
如果子类没有覆盖父类的方法,则子类会通过super调用父类的方法;
Animal a = new Person();
a.getType();//子类没有覆盖,就调用父类的;有覆盖,就调用子类重写的;