理解Java中的多态和instanceof

多态

  • 事物 存在的多种形态

多态的前提

  • 继承关系
  • 有方法重写
  • 有父类引用指向子类对象

多态成员访问特点

  • 成员变量

    • 编译看左边(父类),运行看左边(父类)。
  • 成员方法

    • 编译看左边(父类),运行看右边(子类)。动态绑定
  • 静态方法

    • 编译看左边(父类),运行看左边(父类)。
    • 静态和类相关,算不上重写,只是子类运行时覆盖了父类的静态方法,所以访问还是看父类。
    • 只有非静态成员方法,编译看左边,运行看右边
  • 父类引用指向子类对象,就是向上转型

    • Animal a = new Dog();
  • 指向子类对象的父类引用强转成子类类型,使用子类中特有的方法向下转型。把一个父类对象赋给子类引用变量时,就需要进行强制类型转换。

    1
    2
    Animal  a = new Dog();
    Dog dog = (Dog)a;

引用变量的强制类型转换

  • 引用变量只能调用编译时类型的方法,而不能调用运行时类型的方法,即使他实际所引用的对象确实包含该方法。

  • 如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型

  • 当进行强制类型转换时需要注意:

    • 基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整型浮点型字符型。但数值类型和布尔类型之间不能进行类型转换

    • 引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。

    • 如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时为父类类型,而运行时类型是子类),否则将在运行时引发ClassCastException异常。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      public class ConversionTest
      {
      public static void main(String[] args)
      {
      double d = 13.4;
      long l = (long)d;
      System.out.println(l);
      int in = 5;
      // 试图把一个数值类型的变量转换为boolean类型,下面代码编译出错
      // 编译时候会提示: 不可转换的类型
      // boolean b = (boolean)in;
      Object obj = "Hello";
      // obj变量的编译类型为Object,Object与String存在继承关系,可以强制类型转换
      // 而且obj变量实际上类型是String类型,所以运行时也可通过
      String objStr = (String)obj;
      System.out.println(objStr);
      // 定义一个objPri变量,编译类型为Object,实际类型为Integer
      Object objPri = Integer.valueOf(5);
      // objPri变量的编译时类型为Object,objPri的运行时类型为Integer,Object与Integer存在继承关系
      // 可以强制类型转换,而objPri变量实际上类型是Integer类型,
      // 所以下面代码运行时引发ClassCastException异常
      String str = (String)objPri;
      }
      }
  • 当把子类对象赋给父类引用变量时,称为向上转型(upcasting),向上转型总是可以成功的,因为子类是一种特殊的父类。这种转型只是表明这个引用变量的编译类型是父类,但实际执行它的方法时,依然表现出子类对象的行为方式。

  • 使用instanceof运算符,可以判断是否可以成功转换,从而避免出现转换异常。

    1
    2
    3
    if (obj instanceof String){
    String str = (String)obj;
    }

instanceof 运算符

  • instanceof运算符前一个操作数通常是一个引用类型变量,后一个操作数通常是一个,也可以是一个接口,用于判断前面的对象是否是后面的类,或者其子类、实现类的实例

  • instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则编译错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class InstanceofTest
    {
    public static void main(String[] args)
    {
    // 声明hello时使用Object类,则hello的编译类型是Object,
    // Object是所有类的父类, 但hello变量的实际类型是String
    Object hello = "Hello";
    // String与Object类存在继承关系,可以进行instanceof运算。返回true。
    System.out.println("字符串是否是Object类的实例:"
    + (hello instanceof Object));
    System.out.println("字符串是否是String类的实例:"
    + (hello instanceof String)); // 返回true。
    // Math与Object类存在继承关系,可以进行instanceof运算。返回false。
    System.out.println("字符串是否是Math类的实例:"
    + (hello instanceof Math));
    // String实现了Comparable接口,所以返回true。
    System.out.println("字符串是否是Comparable接口的实例:"
    + (hello instanceof Comparable));
    String a = "Hello";
    // // String类与Math类没有继承关系,所以下面代码编译无法通过
    // System.out.println("字符串是否是Math类的实例:"
    // + (a instanceof Math));
    }
    }
  • instanceof运算符的作用:在进行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以成功转换,从而保证代码健壮性。