前语

了解了反射的含义及用法后,咱们在看看注解的运用,最终叙说一下在Android开发中是怎样结合运用注解与反射。

反射

Java反射(Reflection)界说

Java反射机制是指在运转状态

关于任意一个类,都能知道这个类的一切特点和办法; 关于任何一个目标,都能够调用它的任何一个办法和特点;

这样动态获取新的以及动态调用目标办法的功用就叫做反射。

比方像下面:

           //获取类
        Class c = Class.forName("java.lang.String");
        // 获取一切的特点
        Field[] fields = c.getDeclaredFields();
        StringBuffer sb = new StringBuffer();
        sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() + "{n");
        // 遍历每一个特点
        for (Field field : fields) {
            sb.append("t");// 空格
            sb.append(Modifier.toString(field.getModifiers()) + " ");// 取得特点的润饰符,例如public,static等等
            sb.append(field.getType().getSimpleName() + " ");// 特点的类型的姓名
            sb.append(field.getName() + ";n");// 特点的姓名+回车
        }
        sb.append("}n");
        System.out.println(sb);

就能够取得 String 这个咱们常用类的一切特点:

注解与反射运用小结

再比方:

       //获取类
        Class c = Class.forName("java.lang.String");
        // 获取一切的办法
        Method[] ms = c.getDeclaredMethods();
        //遍历输出一切办法
        for (Method method : ms) {
            //获取办法一切参数
            Parameter[] parameters = method.getParameters();
            String params = "";
            if (parameters.length > 0) {
                StringBuffer stringBuffer = new StringBuffer();
                for (Parameter parameter : parameters) {
                    stringBuffer.append(parameter.getType().getSimpleName() + " " + parameter.getName() + ",");
                }
                //去掉最终一个逗号
                params = stringBuffer.substring(0, stringBuffer.length() - 1);
            }
            System.err.println(Modifier.toString(method.getModifiers())
                    + " " + method.getReturnType().getSimpleName()
                    + " " + method.getName()
                    + " (" +params  + ")");
        }

能够取得String 类的一切办法(图片只截取了部分办法,实践有许多就不占篇幅了):

注解与反射运用小结

Java反射机制API

主要的几个类

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 类的注解

具体完结

为了便利描绘,这儿咱们创建一个类 TestClass

public class TestClass {
    private String address;
    private String port;
    private int number;
   public void printInfo() {
        System.out.println("info is " + address + ":" + port);
    }        
    private void myMethod(int number,String sex) {
    }
    public String getPort() {
        return port;
    }
    public void setPort(String port) {
        this.port = port;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public int getNumber() {
        return number;
    }
    public void setNumber(int number) {
        this.number = number;
    }
}

这个类很简单,包括三个成员变量address,port和number,以及它们各自的get,set办法。 两个自界说的办法printInfo()和myMethod()。

下面咱们就看一下如何经过反射,获取这个TestClass的一切 “信息”

  • 1.获取Class 关于Class的获取有三种写法:
//获取类的三种办法:
Class c = Class.forName("java.lang.String");  //这儿一定要用完整的包名
Class c1=String.class;
String str = new String();
Class c2=str.getClass();

这儿获取的c,c1以及c2都是相等的。一般在反射中会用第一种写法。

  • 2.获取类的特点(成员变量)
Field[] fields = c.getDeclaredFields();

这儿返回的是一个数组 ,包括一切的特点。获取到的每一个特点Filed,包括一系列的办法能够获取及修正他的内容。 如下所示:

// 遍历每一个特点
        for (Field field : fields) {
            sb.append("t");// 空格
            sb.append(Modifier.toString(field.getModifiers()) + " ");// 取得特点的润饰符,例如public,static等等
            sb.append(field.getType().getSimpleName() + " ");// 特点的类型的姓名
            sb.append(field.getName() + ";n");// 特点的姓名+回车
        }

这儿咱们能够得到TestClass的一切特点:

注解与反射运用小结

