Java反射机制详解

什么是反射

  • 反射是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API 取的任何类的内部信息,并能够操作任意对象的内部属性和方法。
  • 加载完类之后再堆内存的方法区会产生一个class类型的对象,这个对象包含了完整的类结构信息。这个对象就像一面镜子,透过这个对象可以看到类的结构,所以形象的将它称之为反射。

反射的过程与正常的执行过程相反。

  • 正常方式 :引入包名和类名=>通过new实例化=>取得实例化对象
  • 反射方式:实例化对象=>获得getClass()方法=>得到包名和类名

反射相关的API

java.lang.Class : 代表一个类

java.lang.reflect.Method :代表类的方法

java.lang.reflect.Field :代表类的成员变量

java.lang.reflect.Constructor :代表类的构造器

……

在Object类中定义了如下方法,此方法被所有类继承

public final Class getClass()

获得Class对象的方法

//已知全类名
Class c = Class.forName("com.example.reflect.User");
//已知某个类的实例
Class c = user.getClass();
//已知具体的类
Class c = User.class;
//获得父类Class
Class c0 = c.getSuperclass();

哪些类型拥有class对象

Class c1 = Object.class;        //类
Class c2 = Comparable.class;    //接口
Class c3 = String[].class;      //一维数组
Class c4 = int[][].class;       //二维数组
Class c5 = Override.class;      //注解
Class c6 = ElementType.class;   //枚举
Class c7 = Integer.class;       //基本数据类型
Class c8 = void.class;          //void
Class c9 = Class.class;         //class

只要元素类型一样,就是同一个class

类的加载过程

  • 类的加载:将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程由类加载器完成(生成Class对象)
  • 类的链接:将类的数据合并到jre中(为类变量static静态代码块分配内存)
  • 类的初始化:JVM对类进行初始化(执行类构造器()方法为对象赋值)

类的运行时结构

测试类:User

package com.example.reflect;
class User{
    private String name;
    private int id;
    private int age;

    public User() {
    }

    public User(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }
}

测试:通过反射获取class对象里的内容

public class GetClassInfo {
    public static void main(String[] args) throws Exception{
		//获取类的三种方法
//        Class c = Class.forName("com.example.reflect.User");

        Class c = User.class;

//        User user = new User();
//        Class c = user.getClass();

        //获取包名加类名
        System.out.println(c.getName());
        //获得类名
        System.out.println(c.getSimpleName());
        //获得类的属性
        System.out.println("--------------------------------分割线(类的公共属性)---------------------------------");

        Field[] fields = c.getFields();        //只能获取public属性
        for (Field field : fields) {
            System.out.println("类的公共属性:"+field);
        }
        System.out.println("--------------------------------分割线(类的全部属性)---------------------------------");
        fields = c.getDeclaredFields();  //可以获取全部属性
        for (Field field : fields) {
            System.out.println("类的全部属性:"+field);
        }
        System.out.println("--------------------------------分割线(类的特定属性)---------------------------------");
        System.out.println("类的特定属性:"+c.getDeclaredField("name"));
        System.out.println("--------------------------------分割线(本类以及父类的公共方法)---------------------------------");
        Method[] methods = c.getMethods();
        for (Method method : methods) {
            System.out.println("本类以及父类的公共方法:"+method);
        }
        System.out.println("--------------------------------分割线(本类的全部方法)---------------------------------");
        methods = c.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println("本类的全部方法:"+method);
        }
        System.out.println("--------------------------------分割线(本类的特定方法)---------------------------------");
        System.out.println("本类的特定方法:"+c.getDeclaredMethod("getName", null));
        System.out.println("本类的特定方法:"+c.getDeclaredMethod("setName", String.class));
        System.out.println("--------------------------------分割线(获得公共构造器)---------------------------------");
        Constructor[] constructors = c.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("公共构造器:"+constructor);
        }
        System.out.println("--------------------------------分割线(获得全部构造器)---------------------------------");
        constructors = c.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("全部构造器:"+constructor);
        }
        System.out.println("--------------------------------分割线(获得指定构造器)---------------------------------");
        System.out.println("有参构造"+c.getConstructor(String.class, int.class, int.class));
        System.out.println("无参构造"+c.getConstructor());
    }
}

输出结果:

