Class类详解

Class类简介

java 世界里,一切皆对象。从某种意义上来说,java 有两种对象:实例对象和 Class 对象。每个类的运行时的类型信息就是用 Class 对象表示的,它包含了与类有关的信息,实例对象就是通过 Class 对象来创建的。Java 使用 Class 对象执行其 RTTI (运行时类型识别,Run-Time Type Identification),多态就是基于 RTTI 实现的。

每一个类都有一个 Class 对象,每当编译一个新类就产生一个 Class 对象,基本类型 (boolean, byte, char, short, int, long, float, double)有 Class 对象,就连关键字 void 也有 Class 对象(void.class)。Class 对象对应着 java.lang.Class 类,如果说类是对象的抽象和集合,那么 Class 类就是对类的抽象和集合。

Class 类只有一个私有的构造方法,不能通过 new 关键字来创建,而是在类加载的时候由 Java 虚拟机以及类加载器来自动构造的。

所有的类都是在对其第一次使用时,动态加载到 JVM 中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用 new 创建类对象的时候也会被当作对类的静态成员的引用。因此 java 程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。

在类加载阶段,类加载器首先检查这个类的 Class 对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找 .class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良 java 代码。一旦某个类的 Class 对象被载入内存,我们就可以它来创建这个类的所有对象。

获取 Class 对象的方式

有三种获得 Class 对象的方式:

  1. Class.forName(“类的全限定名”)
  2. 实例对象 .getClass()
  3. 类名 .class (类字面常量)

第一种方式示例代码如下:

public class Dog {
    static {
        System.out.println("Loading Dog");
    }
}

public class Cat {
    static {
        System.out.println("Loading Cat");
    }
}

public class GetClassSimple {

    public static void main(String[] args) {
        Dog dog = new Dog();
        try {
            Class<?> cat = Class.forName("fanda.zeng.reflect.Cat");
               Class<?> dog2 = Class.forName("fanda.zeng.reflect.Dog");
         } catch (ClassNotFoundException e) {
            System.out.println("can not find class");
            e.printStackTrace();
        }
    }
}

输出:

Loading Dog
Loading Cat

上述的 static{} 代码块只会在第一次加载类的时候被调用,由输出结果得知,new 对象和调用Class.forName 时会判断类是否已经加载,没有加载则会调用类加载器来加载类,已经加载过了,不会重复加载。Class.forName 的好处就在于,不需要为了获得 Class 引用而持有该类型的对象,只要通过全限定名就可以返回该类型的一个 Class 引用。

第二种方式示例代码如下:

Dog dog = new Dog();
Class<?> clazz = dog.getClass();

输出:

Loading Dog

如果你已经有了该类型的对象,那么我们就可以通过调用 getClass() 方法来获取 Class 引用了,这个方法属于根类 Object 的一部分,它返回的是表示该对象的实际类型的 Class 引用。因为 new 操作已经把类加载到内存了,所有 getClass 方法不会再去执行类加载了,而是直接从 java 堆中返回该类型的 Class 引用。

第三种方式示例代码如下:

Class c1 = Integer.class;
Class c2 = int.class;
Class c3 = Integer.TYPE;

Class dog = Dog.class;
Class cat = Cat.class;

System.out.println(c1);
System.out.println(c2);
System.out.println(c3);

输出:

class java.lang.Integer
int
int

这种方式在编译时就会受到检查(因此不需要置于try语句块中),不仅可以应用于普通的类,也可以应用于接口、数组及基本数据类型。上述 c2 等价于 c3 ,因为在包装类中有个一个字段 TYPETYPE 字段是一个引用,指向对应的基本数据类型的 Class 对象,因此不只 Integer 包装类,其他 7 种包装类以及 Void 类型都有对应的 TYPE 字段。

注意:上述代码没有打印 DogCat 静态代码块的内存,因为用 .class 来创建 Class 对象的引用时,不会自动地初始化该 Class 对象,类加载被延迟到了对静态方法或者非常数静态域首次引用时才执行。示例代码如下:

public class Dog {

    static final String DOG_CONSTANT = "DOG_CONSTANT";

    static String staticValue = "dog_staticValue";

    static {
        System.out.println("Loading Dog");
    }
}

Class dog = Dog.class;
System.out.println("===== 常量 =====");
System.out.println(Dog.DOG_CONSTANT);
System.out.println("===== 静态成员 =====");
System.out.println(Dog.staticValue);

输出:

===== 常量 =====
DOG_CONSTANT
===== 静态成员 =====
Loading Dog
dog_staticValue

由输出结果可知,当引用 staticValue 时,类被加载到内存并执行了静态代码块的内容。因为被 static final 修饰的属性在编译期就把结果放入常量池了,所以引用的时候并不需要类真正地被加载。但是,如果只是将一个域设置为 staticfinal 的,还不足以确保这种行为,此时引用会强制加载并初始化类。

Class 对象的唯一性