  • 3.获取类的办法
// 获取一切的办法
Method[] ms = c.getDeclaredMethods();

和特点相似,咱们仍然能够经过一系列的办法获取到办法的返回值类型,称号以及参数。下面的表格中总结了一些要害办法:

注解与反射运用小结

相似的获取到TestClass的一切办法:

注解与反射运用小结

这儿能够看到,获取的TestClass的特点和办法同咱们界说的是完全共同的。

这儿咱们顺便调用一下TestClass的printInfo办法:

new TestClass().printInfo();

因为一切特点没有做初始化,所以得到如下输出:

注解与反射运用小结

能够看到,运用反射咱们能够很便利的去“反编译”一个class。那么咱们用反射这么做的含义是什么呢?不要着急,下面咱们先来了解一下注解

Java 注解(Annotation)

什么是注解

关于注解的界说网上有许多说法,就不再赘述。这儿咱们就说两点

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

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

既然是被迫数据,关于那些现已存在的注解,比方Override,咱们只能看看而已,并不知道它具体的作业机制是什么;所以想要了解注解,就直接从自界说注解开端。

自界说注解

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

这便是自界说注解的形式,咱们用@interface 表明这是一个注解,Annotation只有成员变量,没有办法。Annotation的成员变量在Annotation界说中以“无形参的办法”形式来声明,其办法名界说了该成员变量的姓名,其返回值界说了该成员变量的类型。比方上面的value和canBeNull。

元注解

能够看到自界说注解里也会有注解存在,给自界说注解运用的注解便是元注解。

@Rentention Rentention

@Rentention Rentention用来符号自界说注解的有用规模,他的取值有以下三种:

  1. RetentionPolicy.SOURCE: 只在源代码中保留 一般都是用来增加代码的了解性或许帮助代码查看之类的,比方咱们的Override;

  2. RetentionPolicy.CLASS: 默许的选择,能把注解保留到编译后的字节码class文件中,仅仅到字节码文件中,运转时是无法得到的;

  3. 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的能够在javadoc中找到

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

好了,关于注解就说这么多。

反射&注解的运用

特点值运用注解

下面咱们首要自界说两个注解:BindPort 和 BindAddress

@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″。

同时,咱们修正之前的TestClass

public class TestClass {
    @BindAddress()
    String address;
    @BindPort()
    private String port;
    private int number;
    public void printInfo() {
        System.out.println("info is " + address + ":" + port);
    }
   ........
}

这儿咱们将原先的address 和 port 两个变量分别用这儿界说的注解进行润饰,因为咱们在界说注解时有默许值,所以这儿的注解能够不写参数。

运用反射获取注解信息

前面现已说了,Annotation是被迫的元数据,永远不会有自动行为,所以咱们需要经过运用反射,才能让咱们的注解发生含义。

经过反射能够获取Class的一切特点和办法,因此获取注解信息也不在话下。咱们看代码:

	    //获取类
        Class c = Class.forName(className);
        //实例化一个TestClass目标
        TestClass tc= (TestClass) c.newInstance();
        // 获取一切的特点
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            if(field.isAnnotationPresent(BindPort.class)){
                BindPort port = field.getAnnotation(BindPort.class);
                field.setAccessible(true);
                field.set(tc,port.value());
            }
            if (field.isAnnotationPresent(BindAddress.class)) {
                BindAddress address = field.getAnnotation(BindAddress.class);
                field.setAccessible(true);        
                field.set(tc,address.value());
            }
        }
        tc.printInfo();

咱们运转程序得到如下输出:

注解与反射运用小结

上面代码的逻辑很简单:

首要遍历循环一切的特点,如果当时特点被指定的注解所润饰,那么就将当时特点的值修正为注解中成员变量的值。

上面的代码中,找到被BindPort润饰的特点,然后将BindPort中value的值赋给该特点。

这儿setAccessible(true)的运用时因为,咱们在声明port变量时,其类型为private,为了保证能够拜访这个变量,避免程序出现异常。

理论上来说,这样做是不安全的,不符合面向目标的思想,这儿仅仅为了阐明注解和反射举例。

但是,你也会发现,反射给咱们供给了一种在运转时改变目标的办法。

好了,下面咱们继续修正TestClass

public class TestClass {
    @BindAddress("http://www.google.com.cn")
    String address;
    @BindPort("8888")
    private String port;
    private int number;
    public void printInfo() {
        System.out.println("info is " + address + ":" + port);
    }
    .......
}

咱们为注解设定了参数,再次运转,相信你现已猜到结果了。

注解与反射运用小结

这时候因为咱们在给成员变量设定注解时,写了参数,反射时也取到了相应的值。

办法运用注解

上面关于类特点(成员变量)设定注解,或许还不能让感受到注解&反射的优势,咱们再来看一下类的办法运用注解会怎样。

咱们仍是先界说一个注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BindGet {
    String value() default "";
}

有用规模至运转时,适用于办法。

再次修正TestClass 如下:

public class TestClass {
    @BindAddress("http://www.google.com.cn")
    String address;
    @BindPort("8888")
    private String port;
    private int number;
    @BindGet("mike")
    void getHttp(String param){
        String url="http://www.baidu.com/?username"+param;
        System.err.println("get------->"+url);
    }
    ...........
}

