详细易懂的注解+反射入门2

137 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

本节概述

  这一节主要介绍反射的概念;如何利用反射获取类的方法和属性、调用类的方法或者修改类的属性值;类的加载过程;以及如何获取注解中的值。

什么是反射

  利用private关键字,可以阻止外部访问或者修改某些敏感值,来增加类的封闭性。但是这种方法并不是万无一失的。利用反射API,开发者可以获取到一个类内部的任何信息,并且对其进行任意操作,就像一个功能超级强大的金手指,你可以利用它在游戏里修改你想修改的任何东西。这很有趣,也很危险。

反射的基础操作

  首先介绍三种基础方法,可以用来获取到一个类(class)本身。

public class LearnReflect {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("Test");//传入包名和类名,注意你的项目结构
        Class c2 = new Test().getClass();
        Class c3=Test.class;
        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
    }
}
class Test{
    private int value1=1;
    public String value2="cyh";
}

  一个类本身只会被存储一次,上述三种方法得到的Class实际上是同一个。你可以比较它们的hash值来确定这一点。
  除了这三种基础方法,还可以利用getSuperClass来获取一个类的父类、getinterfaces来获取该类的接口等等,更多的函数用到时再介绍。

  拿到类以后,就可以利用反射获取它的所有信息

public class LearnReflect {
    public static void main(String[] args) throws ClassNotFoundException {
        Test t=new Test();
        Class c1=t.getClass();//获取t的类c1
        System.out.println(c1.getName());//输出类名
        for(Field field:c1.getDeclaredFields()){
            System.out.println(field);//输出c1的所有属性
        }
        System.out.println();
        for(Method method:c1.getDeclaredMethods()){
            System.out.println(method);//输出c1的所有方法
        }
    }
}
class Test{
    private int value1=1;
    public String value2="cyh";
    public void method1(){
        System.out.println("method1!");
    }
    private int method2(){
        return value1;
    }
}

  无论方法或属性是不是private的,都可以用反射来获得它,上面的演示中仅仅是把属性或者方法打印出来,实际上也可以修改属性的值,或者调用拿到的方法,看看下面的demo。

public class LearnReflect {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Test t=new Test();
        Class c =t.getClass();//获取t的类c1
        
        Method m1 = c.getMethod("method1");//传入要获得的方法名
        m1.invoke(t);

        Method m2= c.getDeclaredMethod("method2");//private方法的获取函数
        m2.setAccessible(true);//开启访问权限
        int a=(Integer)m2.invoke(t);
        System.out.println(a);

        Method m3= c.getDeclaredMethod("method3", String.class);//需要参数,按序传入参数的类
        m3.setAccessible(true);
        m3.invoke(t,"you changed value2!");
        System.out.println(t.value2);
    }
}
class Test{
    private int value1=1;
    public String value2="cyh";
    public void method1(){
        System.out.println("method1!");
    }
    private int method2(){
        return value1;
    }
    private void method3(String str){
        this.value2=str;
    }
}

  程序输出结果如下:

method1!
1
you changed value2!

  这段代码可以分为四部分:
  第一部分,从一个已经被创建的对象中获得它的类(除了getClass以外,还有获取类的两种方法,你可以自己尝试修改这一部分)。
  第二部分,利用getMethod获得了一个public的的方法m1,invoke中传递要使用该方法的对象t。等价于t调用m1方法。
  第三部分,利用getDeclaredMethod获得一个private的方法m2,开启访问权限,等价于t调用m2方法,返回一个Object类,强转为Integer再利用自动拆箱机制变成int输出。
  第四部分,和第三部分类似,但是方法m3需要参数,在getDeclaredMethod时要传入参数的类型(多个参数则依次传入各自的类型),invoke时要传入参数。

  上面演示了如何利用反射调用类中的方法,下面演示如何获取或修改类中的属性。

public class LearnReflect {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Test t=new Test();
        Class c =t.getClass();//获取t的类c1
        Field f1=c.getField("value2");
        System.out.println(f1.get(t));
        
        Field f2=c.getDeclaredField("value1");
        f2.setAccessible(true);
        f2.set(t,99);
        System.out.println(f2.get(t));
    }
}
class Test{
    private int value1=1;
    public String value2="cyh";
}

  第一部分中,利用getField方法拿到了public的属性域,再用get方法拿到t中对应域的值。
  第二部分中,拿到prvate的属性域以后,用set方法把t中的对应域的值设置为了99.
  下面是程序输出:

cyh
99

类的加载过程

  创建一个类时,首先类加载器会把class文件字节码内容加载到内存,生成一个对应的java.lang.Class对象,就是把产生对象的模具(类)制作出来了。
  之后将Java类的二进制代码合并到JVM中,这个过程会正式为类的静态变量分配内存并设置初始值。
  最后将常量引用(常量池中的数据)直接替换为地址。

获取注解中的参数

  这是上一节中没有解答的问题,我用一个实例来演示如何利用反射获取注解

public class LearnReflect {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Test t=new Test();
        Class c =t.getClass();//获取t的类c1
        myAnnotationForType anno1=(myAnnotationForType)c.getAnnotation(myAnnotationForType.class);
        System.out.println(anno1.name());
        System.out.println(anno1.num());
        
        Field f=c.getField("value2");
        myAnnotationForField anno2=(myAnnotationForField) f.getAnnotation(myAnnotationForField.class);
        System.out.println(anno2.name());
    }
}

@myAnnotationForType(name="type name",num=1)
class Test{
    private int value1=1;

    @myAnnotationForField(name="CYH")
    public String value2="cyh";
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface myAnnotationForField{
    String name();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface myAnnotationForType{
    String name();
    int num();
}

  首先定义两个注解,一个的作用域是类,另一个是属性,它们的生命周期都是RUNTIME。
  要获取类上的注解,就用类调用getAnnotation方法,传入的参数是注解名.class(要获取属性上的注解,就用属性域调用getAnnotation方法,这都是类似的)拿到注解之后,就像访问类中的属性一样访问它的值就可以了。