Java Class类及反射机制

首先要分清楚Class类和关键词class的不同,虽然Class类名称上和class很相似,但两者其实没有什么关联。Class类也是一个Java类,它也可以实例化得到对象,与普通类不一样的是Class类中存储的是其它类的类型信息。而这些类型信息正是Java反射机制所需要的,Java的反射机制是由Class类和java.lang.reflect包下的Method、Field、Constructor等类支持的。下面就来了解下这几个类。

1.Class类

类是程序的一部分,每个类都有一个Class对象,换而言之每编写一个新类,就会产生一个Class对象(更准确的来说,是被保存在一个同名的.class文件中)。当程序中需要使用到这个类的时候(包括需要这个类的对象或引用这个类的静态变量)就通过类加载器将类加到内存中来。
在进行类加载的时候,类加载器会先检查这个类的Class对象是否已经加载,如果还未加载,则默认的类加载器就会根据类名查找.class文件(这里不一定都是从文件系统中获取,可以从数据库读取字节码或者接受网络中传输过来的字节码)。这些字节码在被加载的时候,会检查验证保证它们没有被破坏。一旦某个类的Class对象被载入内存,它就会被用来创建这个类的所有对象。下面来看下Class类一些操作。
1.1获取Class对象的三种方式
总共有三种方式可以获取一个类的Class对象:Class类的forName()方法、类字面常量、对象的getClass()方法。下面是一个示例,Student类是用来进行测试的对象。

package com.sankuai.lkl;
public class Student {

    public static final String test1 = "final_test";
    public static       String test2 = "static_test";

    static {
        System.out.println("init static");
    }

    private String name;
    private int    id;
    private String grade;
}

下面是一个测试方法:

public static void test1() throws Exception{
      //获取Class对象的三种方式
        try {
            //使用Class.forName()方法载入的时候,需要类的全限定名
            Class<?> cc = Class.forName("com.sankuai.lkl.Student");
            System.out.println(cc);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //使用类字面常量
        Class<?> cc1 = Student.class;
        System.out.println(cc1);

        //通过对象的getClass()
        Student student = new Student();
        Class<?> cc = student.getClass();
        System.out.println(cc);
    }

真正测试的时候每次只会运行一个方法,下面是三种方式对应的输出:
这里写图片描述
这里写图片描述
这里写图片描述
可以看到第二次的输出有点不同,没有输出static初始化块中的值。这是因为使用类字面常量方式获取Class对象的时候,类加载器虽然将类对应的class文件载入到内存中并创建了Class对象,但是没有给类进行初始化工作。这里稍微介绍下类加载的流程,分为三步:

  1. 加载。这一步是类加载器执行的,负责查找字节码(通常是在classpath所指定的路径中查找,但这不是必要的)。并从这些字节码中创建一个Class对象。
  2. 链接。在链接阶段将验证类中的字节码,并为类中的静态变量分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用(静态变量是引用并且直接初始化的情况)。
  3. 初始化。如果该类有父类,则先对其进行初始化,调用静态初始化器和静态初始化块。

在使用类字面常量获取Class对象的时候,初始化这一步被延迟到对类的静态方法或静态变量进行首次调用的时候(这里有一个很有意思的情况,其实构造器也是隐式静态的)。

1.2从Class对象中获取类信息
上面讨论了如何获取一个类的Class对象,那么拿到这个Class对象之后,我们又能做些什么呢。Class对象存储了类的类型信息,对于一个类来说比较重要的信息有其中定义的属性、方法、构造器,还有就是它扩展了哪些父类和接口等等。而这些方法Class对象中都是包含的,并且还提供了获取的方法。下面是一些具体的例子,先给两个测试用的类定义:

package com.sankuai.lkl.reflect;

public interface Person {

    void read();
}
package com.sankuai.lkl.reflect;
public class Student implements Person {

    private String id;
    public String name;
    private String age;

    //构造函数1
    public Student() {

    }
    //构造函数2
    public Student(String id) {
        this.id = id;
    }
    //构造函数3
    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }
     public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
    //静态方法
    public static void update() {

    }
    @Override
    public void read() {

    }
}

可以看到Student类是继承自Person类的。
首先来通过Class对象获取Student的所有属性:

