Java笔记,多态的语法机制(一)

面向对象编程有三大特性:封装、继承、多态。

  封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据。对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法。

  继承是为了重用父类代码。两个类若存在IS-A的关系就可以使用继承。,同时继承也为实现多态做了铺垫。那么什么是多态呢?多态的实现机制又是什么?请看我一 一为你揭开:

  多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。

关于java语言中的多态语法机制:【只是多态的基础语法,多态的作用在后续的例子中会讲解】
  • 1.面向对象三大特征:封装、继承、多态 (编译期形态,运行期形态)
  • 2.关于多态中涉及到的几个概念:
  • 向上转型(upcasting)
     子类型 ——> 父类型
     又被称为:自动类型转换
  • 向下转型(downcasting)
     父类型 ——> 子类型
     又被称为强制类型转换。【需要加强制类型转换符】
  • 需要记忆:
     无论是向上转型还是向下转型,两种类型之间必须要有继承关系。
     没有继承关系,程序是无法编译通过的。
  • 3.Animal、Cat、Bird三个类之间的关系:
    Cat继承Animal
    Bird继承Animal
    Cat和 Bird 之间没有任何继承关系

“猫”类(Cat.class)

public class Cat extends Animal{
    
    

    重写父类中继承过来的父类
    public void move() {
    
    
        System.out.println("猫在走猫步");
    }

    不是从父类继承过来的方法
    这个方法是子类对象特有的方法【不是所有的动物都能抓老鼠!】
    public void catchMouse(){
    
    
        System.out.println("猫抓老鼠!");
    }
}
“鸟”类(Bird.class)
public class Bird extends Animal{
    
    

    重写父类中继承过来的父类
    public void move() {
    
    
        System.out.println("鸟儿在飞翔");
    }

    public void fly(){
    
    
        System.out.println("Bird fly !");
    }
}
“测试”类(Test.class)**,所有的解析都在代码里面,请仔细阅读 ~o( ̄▽ ̄)o
public class Test {
    
    

