java三大特性之多态性和面试题分析
多态性 Polymorphism
一、多态性的理解
1.1 多态性的体现和理解
1.1.1 多态的体现
Java中的体现:对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
可以直接应用在抽象类和接口上。
1.1.2 多态的作用
作用: 提高了代码的通用性,常称作接口重用
1.1.3 多态实现的前提
- 需要存在类的继承或者实现关系;
- 有方法的重写。
1.1.4 区分实现方法与成员方法
实现方法:一个父类有多个子类,在构建方法时以父类的引用作为形参,在调用方法时,传入不同子类对象实参。
成员方法:编译时:要查看引用变量所声明的类中是否有所调用的方法。运行时:调用实际new的对象所属的类中的重写方法。
1.2 变量的角度看多态性
1.2.1 成员变量与引用变量
成员变量: 不具备多态性,只看引用变量所声明的类。
引用变量:
Java引用变量有2个类型: 编译时类型 和 运行时类型。
- 编译时类型由声明该变量时使用的类型决定;
-
运行时类型由实际赋给该变量的对象决定。
简称:编译时,看左边;运行时,看右边。
### 1.2.2 注意
- 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
- 若编译时类型和运行时类型不一致,就出现了对象的多态性 (Polymorphism)
- 多态情况下 ,“ 看左边 ” :看的是父类的引用(父类中不具备子类特有的方法) “ 看右边 ” :看的是子类的对象(实际运行的是子类重写父类的方法)
1.3 对象的多态性
1.3.1 对象的多态性体现
体现:在Java中,子类的对象可以替代父类的对象使用
前提:
- 一个变量只能有一种确定的数据类型
-
一个引用类型变量可能指向(引用)多种不同类型的对象
子类可看做是特殊的父类 ,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
例:Person p = new Student();
Object o = new Person(); //Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象 - 一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。
- 方法声明的形参类型为 父类 类型,可以使用子类的对象 作为实参调用该方法。
1.3.2 子类继承父类
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
1.4 多态的使用
虚拟方法调用(Virtual Method Invocation)
1.4.1 虚拟方法定义
定义:子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法;
1.4.2 虚拟方法的调用
调用:
当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法
父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
例: Person e = new Student();
e.getInfo(); // 调用Student 类的getInfo()
注意:(动态绑定)区分编译时类型和运行时类型;编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。
总结:
多态情况下 ,
“ 看左边 ” :看的是父类的引用(父类中不具备子类特有的方法)
“ 看右边 ” :看的是子类的对象(实际运行的是子类重写父类的方法)
1.5 对象类型转换 (Casting)
1.5.1 基本数据类型的转换
- 自动类型转换:小的数据类型可以自动转换成大的数据类型
-
强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型
如 float f=(float)12.0; int a=(int)1200L
1.5.2 多态情况下类型转换
对Java对象的强制类型转换称为 造型(向下转型)
- 向上转型:(从子类到父类的类型转换)自动进行,通过多态
- 向下转型:(从父类到子类的类型转换)必须通过造型(强制类型转换)实现,使用之前需要用 instanceof 进行条件判断。
-
无继承关系的引用类型间的转换是非法的
注意:使用向下转型,容易出现异常:ClassCastException; 因此需要用 instanceof 进行关系确认。
1.5.3 instanceof 操作符
格式:x instanceof A 检验 x 是否为类 A 的对象,返回值为boolean型。
x是变量名,A是类型。
注意:
- 要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
- x instanceof A 返回值为true;类B为类A的父类,则 x instanceof B 返回值也是true;
- instanceof 运算符判定父类引用指向子类实例对象的具体类型。
二、面试经典例题
2.1 方法的重载与重写区分
- 从定义上区分:
-
从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。
对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。
Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
重写,(多态)只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”
代码展示
// 虚方法调用测试 class PersonPy { String name; int age; int id = 1001; public void eatpy(){ System.out.println("人:吃饭"); } public void walkpy(){ System.out.println("人:走路"); } } class Man extends PersonPy{ boolean isSmoking; int id = 1002; public void earnMoney(){ System.out.println("男人负责挣钱养家"); } public void eat(){ System.out.println("男人多吃肉,长肌肉"); } public void walk(){ System.out.println("男人霸气的走路"); } } class Woman extends PersonPy{ boolean isBeauty; public void goShopping(){ System.out.println("女人喜欢购物"); } public void eat(){ System.out.println("女人少吃,为了减肥"); } public void walk(){ System.out.println("女人窈窕的走路"); } } class PersonPyTest { public static void main(String[] args) { PersonPy p1 = new PersonPy(); p1.eatpy(); Man man = new Man(); man.eat(); man.age = 25; man.earnMoney(); System.out.println("*******************"); //对象的多态性:父类的引用指向子类的对象 PersonPy p2 = new Man(); //Person p3 = new Woman(); //多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 ---虚拟方法调用 p2.eatpy(); p2.walkpy(); // p2.earnMoney(); System.out.println(p2.id); //1001 System.out.println("****************************"); //不能调用子类所特有的方法、属性:编译时,p2是Person类型。 p2.name = "Tom"; // p2.earnMoney(); // p2.isSmoking = true; //有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致 //编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。 //如何才能调用子类特有的属性和方法? //向下转型:使用强制类型转换符。 Man m1 = (Man)p2; m1.earnMoney(); m1.isSmoking = true; //使用强转时,可能出现ClassCastException的异常。 // Woman w1 = (Woman)p2; // w1.goShopping(); if(p2 instanceof Woman){ Woman w1 = (Woman)p2; w1.goShopping(); System.out.println("******Woman******"); } if(p2 instanceof Man){ Man m2 = (Man)p2; m2.earnMoney(); System.out.println("******Man******"); } if(p2 instanceof PersonPy){ System.out.println("******Person******"); } if(p2 instanceof Object){ System.out.println("******Object******"); } //练习: //问题一:编译时通过,运行时不通过 //举例一: // Person p3 = new Woman(); // Man m3 = (Man)p3; //举例二: // Person p4 = new Person(); // Man m4 = (Man)p4; //问题二:编译通过,运行时也通过 // Object obj = new Woman(); // Person p = (Person)obj; //问题三:编译不通过 // Man m5 = new Woman(); // String str = new Date(); // Object o = new Date(); // String str1 = (String)o; } }
本文地址:https://blog.csdn.net/Hugh_Guan/article/details/107683866