Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。
每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象。
Class对象是java语言反射特性,在多态中能够正确的执行相应对象的方法,也是依靠对象的Class对象提供的信息来判断的,这也就是RTTI(Runtime type identify),
举个例子:
public class classTest2 {
public static void main(String[] args) {
Human g=new Girl();
System.out.println(g instanceof Girl);
System.out.println(g.getClass().getName()); //Girl 而不是 Human
}
}
abstract class Human{
public Human(){
}
}
class Girl extends Human{
public Girl(){
}
}
对于每一个class都对应着一个Class对象,每当你创建一个新的class,一个Class对象便会创建并存储在.class文件中。为了创建这个class的对象,JVM将调用一个类加载器(Class loader)的子系统。
类加载器系统其实就是由多个类加载器组成的一个链。类加载器使用了双亲委托模型,当一个装载器被请求装载某个类时,它首先委托自己的parent去装载,若parent能装载,则返回这个类所对应的Class对象,若parent不能装载,则由parent的请求者去装载。这样保证了jvm不会加载恶意的class文件。
所有的class都是只有在第一次引用该类型的静态成员时被动态加载到jvm中的。构造函数是隐式的static方法。
package test;
public class SweetShop {
public static void main(String[] args) {
System.out.println("inside main");
new Candy();
System.out.println("After creatig Candy");
try {
/*
* 加载并返回类型为Gum的Class对象,当使用Class.forName方法时,在classLoader第一次加载class文件时,对所有的static进行初始化
*/
Class.forName("test.Gum");
} catch (Exception e) {
System.out.println("Couldn't find Gum");
}
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
}
}
class Candy{
static{
System.out.println("Loading Candy");
}
}
class Cookie {
static {
System.out.println("Loading Cookie");
}
}
输出
inside main
Loading Candy
After creatig Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
获取Class对象的第一种方法
Class.forName(String className)该方法返回一个Class对象,同时对该类的static进行初始化。
获取Class对象的第二种方法
使用字面量(literals),如Dog.class,int.class。
这两种获取Class对象的主要区别
使用字面量获取Class对象将不会初始化static代码块或变量,这一点是和Class.forName()的区别。
其实是分为三个步骤:
1.加载(Loading),jvm找到.class文件,加载字节码到内存
2.链接(Linking),该阶段验证字节码的正确性,如果有static field话,为其分配存储空间,并处理对其他类型的引用。
3.初始化(initialization),如果有父类,就初始化它。初始化static成员。
第三个步骤-初始化,是被延迟执行的,只用当第一次应用该类型的静态成员或静态方法时,才会触发初始化(构造函数是隐式的方法)
package test;
import java.util.Random;
class Initable {
static final int staticFinal = 47;
static final int staticFinal2 = 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");
// Does not trigger initialization:
System.out.println(Initable.staticFinal);
// Does trigger initialization:
System.out.println(Initable.staticFinal2);
// Does trigger initialization:
System.out.println(Initable2.staticNonFinal);
Class initable3 = Class.forName("test.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
注意:如果一个static final是一个运行时常量,如 Initable.staticFinal,那么读取这个值将不会引起初始化,如果是一个static而不是final,那么访问该值,将会触发初始化,如Initable2.staticNonFinal 。
获取Class对象的第三种方法
使用TYPE,如Integer.TYPE
待续。。