咱们添加了一个名为getHttp的办法,而且这个办法由@BindGet注解。

然后看反射的运用:

	//获取类
        Class c = Class.forName(className);
        TestClass tc= (TestClass) c.newInstance();
	// 获取一切的办法
        Method[] ms = c.getDeclaredMethods();
        for (Method method : ms) {
           if(method.isAnnotationPresent(BindGet.class)){
               BindGet bindGet = method.getAnnotation(BindGet.class);
               String param=bindGet.value();
               method.invoke(tc, param);
           }
        }

这儿的逻辑和对特点的解析相似,依旧是判别当时办法是否被指定的注解(BindGet)所润饰, 如果是的话,就运用注解中的参数作为当时办法的参数去调用他自己。

这样,咱们在运转程序时,经过反射就回去自动调用get办法,得到如下输出:

注解与反射运用小结

这儿咱们就能够经过注解动态的完结username参数的修正,乃至get办法整个http url地址的修正。 (假定咱们这儿的get 办法是做网络请求)

到这儿,你应该现已理解了如何运用反射获取注解的信息,但你一定会困惑这么做有什么用呢?

”动态“”动态“”动态“

这便是运用注解和反射最大的含义,咱们能够动态的拜访目标。

说了这么多,下面咱们看看,在Android开发中,咱们遇到的注解和反射。

Android 中的注解&反射

Butterknife

如果你是一个Android开发者,相信在运用Butterknife插件之前,你一定写了无数次的findViewById。

但是,如果运用了Butterknife 插件,咱们就能够很便利的完结findViewById的作业,乃至是setOnClickListener 的作业。

public class ButtferknifeDemoActivity extends AppCompatActivity {
    @BindView(R.id.textView)
    TextView textView;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_buttferknife);
        ButterKnife.bind(this);
        textView.setText("I'm not null");
    }
}

上面的代码,应该不陌生。试想如果你的activity_bufferknife 布局文件中有许多控件时,这样做不知道能够省多少时刻了

咱们看一下BindView的注解界说:

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

这个注解用于润饰变量,有用规模也是限定到了CLASS(即编译阶段),并没有到运转时。 咱们在Butterknife(8.4.0)的部分源码中能够看到:

 /** Simpler version of {@link View#findViewById(int)} which infers the target type. */
  @SuppressWarnings({ "unchecked", "UnusedDeclaration" }) // Checked by runtime cast. Public API.
  @CheckResult
  public static <T extends View> T findById(@NonNull View view, @IdRes int id) {
    return (T) view.findViewById(id);
  }

咱们能够猜到的,编译时最终的完结必定是到这儿,完结view.findViewById(id)。

在这儿,注解和反射的结合,使咱们能够避免做许多重复的作业。

Retrofit 2.0

第一次运用Retrofit的时候,完全被接口界说的办法搞蒙圈了,完全搞不懂啊。

public interface UserBasicService {
    @GET("users/{user}")
    Call<ResponseBody> getUsers(@Path("user") String uses);
}

为什么要这么写?参数是怎么传递的?@ 是什么意思?带着从前的这些疑问,咱们首要看看这儿的两个注解。

这儿运用了两个注解GET 和Path ,咱们看一下:

GET

/** Make a GET request. */
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
  /**
   * A relative or absolute path, or full URL of the endpoint. This value is optional if the first
   * parameter of the method is annotated with {@link Url @Url}.
   * <p>
   * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how
   * this is resolved against a base URL to create the full endpoint URL.
   */
  String value() default "";
}

Path

@Documented
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface Path {
  String value();
  /**
   * Specifies whether the argument value to the annotated method parameter is already URL encoded.
   */
  boolean encoded() default false;
}

这两个注解的生命周期都连续到了 RUNTIME,即运转时。GET用于办法,Path用于参数。这点和咱们界说getUsers()办法是共同的。

关于Retrofit中反射和注解的运用,涉及到动态署理的相关概念,这儿就不打开来说。总的思路便是经过注解中运用的参数,动态的生成Request有OKHttp去调用。这个以后会做深入分析,这儿仅仅了解注解和反射用法。

好了,关于注解和反射的运用办法及含义就暂时总结到这儿。

小结

以合适的办法运用反射,会让咱们写代码的办法愈加灵活。反射运用不当,反而会拔苗助长,会对功能形成影响。

但是 EventBus,Retrofit 的如此火爆,让咱们有理由相信,对功能的影响或许没那么大,或许说面对现如今硬件装备堪比电脑的手机,这点影响或许能够忽略不计。

所以关于反射的运用仍是仁者见仁智者见智吧。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。