每个类都有一个Class对象
Java中一切皆对象,各种各样的对象提供了丰富的功能,今天说说”对象的对象”。
java.lang.Object
java.lang.Class<T>
每个类都有一个相应的Class
对象,该Class
对象包含了创建对应类所需的各种信息,在我们编译一个新定义的Java类时会生成一个相应的Class
对象(也就是同名的.class
文件),程序中需要实例化该类时,就通过该类对应的Class
对象来创建该类的实例,不过在此之前需要先将相应的.class
文件加载到JVM(Java Virtual Machine, Java虚拟机)
中,这个工作由一个称之为ClassLoader
的类加载器完成。
上面提到了每个类都有一个相应的Class
对象,包括普通类、内部类、匿名类、接口、数组、基本类型,例如下面代码:
interface B{}
class A {
class C {}
B getB() {
return new B() {};
}
}
public class ClassTest {
public static void main (String [] args) {
return;
}
}
生成的.class
文件:
A$1.class
A$C.class
A.class
B.class
ClassTest.class
上面代码中定义了一个接口B
,一个类A
,在类A
中又定义了一个内部类和一个成员函数,该成员函数返回实现了接口B
的匿名类,以及一个包含了程序入口的类ClassTest
,从生成的.class
文件可以看出,每个类都生成了一个相应的.class
文件。
Class对象的加载时机
Java程序运行是并不是一次性加载所有的类,而是”按需加载”,所有类第一次用到时,才会被JVM
加载,同一个类只会被加载一次。
当程序创建第一个对类静态成员的引用时,就会加载这个类,包括使用类的静态成员变量和静态成员函数,此外使用new
关键字创建对象时,该类也会被加载。
通过下面代码来看看类的加载时机:
class A {
static { System.out.println("load class A"); }
public static int a = 0;
}
class B {
static { System.out.println("load class B"); }
public static void func() {}
}
class C {
static { System.out.println("load class C");}
}
public class ClassTest {
public static void main (String [] args) {
System.out.println("main() begain");
int a = A.a; // 使用类的静态字段
B.func(); // 使用类的静态函数
new C(); // 通过new创建对象实例
System.out.println("main() end");
new C();
return;
}
}
输出结果:
main() begain
load class A
load class B
load class C
main() end
上面我定义了三个类A、B、C
,每个类都有一个static语句块,由于static语句只会在类加载时执行一次,所以在static语句插入一个输出语句来标识类什么时候加载。通过main()
函数中的输出顺序可以看到,A、B、C
三个类按照使用顺序依次加载,而不是在程序加载时就全部一次性加载(如果是这样,那么load class A
这样的语句应该出现在main() begain
语句之前)。
在main()
函数返回前又创建了一个对象C
,但是没有输出load class C
,这也证明了同一个类只会被加载一次。类加载器(ClassLoader
)在加载一个Class
对象时,会先检查该Class
对象是否已被加载,如果尚未加载,就会查找该类相应的.class
文件,并加载到JVM
中(该类的字节码在加载时,会接受验证,以确保其没有被破坏,且不包含不良代码)。一旦Class
对象被加载到内存中,他会被用来创建该类的所有对象,不需要再次加载。
获取类的Class对象
有三种获取类对应的`Class对象的方法:
类的实例.getClass()
getClass()
是java.lang.Object
类的一个成员函数,所有类都继承了该方法,如果你手上恰好有类的实例对象,可以通过该方法获取这个类的Class
对象。类名.class
“类字面常量”这种方式不仅简单而且更加安全,因为它在编译器就会接受检查。不仅可以用于普通类,也可以用于接口、数据和基本数据类型。一般推荐使用这种方式。
Class.forName(“类名”)
使用这种方式只需要知道类名就可以创建
Class
对象,不过它可能会出现找不到相应类的情况,此时会抛出ClassNotFoundException
异常,而且只有在运行时才能知晓。
下面代码展示了这三种获取Class
对象的方式:
class A {
}
public class ClassTest {
public static void main (String [] args) {
A a = new A();
Class<?> c1 = a.getClass();
Class<?> c2 = A.class;
Class<?> c3 = null;
try {
c3 = Class.forName("A");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(c1 == c2);
System.out.println(c1 == c3);
return;
}
}
输出结果:
load class A
true
true
前面说过每个类都有一个Class对象且只会被加载一次,所以上面三种方式获取的Class
对象引用都指向同一个Class
对象。
有时会看到类似Class<? extends 类名>
或Class<? super 类名>
的写法,这个是为了是Class
对象的类型更加具体化,是编译器能执行更加严格的检查。例如下面代码:
class A {}
class B extends A {}
public class ClassTest {
public static void main (String [] args) {
Class<A> c = A.class; // 指定具体类型
Class<?> c2 = A.class; // 通过通配符?指定类型
// Class<A> c3 = B.class; // compile error
Class<? extends A> c3 = B.class; // 类B的Class对象对应的类继承自类A
Class<? super B> c4 = A.class; // 类A的Class对象对应的类是类B的超类
return;
}
}
Class类的使用
一旦获取了类的Class
对象,我们就能获取该类相关的各种信息,例如:类的字段、方法、接口、类定义、注释、枚举、父类等,以及相应的类型测试方法。下面代码以java.lang.String
对象的Class
对象为例,演示了Class
中的一些成员函数:
Class<?> c = String.class;
System.out.println(c.getCanonicalName()); // java.lang.String
System.out.println(c.getName()); // java.lang.String
System.out.println(c.getSimpleName()); // String
System.out.println(c.toGenericString()); // public final class java.lang.String
System.out.println(c.toString()); // class java.lang.String
System.out.println(c.getTypeName()); // java.lang.String
下面我自定义了一个类Test
,并定义了一些字段和方法,然后通过其Class
对象获取这其成员字段和方法信息。
class Test {
private String mPrivateField;
protected String mProtectedField;
public String mPublicField;
public Test() {}
public Test(String v) { }
public void publicFunc() { }
protected void protectedFunc() { }
private void privateFunc() {}
}
public class ClassTest {
public static void main (String [] args) {
System.out.println("\nTest.class.getConstructors()");
Constructor<?>[] constructors = Test.class.getConstructors();
for (Constructor<?> v : constructors) {
System.out.println(v.toString());
}
System.out.println("\nTest.class.getDeclaredFields()");
Field[] declaredFields = Test.class.getDeclaredFields();
for (Field v : declaredFields) {
System.out.println(v.toString());
}
System.out.println("\nTest.class.getFields()");
Field[] fields = Test.class.getFields();
for (Field v : fields) {
System.out.println(v.toString());
}
System.out.println("\nTest.class.getDeclaredMethods()");
Method[] declaredMethods= Test.class.getDeclaredMethods();
for (Method v : declaredMethods) {
System.out.println(v.toString());
}
return;
}
}
输出结果:
Test.class.getConstructors()
public Test(java.lang.String)
public Test()
Test.class.getDeclaredFields()
private java.lang.String Test.mPrivateField
public java.lang.String Test.mPublicField
Test.class.getFields()
public java.lang.String Test.mPublicField
Test.class.getDeclaredMethods()
public void Test.publicFunc()
protected void Test.protectedFunc()
private void Test.privateFunc()
getFields()
只返回public
访问权限的成员字段,getDeclaredFields()
返回所有成员字段。
通过Class中的getSuperclass()可以很容易获取类的继承结构,例如下面代码:
public class Test {
// 获取指定类的继承结构
public static Stack<String> getClassInherit(Class<?> c) {
Stack<String> stack = new Stack<String>();
while (c.getSuperclass() != null) {
stack.push(c.getName());
c = c.getSuperclass();
}
stack.push(c.getName());
return stack;
}
public static void main (String [] args) {
Stack<String> stack = getClassInherit(Integer.class);
while (!stack.empty()) {
System.out.println(stack.pop());
}
System.out.println();
Stack<String> stack = getClassInherit(String.class);
while (!stack.empty()) {
System.out.println(stack.pop());
}
}
}
输出结果:
java.lang.Object
java.lang.Number
java.lang.Integer
java.lang.Object
java.lang.String