Java反射机制(Reflection)

反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

1. 基本概念

反射把Java类中的各种结构(方法、成员变量、构造器、类名)映射成为一个个Java对象(在运行期)

反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分

 	Class c = Class.forName("cn.javareflection.Student")

此代码可以将Student类实时、动态地加载到程序中。加载完成后,在堆内存中就产生了一个Class类型的对象,这个对象就包含了完整的类的结构信息。

好处:

  • 可以在程序运行过程中操作这些对象

  • 可以解耦,提高程序的可扩展性

  • 使得java语言具有动态特性


2. Class类

  • java.lang.Class类十分特殊,用来表示java中的类型(class、interface、enum、annotation、primitive type、void)本身

  • Class类是Reflection的根源。想动态加载运行的类,必须先获得相应的Class对象


3. 获取Class类对象的方式

方式一:对象.getClass()

  • Object中方法:类<?> getClass():返回此 Object的运行时类

方式二:类名.class()

方式三:Class.forName("包名.类名")

  • 此方式使用最多,推荐

  • Class类中方法:static 类<?> forName(String className):返回与给定字符串名称的类或接口相关联的类对象

注意:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都一样

示例代码

public class ReflectTest {
    public static void main(String[] args) throws ClassNotFoundException {
        // 三种方式
        // 1、 对象.getClass()
        Iphone iphone = new Iphone();
        Class clz1 = iphone.getClass();
        // 2、 类.class()
        Class clz2 = Iphone.class;
        // 3、 Class.forName("包名.类名")
        Class clz3 = Class.forName("cn.sxt_01.Iphone");

        // 同一个类只会加载一个Class对象
        System.out.println(clz1 == clz2);
        System.out.println(clz2 == clz3);

        int[] arr01 = new int[10];
        int[] arr02 = new int[30];
        int[][] arr = new int[10][30];
        System.out.println(arr01.getClass().hashCode());
        System.out.println(arr02.getClass().hashCode());
        System.out.println(arr.getClass().hashCode());
    }
}
class Iphone {
}

打印结果

true
true
2129789493
2129789493
668386784

打印结果说明:同一个类(同一维度)只会被加载一次,产生一个Class对象


4. Class类对象的功能

1、获取功能

应用反射的API,获取类的信息(类的名称、属性、方法、构造器)

获取类的名称描述
Class.getName()获取包名+类名
Classs.getSimpleName()只获取类名
获取成员变量描述
Field[] getFields()获取所有public修饰的成员变量,包括继承变量
Field getField(String name)获取指定名称的 public修饰的成员变量
Field[] getDeclaredFields()获取所有的成员变量,不考虑修饰符,可以获得私有
Field getDeclaredField(String name)获取指定的成员变量,不考虑修饰符,可以获得私有
获取构造方法描述
Constructor<?>[] getConstructors()获得所有的公共构造方法
Constructor<T> getConstructor(类名,参数列表)获得指定的公共构造方法
获取成员方法描述
方法[] getMethods()获取所有公共的方法,包括继承的方法
方法 getMethod(String name, 类<?>... parameterTypes)获得指定的公共成员方法

示例代码

先自定义公共类User

public class User {
    private int id;
    private int age;
    private String uname;

    //必须要有无参的构造方法!
    public User() {
    }
    public User(int id, int age, String uname) {
        super();
        this.id = id;
        this.age = age;
        this.uname = uname;
    }
    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 getUname() {
        return uname;
    }
    public void setUname(String uname) {
        this.uname = uname;
    }
    public void setUname() {
        this.uname = "张三";
    }
}

测试类

/**
 * 应用反射的API,获取类的信息(类的名称、属性、方法、构造器)
 */
public class ReflectionDemo {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("cn.sxt_01.Reflection.User");

            // 1、获取类的名称
            System.out.println(clazz.getName());    // 获得包名 + 类名
            System.out.println(clazz.getSimpleName());  // 获得类名

            // 2、获取属性信息
            Field[] fields = clazz.getDeclaredFields(); // 获得所有的field
            System.out.println(fields.length);
            for (Field temp : fields) {
                System.out.println("属性:" + temp);
            }

            // 3、获取方法信息
            Method[] methods = clazz.getDeclaredMethods();
            Method method1 = clazz.getMethod("getUname", null);
            Method method2 = clazz.getMethod("setUname", String.class);
            // 若方法有参,则必须传入参数类型对应的class对象。以便于区分重载的方法。
            for (Method method : methods) {
                System.out.println("方法:" + method);
            }