public static void main(String[] args) {
        Class<?> cc = Student.class;

        //getFields返回的是申明为public的属性,包括父类中定义
        Field[] fields = cc.getFields();
        System.out.println("cc.getFields()");
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("\n");

        //getDeclaredFields返回的是指定类定义的所有定义的属性,不包括父类的。
        Field[] fields1 = cc.getDeclaredFields();
        System.out.println("cc.getDeclaredFields()");
        for (Field field : fields1) {
            System.out.println(field);
        }
    }

输出如下:
cc.getFields()
public java.lang.String com.sankuai.lkl.reflect.Student.name
public static final java.lang.String com.sankuai.lkl.reflect.Person.test

cc.getDeclaredFields()
private java.lang.String com.sankuai.lkl.reflect.Student.id
public java.lang.String com.sankuai.lkl.reflect.Student.name
private java.lang.String com.sankuai.lkl.reflect.Student.age

除了上面两个方法,还有getField(String name)和 getDeclaredField(String name)两个可以通过名称拿到指定属性Field对象的方法,它们的区别同上。

获取类所有的方法:

         Class<?> cc = Student.class;
        //获取所有pulbic类型方法,包括父类中定义的(Object和Person类中)
        //被子类重写过的方法,只算子类的
        Method[] methods = cc.getMethods();
        System.out.println("cc.getMethods()");
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("\n");

        //获取本类中所有方法
        Method[] methods1 = cc.getDeclaredMethods();
        System.out.println("cc.getDeclaredMethods()");
        for (Method method : methods1) {
            System.out.println(method);
        }
