anonymous的Java学习笔记(11)——Java面向对象之多态

多态

Java引用变量有两个类型

  1. 编译时类型
    • 编译时类型由声明该变量时使用的类型决定。(相当于Object p = new Person();中的Object)
  2. 运行时类型
    • 运行时类型由实际赋给该变量的对象决定。(相当于Object p = new Person();中的new Person())

如果编译时类型和运行时类型不一致,就可能出现所谓的多态(Polymorphism)。

代码示例

父类——BaseClass2

package com.abc.part4;

/**
 * @author mi
 */
public class BaseClass2 {
    public int age = 21;

    public void base() {
        System.out.println("父类中的普通方法。");
    }

    public void test() {
        System.out.println("父类中的被覆盖的方法。");
    }

}

子类——SubClass2.java

package com.abc.part4;

/**
 * @author mi
 */
public class SubClass2 extends BaseClass2 {
    public int age = 25;

    @Override
    public void test() {
        System.out.println("子类中覆写父类test()的方法。");
    }

    public void info() {
        System.out.println("子类中的普通方法");
    }

    public static void main(String[] args) {
        BaseClass2 baseClass2 = new BaseClass2();
        //输出:baseClass2.age:21
        System.out.println("baseClass2.age:" + baseClass2.age);
        //输出:父类中的普通方法。
        baseClass2.base();
        //输出:父类中的被覆盖的方法。
        baseClass2.test();

        SubClass2 subClass2 = new SubClass2();
        //输出:subClass2.age:25
        System.out.println("subClass2.age:" + subClass2.age);
        //输出:子类中覆写父类test()的方法。
        subClass2.test();
        //输出:子类中的普通方法
        subClass2.info();
        /*
        SubClass2是BaseClass2的子类,属于继承关系,所以可以调用父类中的方法。
         */
        //输出:父类中的普通方法。
        subClass2.base();

        BaseClass2 bs = new SubClass2();
        //输出:子类中覆写父类test()的方法。
        bs.test();
        //输出:父类中的普通方法。
        bs.base();
        //输出:bs.age:21
        System.out.println("bs.age:" + bs.age);
        //bs.info();

    }
}

上边代码中,第三个引用变量bs则比较特殊,它的编译时类型是BaseClass2,而运行时类型是SubClass2,当调用该引用变量的test()方法(BaseClass2类中定义了该方法,子类SubClass2覆盖了父类的该方法)时,实际执行的是SubClass2类中覆盖后的test()方法,这就是多态的一种体现。

子类其实是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换,或者被称为向上转型(upcasting),向上转型由系统自动完成。

**当把一个子类对象直接赋给父类引用变量时,例如上面的BaseClass2 bs = new SubClass2(); , 这个bs引用变量的编译时类型是BaseClass2,而运行时类型是SubClass2,当运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。

与方法不同的是,对象的实例变量则不具备多态性,比如上面的bs引用变量,程序中输出它的age实例变量时,并不是输出SubClass2类里定义的实例变量age:25,而是输出BaseClass2类中的实例变量age:21。

总结

  • 引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。
  • 编写Java代码时,引用变量只能调用声明该变量时所用类里包含的方法。例如,通过Object p = new Person();代码定义一个变量p,则这个p只能调用Object类的方法,而不能调用Person类里定义的方法。(有点绕~得好好琢磨琢磨)
  • 通过引用变量来访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。

引用变量的强制类型转换

编写Java程序时,引用变量只能调用它编译时类型方法,而不能调用它运行时类型方法,即使它实际所引用的对象确实包含该方法。如果需要让这个引用变量调用它运行时类型方法,则必须把它强制类型转换运行时类型,强制类型转换需要借助于类型转换运算符(type)variable,这种用法可以将variable变量转换成type类型的变量。

基本类型、引用类型分别进行强制类型转换时注意事项

  • 基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整数型字符型浮点型。但数值类型布尔类型之间不能进行类型转换。
  • 引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则将在运行时引发ClassCastException异常。

代码示例

package com.abc.part4;

/**
 * @Auther: ABC
 * @Date: 2020/5/13 22:58
 * @Description:
 */
public class ConversionTest {
    public static void main(String[] args) {
        double a = 14.9;
        long b = (long) a;
        //输出:14 双精度浮点型强转至长整形会变为一个整数。
        System.out.println(b);

        int c = 4;
        // `数值类型`和`布尔类型`之间不能进行类型转换
        //boolean i = (boolean) c;//Inconvertible types; cannot cast 'int' to 'boolean'

        //object变量的编译时类型为Object,Object与String存在继承关系,可以强制类型转换
        //而且object变量的实际类型是String,所以运行时也可通过。
        Object object = "哈哈哈";
        String objStr = (String) object;

        // 定义一个objPri变量,编译时类型为Object,实际类型为Integer
        Object objPri = Integer.valueOf(5);
        // objPri变量的编译时类型为Object,objPri的运行时类型为Integer
        // Object与Integer存在继承关系
        // 可以强制类型转换,而objPri变量的实际类型是Integer
        // 所以下面代码运行时引发ClassCastException异常
        //String str = (String)objPri;//Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

        //为了使程序更健壮,可以事先先用instanceof运算符来判断是否可以成功转换。
        //在进行强制类型转换之前,先用instanceof运算符判断是否可以成功转换,从而避免出现ClassCastException异常,这样可以保证程序更加健壮
        //下面程序将会输出:objPri不能被强制转换为String类型!
        if (objPri instanceof String) {
            String str = (String)objPri;
        }else {
            System.out.println("objPri不能被强制转换为String类型!");
        }
        /*
        当把子类对象赋给父类引用变量时,被称为向上转型(upcasting),这种转型总是可以成功的,这也从另一个侧面证实了子类是一种特殊的父类。
        这种转型只是表明这个引用变量的编译时类型是父类,但实际执行它的方法时,依然表现出子类对象的行为方式。
        但把一个父类对象赋给子类引用变量时,就需要进行强制类型转换,而且还可能在运行时产`ClassCastException`异常,使用instanceof运算符可以让强制类型转换更安全。
         */
    }
}

向上转型和强制类型转换

  • 向上转型,此处多指Java面向对象中的多态
    • 当把子类对象赋给父类引用变量时,被称为向上转型(upcasting)。如:BaseClass2 bs = new SubClass2();
    • 证实了子类是一种特殊的父类。
    • 这种转型只是表明这个引用变量的编译时类型是父类,但实际执行它的方法时,依然表现出子类对象的行为方式。(多态的一种体现)
  • 强制类型转换
    • 把一个父类对象赋给子类引用变量时,就需要进行强制类型转换。如:SubClass2 sc = (SubClass2) new BaseClass2();
    • 而且还可能在运行时产ClassCastException异常,使用instanceof运算符可以让强制类型转换更安全。
      if (objPri instanceof String) {
          String str = (String)objPri;
      }else {
          System.out.println("objPri不能被强制转换为String类型!");
      }
      

关于引用变量的强制类型转换具体详解见Java面向对象之引用数据类型的强制类型转换

instanceof运算符

详解见Java面向对象之引用数据类型的强制类型转换 文末部分。