Java变成动态语言啦--注解和反射整合使用 | Java注解和反射系列(三)

1,610 阅读5分钟

这是我参与更文挑战的第20天,活动详情查看: 更文挑战


相关文章

Java注解和反射系列:Java注解和反射系列


前言

前面我们讲了反射对象的获取和类的加载过程,接下来我们得讲下如何使用反射的class对象!

一、获取运行时类的结构

通过反射可以获取运行时类的完整结构:

①FIeld(属性)

②Method(方法)

③Constructor(构造器)

④Superclass(超类)

⑤Interface(接口)

⑥Annotation(注解)

示例代码如下: Person类:

import lombok.Data;

@Data
public class Person extends Object{
    private String name;
    private int high;
    private String like;
    public Integer age;

    public Person(){
    }

    private void priTest(String name){

    }
    public Person(String name){

    }
}

测试代码:

public class test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
        Class c1 = Class.forName("com.dbright.Java反射.Person");//forName获取
        Person person = new Person();
        Class c2 = person.getClass();//对象.class获取
        Class c3 = Person.class;//类.对象

        //获取类的名字
        System.out.println("c1"+c1.getName());
        System.out.println("c2"+c2.getName());
        System.out.println("c3"+c3.getName());

        System.out.println("简单:"+c1.getSimpleName());

        System.out.println("* ****************************************************");
        //获取类的属性
        Field[] fields = c1.getFields();//只能获取public属性
        for (Field field : fields) {
            System.out.println("getFields:"+field);
        }
        Field[] fields1 = c1.getDeclaredFields();//获取所有属性
        for (Field field : fields1) {
            System.out.println("getDeclaredFields:"+field);
        }
        //获取指定名称的属性
        System.out.println("getDeclaredField:"+c1.getDeclaredField("name"));

        System.out.println("* ****************************************************");
        //获得类的方法--包含父类的方法
        Method[] methods = c1.getMethods();//只能获得公共方法
        for (Method method : methods) {
            System.out.println("getMethods:"+method);
        }
        Method[] methods1 = c1.getDeclaredMethods();//获得所有方法
        for (Method method : methods1) {
            System.out.println("getDeclaredMethods"+method);
        }
        //获得指定方法
        Method method = c1.getMethod("getName",null);//无参直接传个null即可
        Method method1 = c1.getMethod("setName", String.class);//带参传参数的对象即可
        System.out.println("获得指定方法1:"+method);
        System.out.println("获得指定方法2:"+method1);


        System.out.println("* ****************************************************");
        //获得构造器,注释不再加,和上面一致,公共和私有
        Constructor[] constructors = c1.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("getConstructors:"+constructor);
        }
        Constructor[] constructors1 = c1.getDeclaredConstructors();
        for (Constructor constructor : constructors1) {
            System.out.println("getDeclaredConstructors:"+constructor);
        }
        //获取指定的构造器--根据参数来确定是哪一个构造器
        System.out.println("获取指定的构造器:"+c1.getConstructor());
        System.out.println("获取指定的构造器:"+c1.getConstructor(String.class));
    }
}

执行结果:

在这里插入图片描述 在这里插入图片描述

二、动态创建对象执行方法

上面介绍了如何通过反射获取类的属性、方法和构造器等。那么如何动态的创建对象并且 执行 方法呢?

1、创建类的对象:调用Class对象的newInstance()方法

  • 类必须有一个无参构造器
  • 类的构造器的访问权限需要足够

2、只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作

  • 通过Class类的getDeclaredConstructor(Classs … parameterTypes)取得本类的指定形参类型的构造器
  • 像构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
  • 通过Constructor实例化对象

