Java注解

Java注解

一、注解的定义和作用

  • 定义:注解是一种标识

    1.注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义。

    2.Java 注解(Annotation)又称 Java 标注,是 JDK1.5 引入的一种注释机制。

  • 作用:标识 / 解释 Java 代码

二、应用场景

根据注解的保留级别不同,对注解的使用自然存在不同场景。

级别 技术 说明
源码 APT 在编译期能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类。
字节码 字节码增强 在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解。
运行时 反射 在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。

在Android开发过,有很多框架都采用了注解来实现功能,

比如JUnit单元测试框架:

使用@Test 标记了要进行测试的方法Method() 
public class ExampleUnitTest {
    @Test
    public void Method() throws Exception {
          ...
    }
}

Http网络请求库Retrofit:

public interface GetRequest {

 @GET("url")
    Call<Translation> getCall();

}

ButterKnife:

@BindView(R.id.test)
TextView mTv;

三、注解的类型

1.元注解

是一种 Android系统内置的注解

在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)。声明的注解允许作用于哪些节点使用@Target声明;保留级别由@Retention 声明。其中保留级别如下。

RetentionPolicy.SOURCE
标记的注解仅保留在源级别中,并被编译器忽略。

RetentionPolicy.CLASS
标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。

RetentionPolicy.RUNTIME
标记的注解由 JVM 保留,因此运行时环境可以使用它。

  • 元注解作用于注解 & 解释注解

2.元注解类型介绍

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
@Repeatable()
@Documented

@Retention

  • 定义:保留注解
  • 作用:解释 / 说明了注解的生命周期

@Documented

  • 定义:Java文档注解
  • 作用:将注解中的元素包含到 Javadoc文档中

@Target

  • 定义:目标注解
  • 作用:限定了注解作用的目标范围,包括类、方法等等
<-- @Target取值参数说明 -->
// ElementType.PACKAGE:可以给一个包进行注解
// ElementType.ANNOTATION_TYPE:可以给一个注解进行注解
// ElementType.TYPE:可以给一个类型进行注解,如类、接口、枚举
// ElementType.CONSTRUCTOR:可以给构造方法进行注解
// ElementType.METHOD:可以给方法进行注解
// ElementType.PARAMETER 可以给一个方法内的参数进行注解
// ElementType.FIELD:可以给属性进行注解
// ElementType.LOCAL_VARIABLE:可以给局部变量进行注解

@Inherited

  • 定义:继承注解
  • 作用:使得一个 被@Inherited注解的注解 作用的类的子类可以继承该类的注解
// 元注解@Inherited 作用于 注解Carson_Annotation
@Inherited
public @interface Carson_Annotation {
}


// 注解Carson_Annotation 作用于A类
@Carson_Annotation
public class A {
  }

// B类继承了A类,即B类 = A类的子类,且B类没被任何注解应用
// 那么B类继承了A类的注解 Carson_Annotation
public class B extends A {}

@Repeatable

  • 定义:可重复注解

Java 1.8后引进

  • 作用:使得作用的注解可以取多个值
// 1. 定义 容器注解 @ 职业
public @interface Job {
    Person[]  value();
}
<-- 容器注解介绍 -->
// 定义:本身也是一个注解
// 作用:存放其它注解
// 具体使用:必须有一个 value 属性;类型 = 被 @Repeatable 注解的注解数组
// 如本例中,被 @Repeatable 作用 = @Person ,所以value属性 = Person []数组

// 2. 定义@Person 
// 3. 使用@Repeatable 注解 @Person
// 注:@Repeatable 括号中的类 = 容器注解
@Repeatable(Job.class)
public @interface Person{
    String role default "";
}

// 在使用@Person(被@Repeatable 注解 )时,可以取多个值来解释Java代码
// 下面注解表示:Carson类即是产品经理,又是程序猿
@Person(role="coder")
@Person(role="PM")
public class Carson{

}

