Java反射机制

认识class类

  • class类是Java中所有类的一个映射,在反射中通过java.lang.Class这个类来对所有类进行操作。
  • 一般如果我们使用一个学生类student,我们要先对该类使用new进行实例化,但是有了class类后,我们可以通过class类来使用student类的成员变量和方法。这就是反射的思想。
  • 与class类类似还有Constructor 类是对所有构造方法的映射,Method 类是对所有成员方法的映射,Field 类是对所有成员变量的映射。

反射概述

  • 反射机制是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息去创建对象并使用,反射是一种解释的操作,是丢失类型的。

  • 由于这种动态性,反射机制可以让Java程序 在运行期 动态扩展,不用受到静态编译的限制。

  • 比如我们new 一个对象的时候,系统默认会将对应的class文件加载到内存,因为程序是死的,所以这个过程在编译时期要加载哪些class文件就确定了。而使用反射可以在运行时期灵活将一个class文件加载到内存去使用 不用受静态编译的限制。

  • 如下代码,在编译时 Persion类就必须在 classpath路径中存在,所以Persion受到了静态编译的限制。
    Persion p = new Persion();

  • 而如下代码,在编译时Persion类可以完全不存在,只需要在真正执行时在classpath目录即可。
    Class class1 = Class.forName(“com.demo.Persion”);

  • 但是使用反射的性能不如直接创建对象,因为反射是一种解释动作,要慢于直接代码。

反射的应用

  • 编写一个使用接口的应用程序,可以等到运行时再指定其实际的实现。比如编写一个Persion接口,在运行期间再去决定是加载 Persionimpl1实现类使用,还是加载 Persionimpl2实现类使用。
  • 反射机制的应用有, SPI机制,代码热加载,自定义类加载器,spring IOC容器 等等。

反射调用时类型丢失问题

  • 如下代码属于标准反射调用,这种方法是丢失所有类型信息的
public class Test {
    public static void main(String[] args) throws Exception {
        Class class1 = Class.forName("com.demo.Student");
        Object obj1 = class1.newInstance();
        Method method = class1.getMethod("eat");
        method.invoke(obj1);
    }
}
class Student{
    public void eat(){
        System.out.println("eat run");
    }
}
  • 我们也可以这样来编写代码进行类型强转,但是这种方式是默认在classpath中有Student这个类的
Class class1 = Class.forName("com.demo.Student");
Student obj1 = (Student) class1.newInstance();
obj1.eat();
  • 如果 Student类在编译时还不在classpath路径或者 Student这个类在第三方目录 由自定义类加载器加载,那么就无法使用强转了,因为类型信息是缺失的。
  • 换句话说,只有Student这个类和当前程序是静态编译的才能使用类型强转,如果不是静态编译的无法进行类型强转,虽然丢失了原本类型,但是也增加了灵活性,在编译项目时Student这个类完全可以不存在。
  • 这时候我们可以定义一个接口,比如 Persion接口,让第三方目录的Student实现Persion接口,反射调用Student类,然后强转为Persion类型使用。
Class class1 = Class.forName("com.demo.Student");
Persion per = (Persion) class1.newInstance();
per.eat();
  • 虽然这时候实现了静态编译的解耦,上层不需要知道下层的类型,但是上层还是必须知道下层类的全类名(com.demo.Student)。
  • 可以使用SPI机制来实现完全的解耦,上层连下层的全类名也不需要知道,直接 通过 Persion这个接口从指定环境中寻找Persion的实现类来使用,上层只知道它可以向上转型为Persion类型即可。
  • 这样就实现了面向接口编程,SPI自动根据接口进行服务发现。而且下层的实现类可以随时替换为别的比如Teacher类,这对上层的程序是透明的。
  • 具体可以看jvm类加载部分的SPI机制。

反射调用与直接调用性能比较

  • 方法1 直接调用
  • 方法2 加载类创建对象,强转,直接调用
  • 方法3 标准反射调用
public class Test {
    public static void main(String[] args) throws Exception {
    }

    public static void fun1(){
         new Student().eat();
    }

    public static void fun2() throws Exception {
        Class class1 = Class.forName("com.demo.Student");
        Student obj1 = (Student) class1.newInstance();
        obj1.eat();
    }

    public static void fun3() throws Exception {
        Class class1 = Class.forName("com.demo.Student");
        Object obj1 = class1.newInstance();
        Method method = class1.getMethod("eat");
        method.invoke(obj1);
    }
}
class Student{
    public void eat(){
        System.out.println("eat run");
    }
}
  • 三个方法分别执行10万次速度比较
fun1  364.2 ms
fun2  424.4 ms
fun3  469.0 ms

获取class类的对象

  • 要想通过反射去使用一个类,首先要获取到该类的字节码文件对象,也就是类型为Class类型的对象。
//2使用类的class属性来获取该类对应的Class对象
Class c1 = student.class;
//1调用对象的getClass方法来获取对象所属类的class对象
student s = new student("张三");
Class c2 = s.getClass();
//3使用Class类的静态方法forName(String classname)来获取类的class对象。
//该方法要传入代表一共类完整路径的字符串。
Class c3 = Class.forName("study.student");

通过反射的方式创建对象并使用

  • 然后加载该类获取无参构造方法并创建对象来使用
//获取Dog类对应的字节码文件对象
Class c = Class.forName("study.Dog");
//获取对应类的公共无参构造方法
Constructor<?> con1 = c.getConstructor();
//根据这个构造方法创建类的实例化对象
Object obj = con1.newInstance();
Dog dog = (Dog)obj;
//直接使用对象的成员和方法
System.out.println(dog.name);
dog.show();
  • 需要注意的是以下方式创建对象,必须保证Persion类有无参构造才行,否则无法创建对象
Persion.class.newInstance();

通过反射访问对象的指定成员

  • 反射访问dog对象的name字段
//获取Dog类对应的字节码文件对象
Class c = Class.forName("study.Dog");
//获取对应类的公共无参构造方法
Constructor<?> con1 = c.getConstructor();
//根据这个构造方法创建类的实例化对象
Object obj = con1.newInstance();

//获取表示成员变量的对象 只能获取公共权限
Field f1 = c.getField("name");
//访问对象obj的name字段
String name = (String)f1.get(obj);
System.out.println(name);
  • 反射调用dog对象的指定方法
//获取Dog类对应的字节码文件对象
Class c = Class.forName("study.Dog");
//获取对应类的公共无参构造方法
Constructor<?> con1 = c.getConstructor();
//根据这个构造方法创建类的实例化对象
Object obj = con1.newInstance();

//获取表示成员方法的对象 只能获取公共权限
Method m1 = c.getMethod("show");
Method m2 = c.getMethod("eat",String.class);
//调用对象show方法
m1.invoke(obj); //小黄今年3岁了
//调用对象eat方法
m2.invoke(obj,"苹果"); //小黄正在吃苹果
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值