类的包名加类名:com.example.reflect.User
类名:User
--------------------------------分割线(类的公共属性)---------------------------------
--------------------------------分割线(类的全部属性)---------------------------------
类的全部属性:private java.lang.String com.example.reflect.User.name
类的全部属性:private int com.example.reflect.User.id
类的全部属性:private int com.example.reflect.User.age
--------------------------------分割线(类的特定属性)---------------------------------
类的特定属性:private java.lang.String com.example.reflect.User.name
--------------------------------分割线(本类以及父类的公共方法)---------------------------------
本类以及父类的公共方法:public java.lang.String com.example.reflect.User.toString()
本类以及父类的公共方法:public java.lang.String com.example.reflect.User.getName()
本类以及父类的公共方法:public int com.example.reflect.User.getId()
本类以及父类的公共方法:public void com.example.reflect.User.setName(java.lang.String)
本类以及父类的公共方法:public void com.example.reflect.User.setAge(int)
本类以及父类的公共方法:public void com.example.reflect.User.setId(int)
本类以及父类的公共方法:public int com.example.reflect.User.getAge()
本类以及父类的公共方法:public final void java.lang.Object.wait() throws java.lang.InterruptedException
本类以及父类的公共方法: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 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 com.example.reflect.User.toString()
本类的全部方法:public java.lang.String com.example.reflect.User.getName()
本类的全部方法:public int com.example.reflect.User.getId()
本类的全部方法:public void com.example.reflect.User.setName(java.lang.String)
本类的全部方法:public void com.example.reflect.User.setAge(int)
本类的全部方法:public void com.example.reflect.User.setId(int)
本类的全部方法:public int com.example.reflect.User.getAge()
--------------------------------分割线(本类的特定方法)---------------------------------
本类的特定方法:public java.lang.String com.example.reflect.User.getName()
本类的特定方法:public void com.example.reflect.User.setName(java.lang.String)
--------------------------------分割线(获得公共构造器)---------------------------------
公共构造器:public com.example.reflect.User()
公共构造器:public com.example.reflect.User(java.lang.String,int,int)
--------------------------------分割线(获得全部构造器)---------------------------------
全部构造器:public com.example.reflect.User()
全部构造器:public com.example.reflect.User(java.lang.String,int,int)
--------------------------------分割线(获得指定构造器)---------------------------------
有参构造public com.example.reflect.User(java.lang.String,int,int)
无参构造public com.example.reflect.User()

class的使用

获得class对象以后如何才能使用它呢?

  • 首先获得类的class对象
//获得class对象
Class c = Class.forName("com.example.reflect.User");
  • 通过无参构造得到对象
//通过无参构造器创建对象
User user = (User) c.newInstance();   //本质是调用了类的无参构造器,没有无法构造无法创建
System.out.println(user);
  • 通过有参构造得到对象
//通过有参构造器创建对象
Constructor constructor = c.getDeclaredConstructor(String.class, int.class, int.class);
User user = (User) constructor.newInstance("香草拿铁",  8, 18);
  • 通过反射操作方法
//通过无参构造器创建对象
User user = (User) c.newInstance();
//获取setName方法
Method setName = c.getDeclaredMethod("setName", String.class);
//调用invoke方法给方法传递参数(对象,方法参数)
setName.invoke(user,"香草拿铁");

System.out.println(user.getName());
  • 通过反射操作属性
//通过无参构造器创建对象
User user = (User) c.newInstance();
//获取name属性
Field name = c.getDeclaredField("name");
//由于属性是private属性,因此需要添加权限才能修改
name.setAccessible(true);
//给name属性赋值
name.set(user,"香草拿铁");

System.out.println(user.getName());

普通方式和反射方式性能对比

反射机制虽然功能强大,但在性能方面消耗资源的也比较多

测试同时调用一个方法使用的时间对比

public class TimeTest {
    //普通方式调用
    public static void test1(){
        User user = new User();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        long time = endTime-startTime;
        System.out.println("普通方式执行时间:"+time+"ms");
    }
    //反射方式调用
    public static void test2() throws Exception {
        User user = new User();
        Class c = user.getClass();
        Method getName = c.getMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        long time = endTime-startTime;
        System.out.println("反射方式执行时间:"+time+"ms");
    }