    public static void main(String[] args) {
    
    

        以前编写方式(没有使用多态)
        Animal a1 = new Animal();
        a1.move();

        Cat c1 = new Cat();
        c1.move();
        c1.catchMouse();

        Bird b1 = new Bird();
        b1.move();


        下面是使用多态语法机制
         * 1. Animal和Cat之间存在继承关系,Animal是父类,Cat是子类
         * 2. Cat is a Animal
         * 3. "new Cat()" 创建的对象的类型是Cat,a2这个引用的数据类型是Animal,可见它们进行了类型转换
         *    子类型转换成父类型,称为向上转型/upcasting,或者称为自动类型转换。
         * 4. Java中允许这种语法:父类型引用指向子类型对象。
         * 5. 无论是Cat类有没有重写move方法,运行阶段一定调用的是Cat对象的move方法,因为底层真实对象就是Cat对象。
         * 6. 父类型引用指向子类型对象这种机制导致程序存在编译阶段绑定和运行阶段绑定两种不同的形态/状态。
         *    这种机制可以成为一种多态语法机制。
         
        Animal a2 = new Cat(); // 父类型引用指向子类型对象

        
         * 1. java程序永远都分为编译阶段和运行阶段。
         * 2. 先分析编译阶段,再分析运行阶段,编译无法通过,根本是无法运行的。
         * 3. 编译阶段编译器检查 a2这个引用的数据类型为Animal,由于Animal.class
         *    字节码当中有move()方法,所以编译器通过了。这个过程我们称为 "静态绑定/编译阶段绑定"
         * 4. 在程序运行阶段,JVM堆内存当中真实创建的对象是Cat对象,那么一下程序在运行阶段
         *    一定会调用Cat对象的move()方法,此时发送了程序的多态绑定,运行阶段绑定。
         * 5. 如果Cat没有重写父类方法,但是还是会调用Cat中继承过来的方法,虽然看起来父类的,但是是Cat的。
         
        a2.move();  // 运行结果:猫在走猫步

        
         * 分析以下程序为什么不能调用?
         *      因为编译阶段编译器检查到a2的类型是Animal类型。
         *      从Animal.class字节码文件当中查找catchMouse()
         *      方法,最终没有找到该方法。导致静态绑定是吧,
         *      没有绑定成功,也就是说编译失败了。别谈运行了。
         
        // a2.catchMouse();  // ✖

        
         * 需求:
         *      假设想让以上的对象指向catchMouse()方法,怎么办?
         *      a2是无法直接掉哎呀的,因为a2的类型Animal,Animal中没有catchMouse()方法。
         *      我们可以将a2强制类型转换为Cat类型。
         *      a2的类型是Animal(父类),转换成Cat类型(子类),被称为向下转型/downcasting/强制类型转换。
         *
         *  注意:向下转型也需要两个类型之间必须有继承关系。不然编译器报错。强制类型转换需要加强制类型转换符。
         *
         *  什么时候需要使用向下转型呢?
         *      当调用的方法是子类型中特有的,在父类型当中不存在,必须进行向下转型。
         *
        
        向下转型  父类型引用转成子类型引用 Anima a2 =》Cat a2
        Cat c2 = (Cat)a2;
        c2.catchMouse();

        /* 之前学的类型转换
        long x =100L;
        int i = (int)x;
         */

        进阶版
        Animal a3 = new Bird();

        
         * 1. 以下程序编译时没有问题的,因为编译器检查到a3的数据类型是Animal
         *    Animal和 Cat之间存在继承关系,并且Animal是父类型,Cat是子类型。
         * 2. 程序虽然编译通过了,但是程序在运行阶段会出现异常,因为JVM堆内存
         *    当中真实存在的对象是Bird类型,Bird对象无法转换成Cat对象,因为两种
         *    类型直接不存在任继承关系,此时出现了著名的异常。
         *          java.lang.ClassCastException
         *          类型转换异常,这种异常总是在"向下转型的时候"会发生
         
        // Cat c3 =(Cat)a3; // ✖  错误类型(类型转换异常): java.lang.ClassCastException

        
         *  1. 以上异常只是在强制类型转换的时候会发生,也就是说“向下转换”存在隐患 (编译过了,但是运行错了!)
         *  2. 向下转型只要编译通过,运行一定不会出问题:Animal a = new Cat()
         *  3. 向下转换编译通过,运行可能出错误:Animal a3 = new Bird(); Cat c3 = (Cat)a3;
         *  4. 怎么避免向下转换出现的ClassCaseException呢?
         *          使用 "instanceof" (判断对象类型)运算符可以避免出现以上的异常。
         *  5. instanceof运算符怎么用?
         *      5.1 语法格式:(引用 instanceof 数据类型名)
         *      5.2 以上运算符的指向结果类型是布尔类型,结果可能是true/false
         *      5.3 关于运算结果true/false:
         *          假设:(a instanceof Animal)
         *          true表示:
         *              a这个引用指向的对象是一个Animal类型。
         *          false表示:
         *              a这个引用指向的对象不是一Animal类型。
         *  6. Java规范中要求:在进行强制类型转换之前,建议采用instanceof运算符进行判断,
         *     避免ClassCastException异常的发送。这是一个编程的好习惯。
         
         
        if (a3 instanceof Cat){
    
     // a3是一个Cat类型的对象
            Cat c3 = (Cat) a3;
            // 调用子类对象中特有的方法
            c3.catchMouse();
        }else if(a3 instanceof Bird){
    
     // a3是一个Bird类型的对象
            Bird b2 = (Bird)a3;
            // 调用子类对象中特有的方法
            b2.fly();
        }
    }
}
所以对于多态我们可以总结如下:

  指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。

  对于面向对象而已,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成多个不同的方法(函数),在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

实现条件:

  在刚刚开始就提到了继承在为多态的实现做了准备。子类Child继承父类Father,我们可以编写一个指向子类的父类类型引用,该引用既可以处理父类Father对象,也可以处理子类Child对象,当相同的消息发送给子类或者父类对象时,该对象就会根据自己所属的引用而执行不同的行为,这就是多态。即多态性就是相同的消息使得不同的类做出不同的响应。即多态性就是同一个动作让同类型的不同对象去执行。

