【Java】Class 对象

每个类都有一个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;    // 类BClass对象对应的类继承自类A

        Class<? super B> c4 = A.class;      // 类AClass对象对应的类是类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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值