            // 4、获得构造器信息
            Constructor[] constructors = clazz.getDeclaredConstructors();   // 获得所有构造器
            for (Constructor constructor : constructors) {
                System.out.println("构造器:" + constructor);
            }
            Constructor constructor = clazz.getConstructor(int.class,
                    int.class, String.class);
            System.out.println("指定构造器:" + constructor);


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

打印结果

cn.sxt_01.Reflection.User
User
3
属性:private int cn.sxt_01.Reflection.User.id
属性:private int cn.sxt_01.Reflection.User.age
属性:private java.lang.String cn.sxt_01.Reflection.User.uname
方法:public int cn.sxt_01.Reflection.User.getId()
方法:public void cn.sxt_01.Reflection.User.setAge(int)
方法:public void cn.sxt_01.Reflection.User.setUname()
方法:public void cn.sxt_01.Reflection.User.setUname(java.lang.String)
方法:public java.lang.String cn.sxt_01.Reflection.User.getUname()
方法:public void cn.sxt_01.Reflection.User.setId(int)
方法:public int cn.sxt_01.Reflection.User.getAge()
构造器:public cn.sxt_01.Reflection.User()
构造器:public cn.sxt_01.Reflection.User(int,int,java.lang.String)
指定构造器:public cn.sxt_01.Reflection.User(int,int,java.lang.String)

2、反射调用

Field:提供有关类或接口的单个字段的信息和动态访问, 可以理解成 成员变量

反射新建实例描述
Class.newInstance()创建由此类对象表示的类的新实例
反射调用成员变量描述
Field.get(Object obj)返回指定对象上由此Field表示的字段的值
Field.set(Object obj, Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值
Field.setAccessible(true)忽略访问权限修饰符的安全检查,暴力反射,私有成员允许被访问
反射调用成员方法描述
Field.setAccessible(true)私有方法允许被访问
Object invoke(Object obj, Object... args)在具有指定参数的方法对象上调用此方法对象表示的底层方法

示例代码

public class ReflectionDemo02 {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("cn.sxt_01.Reflection.User");

            // 1、通过反射API动态调用构造方法,构造对象
            User user = (User) clazz.getDeclaredConstructor().newInstance();
            // 这里是调用了User的无参构造
            System.out.println(user);

            // 有参构造
            Constructor<User> c = clazz.getDeclaredConstructor(int.class, int.class, String.class);
            User user1 = c.newInstance(1001, 18, "张三");
            System.out.println(user1.getUname());

            // 2、通过反射API调用普通方法
            Method method = clazz.getDeclaredMethod("setUname", String.class);
            method.invoke(user1, "李四");
            System.out.println(user1.getUname());

            // 3、通过反射API操作属性
            Field f = clazz.getDeclaredField("uname");
            f.setAccessible(true);  // 暴力反射,使得可以操作私有属性
            f.set(user1, "王五");
            System.out.println(user1.getUname());

        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

打印结果

cn.sxt_01.Reflection.User@7ef20235
张三
李四
王五

5. 反射机制的性能问题

setAccessible

  • 启用和禁用访问安全检查的开关,值为true,则表示反射的对象在使用时应该取消Java语言访问检查。值为false,则指示反射的对象应该实施Java语言访问检查。

  • 禁止安全检查,可以提高反射的运行速度

示例代码

@SuppressWarnings("all")
public class Demo {
    public static void main(String[] args) throws Exception {
        test1();
        test2();
        test3();
    }

    public static void test1() {
        User user = new User();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000000L; i++) {
            user.getUname();
        }
        long end = System.currentTimeMillis();
        System.out.println("普通调用,执行10亿次,耗时:" + (end - start) + "ms");
    }

    public static void test2() throws Exception {
        User user = new User();
        Class clazz = user.getClass();
        Method method = clazz.getDeclaredMethod("getUname");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000000L; i++) {
            method.invoke(user, null);
        }
        long end = System.currentTimeMillis();
        System.out.println("反射动态方法调用,通过安全检查,执行10亿次,耗时:" + (end - start) + "ms");
    }

    public static void test3() throws Exception {
        User user = new User();
        Class clazz = user.getClass();
        Method method = clazz.getDeclaredMethod("getUname");
        method.setAccessible(true);		// 跳过安全检查
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000000L; i++) {
            method.invoke(user, null);
        }
        long end = System.currentTimeMillis();
        System.out.println("反射动态方法调用,不通过安全检查,执行10亿次,耗时:" + (end - start) + "ms");
    }
}

打印结果

普通调用,执行10亿次,耗时:920ms
反射动态方法调用,通过安全检查,执行10亿次,耗时:5332ms
反射动态方法调用,不通过安全检查,执行10亿次,耗时:2648ms

6. 反射操作泛型

Java采用泛型擦除机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除。

和反射+泛型有关的接口类型

  • java.lang.reflect.Type:java语言中所有类型的公共父接口
  • ParameterizedType:表示一种参数化的类型,比如Collection< String >
  • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable:是各种类型变量的公共父接口
  • WildcardType:代表一种通配符类型表达式,比如?、? extends Number、? super Integer。(wildcard是一个单词:就是”通配符“)

此部分内容用到情况较少,用到时可自行百度


7. 反射操作注解

可通过反射API获得相关注解信息

常用的相关方法说明
Class.getAnnotations()获得类的所有有效注解
Class.getAnnotation(类<A> annotationClass)获得指定注解信息

示例代码

public class AnnotationDemo {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("cn.sxt.annotation_03.Student");

            // 获得类的所有有效注解
            Annotation[] annotations = clazz.getAnnotations();
            for (Annotation a : annotations) {
                System.out.println(a);
            }

            // 获得指定注解
            myTable st = (myTable) clazz.getAnnotation(myTable.class);
            System.out.println(st.value());

            // 获得类的所有属性的注解
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                myField  myfield = field.getAnnotation(myField.class);
                System.out.println(myfield.columnName()+" - "+myfield.type()+" - "+myfield.length());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

imByte

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值