Java实现多态有三个必要条件:继承、重写、向上转型。
  • 继承:在多态中必须存在有继承关系的子类和父类。

  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

  • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

  • 只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

  • 对于Java而言,它多态的实现机制遵循一个原则:当父类的‘引用变量’引用子类对象时,调用谁的成员方法是:被引用对象的类型(子类),而不是引用变量的类型(父类)决定的,但是这个被调用的方法必须是在父类(超类)中定义过的,也就是说被子类覆盖的方法。

实现形式:

  在Java中有两种形式可以实现多态。继承和接口。

基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。

public class Wine {
    
    
    private String name;
    
    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public Wine(){
    
    }
    
    public String drink(){
    
    
        return "喝的是 " + getName();
    }
    
    public String toString(){
    
    
        return null;
    }
}


public class JNC extends Wine{
    
    
    public JNC(){
    
    
        setName("JNC");
    }  
    /**
     * 重写父类方法,实现多态
     */
    public String drink(){
    
    
        return "喝的是 " + getName();
    }
    /**
     * 重写toString()
     */
    public String toString(){
    
    
        return "Wine : " + getName();
    }
}


public class JGJ extends Wine{
    
    
    public JGJ(){
    
    
    	// 调用父类的setName方法,进行赋值
        setName("JGJ");
    }
    /**
     * 重写父类方法,实现多态
     */
    public String drink(){
    
    
        return "喝的是 " + getName();
    }
    /**
     * 重写toString()
     */
    public String toString(){
    
    
        return "Wine : " + getName();
    }
}


public class Test {
    
    
    public static void main(String[] args) {
    
    
        //定义父类数组
        Wine[] wines = new Wine[2];
        //定义两个子类
        JNC jnc = new JNC();
        JGJ jgj = new JGJ();
        
        //父类引用子类对象
        wines[0] = jnc;
        wines[1] = jgj;
        
        for(int i = 0 ; i < 2 ; i++){
    
    
            System.out.println(wines[i].toString() + "--" + wines[i].drink());
        }
        System.out.println("-------------------------------");
    }
}


运行结果:
Wine : JNC--喝的是 JNC
Wine : JGJ--喝的是 JGJ
-------------------------------

  在上面的代码中JNC、JGJ继承Wine,并且重写了drink()、toString()方法,程序运行结果是调用子类中方法,输出JNC、JGJ的名称,这就是多态的表现。不同的对象可以执行相同的行为,但是他们都需要通过自己的实现方式来执行,这就要得益于向上转型了。

我们都知道所有的类都继承自超类Object,toString()方法也是Object中方法,当我们这样写时:

Object o = new JGJ();

System.out.println(o.toString());

输出的结果是Wine : JGJ。

Object、Wine、JGJ三者继承链关系是:JGJ—>Wine—>Object。所以我们可以这样说:当子类重写父类的方法被调用时,只有对象继承链中的最末端的方法才会被调用。但是注意如果这样写:

Object o = new Wine();

System.out.println(o.toString());

输出的结果应该是Null,因为JGJ并不存在于该对象继承链中。

所以基于继承实现的多态可以总结如下:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。

如果父类是抽象类,那么子类必须要实现父类中所有的抽象方法,这样该父类所有的子类一定存在统一的对外接口,但其内部的具体实现可以各异。这样我们就可以使用顶层类提供的统一接口来处理该层次的方法。

对于多态,可以总结为:
一、使用父类类型的引用指向子类的对象。
二、该引用只能调用父类中定义的方法和变量;
 三、如果子类中重写了父类中的一个方法,那么在调用这个方法的时候,将会调用子类中的这个方法;(动态连接、动态调用)
 四、变量不能被重写(覆盖),”重写“的概念只针对方法,如果在子类中”重写“了父类中的变量,那么在编译时会报错。

子类对象特有的方法调用,一定要用子类对象引用。

相关资料:

百度文库链接:https://wenku.baidu.com/view/73f66f92daef5ef7ba0d3c03.html

猜你喜欢

转载自blog.csdn.net/weixin_44630560/article/details/105817645