Android 中的反射与注解

反射定义

Java反射机制是指在运行状态中

对于任意一个类,都能知道这个类的所有属性和方法;
对于任何一个对象,都能够调用它的任何一个方法和属性;

这样动态获取新的以及动态调用对象方法的功能就叫做反射。

主要的几个类

用途
java.lang.Class编译后的class文件的对象
java.lang.reflect.Constructor构造方法
java.lang.reflect.Field类的成员变量(属性)
java.lang.reflect.Method类的成员方法
java.lang.reflect.Modifier方法类型
java.lang.annotation.Annotation类的注解
1. 获取Class
//获取类的三种方法:
Class c = Class.forName("java.lang.String");//这里一定要用完整的包名
Class c1 = String.class;
String str = new String();
Class c2 = str.getClass();

这里以String类为例,获取的c,c1以及c2都是相等的,一般用第一种写法。

2. Field-获取类的属性(成员变量) API

A Field provides information about, and dynamic access to, a single field of a class or an interface. The reflected field may be a class (static) field or an instance field.
A Field permits widening conversions to occur during a get or set access operation, but throws an IllegalArgumentException if a narrowing conversion would occur.

Class.getFields()返回一个包含某些 Field 对象的数组,该数组包含此 Class 对象所表示的类或接口的所有可访问公共字段
Class.getField(String)返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段
Class.getDeclaredFields() 返回 Field 对象的一个数组,该数组包含此 Class 对象所表示的类或接口所声明的所有字段(包括私有成员)
Class.getDeclaredField(String)返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段(包括私有成员)

For example:

//获取类的属性(成员变量)
Field[] fields = c.getDeclaredFields();
StringBuffer sb = new StringBuffer();
//遍历每一个属性
for(Field field : fields){
    sb.append("\t");    //空格
    sb.append(Modifier.toString(field.getModifiers()) + " ");//获得属性的修饰符
    sb.append(field.getType().getSimpleName() + " ");//属性的类型的名字
    sb.append(field.getName() + ";\n");//属性的名字 + 回车
}
3. Method-获取类的方法 API

A Method provides information about, and access to, a single method on a class or interface. The reflected method may be a class method or an instance method (including an abstract method).
A Method permits widening conversions to occur when matching the actual parameters to invoke with the underlying method’s formal parameters, but it throws an IllegalArgumentException if a narrowing conversion would occur.

Class.getMethods()
Class.getMethod(String, Class[])
Class.getDeclaredMethods()
Class.getDeclaredMethod(String, Class[])

For example:

//获取所有的方法
Method[] ms = c.getDeclaredMethods();
StringBuffer sb = new StringBuffer();
for(Method method : ms){
    sb.append("\t");    //空格
    sb.append(Modifier.toString(method.getModifiers()) + " ");
    sb.append(method.getReturnType().getSimpleName() + " ");
    sb.append(method.getName() + ";\n");
}

注解

Annotation(注解)就是Java提供了一种源程序中的元素关联任何信息或者任何元数据(metadata)的途径和方法。

Annotation是被动的元数据,永远不会有主动行为

自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited
public @interface Bind {
    int value() default 1;
    boolean canBeNull() default false;
}

用 @interface 表明这是一个注解,Annotation 只有成员变量,没有方法。Annotation 的成员变量在 Annotation 定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

元注解

可以看到自定义注解里也会有注解存在,给自定义注解使用的注解就是元注解。

@Rentention

用来标记自定义注解的有效范围,他的取值有几下三种:

RetentionPolicy.SOURCE只在源代码中保留 一般都是用来增加代码的理解性或者帮助代码检查之类的,比如我们的Override
RetentionPolicy.CLASS默认的选择,能把注解保留到编译后的字节码class文件中,仅仅到字节码文件中,运行时是无法得到的
RetentionPolicy.RUNTIME注解不仅 能保留到class字节码文件中,还能在运行通过反射获取到,这也是我们最常用的
@Target

@Target指定Annotation 用于修饰哪些程序元素。

@Target也包含一个名为”value“的成员变量,该value成员变量类型为ElementType[ ],ElementType为枚举类型,值有如下几个:

  • ElementType.TYPE:能修饰类、接口或枚举类型
  • ElementType.FIELD:能修饰成员变量
  • ElementType.METHOD:能修饰方法
  • ElementType.PARAMETER:能修饰参数
  • ElementType.CONSTRUCTOR:能修饰构造器
  • ElementType.LOCAL_VARIABLE:能修饰局部变量
  • ElementType.ANNOTATION_TYPE:能修饰注解
  • ElementType.PACKAGE:能修饰包
@Documented

使用了 @Documented 的可以再 javadoc 中找到