可以使用多个元注解来作用你自定义的注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
@Documented
public @interface InjectView {
    @IdRes int value();
}

2.Java内置的注解

@Deprecated

  • 定义:过时注解
  • 作用:标记已过时 & 被抛弃的元素(类、方法等)

@Override

  • 定义:复写注解
  • 作用:标记该方法需要被子类复写

@SuppressWarnings

  • 定义:阻止警告注解
  • 作用:标记的元素会阻止编译器发出警告提醒

@SafeVarargs

  • 定义:参数安全类型注解

Java 1.7 后引入

  • 作用:提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked警告

  • 具体使用

// 以下是官方例子
// 虽然编译阶段不报错,但运行时会抛出 ClassCastException 异常
// 所以该注解只是作提示作用,但是实际上还是要开发者自己处理问题
@SafeVarargs // Not actually safe!
    static void m(List<String>... stringLists) {
    Object[] array = stringLists;
    List<Integer> tmpList = Arrays.asList(42);
    array[0] = tmpList; // Semantically invalid, but compiles without warnings
    String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}

@FunctionalInterface

  • 定义:函数式接口注解

Java 1.8 后引入的新特性

  • 作用:表示该接口 = 函数式接口

函数式接口 (Functional Interface) = 1个具有1个方法的普通接口

  • 具体使用
// 多线程开发中常用的 Runnable 就是一个典型的函数式接口(被 @FunctionalInterface 注解)
@FunctionalInterface
public interface Runnable {
   
    public abstract void run();
}

<--额外:为什么要用函数式接口标记 -->
// 原因:函数式接口很容易转换为 Lambda 表达式
// 这是另外一个很大话题,此处不作过多讲解,感兴趣的同学可自行了解

四、自定义注解

Java中所有的注解,默认实现 Annotation 接口:

package java.lang.annotation; 

    public interface Annotation { 

    boolean equals(Object obj); 

    int hashCode(); 

    String toString(); 

    Class<? extends Annotation> annotationType(); 

}

与声明一个"Class"不同的是,注解的声明使用 @interface 关键字。一个注解的声明如下:

public @interface Test{ }

1.声明自定义注解:

可以使用元注解来标记你的自定义注解的规则:

@Retention(RetentionPolicy.RUNTIME)//注解保留在运行时
@Target(ElementType.FIELD)// 允许在类与类属性上标记该注解
public @interface InjectView {
    int value();
}

2.注解的属性

注解的属性 = 成员变量

注解只有成员变量,没有方法

<-- 1. 定义 注解的属性 -->
public @interface Test {
    // 注解@Test中有2个属性:age 和 name 
    int age();
    String name() default "zhang" ;

    // 说明:
      // 注解的属性以 “无形参的方法” 形式来声明
      // 方法名 = 属性名
      // 方法返回值 = 属性类型 = 8 种基本数据类型 + 类、接口、注解及对应数组类型
      // 用 default 关键值指定 属性的默认值,如上面的name的默认值 = ”zhang“
}

3.注解属性赋值

注解的属性在使用时进行赋值

注解属性的赋值方式 = 注解括号内以 “value=”xx” “ 形式;用 ”,“隔开多个属性

备注:若注解只有一个属性,则赋值时”value“可以省略

<-- 2. 赋值 注解的属性 -->
// 注解Test 作用于A类
// 在作用 / 使用时对注解属性进行赋值
@Test(age=18,name="zhangsan")
public class A {
  }

4.注解的应用

我们利用注解,在Activity中不使用findViewbyID方法,来实现动态加载Android View组件。

步骤一:

定义一个注解InjectView.java,用来标识View

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectView {
    @IdRes int value();
}

步骤二:

编写注解的处理规则:

获取使用注解的Activity类,并获取该类下的使用了InjectView注解的成员变量,然后执行findViewbyID注入view。

public class InjectUtils {


