02.Java基础注解和反射

159 阅读8分钟

(记录一下一些知识概念,用于自己遗忘时候的查缺补漏)

java注解知识

1.元注解

就是用来负责注解其他注解的注解. 主要有5个.

  1. @ Target 注解目标
  2. @ Retention 注解时长
  3. @ Documented 文档
  4. @ Inherited 继承

1.1 @ Target 注解

Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明确其修饰的目标。 作用:用于更加精确描述注解的使用范围(即:被描述的注解可以用在什么地方) 取值(ElementType)有:

  1. CONSTRUCTOR:用于描述构造器
  2. FIELD:用于描述成员变量
  3. LOCAL_VARIABLE:用于描述局部变量
  4. METHOD:用于描述方法
  5. PACKAGE:用于描述包
  6. PARAMETER:用于描述参数
  7. TYPE:用于描述类、接口(包括注解类型) 或enum声明

1.2 @ Retention注解

@ Retention定义了该Annotation被保留的时间长短

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值(RetentionPoicy)有: 1.SOURCE:在源文件中有效(即源文件保留,使用场景比如语法检测,ATP:annotation processor tools) 2.CLASS:在class文件中有效(即class保留,使用比如说字节码插桩) 3.RUNTIME:在运行时有效(即运行时保留,使用比如说在反射机制里面)

1.3 @ Documented 注解

@ Documented用于描述其它类型的annotation应该被 javadoc工具记录

1.4 @ Inherited 注解

作用:指定被它修饰的注解具有继承性(比如InheritedAnnotation注解了BaseClass,SubClass继承了BaseClass,这时SubClass也会具有InheritedAnnotation注解,但方法跟参数等的注解不会被继承)

2. java1.8 多重注解

java 在1.8之后支持多重注解,以前注解只能注解一次.多次注解就会报错误提示信息"duplicate annotatiion". 现在以类似方法进行多重注解.

@interface Hints {
    Hint[] value();
}

@Repeatable(Hints.class)
@interface Hint {
    String value();
}

前一个注解用来存储后面一个注解.在使用时候,使用后面的注解进行代码的注解,但是解析注解时候需要使用前面的注解进行解析.

3.自定义注解

使用@ interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@ interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。 自定义注解举例:

//注解目标:字段
@Target(ElementType.FIELD)
//注解存续期:java运行时
@Retention(RetentionPolicy.RUNTIME)
//可以继承的
@Inherited
public @interface FruitColor{
  enum Color{RED,GREEN,BLUE};
  public Color fruitColor() default Color.GREEN;
}

在例子里面使用了一个枚举类,列举三种水果颜色 red,green和blue. 注解类里面的方法值都是注解的参数,注解参数可以有默认值,但是不能为空. 注解类里面一个方法fruitColor(),就是一个注解使用时候的参数. 自定义注解使用:

@FruitColor(fruitColor = FruitColor.Color.GREEN)
private String appleColor;

参数里面就是注解类中的方法名=使用的枚举值.

4. 注解和注解解释器的实例

在代码中使用了注解,就要使用注解解释器,否则注解跟注释就没有什么区别.

4.1 水果注解实例

定义注解水果颜色FruitColor.java:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface FruitColor{
     enum Color{RED,GREEN,BLUE};
  public Color fruitColor() default Color.GREEN;
}

定义注解水果名字FruitName.java:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    //value指是默认的
  String value() default "";
}

定义注解水果供货商FruitProvider.java

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider{
    /**
     * 供应商编号
     */
  int id() default -1;
  /**
   * 供应商名称
   */
  String name() default "";
  /**
   * 供应商地址 
   */
  String address() default "";
}

注解解释器:

/**
 * 注解解释器
 */
 public class FruitInfoUtil{
    public static void getFruitInfo(Class<?> clazz) {
        String strFruitName = " 水果名称:";
        String strFruitColor = " 水果颜色:";
        String strFruitProvicer = "供应商信息:";
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(FruitName.class)) {
                FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
                strFruitName = strFruitName + fruitName.value();
                System.out.println(strFruitName);
              } else if (field.isAnnotationPresent(FruitColor.class)) {
                FruitColor fruitColor = (FruitColor) field.getAnnotation(FruitColor.class);
                strFruitColor = strFruitColor + fruitColor.fruitColor().toString();
                System.out.println(strFruitColor);
              } else if (field.isAnnotationPresent(FruitProvider.class)) {
                FruitProvider fruitProvider = (FruitProvider)field.getAnnotation(FruitProvider.class);
               strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:" +  fruitProvider.name() + " 供应商地址:" + fruitProvider.address();
             System.out.println(strFruitProvicer);
             }
        }
    }
}

类里面使用注解

public class Apple{
    @FruitName("Apple")
    private String appleName;
    @FruitColor(fruitColor = FruitColor.Color.GREEN)
    private String appleColor;
    @FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路89号红富士大厦")
    private String appleProvider;
 public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
  }
    public String getAppleColor() {
        return appleColor;
  }
    public void setAppleName(String appleName) {
        this.appleName = appleName;
  }
    public String getAppleName() {
        return appleName;
  }
    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
  }
    public String getAppleProvider() {
        return appleProvider;
  }
    public void displayName() {
        System.out.println("水果的名字是:苹果");
  }
}

运行输出结果:

/************ 输出结果 ***************/
 public class FruitRun{
    /**
     * @param args
     */
  public static void main(String[] args) {
        FruitInfoUtil.getFruitInfo(Apple.class);
  }
}

image.png

4.2 Method注解实例(含多重注解)

定义注解GetMethod.java:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(GetMethods.class)
public @interface GetMethod{
    public enum Type{ONE, TWO}
    Type setType() default Type.ONE;
}

