<学习笔记><Java 基础>2. 由 instahceof 引发的一些思考

复习到instanceof的时候,发现对其中的一些要点有些淡忘,特此记录。

  • instanceof 本质上是一个双目运算符,用来测试一个引用所指向的对象是否为一个类/类的子类/接口的实现类的实例

    具体的情况请看以下代码:

    • 基本的使用情况,该对象为该类的实例
      Integer i  = 3;
      System.out.println(i instanceof Integer);//true
    
    • 该对象为该类的子类的实例
    public class Person {
      public static void main(String[] args) {
          Man m = new Man();
          Person p = new Man();
    
          System.out.println(m instanceof Person);//true
          System.out.println(p instanceof Person);//true
          System.out.println(p instanceof Man);//true
      }
    }
    
    class Man extends Person{
    }
    

    可见 instance 判断时,只考虑该引用指向的对象是否为该类的实例,而不管其声明时的类型。

    • 该对象为其父类的对象
    Person p = new Person();
    System.out.println(p instanceof Man);//false
    
    • 该对象为null
    Person p = null;
    System.out.println(p instanceof Man);//false
    
    • 该对象为接口的实现类
    List l1 = new ArrayList();
    ArrayList l2 = new ArrayList();
    
    System.out.println(l1 instanceof List);//true
    System.out.println(l2 instanceof List);//true
    System.out.println(l2 instanceof ArrayList);//true
    
    • 不能为基本类型
    int i = 0;
    System.out.println(i instanceof Integer);//编译不通过
    
    • 其他问题
    Person p1 = new Person();
    
    System.out.println(p1 instanceof String);//编译报错
    System.out.println(p1 instanceof List);//false
    System.out.println(p1 instanceof List<?>);//false
    System.out.println(p1 instanceof List<Person>);//编译报错
    

    编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

    按照我们上面的说法,这里就存在问题了,Person 的对象 p1 很明显不能转换为 String 对象,那么自然 Person 的对象 p1 instanceof String

    不能通过编译,但为什么 p1 instanceof List 却能通过编译呢?而 instanceof List 又不能通过编译了?

    • 深入分析,用伪代码描述 instanceof 的逻辑
    boolean result;
    if (obj == null) {
    result = false;
    } else {
    try {
        T temp = (T) obj; // checkcast
        result = true;
      } 
    catch (ClassCastException e) {
        result = false;
      }
    }
    

    也就是说有表达式 obj instanceof T,instanceof 运算符的 obj 操作数的类型必须是引用类型或空类型; 否则,会发生编译时错误。

 如果 obj 强制转换为 T 时发生编译错误,则关系表达式的 instanceof 同样会产生编译时错误。 在这种情况下,表达式实例的结果永远为false。

 在运行时,如果 T 的值不为null,并且 obj 可以转换为 T 而不引发ClassCastException,则instanceof运算符的结果为true。 否则结果是错误的

 简单来说就是:如果 obj 不为 null 并且 (T) obj 不抛 ClassCastException 异常则该表达式值为 true ,否则值为 false 。所以对于上面提出的

问题就很好理解了,为什么 p1 instanceof String 编译报错,因为(String)p1 是不能通过编译的,而 (List)p1 可以通过编译。

  • 在上述学习过程中,发现 在声明一个对象的引用时,可以使用接口来声明,那么这样做有什么好处呢?

    对于参数类型,要优先使用接口而不是类。通俗地讲,应该优先使用接口而不是类来引用对象。如果有合适的接口类型存在,那么对于参数、返回值、变量

    和域来说,就应该使用接口类型来声明。只有当你利用构造函数创建某个对象的时候,才真正引用这个对象的类。

    考虑Vector的情形,它是List接口的一个实现,在声明变量的时候应该养成这样的习惯:

    //Good - use interface as type
    List<?> list= new Vector<?>();
    

    而不是像这样的声明:

    //Bad - use class as type
    Vector<?> list= new Vector<?>();
    

    优点:

    1.假如一个类实现了多个接口,那么用接口类型来定义它的引用变量的话,一眼就可以明白,这里是需要这个类的哪些方法。

    2.程序更加灵活。当你决定更换实现时,只需要改变构造器中类的名称。其他使用list地方的代码根本不需要改动。第一个声明可以被改变为:

    注意:

    list只能使用ArrayList已经实现了的List接口中的方法,ArrayList中那些自己的、没有在List接口中定义的方法是不可以被访问到的。

    List.add其实是List接口的方法,但是调用ArrayList方法如clone()方法是调用不到的。

    适合于用类来引用对象的情形:
    1.如果没有合适的接口存在,可以用类来引用对象。

    例如,考虑值类(String、BigInteger)很少用多个实现编写,他们通常是final的,并且很少有对应的接口。使用这种值类作为参数、变量、域或者返回值类型就比较合适。

    2.对象属于一个框架,而框架的基本类型是类,不是接口。(对象属于基于类的框架)

    例如java.util.TimerTask抽象类。应该用相关的基类(往往是抽象类)来引用对象,而不是它的实现类。

    3.类实现了接口,但是它提供了接口中不存在的额外方法。

    例如LinkedHashMap,程序依赖于这些额外的方法,这种类就应该只被用来引用它的实例。

    以上这些例子并不全面,而只是代表了一些“适合于用类来引用对象”的情形

    总结:给定的对象是否具有适当的接口应该是很明显的。如果是,用接口引用对象就会使程序更加灵活;如果不是,则使用类层次结构中提供了必要功能的最基础的类。

  • 我们还发现,在向下转型时,出现了编译期报错和运行时的报错,这又是因为什么呢?

    对于java的向下转型,强制向下转型,编译不会报错。但是在运行时,如果对象本身并非是该类型,强制转型,

    在运行时会报java.lang.ClassCastException。因此只有把子类对象赋给父类的引用,然后把该引用向下转型的情况,才不会报错。

    父类不能强制向下转型!向上转型则是安全的,但是在向上转型的过程中,会失去子类特有的方法,但是子类重写的方法会被保存下来。

    如以下代码所示

    public class Person {
      public static void main(String[] args) {
          Man p = new Man();
          p.run();// run
          ((Person) p).run();// run
          ((Person) p).walk;//编译错误
          
      }
       void eat(){};
       void run(){};
    }
    
    class Man extends Person{
      @Override
      void eat() {
          System.out.println("eat");
      }
    
      @Override
      void run() {
          System.out.println("run");
      }
    
      void walk()
      {
          System.out.println("walk");
      }
    }
    

    一个对象具有编译时类型和运行时类型,该对象只能调用其编译时类型有的方法,否则编译期会报错。如果想使用其运行时类型具有的方法

    这时我们就需要对其进行向下转型,在向下转型时,我们就要使用 instanceof 判断是否可以向下转型。

参考链接:

https://www.cnblogs.com/ysocean/p/8486500.html
https://www.jianshu.com/p/a5fa6300177e
https://blog.csdn.net/veggetto/article/details/89680208

posted @ 2021-01-31 19:12  echocxl  阅读(74)  评论(0)    收藏  举报