    //反射方式调用(关闭检测)
    public static void test3() throws Exception {
        User user = new User();
        Class c = user.getClass();
        Method getName = c.getMethod("getName", null);
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        long time = endTime-startTime;
        System.out.println("反射方式(关闭检测)执行时间:"+time+"ms");
    }

    public static void main(String[] args) throws Exception {
        test1();
        test2();
        test3();
    }
}

输出结果分析:

普通方式执行时间:4ms
反射方式执行时间:2679ms
反射方式(关闭检测)执行时间:1211ms

很显然,使用反射机制调用时间更长,使用 反射获取的对象.setAccessible(true); 关闭检测可以适当提高性能,但却仍比普通方式要慢很多,因此一般情况下不建议大量使用反射机制。

通过反射操作泛型

通过getGenericParameterTypes()和getGenericReturnType()方法可以获取参数和结果中的泛型信息

public class Test {
    public void test01(Map<String,User> map, List<String> list){
        System.out.println("test01");
    }
    public Map<String,User> test02(){
        System.out.println("test02");
        return null;
    }

    public static void main(String[] args) throws Exception {
        Class c = MyTest.class;

        Method method01 = c.getMethod("test01", Map.class, List.class);
        Type[] genericParameterTypes = method01.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("参数值泛型信息:"+genericParameterType);
            if (genericParameterType instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("参数值泛型内部参数信息:"+actualTypeArgument);
                }
            }
        }
        System.out.println("-----------------------------------------我是分割线---------------------------------------------");
        Method method02 = c.getMethod("test02", null);
        Type genericReturnType = method02.getGenericReturnType();
        System.out.println("返回值泛型信息:"+genericReturnType);
        if (genericReturnType instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println("返回值泛型内部参数信息:"+actualTypeArgument);
            }
        }
    }
}

输出结果:打印出Map和List的信息,以及Map和List内部参数信息

参数值泛型信息:java.util.Map<java.lang.String, com.example.reflect.User>
参数值泛型内部参数信息:class java.lang.String
参数值泛型内部参数信息:class com.example.reflect.User
参数值泛型信息:java.util.List<java.lang.String>
参数值泛型内部参数信息:class java.lang.String
-----------------------------------------我是分割线---------------------------------------------
返回值泛型信息:java.util.Map<java.lang.String, com.example.reflect.User>
返回值泛型内部参数信息:class java.lang.String
返回值泛型内部参数信息:class com.example.reflect.User

通过反射操作注解

通过反射我们还可以获得自定义的一些注解的值。

  • 实现两个注解,分别为类注解与属性注解
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table{
    String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Filed{
    String columnName();
    String type();
    int length();
}
  • 测试类
@Table("db.user")
class User{
    @Filed(columnName = "db_id",type = "int",length = 10)
    private int id;
    @Filed(columnName = "db_age",type = "int",length = 10)
    private int age;
    @Filed(columnName = "db_name",type = "varchar",length = 3)
    private String name;

    public User() {
    }

    public User(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
  • 获取注解中的值进行操作
public class TestAnnotation {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("com.example.reflect.User");
        Annotation[] annotations = c.getAnnotations();
        //通过反射获得注解
        for (Annotation annotation : annotations) {
            System.out.println("类注解对象:"+annotation);
        }

        //获得注解的value值
        Table table = (Table) c.getAnnotation(Table.class);
        System.out.println("类注解的值:"+table.value());

        //获取特定属性的注解值
        Field[] declaredFields = c.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("--------------------------属性名:"+declaredField.getName()+"----------------------------------");
            Filed annotation = declaredField.getAnnotation(Filed.class);
            System.out.println("注解columnName:"+annotation.columnName());
            System.out.println("注解type:"+annotation.type());
            System.out.println("注解length:"+annotation.length());
        }
    }
}
  • 输出结果

类注解对象:@com.example.reflect.Table(value=db.user)
类注解的值:db.user
--------------------------属性名:id----------------------------------
注解columnName:db_id
注解type:int
注解length:10
--------------------------属性名:age----------------------------------
注解columnName:db_age
注解type:int
注解length:10
--------------------------属性名:name----------------------------------
注解columnName:db_name
注解type:varchar
注解length:3

使用这种方式就可以通过反射获取一些注解的信息,从而进行数据的操作,这种方式被大量运用在框架当中。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值