<学习笔记><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

浙公网安备 33010602011771号