每个通过关键字 class 标识的类,在内存中有且只有一个与之对应的 Class 对象来描述其类型信息,不论通过哪种方式获得该类的 Class 对象,它们返回的都是指向同一个 java 堆地址上的 Class 引用,jvm 不会创建两个相同类型的 Class。也就是说,无论创建多少个实例对象,其依据的都是同一个 Class 对象。java 虚拟机中使用双亲委派模型来组织类加载器之间的关系,来保证 Class 对象的唯一性。验证代码如下:

Class dog = Dog.class;
Class dog2 = Class.forName("fanda.zeng.reflect.Dog");
Class dog3 = new Dog().getClass();
Class dog4 = new Dog().getClass() ;

if (dog == dog2 && dog2 == dog3 && dog3 == dog4) {
    System.out.println(true);
} else {
    System.out.println(false);
} 

输出结果为 true ,表示都是同一个堆上的 Class 引用。

Class 类的方法

先说明一下,构造方法都是获取本类的(一定不包括父类),只是公共和其他修饰符的区别。以下其他带有 Declared 的方法,都表示只能获取本类的属性(不包括父类的),不过可以获取所有修饰符修饰的属性(公有,保护,默认,私有)。不带有 Declared 的方法,可以获取本类及父类的所有的属性,不过只能获取 公有 修饰符的 。

获取构造函数

//返回 Constructor 对象数组
Constructor<?>[] getDeclaredConstructors()
Constructor<?>[] getConstructors()       

// 返回指定参数的 Constructor 对象
Constructor<T> getConstructor(Class<?>... parameterTypes)     
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 

获取成员变量

// 返回 Field 对象数组
Field[] getFields() 
Field[] getDeclaredFields() 

// 返回指定的 Field 对象 
Field getField(String name) 
Field getDeclaredField(String name) 

获取成员方法

// 返回 Method 对象数组 
Method[] getMethods() 
Method[] getDeclaredMethods() 

/ 返回指定的 Method 对象 
Method getMethod(String name, Class<?>... parameterTypes) 
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 

获取注解方法