    public static void injectView(Activity activity) {
        Class<? extends Activity> cls = activity.getClass();

        //获得此类所有的成员
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field filed : declaredFields) {
            // 判断属性是否被InjectView注解声明
            if (filed.isAnnotationPresent(InjectView.class)){
                InjectView injectView = filed.getAnnotation(InjectView.class);
                //获得了注解中设置的id
                int id = injectView.value();
                View view = activity.findViewById(id);
                //反射设置 属性的值
                filed.setAccessible(true); //设置访问权限,允许操作private的属性
                try {
                    //反射赋值
                    filed.set(activity,view);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

步骤二:

在MainActivity中声明需要用的组件TextView,用InjectView注解标识,在onCreate中调用injectView方法完成View的注入:

public class MainActivity extends AppCompatActivity {

    @InjectView(R.id.tv)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InjectUtils.injectView(this);
        textView.setText("注解+反射注入View");
    }
}

运行效果:

image-20201130230506922

注意:该方式即为xUtils实现View绑定的方式,但是由于该注解作用于RUNTIME时期,在运行时期使用了反射将会降低程序运行的效率,所有目前View注入一般使用ButterKnife,ButterKnife在编译期间根据注解自动生成View注入的类,效率会好很多,但是xUtils是一个轻量级的,而且功能强大,我们可以根据项目实际需求选择。

另外Kotlin语言已经实现了View的自动注入,在写代码的时候不需要大量的去写findViewbyID,所以使用Kotlin将不再需要View注入的代码。

xUtils ViewInject源码:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {

    int value();

    /* parent view id */
    int parentId() default 0;
}


注入过程:

private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {

        if (handlerType == null || IGNORED.contains(handlerType) || handlerType.getName().startsWith("androidx.")) {
            return;
        }

        // 从父类到子类递归
        injectObject(handler, handlerType.getSuperclass(), finder);

        // inject view
        Field[] fields = handlerType.getDeclaredFields();
        if (fields != null && fields.length > 0) {
            for (Field field : fields) {

                Class<?> fieldType = field.getType();
                if (
                    /* 不注入静态字段 */     Modifier.isStatic(field.getModifiers()) ||
                        /* 不注入final字段 */    Modifier.isFinal(field.getModifiers()) ||
                        /* 不注入基本类型字段 */  fieldType.isPrimitive() ||
                        /* 不注入数组类型字段 */  fieldType.isArray()) {
                    continue;
                }

                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                if (viewInject != null) {
                    try {
                        View view = finder.findViewById(viewInject.value(), viewInject.parentId());
                        if (view != null) {
                            field.setAccessible(true);
                            field.set(handler, view);
                        } else {
                            throw new RuntimeException("Invalid @ViewInject for "
                                    + handlerType.getSimpleName() + "." + field.getName());
                        }
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }
            }
        } // end inject view

        // inject event
        Method[] methods = handlerType.getDeclaredMethods();
        if (methods != null && methods.length > 0) {
            for (Method method : methods) {

                if (Modifier.isStatic(method.getModifiers())
                        || !Modifier.isPrivate(method.getModifiers())) {
                    continue;
                }

                //检查当前方法是否是event注解的方法
                Event event = method.getAnnotation(Event.class);
                if (event != null) {
                    try {
                        // id参数
                        int[] values = event.value();
                        int[] parentIds = event.parentId();
                        int parentIdsLen = parentIds == null ? 0 : parentIds.length;
                        //循环所有id,生成ViewInfo并添加代理反射
                        for (int i = 0; i < values.length; i++) {
                            int value = values[i];
                            if (value > 0) {
                                ViewInfo info = new ViewInfo();
                                info.value = value;
                                info.parentId = parentIdsLen > i ? parentIds[i] : 0;
                                method.setAccessible(true);
                                EventListenerManager.addEventMethod(finder, info, event, handler, method);
                            }
                        }
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }
            }
        } // end inject event

    }

参考:https://www.jianshu.com/p/9f29fb37c840

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容