《Thinking in Java》十四章类型信息_习题解
1~10 Page 318
练习1. 在ToyTest.java中,将Toy的默认构造器注释掉,并解释发生的现象。
书中代码如下(略有改动):
1 package org.cc.foo_008; 2 3 public class ToyTest { 4 5 static void printInfo(Class c){ 6 log("Class Name: "+c.getName()); 7 log("Is interface? "+c.isInterface()); 8 log("Simple name: "+c.getSimpleName()); 9 log("Canonical name: "+c.getCanonicalName()); 10 log(); 11 } 12 13 public static void main(String[] args) { 14 15 Class c=null; 16 17 try { 18 c=Class.forName("org.cc.foo_008.FancyToy"); 19 } catch (ClassNotFoundException e) { 20 e.printStackTrace(); 21 } 22 printInfo(c); 23 for(Class face:c.getInterfaces()){ 24 printInfo(face); 25 } 26 Class up=c.getSuperclass(); 27 Object obj=null; 28 try { 29 obj=up.newInstance(); 30 } catch (InstantiationException | IllegalAccessException e) { 31 e.printStackTrace(); 32 } 33 34 printInfo(obj.getClass()); 35 36 } 37 38 39 public static void log(Object ...args){ 40 for(Object o:args) System.out.print(o+" "); 41 System.out.println(); 42 } 43 44 } 45 46 interface HasBatteries {} 47 interface Waterprood {} 48 interface Shoots {} 49 50 class Toy{ 51 Toy(int i) { 52 } 53 // Toy() { 54 // } 55 } 56 57 class FancyToy extends Toy implements HasBatteries, Waterprood, Shoots{ 58 FancyToy() { 59 super(1); 60 } 61 }
将Toy的空构造器注释之后就会出现
首先查看这个异常在什么情况下会被抛出呢:
当应用程序试图使用Foo.class.newInstance()实例化但是失败的时候抛出此异常,可能但不限于的原因也许是:
- 试图实例化的是一个抽象类或接口、原始类型(基本类型)、void。
- 这个类没有默认的无参构造器。
好了,看到这里基本上就可以确定是没有无参构造器引起的异常了,那么为什么呢?
我们跟进执行过程看一下:
这个是newInstance()方法:
在412行的时候调用了一个私有的方法getConstructor0来获取构造器(顺带一提,好多私有的内部实现都是加个0,难道是什么约定俗成的东西么...)
传入了一个长度为0的Class数组和一个Member.DECLARED,我们先打个岔来看一下Member是干嘛的:
它是用来标识单个成员(方法或变量)或构造器的识别信息的,比如里面的那个PUBLIC常量的意思是访问修饰符为public的,不论是本类的还是继承而来的,而DECLARED的意思就是说只有在本类中显示声明的,不是继承来的,用白话说就是只有你写在Class名那个花括号里面的才算数,其它都不算数。
可以看到这个接口的继承树也很实在:
这几个类都是Member。
好了,我们已经知道了Member.DECLARED的意思是只有写在本类中的才算数,继续看getConstructor0()方法:
可以看到这个方法是使用构造器的参数类型、访问类型来确定到底是选择哪一个构造器的,newInstance()方法中调用的时候传进来一个空的Class数组和Member.DECLARED的意思就是说我要找这个类的写上去的参数数量为0即没有参数的构造器(当你没有其它构造器的时候默认无参构造器会自动给你写上去的,所以也算数):
哇去下面的感觉好复杂搞得我有点晕,这个方法的基本逻辑就是获得所有的构造器然后遍历,看看能不能找到,如果找不到就抛出一个NosuchMethodException(),然后结合上上上面的图newInstance()方法:
一个catch到一个NosuchMethodException()就抛出一个new InstantiationException()异常,所以这个异常就是这么来的,总结起来就一句话,没找到无参构造器。
为什么没找到无参构造器就要抛出异常呢?
这个是newInstance()得到无参构造器之后的逻辑(之前会进行一次判断,因为有一个缓存,如果缓存为空的话才会获得,否则的话就获取填到缓存,所以同一个Class<?>的newInstance()只会在第一次被调用的时候去获取无参构造器,之后就使用缓存了):
很明白了,因为我要用这个构造器来实例化对象啊。
为什么一定要使用这个构造器呢?
因为你丫没给我传递参数我只好使用无参构造器来实例化了....
但是newInstance()没有提供传递参数的重载,不过我们可以手动的得到所有的构造器然后使用有参数的,比如:
练习2.
会。
练习3.
编译都通不过,IDE还是很智能的,我猜判断依据的算法是如果它们之间有继承关系的话顺着一个往继承链顶端找必然能找到另一个,比如A、B,B继承了A,顺着A->Object to find B || B->Object find A 必然有一个是成立的。嗯,又胡乱猜测了一条,在进行类型转换前进行编译器检查,这样可以尽量的将可能的错误提前发现,然而这一条对于泛型基本没什么卵用,而只要明确的知道了两个类型就可以判定他们是不是可以进行类型转换了,大概算法如下:
但是这个样呢是不能判断接口的,最主要的是接口是多继承的并且接口的getSuperClass()可能会返回null,如果是多继承的话从下面往上找就是一颗颗的树了,可能会很大很大
练习4.