// 返回指定 Annotation 对象
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) {
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {

// 返回 Annotation 对象数组
Annotation[] getDeclaredAnnotations()
public Annotation[] getAnnotations ()

创建对象的方法

// 创建此 Class 对象所表示的类的一个新实例
T newInstance() 

返回字符串(String)的方法

// 返回底层类的规范化名称 
String getCanonicalName() 

// 返回全限定名(全限定名:包名.类名)
String getName() 

// 返回源代码中给出的底层类的简称
String getSimpleName() 

返回 boolean 的方法

// 判断是不是局部类,也就是方法里面的类 
boolean isLocalClass()     

// 判断是不是成员内部类,也就是一个类里面定义的类
boolean isMemberClass()    

// 判断当前类是不是匿名类,匿名类一般用于实例化接口
boolean isAnonymousClass() 

// 如果指定类型的注释存在于此元素上
isAnnotationPresent (Class<? extends Annotation> annotationClass)

// 判断当前Class对象是否是注释类型
boolean isAnnotation() ;

// 判断该类是否是枚举类
boolean isEnum() 

// 判断指定的 Object 是否 Class 的实例
boolean isInstance(Object obj) 

// 判断指定的 Class 对象是否表示一个接口类型
boolean isInterface() 

// 判断指定的 Class 对象是否表示一个基本类型
boolean isPrimitive() 

获取 Class 的修饰符

//返回此类或接口以整数编码的 Java 语言修饰符。
public int getModifiers();

我们可以使用 Class.getModifiers() 获得调用类的修饰符的二进制值,然后使
Modifier.toString(int modifiers) 将获取到的二进制值转换为字符串。

代码示例

// 动物类
public class Animal {
    private int mAnimalPrivate;
    protected int mAnimalProtected;
    public int mAnimalpublic;

    public Animal() {
    }

    public Animal(int mAnimalPrivate) {
        this.mAnimalPrivate = mAnimalPrivate;
    }

    public Animal(int mAnimalPrivate, int mAnimalProtected) {
        this.mAnimalPrivate = mAnimalPrivate;
        this.mAnimalProtected = mAnimalProtected;
    }

    public Animal(int mAnimalPrivate, int mAnimalProtected, int mAnimalpublic) {
        this.mAnimalPrivate = mAnimalPrivate;
        this.mAnimalProtected = mAnimalProtected;
        this.mAnimalpublic = mAnimalpublic;
    }

    public void animalPublic() {
        System.out.println("Method : animalPublic");
    }

    private void animalPrivate() {
        System.out.println("Method : animalPrivate");
    }

    protected void animalProtected() {
        System.out.println("Method : animalProtected");
    }

}

// 猫类,继承动物类
public class Cat extends Animal {

    static {
        System.out.println("Loading Cat");
    }

    private int mCatPrivate;
    protected int mCatProtected;
    public int mCatpublic;


    public Cat() {
    }

    public Cat(int mCatPrivate) {
        this.mCatPrivate = mCatPrivate;
    }

    private Cat(int mCatPrivate, int mCatProtected) {
        this.mCatPrivate = mCatPrivate;
        this.mCatProtected = mCatProtected;
    }

    public void catPublic() {
        System.out.println("Method : catPublic");
    }

    private void catPrivate() {
        System.out.println("Method : catPrivate");
    }

    protected void catProtected() {
        System.out.println("Method : catProtected");
    }

    @Override
    public String toString() {
        return "mCatPrivate = " + mCatPrivate + "-----------" + "mCatProtected = " + mCatProtected + "-----------" + "mCatpublic = " + mCatpublic;
    }
}

获取构造函数示例:

  public static void main(String[] args) throws Exception {

    Class clazz = Cat.class;
    System.out.println("获取本类所有 public 构造方法");
    for (Constructor constructor : clazz.getConstructors()) {
        System.out.println(constructor.toString());
    }

    System.out.println();
    System.out.println("获取本类所有构造方法");
    for (Constructor declaredConstructor : clazz.getDeclaredConstructors()) {
        System.out.println(declaredConstructor.toString());
    }

    System.out.println();
    System.out.println("这里获取指定的带有两个参数的私有构造方法");
    Constructor privateConstructor = clazz.getDeclaredConstructor(int.class, int.class);
    System.out.println(privateConstructor.toString());
    //  改变访问权限
    privateConstructor.setAccessible(true);
    Cat cat = (Cat) privateConstructor.newInstance(1, 2);
    System.out.println(cat.toString());

    System.out.println();
    System.out.println("这里获取指定的带有一个参数的公有构造方法");
    Constructor publicConstructor = clazz.getConstructor(int.class);
    System.out.println(publicConstructor.toString());
    Cat cat2 = (Cat) publicConstructor.newInstance(100);
    System.out.println(cat2.toString());
}

输出:

获取本类所有 public 构造方法
public fanda.zeng.reflect.Cat(int)
public fanda.zeng.reflect.Cat()

获取本类所有构造方法
private fanda.zeng.reflect.Cat(int,int)
public fanda.zeng.reflect.Cat(int)
public fanda.zeng.reflect.Cat()

这里获取指定的带有两个参数的私有构造方法
private fanda.zeng.reflect.Cat(int,int)
Loading Cat
mCatPrivate = 1-----------mCatProtected = 2-----------mCatpublic = 0

这里获取指定的带有一个参数的公有构造方法
public fanda.zeng.reflect.Cat(int)
mCatPrivate = 100-----------mCatProtected = 0-----------mCatpublic = 0

获取成员变量示例:

public static void main(String[] args) throws Exception {

    Class clazz = Cat.class;
    System.out.println("获取所有 public 成员变量");
    for (Field field : clazz.getFields()) {
        System.out.println(field.toString());
    }

    System.out.println();
    System.out.println("获取本类所有成员变量");
    for (Field field : clazz.getDeclaredFields()) {
        System.out.println(field.toString());
    }

    System.out.println();
    System.out.println("这里获取指定名字的成员变量");
    Field mCatPrivateField = clazz.getDeclaredField("mCatPrivate");
    System.out.println(mCatPrivateField.toString());
    //  改变访问权限
    mCatPrivateField.setAccessible(true);
    Cat cat = new Cat();
    mCatPrivateField.set(cat, 100);
    System.out.println(cat.toString());

}

输出:

获取所有 public 成员变量
public int fanda.zeng.reflect.Cat.mCatpublic
public int fanda.zeng.reflect.Animal.mAnimalpublic

获取本类所有成员变量
private int fanda.zeng.reflect.Cat.mCatPrivate
protected int fanda.zeng.reflect.Cat.mCatProtected
public int fanda.zeng.reflect.Cat.mCatpublic

这里获取指定名字的成员变量
private int fanda.zeng.reflect.Cat.mCatPrivate
Loading Cat
mCatPrivate = 100-----------mCatProtected = 0-----------mCatpublic = 0

获取成员方法示例:

public static void main(String[] args) throws Exception {

    Class clazz = Cat.class;
    System.out.println("获取所有 public 成员方法");
    for (Method method : clazz.getMethods()) {
        System.out.println(method.toString());
    }

    System.out.println();
    System.out.println("获取本类所有成员方法");
    for (Method method : clazz.getDeclaredMethods()) {
        System.out.println(method.toString());
    }

    System.out.println();
    System.out.println("这里获取指定名字的成员方法");
    Method catPrivateMethod = clazz.getDeclaredMethod("catPrivate");
    System.out.println(catPrivateMethod.toString());
    //  改变访问权限
    catPrivateMethod.setAccessible(true);
    Cat cat = new Cat();
    catPrivateMethod.invoke(cat);
}

输出:

获取所有 public 成员方法
public java.lang.String fanda.zeng.reflect.Cat.toString()
public void fanda.zeng.reflect.Cat.catPublic()
public void fanda.zeng.reflect.Animal.animalPublic()
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

获取本类所有成员方法
public java.lang.String fanda.zeng.reflect.Cat.toString()
public void fanda.zeng.reflect.Cat.catPublic()
protected void fanda.zeng.reflect.Cat.catProtected()
private void fanda.zeng.reflect.Cat.catPrivate()

这里获取指定名字的成员方法
private void fanda.zeng.reflect.Cat.catPrivate()
Loading Cat
Method : catPrivate

上述获取所有公共方法时,包括本类及所有的父类的公共方法都会查找出来。