您的位置:首页 > 编程语言 > Java开发

java三大特性之多态性(全面解读)和面试分析

2020-08-09 17:18 183 查看

多态性 Polymorphism

  • 1.2 变量的角度看多态性
  • 1.3 对象的多态性
  • 1.4 多态的使用
  • 1.5 对象类型转换 (Casting)
  • 二、面试经典例题
  • 代码展示
  • 一、多态性的理解

    1.1 多态性的体现和理解

    1.1.1 多态的体现

    Java中的体现:对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
    可以直接应用在抽象类和接口上。

    1.1.2 多态的作用

    作用: 提高了代码的通用性,常称作接口重用

    1.1.3 多态实现的前提

    1. 需要存在类的继承或者实现关系;
    2. 有方法的重写。

    1.1.4 区分实现方法与成员方法

    实现方法:一个父类有多个子类,在构建方法时以父类的引用作为形参,在调用方法时,传入不同子类对象实参。
    成员方法:编译时:要查看引用变量所声明的类中是否有所调用的方法。运行时:调用实际new的对象所属的类中的重写方法。

    1.2 变量的角度看多态性

    1.2.1 成员变量与引用变量

    成员变量: 不具备多态性,只看引用变量所声明的类。
    引用变量
    Java引用变量有2个类型: 编译时类型 和 运行时类型。

    1. 编译时类型由声明该变量时使用的类型决定;
    2. 运行时类型由实际赋给该变量的对象决定。
      简称:编译时,看左边;运行时,看右边。
    ### 1.2.2 注意
    1. 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
    2. 若编译时类型和运行时类型不一致,就出现了对象的多态性 (Polymorphism)
    3. 多态情况下 ,“ 看左边 ” :看的是父类的引用(父类中不具备子类特有的方法) “ 看右边 ” :看的是子类的对象(实际运行的是子类重写父类的方法)

    1.3 对象的多态性

    1.3.1 对象的多态性体现

    体现:在Java中,子类的对象可以替代父类的对象使用
    前提

    1. 一个变量只能有一种确定的数据类型
    2. 一个引用类型变量可能指向(引用)多种不同类型的对象
      子类可看做是特殊的父类 ,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
      例:Person p = new Student();
      Object o = new Person(); //Object类型的变量o,指向Person类型的对象
      o = new Student(); //Object类型的变量o,指向Student类型的对象
    3. 一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。
    4. 方法声明的形参类型为 父类 类型,可以使用子类的对象 作为实参调用该方法。

    1.3.2 子类继承父类

    1. 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
    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 基本数据类型的转换

    1. 自动类型转换:小的数据类型可以自动转换成大的数据类型
    2. 强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型
      如 float f=(float)12.0; int a=(int)1200L

    1.5.2 多态情况下类型转换

    对Java对象的强制类型转换称为 造型(向下转型)

    1. 向上转型:(从子类到父类的类型转换)自动进行,通过多态
    2. 向下转型:(从父类到子类的类型转换)必须通过造型(强制类型转换)实现,使用之前需要用 instanceof 进行条件判断。
    3. 无继承关系的引用类型间的转换是非法的
      注意:使用向下转型,容易出现异常:ClassCastException; 因此需要用 instanceof 进行关系确认。

    1.5.3 instanceof 操作符

    格式:x instanceof A 检验 x 是否为类 A 的对象,返回值为boolean型。
    x是变量名,A是类型。
    注意

    1. 要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
    2. x instanceof A 返回值为true;类B为类A的父类,则 x instanceof B 返回值也是true;
    3. instanceof 运算符判定父类引用指向子类实例对象的具体类型。

    二、面试经典例题

    2.1 方法的重载与重写区分

    1. 从定义上区分:
    2. 从编译和运行的角度看:
      重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。
      对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。
      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;
    
    }
    }
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: