本文将介绍反射,注解的定义,用途,相关API,示例。
一、反射
定义:
Java 反射是可以让我们在运行时获取类的方法、属性、父类、接口等类的内部信息的机制。也就是说,反射本质上是一个“反着来”的过程。我们通过new创建一个类的实例时,实际上是由Java虚拟机根据这个类的Class对象在运行时构建出来的,而反射是通过一个类的Class对象来获取它的定义信息,从而我们可以访问到它的属性、方法,知道这个类的父类、实现了哪些接口等信息。

反射是可以在运行时获取类的函数、属性、父类、接口等 Class 内部信息的机制。通过反射还可以在运行期实例化对象,调用方法,通过调用 get/set 方法获取变量的值,(包括私有的方法也可以)。

这种机制在框架源码中很常见。 一个比较常见的场景就是编译时对于类的内部信息不可知,必须得到运行时才能获取类的具体信息。

比如 部分ORM 框架,在运行时才能够获取类中的各个属性,然后通过反射的形式获取其属性名和值,存入数据库。

用途:

1) 在运行时判断任意一个对象所属的类;
2) 在运行时构造任意一个类的对象;
3) 在运行时判断任意一个类所具有的成员变量和方法;
4) 在运行时调用任意一个对象的方法;
5) 生成动态代理。

相关API
1 、获取Class对象:

// 加载指定的 Class 对象,参数 1 为要加载的类的完整路径 :包名+类名 ( 常用方式 )
public static Class<?> forName (String className)
// 加载指定的 Class 对象,参数 1 为要加载的类的完整路径;
// 参数 2 为是否要初始化该 Class 对象,参数 3 为指定加载该类的 ClassLoader.
public static Class<?> forName (String className, boolean shouldInitialize, ClassLoader classLoader)

2 、通过反射构造对象,首先要获取类的 Constructor(构造器)对象,然后通过 Constructor 来创建目标类的对象。

Constructor<?> constructor = clz.getConstructor(String.class);
   // 设置 Constructor 的 Accessible
   constructor.setAccessible(true);

3 反射获取类中函数

// 获取 Class 对象中指定函数名和参数的函数,参数一为函数名,参数 2 为参数类型列表
public Method getDeclaredMethod (String name, Class...<?> parameterTypes)
// 获取该 Class 对象中的所有函数( 不包含从父类继承的函数 )
public Method[] getDeclaredMethods ()
// 获取指定的 Class 对象中的**公有**函数,参数一为函数名,参数 2 为参数类型列表
public Method getMethod (String name, Class...<?> parameterTypes)
// 获取该 Class 对象中的所有**公有**函数 ( 包含从父类和接口类集成下来的函数 )
public Method[] getMethods ()

4、 反射获取类中的属性

// 获取 Class 对象中指定属性名的属性,参数一为属性名
public Method getDeclaredField (String name)
// 获取该 Class 对象中的所有属性( 不包含从父类继承的属性 )
public Method[] getDeclaredFields ()
// 获取指定的 Class 对象中的**公有**属性,参数一为属性名
public Method getField (String name)
// 获取该 Class 对象中的所有**公有**属性 ( 包含从父类和接口类集成下来的公有属性 )
public Method[] getFields ()

5、 反射获取指定类型的注解

// 获取指定类型的注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) ;
// 获取 Class 对象中的所有注解
public Annotation[] getAnnotations()

具体示例

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;
    }


}

1、获取Class
关于Class的获取有三种写法:

//获取类的三种方法:
Class c = Class.forName("java.lang.String");  //这里一定要用完整的包名
Class c1=String.class;
String str = new String();
Class c2=str.getClass();

2、获取类的属性(成员变量)

Field[] fields = c.getDeclaredFields();
// 遍历每一个属性
        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");// 属性的名字+回车
        }

3、获取类的方法

// 获取所有的方法
Method[] ms = c.getDeclaredMethods();

二、注解
定义:

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

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

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

作用分类:

①编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】

②代码分析:通过代码里标识的元数据对代码进行分析【使用反射】

③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】

annotation是一种在类、类型、属性、参数、局部变量、方法、构造方法、包、annotation本身等上面的一个附属品(ElementType这个枚举中有阐述),他依赖于这些元素而存在,他本身并没有任何作用,annotation的作用是根据其附属在这些对象上,根据外部程序解析引起了他的作用


元注解
元注解即用来描述注解的注解,示例如下

@Target(ElementType.METHOD)
public @interface MethodInfo { 
    ...
}

1、Retention
这个元注解表示一个注解会被保留到什么时候,比如以下代码表示Developer注解会被保留到运行时(也就是说在运行时依然能发挥作用):

@Retention(RetentionPolicy.RUNTIME)
public @interface Developer {
    String value();
}

SOURCE:表示在编译时这个注解会被移除,不会包含在编译后产生的class文件中;
CLASS:表示这个注解会被包含在class文件中,但在运行时会被移除;
RUNTIME:表示这个注解会被保留到运行时,在运行时可以JVM访问到,我们可以在运行时通过反射解析这个注解。
2、Documented
当一个注解被@Documented元注解所修饰时,那么无论在哪里使用这个注解,都会被Javadoc工具文档化

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

3、Inherited
表明被修饰的注解类型是自动继承的。如果你想让一个类和它的子类都包含某个注解,就可以使用@Inherited来修饰这个注解。也就是说,假设Parent类是Child类的父类,那么我们若用被@Inherited元注解所修饰的某个注解对Parent类进行了修饰,则相当于Child类也被该注解所修饰了

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

4、Target

这个元注解说明了被修饰的注解的应用范围,也就是被修饰的注解可以用来注解哪些程序元素

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)public @interface Target {
    ElementType[] value();
}

从以上定义我们可以看到它也会保留到运行时,而且它的取值是为ElementType[]类型(一个数组,意思是可以指定多个值),ElementType是一个枚举类型,它可以取以下值:

TYPE:表示可以用来修饰类、接口、注解类型或枚举类型;
PACKAGE:可以用来修饰包;
PARAMETER:可以用来修饰参数;
ANNOTATION_TYPE:可以用来修饰注解类型;
METHOD:可以用来修饰方法;
FIELD:可以用来修饰属性(包括枚举常量);
CONSTRUCTOR:可以用来修饰构造器;
LOCAL_VARIABLE:可用来修饰局部变量。

常见内建注解
Java本身内建了一些注解,下面我们来介绍一下我们在日常开发中比较常见的注解:@Override、@Deprecated、@SuppressWarnings。

1、@Override注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

这个注解可以用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。这个注解的作用我们大家都不陌生,那就是告诉编译器被修饰的方法是重写的父类中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。

2、Deprecated

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是说明被修饰的程序元素已被“废弃”,不再建议用户使用。

3、SuppressWarnings

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
  String[] value();
}
它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示:

deprecation:忽略使用了废弃的类或方法时的警告;
unchecked:执行了未检查的转换;
fallthrough:swich语句款中case忘加break从而直接“落入”下一个case;
path:类路径或原文件路径等不存在;
serial:可序列化的类缺少serialVersionUID;
finally:存在不能正常执行的finally子句;
all:以上所有情况产生的警告均忽略。

运行时注解解析

首先我们把MethodInfo注解类型中Retention的值改回原来的RUNTIME,接下来我们介绍如何通过反射机制在运行时解析我们的自定义注解类型。

java.lang.reflect包中有一个AnnotatedElement接口,这个接口定义了用于获取注解信息的几个方法:

T getAnnotation(Class annotationClass) //返回该程序元素的指定类型的注解,若不存在这个类型的注解则返回null 
 Annotation[] getAnnotations() //返回修饰该程序元素的所有注解 
 Annotation[] getDeclaredAnnotations() //返回直接修饰该元素的所有注解 
 boolean isAnnotationPresent(Classpublic class AnnotationParser {
  public static void main(String[] args) {
    try {
      Class cls = AnnotationTest.class;
      for (Method method : cls.getMethods()) {
        MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);
        if (methodInfo != null) {
          System.out.println("method name:" + method.getName());
          System.out.println("method author:" + methodInfo.author());
          System.out.println("method date:" + methodInfo.date());
          System.out.println("method version:" + methodInfo.version());
        }
      }
    } catch (Exception e) {

    }
  }
}

Android 中的注解&反射

Butterknife

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");

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

Retrofit 2.0

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()方法是一致的。

参考文章:https://mp.weixin.qq.com/s/Cb17zyR686sRQf_NfFcANg
https://gold.xitu.io/entry/57c9f5890e3dd90063e83461
http://efany.github.io/2016/04/02/Android%E6%B3%A8%E8%A7%A3%E4%B8%8E%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6/