输出如下:
cc.getMethods()
public java.lang.String com.sankuai.lkl.reflect.Student.getName()
public void com.sankuai.lkl.reflect.Student.setName(java.lang.String)
public java.lang.String com.sankuai.lkl.reflect.Student.getId()
public void com.sankuai.lkl.reflect.Student.read()
public static void com.sankuai.lkl.reflect.Student.update()
public void com.sankuai.lkl.reflect.Student.setId(java.lang.String)
public java.lang.String com.sankuai.lkl.reflect.Student.getAge()
public void com.sankuai.lkl.reflect.Student.setAge(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
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()


cc.getDeclaredMethods()
public java.lang.String com.sankuai.lkl.reflect.Student.getName()
public void com.sankuai.lkl.reflect.Student.setName(java.lang.String)
public java.lang.String com.sankuai.lkl.reflect.Student.getId()
public void com.sankuai.lkl.reflect.Student.read()
public static void com.sankuai.lkl.reflect.Student.update()
public void com.sankuai.lkl.reflect.Student.setId(java.lang.String)
public java.lang.String com.sankuai.lkl.reflect.Student.getAge()
public void com.sankuai.lkl.reflect.Student.setAge(java.lang.String)

除了上面的批量方法,也有通过名称和参数来获取单个Method对象的方法:
这里写图片描述
分别是getMethod()和getDeclaredMethod(),这里需要注意的是光通过名字是可能无法唯一确定一个方法的,所以这里还需要传入参数,两个方法都是传入参数的Class对象数组,注意int.class和Integer.class分别对应到参数中的int和Integer类型,不能通用。

获取类的所有构造器
在展示Class对象的方法之前,先将Person类进行改造:将其改造成抽象类,并增加两个构造器。Student仍然是Person类的子类。

public abstract class Person {

    static String test = "test";

    private int age;

    public abstract void read();

    private Person() {
    }

    private Person(int age) {
        this.age = age;
    }
}

下面是具体方法和输出结果

  Class<?> cc = Student.class;
        //获取所有构造器
        Constructor<?>[] constructors = cc.getConstructors();
        System.out.println("cc.getConstructors()");
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("\n");

        Constructor<?>[] constructors1 = cc.getDeclaredConstructors();
        System.out.println("getDeclaredConstructors()");
        for (Constructor<?> constructor : constructors1)                 
        {
            System.out.println(constructor);
        }

输出:
cc.getConstructors()
public com.sankuai.lkl.reflect.Student(java.lang.String,java.lang.String)
public com.sankuai.lkl.reflect.Student(java.lang.String)
public com.sankuai.lkl.reflect.Student()


getDeclaredConstructors()
public com.sankuai.lkl.reflect.Student(java.lang.String,java.lang.String)
public com.sankuai.lkl.reflect.Student(java.lang.String)
public com.sankuai.lkl.reflect.Student()

这次看起来倒是两个方法表现一致,所有私有的构造器都没查找出来。对于构造器来说也有根据名字和参数获取类单个构造器Constructor对象的方法。

通过Class获取类继承的父类和实现的接口的Class对象:

  Class<?> cc = Student.class;
        //获取该类实现了的所有接口
        Class<?>[] ccs = cc.getInterfaces();
        System.out.println("getInterfaces");
        for(Class<?> c : ccs){
            System.out.println(c);
        }

        System.out.println("\n");

        //获取该类的直接父类,如果没有显示声明的父类,则是Object
        Class<?> cc1 = cc.getSuperclass();
        System.out.println(cc1);

通过Class对象判断当前类是不是接口并创建类的实例:

        Class<?> cc = Student.class;
        if (!cc.isInterface()) {
            //调用Class对象的newInstance创建对象,要求类必须具有无参构造器
            //并且创建出来的对象是Object类型,必须强制转型
            Student student = (Student) cc.newInstance();
            System.out.println(cc);
        }

 输出:
 com.sankuai.lkl.reflect.Student@19501026

通过上面几个方法的介绍,大致上明白了Class类的作用,下面看下它是如何和反射来结合的。

2.Java反射机制

通过Class对象可以拿到类的类型信息,方法、属性、构造器;但是如果只能拿到这些信息,没有办法对其进行操作其实意义也不大。Java的反射机制正是提供了对上面获取的Method、Field、Constructor等进行操作的能力。其实这些类本身就属于java.lang.reflect包下的一部分,都实现了Member接口。下面来看下如何对它们进行操作。

2.1通过Field改变对象属性

public static void test1() throws Exception{
      try{
          Class<?> cc = Class.forName("com.sankuai.lkl.reflect.Student");
          Object object =  cc.newInstance();

          Field field = cc.getDeclaredField("id");
          //id是private类型的,如果没有下面的设置,会抛出IllegalAccessException
          field.setAccessible(true);
          field.set(object,"12313");
          System.out.println(((Student)object).getId());
      }catch (ClassNotFoundException e){
          e.printStackTrace();
      }
    }
    //输出:12313

上面的id属性是私有的,但是通过将其对应Field的accessible设置为true,就可以对其进行修改了,这其实破坏了Java的封装。

2.2通过Method来调用对象的方法

 public static void test1() throws Exception{
      try{
          Class<?> cc = Class.forName("com.sankuai.lkl.reflect.Student");
          Object object =  cc.newInstance();
          Method method = cc.getDeclaredMethod("setName",String.class);
          method.invoke(object,"testName");
          Method method1 = cc.getDeclaredMethod("getName");
          //将getName()方法设置成private,但将accessible设置为true之后,仍然可以方法
          method1.setAccessible(true);
          System.out.println(method1.invoke(object));
      }catch (ClassNotFoundException e){
          e.printStackTrace();
      }
    }
   //输出:testName

上面展示了通过Class对象获得一个类实例,然后通过Method来操作这个类实例方法的过程。需要注意的是调用Class对象的getDeclaredMethod方法时,如果是想获得一个有参数的方法的Method对象,那么是需要传入参数类型的Class对象的,这里对顺序是有严格要求的并且int.class和Integer.class是不一样的,对其它基本类型也是同样的情况。并且和Field一样,只要将accessible设置为true,就可以实现对私有方法的调用。

2.3通过Constructor来构造对象

 public static void test1() throws Exception {
        try {
            Class<?> cc = Class.forName("com.sankuai.lkl.reflect.Student");
            Constructor<?> constructor = cc.getDeclaredConstructor();
            System.out.println(constructor.newInstance());
            Constructor<?> constructor1 = cc.getConstructor(int.class, String.class);
            constructor1.setAccessible(true);
            Student student = (Student) constructor1.newInstance(1, "2321");
            System.out.println(student.getId() + " " + student.getName());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

输出:
com.sankuai.lkl.reflect.Student@24e5ddd0
1 2321

看起来构造器调用的形式和方法有些类似,不同的只是不再需要指定名字。并且对于私有构造器,也可以通过设置accessible为true来进行访问。

总的来说反射机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。
Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。

  • 10
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值