3、调用指定的方法:通过反射调用类中的方法,通过Method类完成

  • 通过Class类的getMethod(String name,Class … parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型
  • 之后使用Object invoke(Object obj,Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息

4、Object invoke(Object obj, Object … args)

  • Object对应原方法的返回值,若原方法无返回值,此时返回null
  • 若原方法为静态方法,此时形参Object obj可为null
  • 若原方法形参列表为空,则Object[] args为null
  • 若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法

5、setAccessible

  • Method和Field、Constructor对象都有setAccessible()方法
  • setAccessible作用是启动和禁止访问安全检查的开关
  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调 用,那么请设置为true
    • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施Java语言访问检查

代码示例: 前置代码:

import lombok.Data;

@Data
public class Person extends Object{
    private String name;
    private int high;
    private String like;
    public Integer age;

    public Person(){
    }

    private void priTest(String name){

    }
    public Person(String name){
        this.name = name;
    }

}

测试代码:

/**
 * 通过反射动态的创建对象
 */
public class Test01 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {

        //获得class对象
        Class c = Class.forName("com.dbright.Java反射.Person");

        //构造一个对象,本质上是调用了无参构造器
        Person person = (Person) c.newInstance();
        System.out.println("person:"+person);

        //通过有参构造器创建对象
        Constructor constructor = c.getDeclaredConstructor(String.class);
        Person person1 = (Person) constructor.newInstance("ding");//需要重写tostring方法
        System.out.println("person1:"+person1);

        //通过反射获取和操作方法
        System.out.println("************************************************************");
        Person person2 = (Person) c.newInstance();
        Method setLike = c.getDeclaredMethod("setLike", String.class);
        setLike.invoke(person2,"钓鱼");
        System.out.println("person2:"+person2);

        //通过反射操作属性
        System.out.println("************************************************************");
        Person person3 = (Person) c.newInstance();
        Field name = c.getDeclaredField("name");

        //关掉权限检测:不能直接操作私有属性,我们需要关闭程序的安全检测,属性或者方法
        name.setAccessible(true);

        name.set(person3,"丁大大");
        System.out.println("name:"+name.getName());

    }
}

执行结果:

在这里插入图片描述

      • 性能测试:

那么这两种方法有什么区别呢?

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

    /**
     *普通方式调用
     */
    public static void test1(){
        Person person = new Person();

        //获取当前系统时间
        long startTime = System.currentTimeMillis();
        for (int i=0; i<2000000000; i++){
            person.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式调用所需时间:"+(endTime-startTime)+"ms");
    }
    /**
     *反射方式调用
     */
    public static void test2() throws NoSuchMethodException {
        Person person = new Person();
        Class c = person.getClass();
        Method getName = c.getDeclaredMethod("getName",null);
        //获取当前系统时间
        long startTime = System.currentTimeMillis();
        for (int i=0; i<2000000000; i++){
            getName.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式调用所需时间:"+(endTime-startTime)+"ms");
    }
    /**
     *反射方式调用 关闭检测
     */
    public static void test3() throws NoSuchMethodException {
        Person person = new Person();
        Class c = person.getClass();
        Method getName = c.getDeclaredMethod("getName",null);
        //获取当前系统时间
        long startTime = System.currentTimeMillis();
        getName.setAccessible(true);//关闭安全检测
        for (int i=0; i<2000000000; i++){
            getName.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式调用所需时间:"+(endTime-startTime)+"ms");
    }

}

执行结果如下:

在这里插入图片描述

结论: 使用反射调用时效率比较低,如果需要频繁使用反射调用的前提,请关闭安全检测!!!

三、反射操作泛型
  • Java采用泛型擦出的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除
  • 为了通过反射操作这些类型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型
    • ParameterizedType:表示一种参数化类型,比如Collection
    • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
    • TypeVariable:是各种类型变量的公共夫接口
    • WildcardType:代表一种通配符类型表达式

测试代码如下:

public class Test03 {
    //通过反射获取泛型
        public void test01(Map<String, Person> map, List<Person> list){
            System.out.println("test01");
        }

        public Map<String, Person> test02(){
            System.out.println("test02");
            return null;
        }

        public static void main(String[] args) throws NoSuchMethodException {
            Method method = Test03.class.getMethod("test01", Map.class, List.class);

            Type[] geners = method.getGenericParameterTypes();

            for (Type gener : geners) {
                System.out.println("*" + gener);
                if (gener instanceof ParameterizedType){
                    Type[] actuals = ((ParameterizedType)gener).getActualTypeArguments();
                    for (Type actual : actuals) {
                        System.out.println(actual);
                    }
                }
            }

            method = Test03.class.getMethod("test02", null);
            Type geners1 = method.getGenericReturnType();

            if (geners1 instanceof ParameterizedType){
                Type[] actuals = ((ParameterizedType)geners1).getActualTypeArguments();
                for (Type actual : actuals) {
                    System.out.println(actual);
                }
            }
        }
}

执行结果:

在这里插入图片描述

四、反射操作注解

前面我们学习了注解,接下来就看下注解和反射怎么使用?

public class Test04 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c = Person1.class;

        //通过反射获取注解
        Annotation[] annotations = c.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println("annotation:"+annotation);
        }

        //获取注解的value的值
        classAno classAno1 = (classAno) c.getAnnotation(classAno.class);
        String value = classAno1.value();
        System.out.println("value:"+value);

        //获得类的指定注解
        Field field = c.getDeclaredField("name");
        methodAno methodAno1 = field.getAnnotation(methodAno.class);
        System.out.println("name:"+methodAno1.name());
        System.out.println("type:"+methodAno1.type());
        System.out.println("length:"+methodAno1.length());
    }
}

/**
 * 测试类
 */
@Data
@classAno("Person1")
class Person1{
    @methodAno(name = "丁大大",type = "超人类",length = 2)
    private String name;
    private int high;
    private String like;
    public Integer age;
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface classAno{
    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface methodAno{
    String name();
    String type();
    int length();
}

执行结果:

在这里插入图片描述

五、总结:

第一遍看完可能有点懵,多看几遍加深理解吧! 在实际项目中多多使用,看看框架的底层加强自身的理解,冰冻三尺非一日之寒,有个大概的理解和印象即可!后面在实际中逐渐加强理解,这样才能融会贯通!


路漫漫其修远兮,吾必将上下求索~

如果你认为i博主写的不错!写作不易,请点赞、关注、评论给博主一个鼓励吧~hahah