什么是多态?
多态是面向对象程序设计的一个重要特征,指同一个实体同时具有多种形式,即同一个对象,在不同时刻,代表的对象不一样,指的是对象的多种形态。
以一段简单的代码来说明Java中的多态:
public class Animal {
public void run(){
System.out.println("父类Animal的run方法");
}
}
class Cat extends Animal{
public void run(){
System.out.println("子类Cat的run方法");
}
}
class Test{
public static void main(String[] args) {
Animal a2= new Cat();
a2.run();
}
}
结果:子类Cat的run方法
分析a2.run();
Java程序分为编译阶段和运行阶段。
先分析编译阶段:
对于编译器来说,编译器只知道a2的类型是Animal,
所以编译器在检查语法的时候,会去Animal.class字节码文件中找run()方法,
找到了,绑定run()方法,编译通过,静态绑定成功。(编译阶段属于静态绑定)
运行时阶段:
运行阶段的时候,实际上在堆内存中创建的对象是Cat对象,所以run的时候,真正参与run的是一只猫,所以运行阶段会动态执行Cat对象的run()方法。这个过程属于运行阶段绑定。(运行阶段属于动态绑定。)
从上面可以看出:其实多态就是表示多种形态,编译时候一种形态,运行时时另一种形态。
其实多态包括编译时多态和运行时多态两个阶段:
1,编译时多态:
方法的重载
编译时多态:方法的重载
方法重载就是在同一个类中,出现了多个同名的方法,他们的参数列表(方法签名)不同
根据实际参数的数据类型、个数和次序,Java在编译时能够确定执行重载方法中的哪一个。
方法重写时的编译时多态:
除了重载,重写也表现出两种多态性。当一个对象的引用指向的是当前对象所属类的对象时,为编译时多态,其他则为运行时多态。
意思就是方法名相同通过参数列表的不同(多种状态)来确定使用的方法。
2,运行时多态:
运行时多态: 当父类引用指向子类对象时:
父类只能执行那些在父类中声明、被子类覆盖了的子类方法,而不能执行子类新增加的成员方法。在编译时期,首先会去查看父类里面有没有这个方法,如果没有的话向上继续查找,直到找到Object类如果还没有的话就报错,如果有的话,到运行阶段,再去看一下子类中有没有覆盖该方法,如果覆盖了,则执行子类覆盖的方法。如果没有则执行父类中原本的方法。
1,当子类和父类有相同属性时,父类还是会执行自己所拥有的属性,若父类中没有的属性子类中有,当父类对象指向子类引用时(向上转型),在编译时期就会报错
2,对于static方法还是会执行父类中的方法,这是由于在运行时,虚拟机已经认定static方法属于哪个类。“重写”只能适用于实例方法,不能用于静态方法。对于静态方法,只能隐藏,重载,继承。子类会将父类静态方法的隐藏(hide),但子类的静态方法完全体现不了多态,就像子类属性隐藏父类属性一样,在利用引用访问对象的属性或静态方法时,是引用类型决定了实际上访问的是哪个属性,而非当前引用实际代表的是哪个类。因此,子类静态方法不能覆盖父类的静态方法
总结:多态是指不同子类型的对象对同一行为作出不同的响应。 多态性分为编译时的多态性和运行时的多态性。方法重载实现的是编译时的多态性,而方法重写实现的是运行时的多态性。 对于运行时多态,特别注意,父类引用指向子类对象,在调用实例方法时,调用的是子类重写之后的,并且不能调用子类新增的方法,对于属性和static方法来说,还是执行父类原有的。
为什么要使用多态?
11,多态的好处/缺点
1.不使用多态时:
好处:可以调用子类的特有行为
弊端:扩展性极差
2.使用多态时:(可替换,可扩充,接口性,灵活,简化性)
好处:扩展性强,让代码更灵活,提高代码的复用性
弊端:不能调用子类对象的特有行为(方法)
在使用多态时,要调用子类对象特有方法是,我们可以向下转型,这时,为了避免强转出现问题,我们应该使用instanceof关键字来判断关键字左边的变量是否为右边的类型,返回boolean类型的结果即可。
通过以下可以看出代码的灵活性,改变的代码更少(不需要再重新写方法的调用,只需要改变右边的对象)
public class Animal {
public void run(){
System.out.println("父类Animal在跑");
}
}
class Cat extends Animal{
public void run(){
System.out.println("子类Cat在跑");
}
}
class Dog extends Animal{
public void run(){
System.out.println("子类Dog在跑");
}
}
class Test{
public static void main(String[] args) {
// Animal animal = new Cat();
Animal animal= new Dog();//只需要改变右边对象
animal.run();//不需要改变了
}
}
实现多态的三个必要条件:
1,继承
2,重写(没有重写的话无法完成运行时多态,执行的依旧是父类的方法)
3,父类引用指向子类对象
向上转型,向下转型
向上转型:
当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。
使用格式:
父类类型 变量名 = new 子类类型();
向下转型:
一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。如果是直接创建父类对象,是无法向下转型的。
使用格式:
子类类型 变量名 = (子类类型) 父类类型的变量;
向下转型的代码示例:
public class Animal {
public void run(){
System.out.println("父类Animal的run方法");
}
public void move(){
System.out.println("父类Animal的move方法");
}
}
class Cat extends Animal{
public void run(){
System.out.println("子类Cat的run方法");
}
}
class Test{
public static void main(String[] args) {
Animal animal = new Cat();
Cat cat=(Cat)animal;//向下转型
cat.move();//子类调用父类的方法
cat.run();
animal.run();
}
}
结果:父类Animal的move方法
子类Cat的run方法
子类Cat的run方法
instanceOf
它是 Java 的保留关键字。它的作用是在运行阶段判断它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。
严格来说,instanceof 是 Java 的一个二元操作符(双目运算符),类似于 ==,>,< 等操作符。用来测试一个对象是否是为一个类的实例,用法为:
boolean result = obj instanceof class
其中 obj 为一个对象,Class 表示一个类或者一个接口。
当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。 注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
代码示例:
注意:只有两个类有继承关系才能使用instanceof要不然会编译错误。
Java规范当中规定,对类型进行向下转型的时候,一定要使用instanceof运算符来进行判断。(避免ClassCastException,类型转换异常)
public class Animal {
}
class Cat extends Animal{
}
class Dog extends Animal{
}
class Test{
public static void main(String[] args) {
Animal animal = new Cat();
Animal animal01= new Dog();
if(animal01 instanceof Cat){
System.out.println("animal01是一只Cat");
}else {
System.out.println("animal01不是一只Cat");
}
}
}
结果:animal01不是一只Cat