@Interited

使用了@Interited表示注解里的内容可以被子类继承,比如父类中某个成员使用了上述@From(value),From中的value能给子类使用到。

反射 & 注解 的使用

1. 属性值使用注解
  • 自定义两个注解:
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface BindPort {
        String value() default "8080";
    }

指定BindPort 可以保留到运行时,并且可以修饰成员变量,包含一个成员变量默认值为”8080“。

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface BindAddress {
        String value() default "127.0.0.0";
    }

这个和上面类似,只是默认值为”127.0.0.0”。

  • 修饰属性:
    @BindAddress()
    private String address;
    @BindPort()
    private String port;

    public void printInfo() {
        System.out.println("info is " + address + ":" + port);
    }

这里我们将 address 和 port 两个变量分别用这里定义的注解进行修饰,由于我们在定义注解时有默认值,所以这里的注解可以不写参数。

  • 使用反射获取注解信息
    前面已经说了,Annotation是被动的元数据,永远不会有主动行为,所以我们需要通过使用反射,才能让我们的注解产生意义。

通过反射可以获取Class的所有属性和方法,因此获取注解信息也不在话下。我们看代码:

public void reflectTest() {

        try{
            //获取类
            Class c = Class.forName("com.example.test.TestClass");    //这里一定要用完整的包名
            //实例化一个TestClass对象
            TestClass tc = (TestClass) c.newInstance();
            //获取类的属性(成员变量)
            Field[] fields = c.getDeclaredFields();
            //遍历每一个属性
            for(Field field : fields){
                if(field.isAnnotationPresent(TestClass.BindPort.class)){
                    TestClass.BindPort port = field.getAnnotation(TestClass.BindPort.class);
                    field.setAccessible(true);
                    field.set(tc,port.value());
                }
                if(field.isAnnotationPresent(TestClass.BindAddress.class)){
                    TestClass.BindAddress address = field.getAnnotation(TestClass.BindAddress.class);
                    field.setAccessible(true);
                    field.set(tc,address.value());
                }
            }

            tc.printInfo();

        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }catch (InstantiationException e){
            e.printStackTrace();
        }catch (IllegalAccessException e){
            e.printStackTrace();
        }
    }

程序得到输出:info is 127.0.0.0:8080

  • 代码逻辑

首先遍历循环所有的属性,如果当前属性被指定的注解所修饰,那么就将当前属性的值修改为注解中成员变量的值。

上面的代码中,找到被BindPort修饰的属性,然后将BindPort中value的值赋给该属性。

这里setAccessible(true)的使用时因为,我们在声明port变量时,其类型为private,为了确保可以访问这个变量,防止程序出现异常。

理论上来说,这样做是不安全的,不符合面向对象的思想,这里只是为了说明注解和反射举例。但是,你也会发现,反射给我们提供了一种在运行时改变对象的方法

  • 继续修改
    @BindAddress("http://www.google.com.cn")
    private String address;
    @BindPort("8888")
    private String port;
  • 再次运行结果:info is http://www.google.com.cn:8888
    这时候由于我们在给成员变量设定注解时,写了参数,反射时也取到了相应的值。
2. 方法使用注解
  • 定义注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface BindGet {
        String value() default "";
    }
  • 修饰方法
    @BindGet("mike")
    void getHttp(String param) {
        String url = "http://www.baidu.com/?username" + param;
        System.err.println("get------>" + url);
    }
  • 使用反射
    public void reflectTest() {

        try{
            //获取类
            Class c = Class.forName("com.example.test.TestClass");    //这里一定要用完整的包名
            //实例化一个TestClass对象
            TestClass tc = (TestClass) c.newInstance();
            //获取类的方法
            Method[] ms = c.getDeclaredMethods();
            for(Method method : ms){
                if(method.isAnnotationPresent(TestClass.BindGet.class)){
                    TestClass.BindGet bindGet = method.getAnnotation(TestClass.BindGet.class);
                    String param = bindGet.value();
                    method.invoke(tc,param);
                }
            }
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }catch (InstantiationException e){
            e.printStackTrace();
        }catch (IllegalAccessException e){
            e.printStackTrace();
        }catch (InvocationTargetException e){
            e.printStackTrace();
        }

    }

这里的逻辑和对属性的解析相似,依旧是判断当前方法是否被指定的注解(BindGet)所修饰,如果是的话,就使用注解中的参数作为当前方法的参数去调用他自己。

  • 运行结果:get——>http://www.baidu.com/?usernamemike
    这里我们就可以通过注解动态的实现username参数的修改,甚至getHttp方法整个http url地址的修改。
    (假设我们这里的getHttp方法是做网络请求)

参考:理解Android中的注解与反射

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值