2022.2.11
on java 8 摘录(反射)
- 加载。这是由类加载器执行的。该步骤会先找到字节码(通常在类路径中的磁盘上,但也不一定),然后从这些字节码中创建一个
Class
对象。- 链接。链接阶段会验证类中的字节码,为静态字段分配存储空间,并在必要时解析该类对其他类的所有引用。
- 初始化。如果有基类的话,会先初始化基类,执行静态初始化器和静态初始化块。
初始化被延迟到首次引用静态方法(构造器是隐式静态的)或非常量静态字段时:
// reflection/ClassInitialization.java import java.util.*; class Initable { static final int STATIC_FINAL = 47; static final int STATIC_FINAL2 = ClassInitialization.rand.nextInt(1000); static { System.out.println("Initializing Initable"); } } class Initable2 { static int staticNonFinal = 147; static { System.out.println("Initializing Initable2"); } } class Initable3 { static int staticNonFinal = 74; static { System.out.println("Initializing Initable3"); } } public class ClassInitialization { public static Random rand = new Random(47); public static void main(String[] args) throws Exception { Class initable = Initable.class; System.out.println("After creating Initable ref"); // 不会触发初始化 System.out.println(Initable.STATIC_FINAL); // 触发初始化 System.out.println(Initable.STATIC_FINAL2); // 触发初始化 System.out.println(Initable2.staticNonFinal); Class initable3 = Class.forName("Initable3"); System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal); } } /* 输出: After creating Initable ref 47 Initializing Initable 258 Initializing Initable2 147 Initializing Initable3 After creating Initable3 ref 74 */
实际上,初始化会“尽可能懒惰”。从
initable
引用的创建过程中可以看出,仅使用.class
语法来获取对类的引用不会导致初始化。而Class.forName()
会立即初始化类以产生Class
引用,如initable3
的创建所示。如果一个
static final
字段的值是“编译时常量”,比如Initable.staticFinal
,那么这个值不需要初始化Initable
类就能读取。但是把一个字段设置为static
和final
并不能保证这种行为:对Initable.staticFinal2
的访问会强制执行类的初始化,因为它不是编译时常量。如果
static
字段不是final
的,那么访问它时,如果想要正常读取,总是需要先进行链接(为字段分配存储)和初始化(初始化该存储),正如在对Initable2.staticNonFinal
的访问中看到的那样。
一个
Class
引用表示的就是它所指向的确切类型:Class
类的一个对象。
要想放松使用泛化的
Class
引用时的限制,请使用通配符?
,它是Java泛型的一部分,表示“任何事物”。尽管如我们看到的那样,普通的
Class
并不会产生编译器警告,但是和普通的Class
相比,我们还是倾向于Class<?>
,即使它们是等价的。Class<?>
的好处在于,它表明了你不是偶然或无意识地使用了非具体的类引用。你就是选择了这个非具体的版本。
这种写法的话如果想表示子类可以这样<? extend 父类>
package reflection.toys; public class GenericToyTest { public static void main(String[] args) throws Exception { Class<FancyToy> ftc = FancyToy.class; // 生成确切的类型: FancyToy fancyToy = ftc.getConstructor().newInstance(); Class<? super FancyToy> up = ftc.getSuperclass(); // 下面的代码无法通过编译: // Class<Toy> up2 = ftc.getSuperclass(); // 只能生成Object Object obj = up.getConstructor().newInstance(); } }
这段代码很有意思,如果去掉第六行括号内的内容,会导致ftc得到的是一个Object类型,
问题
19.2 Class
类
-
首先Class类的作用就是描述了一个类的Class类文件中的信息,包括它的初始化器,它的接口,它的静态变量,它的父类这些信息
-
Class类的每一个类的实例都是唯一确定,例如一个类叫做Cat,那么我在实例化Cat以后,它的Class对象也就生成了,同时,此时我们初始化变量
Cat one = new Cat(); Class cat = Cat.class; Cat two = new Cat(); Class cat1 = Cat.class;
应该理解成这两个引用是相同的,不管我后面会不会再去实例化Cat一个新的对象
-
从上面可以推导出,Class类实际上是让我们有了直接操作类的实例化的可能性,因为不使用Class类时,这些都是java帮我们解决的
-
还要注意Class类的加载是在编译阶段的,而Class类的实例化是在运行时的,而前缀为static final就是编译时常量,如果去掉final,那么静态常量就是可以修改的,所以去掉以后算运行时常量,而运行的化会导致Class对象被初始化,
-
如何利用Class类生成实例对象
class cat { private String name="hajdka"; private int age=131; public cat(){ } public String getName() { return name; } public int getAge() { return age; } } public class GenericClassReferences { public static void main(String[] args) { try{ System.out.println(cat.class.getConstructor().newInstance().getAge()+" "+cat.class.getConstructor().newInstance().getName()); }catch (Exception e){ e.printStackTrace(); } } }