定义多重注解包含体GetMethods.java:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GetMethods{
    GetMethod[] value();
}

定义注解解释器MethodUtil,java:

/*** Created by WenpengL1 on 2017/4/19. */
 public class MethodUtil{
    public static void getInfo(Class<?> clazz) {
        Method[] methods = clazz.getDeclaredMethods();
 for (Method method : methods) {
            method.getAnnotation(GetMethod.class);
            method.getDeclaredAnnotations();
            AnnotatedType annotatedType=method.getAnnotatedReturnType();
            if (method.isAnnotationPresent(GetMethods.class)) {
                if (GetMethod.Type.ONE==method.getAnnotation(GetMethods.class).value()[0].setType())                     {
                    try {
                        method.invoke(clazz.newInstance());
                        } catch (Exception e) {
                        e.printStackTrace();
                        }
                }
            }
        }
    }
}

使用注解MyMethod.java:

public class MyMethod{
    @GetMethod(setType = GetMethod.Type.ONE)
    @GetMethod(setType = GetMethod.Type.TWO)
    public void printMessage() {
        System.out.print("this is one message");
  }
}

运行结果

public class MethodRun{
    public static void main(String[] args) {
        MethodUtil.getInfo(MyMethod.class);
  }
}

运行结果:

image.png

4.3 apt注解举例

实现注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
    String value() default "1";
}

使用注解:

public class MainActivity extends AppCompatActivity {

    @MyAnnotation
    int anInt;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

定义注解处理module,记得使用Java library:

image.png 注解处理器的实现,此处仅打印提示字符:

@SupportedAnnotationTypes("com.example.myapplication.annotation.MyAnnotation")
public class MyClassProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Messager messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE,"=================");
        return false;
    }
}

配置注解处理器(使用AutoService 容易有gradle的兼容问题)

image.png

文件里面声明处理的注解的全类名 com.example.lib.MyClassProcessor

然后编译就会发现,编译时候,我们的注解处理器打印的日志了

The following annotation processors are not incremental: lib.jar (project :lib).
Make sure all annotation processors are incremental to improve your build speed.
注: =================
注: =================

Java 反射内容

1. 反射

基本的反射内容很简单,放在这里备查吧.

  1. 在运行时判断任意一个对象所属的类;
  2. 在运行时构造任意一个类的对象;
  3. 在运行时判断任意一个类所具有的成员变量和方法;
  4. 在运行时调用任意一个对象的方法;
  5. 生成动态代理。
  • Class类 代表类的实体,在运行的Java应用程序中表示类和接口 获取一个类的class对象,如String.Class或者对象调用getClass().
  • Constructor类 代表类的构造方法
    • 获取所有构造方法,   getDeclaredConstructors() 获取所有的构造方法  getDeclaredConstructor(传入对应的参数)就可以获得相应的构造方法对象(无论私有与否)
    • 获取公共的构造方法 getConstructors()得到所有的公共构造方法 getConstructor(传入相应参数)就可以获得响应的公共构造方法对象
    • 获取类的实例对象 用获取到的constructor对象,传入响应的参数,就可以获得类对象 constructor.newInstance(传入参数)
  • Field类 代表类的成员变量(成员变量也称为类的属性) 获取字段对象, getDeclaredFields()获取所有的字段对象
  • Method类 代表类的方法 getDeclaredMethods()获取类中所有的方法 getMethods() 获取类中的公共方法 activityClass.getMethod("setContentView", int.class); method.invoke(activity, layoutId);这样传递进参数,可以用对象来调用对应的方法.

2. 字段修饰符

可以通过反射获取 修饰符,包括类、方法、字段,都有类似的方法.

Class.getModifiers;
Method.getModifiers;
Field.getModifiers;

返回是int 的编码值,代表了 public, protected, private, final, static, abstract这些的修饰符.

一般被final修饰的字段是不允许修改的,下面例子里面进行两次反射进行修改.



public class Test1228 {
    private static final Integer aInt = 2;

    @Override
    public String toString() {
        return "Test1228{aInt=" + aInt + "}";
    }

    public static void main(String[] args) {
        Test1228 test1228 = new Test1228();
        String modifiers = null;
        try {
            //获取aInt属性,并打印修饰符
            System.out.println(test1228);
            Field aInt = test1228.getClass().getDeclaredField("aInt");
            modifiers = Modifier.toString(aInt.getModifiers());
            System.out.println("先前修饰符" + modifiers);

            //修改修饰符,去除掉final
            Field modifierField = Field.class.getDeclaredField("modifiers");
            modifierField.setAccessible(true);
            modifierField.setInt(aInt, aInt.getModifiers() & ~Modifier.FINAL);

            //修改aInt的值
            aInt.setAccessible(true);
            aInt.set(test1228, Integer.valueOf(6));
            //aInt.setInt(test1228, 6);


            System.out.println(test1228);
            System.out.println("后来修饰符" + Modifier.toString(aInt.getModifiers()));
            System.out.println("aInt值 :"+Test1228.aInt);
            new Thread(() -> System.out.println("aInt值 :"+Test1228.aInt)).start();
            System.out.println("反射获取aInt值 :" + aInt.get(test1228));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

打印结果:

Test1228{aInt=2}
先前修饰符private static final
Test1228{aInt=6}
后来修饰符private static
aInt值 :6
反射获取aInt值 :6
aInt值 :6

代码里面的aInt 属性为Integer.当属性为对象时候反射修改完成后,调用属性都是正常的修改之后的数值了;当属性为基本数据类型或者是String a="ss"; 字符串对象时候,这时候用反射修改完成之后,用对象方式调用(如test.aInt)发现还是先前的数值,这是因为虚拟机会把属性优化成为基本的常量,只能使用反射来获取最新的数值.