每个类都有一个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
本文详细介绍了Java中Class对象的概念,包括每个类都有的Class对象、Class对象的加载时机、获取Class对象的方法以及Class类的使用。通过实例代码展示了如何通过Class对象获取类的相关信息,如构造函数、成员字段和方法。

427

被折叠的 条评论
为什么被折叠?



