当前位置:   article > 正文

Java、Kotlin、Android中的注解(Annotation)_kotlin annotation class

kotlin annotation class

注解机制:
JDK1.5后,Java引入了一种新的注释机制(Annotation),其一般作为说明信息,与程序的业务逻辑无关。
注解仅仅是一种说明信息,它广泛的应用于一些工具或框架中。

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

  • 什么是注解?
    “元数据”可以描述它,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。注解这个东西绝对不是Spring为我们提供的,而是JDK字带的,JDK自己也有很多内置注解,比如@Override注解的功能其实就是为了一些加了注解的类、方法等赋予特殊的含义,具体如何函数自定义的含义,其实就是注解处理器了。
    例如下面的代码:
@Override
public String toString(){
		return "This is String Representation of current object.";
}
  • 1
  • 2
  • 3
  • 4

上述代码中,重写toString()方法并使用了@Override注解。但是,即使不使用该注解标记代码,程序也能正常执行。事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类不存在该方法,则编译器报错,提示该方法没有重写父类中的方法。假如,把上方的函数名及参数toString()写成toString(){double r},并去掉注解,程序仍然能编译运行。但结果和期望大不相同。
Annotation 是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。

  • 引入注解的原因:
    使用Annotation之前甚至是在使用之后,XML被广泛的应用于描述元数据。一些应用开发人员和架构师发现XML的维护越来越糟糕。希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。
    假如你想为应用设置很多的常量或参数,XML是一个很好的选择,它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,这种情况下需要注解和方法紧密耦合起来。
    Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用自己的方式定义元数据。例如,使用标记interfaces,注释,transient关键字等等,不像Annotation这种标准的方式。目前,很多框架将XML和Annotation方式结合使用,平衡2者之间的利弊。

  • 4种元注解:
    元注解,就是JDK自带的注解。这些注解的用途其实就是在我们自定义注解时,注解到我们自定义的注解上的。
    举例:

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

public class AnnotationDemo {
    @Target(ElementType.TYPE)
    public  @interface Table{
        public String tableName() default "className";
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上述中,Table就是自定义的一个注解,使用方法:@Table。那么这个@Target其实就是元注解了。

Java自带的元注解如下:

@Target 注解用于什么地方
@Retention 什么时候使用该注解
@Documented 注解是否将包含在JavaDoc中
@Inherited 是否允许子类继承该注解

  1. @Target:用于描述注解的使用范围,有一个枚举ElementType来指定,具体如下:

ElementType.CONSTRUCTOR:用于描述构造器。
ElementType.FIELD:用于描述域、实例变量。
ElementType.LOCAL_VARIABLE:用于描述局部变量。
ElementType.METHOD:用于描述方法。
ElementType.PACKAGE:用于描述包,记录java文件的package信息。
ElementType.PARAMETER:用于描述参数。
ElementType.TYPE:用于描述类、接口(包括注解类型)或enum声明。
ElementType.ANNOTATION_TYPE:另一个注释

  1. @Retention:
    表示需要在什么级别保存该注释信息,用于描述注解的生命周期,也是一个枚举RetentionPoicy来决定的。关于这个枚举和这个注解的具体怎么决定注解的生命周期请查阅相关书籍资料或者Google。一般填RententionPoicy.RUNTIME,此值注解处理器才能通过反射拿到注解信息,实现自己的语义。

RetentionPolicy.SOURCE:在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以他们不会写入字节码。@Override,@SuppressWarnings都属于这类注解。
RetentionPolicy.CLASS:在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。
RetentionPolicy.RUNTIME:始终不丢弃,运行期也保留该注解。因此可用反射机制读取该注解的信息(自定义的注解常使用这种方式)。

  1. @Documented:
    用javadoc生成文档时,想把注解也生成文档,就使用这个。
  2. @Inherited:
    @Inherited元注解是一个标记注解,阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该类的子类。@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation.
    上述注解中,最有用的就是@Target。
    Annotations只支持基本类型、String及枚举类型。注释中所有的属性被定义成方法,并允许提供默认值。

本文分为4个版本介绍Annotation:

  1. Java 中的Annotation.
  2. Java中自定义注解.
  3. Kotlin中的Annotation.
  4. Android中的Annotation.

一. Java中的Annotation:

在源码java.lang.annotation中,有几个比骄重要的类:(可打开IDEA,在里面追朔annotation的源代码)

  1. Annotation.java:
public interface Annotation {
	 boolean equals(Object obj);
 	 int hashCode();
     String toString();
     Class<? extends Annotation> annotationType();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意:annotationType的返回值。

  1. ElementType.java:
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

这个类是一个枚举类,用来指定Annotation的类型,表名Annotation可以用在什么地方。
注意:一个Annotation可以与多个ElementType关联。
TYPE_PARAMETER/TYPE_USE是JDK1.8的新特性,代表此Annotation可以用于泛型。
3. RententionPolicy.java:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

RetentionPolicy正如它名字所示,它代表了Annotation“保留”的策略。CLASS代表编译器将注解存储于类对应的.class文件中,但是在运行时不能通过JVM读取,在Java中这是Annotation的默认行为(在Kotlin中默认行为是RUNTIME)。RUNTIME则代表编译器将注解存储于.class文件中,并且可由反射获取,由于Kotlin的语言特性,它在是Kotlin中的默认策略。
注意:一个完整的Annotation与多个ElementType和一个RetentionPolicy相关联。

在Java中定义Annotation:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface MyAnnotation{
    }
  • 1
  • 2
  • 3
  • 4

上述代码定义了一个名为MyAnnotation的注解,它可以用来修饰类/接口/Annotation或者枚举,并将注解存储于类对应的.class文件中。在代码中,可以通过@MyAnnotation来使用它:

   @MyAnnotation
    class water{
        @MyAnnotation//MyAnnotation不能用作于方法
        public void flow(){
            
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

定义注解需要注意:

  1. 定义注解时,必须通过@interface方式,而不是通过implement Annotation的方式,Annotation接口的实现细节是编译器完成,不是通过我们的代码去完成。通过@interface定义说注解后,该注解不能继承其他的注解或接口。
  2. @Target(ElementType.TYPE)指定该注解的类型是ElementType.TYPE,可以定义多个ElementType:
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD})
    @interface  CursomAnnotation{
        
    }
  • 1
  • 2
  • 3
  • 4
  1. @Retention(RetentionPolicy.CLASS)表示这个注解的保留策略为CLASS(将注解存储于.class文件中,但是不能被JVM访问)在Java中如果不指定的话默认保留策略为它。
    综上,定义一个注解的3个步骤:
    1):实现接口
    2):定义类型
    3):定义保留策略

Java中的常见注解:

@Documented标记表示被注解的内容会被收录入javadoc中,只能用在其他的注解上。喜感的是,它在自己的定义中使用了它自己。
@Deprecated 表示所标注的内容不再被建议使用,它可以用在任何地方。
@Inherited 表示它所标注的Annotation将具有继承性,这意味着它所标注的类的子类也将拥有这个标注(子类默认是不继承注解的)。
@SuppressWarnings可以让编译器忽略掉对它所标注的内容的警告
重复注释允许相同注释在声明使用的时候重复使用超过一次,这是Java 8中的新特性。

从Java程序上认识注解:

public interface AnnotatedElement {
    default boolean isAnnotationPresent(Class<? extends Annotation> var1) {
        return this.getAnnotation(var1) != null;
    }

    <T extends Annotation> T getAnnotation(Class<T> var1);

    Annotation[] getAnnotations();

    default <T extends Annotation> T[] getAnnotationsByType(Class<T> var1) {
        Annotation[] var2 = this.getDeclaredAnnotationsByType(var1);
        if(var2.length == 0 && this instanceof Class && AnnotationType.getInstance(var1).isInherited()) {
            Class var3 = ((Class)this).getSuperclass();
            if(var3 != null) {
                var2 = var3.getAnnotationsByType(var1);
            }
        }

        return var2;
    }

    default <T extends Annotation> T getDeclaredAnnotation(Class<T> var1) {
        Objects.requireNonNull(var1);
        Annotation[] var2 = this.getDeclaredAnnotations();
        int var3 = var2.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            Annotation var5 = var2[var4];
            if(var1.equals(var5.annotationType())) {
                return (Annotation)var1.cast(var5);
            }
        }

        return null;
    }

    default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> var1) {
        Objects.requireNonNull(var1);
        return AnnotationSupport.getDirectlyAndIndirectlyPresent((Map)Arrays.stream(this.getDeclaredAnnotations()).collect(Collectors.toMap(Annotation::annotationType, Function.identity(), (var0, var1) -> {
            return var0;
        }, LinkedHashMap::<init>)), var1);
    }

    Annotation[] getDeclaredAnnotations();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

AnnotatedElement接口定义了一些判断注解以及获取注解的方法。isAnnotationPresent方法可以判断该元素是否拥有指定注解类的注解,getAnnotation(Class var1) 则返回指定注解类的注解,getAnnotations()则会返回所有的注解。
Class,Constructor,Executable,Field,Method,Package,Parameter,AccessibleObject这些类都位于java.lang.reflect包,这意味着我们的程序需要通过反射来识别注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
}

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@interface MyAnnotation3{
}

@MyAnnotation
class Country{
    @MyAnnotation
    int pop = 0;
    int coin = 0;
    String name = "";
    public Country(String name,int pop,int coin){
        this.name = name;
        this.pop = pop;
        this.coin = coin;
    }
    @MyAnnotation2
    public String describe()
    {
        return name+" pop: "+pop+" coin: "+coin;
    }
    @MyAnnotation2
    public int doublepop()
    {
        return pop*2;
    }
    @MyAnnotation3
    public int averageCoin()
    {
        if(pop == 0) return Integer.MAX_VALUE;
        else return coin/pop;
    }
}

public class Anotest {

    public static void main(String[] args) {
        Country sr = new Country("Ravenland",200,10000);
        Class<Country> c = Country.class;
        if(c.isAnnotationPresent(MyAnnotation.class))
        {
            System.out.println("Class Country has MyAnnotation");
        }
        Method[] methods = c.getMethods();
        for(Method method : methods)
        {
            Annotation[] aList = method.getAnnotations();
            if(aList!=null && aList.length > 0)
            {
                for(Annotation b : aList)
                {
                    System.out.println(method.getName()+"Has Annotation: "+b);
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

结果:

Class Country has MyAnnotation
doublepopHas Annotation: @test.MyAnnotation2()
describeHas Annotation: @test.MyAnnotation2()
  • 1
  • 2
  • 3

以上的例子深刻地表明只有RetentionPolicy.RUNTIME的注解能够通过反射访问。
注解广泛地使用在各种工具(例如Retrofit,GSON等)和框架(JUnit,DataBinding)中。

二. Java中自定义Annotation:

自定义注解又叫组合注解。
自定义注解格式:
在这里插入图片描述
注意:固定格式,只能用public或默认(default)这2个访问权修饰符。定义注解参数的类型,只能为:所有基本数据类型、String类型、Class类型、enum类型、Annotation类型的数组。方法名,就是这个注解的支持的属性名,表示该属性名在不指定时的默认值,可以不要。
自定义注解举例:

@Target(ElementType.TYPE)
    public  @interface Table{
        public String tableName() default "className";
    }
  • 1
  • 2
  • 3
  • 4

自定义注解的2种用法:
自定义注解处理器专门为你自定义的注解实现语义。【自定义注解处理器请查阅Google】

  1. 配合Spring使用:
    注解是可以组合的。在自定义注解时,可以加元注解还可以加自定义其他自定义注解。假设为@Table注解加了@Component,那么这个注解就会有@Component的功能(被Spring当作bean加入Spring容器里)。
    属性修饰符public和默认的区别:public修饰的属性能被覆盖,默认的则不能。
    举个例子:@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented @Component @Dog public @interface StupidDog { public String dog() default “我是一条StupidDog”; }我现在又定义了一个StupidDog注解,这个注解覆盖掉了@Dog的dog属性,把默认值从"“改成了"我是一条StupidDog”…
  2. ADF (应用程序框架)和注解:
    应用程序框架,被称为ADF,由Oracle开发用来创建Oracle融合应用。我们已经了解了注解的优缺点,也知道如何编写自定义的注解,但我们应该将注解应用在ADF的哪部分呢?ADF是否提供了一些朴素的注解?很好的问题,确实在ADF中大量使用注解有一些限制。之前提到的应用框架如Spring和Hibernate使用AOP(面向侧面的程序设计)。在AOP中,框架提供了一种机制,在事件的预处理和后续处理中注入代码。例如:你有一个钩子用来在方法执行之前和之后添加代码,所以你可以在这些地方编写你的用户代码。ADF不使用AOP。如果我们有任何注解的用例可用,我们可能需要通过继承的方式实现。应用程序框架,被称为ADF,由Oracle开发用来创建Oracle融合应用。我们已经了解了注解的优缺点,也知道如何编写自定义的注解,但我们应该将注解应用在ADF的哪部分呢?ADF是否提供了一些朴素的注解?很好的问题,确实在ADF中大量使用注解有一些限制。之前提到的应用框架如Spring和Hibernate使用AOP(面向侧面的程序设计)。在AOP中,框架提供了一种机制,在事件的预处理和后续处理中注入代码。例如:你有一个钩子用来在方法执行之前和之后添加代码,所以你可以在这些地方编写你的用户代码。ADF不使用AOP。如果我们有任何注解的用例可用,我们可能需要通过继承的方式实现。

摘选自简书https://www.jianshu.com/p/c25b08a25368?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=qq

三. Kotlin中的Annotation:

大部分相关的类位于包kotlin.annotation中。(同样可以使用IDEA跟踪相关源码

  1. AnnotationTarget.kt
public enum class AnnotationTarget {
    /** Class, interface or object, annotation class is also included */
    CLASS,
    /** Annotation class only */
    ANNOTATION_CLASS,
    /** Generic type parameter (unsupported yet) */
    TYPE_PARAMETER,
    /** Property */
    PROPERTY,
    /** Field, including property's backing field */
    FIELD,
    /** Local variable */
    LOCAL_VARIABLE,
    /** Value parameter of a function or a constructor */
    VALUE_PARAMETER,
    /** Constructor only (primary or secondary) */
    CONSTRUCTOR,
    /** Function (constructors are not included) */
    FUNCTION,
    /** Property getter only */
    PROPERTY_GETTER,
    /** Property setter only */
    PROPERTY_SETTER,
    /** Type usage */
    TYPE,
    /** Any expression */
    EXPRESSION,
    /** File */
    FILE,
    /** Type alias */
    @SinceKotlin("1.1")
    TYPEALIAS
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

在Kotlin中,AnnotationTarget是一个枚举类,它的作用和ElementType类似,表示注解能应用到哪些地方。
其中需要特别注意的是FIELD和PROPERTY,Kotlin的类不能有字段(FIELD)而只有属性(PROPERTY),只有在使用自定义访问器时才可能需要有一个后备字段。另外,由于在Kotlin中的单个申明往往对应了多个Java声明(例如var属性),因此在标注中也特意进行了PROPERTY_GETTER和PROPERTY_SETTER来特意区分它们。
在Kotlin中也可以对文件(FILE)和别名(TYPEALIAS)进行注解。
如果在声明注解时没有特意指明AnnotationTarget,说明这个注解可以用到任何地方(除了泛型、表达式和文件).

  1. AnnotationRetention.kt
public enum class AnnotationRetention {
    /** Annotation isn't stored in binary output */
    SOURCE,
    /** Annotation is stored in binary output, but invisible for reflection */
    BINARY,
    /** Annotation is stored in binary output and visible for reflection (default retention) */
    RUNTIME
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

AnnotationRetention和Java中的RetentionPolicy相对应,表示注解的保留策略。在Kotlin中的BINARY和Java中的CLASS对应,都代表无法通过反射访问,但是会被保留在class文件中。
在Kotlin中,默认的保留策略为RUNTIME,这表明我们可以通过反射在程序中获取它。

在Kotlin中定义注解:

在Kotlin中声明注解和声明普通的类很相似,区别在于在class前面需要加上annotation关键字:

/*不接收参数的注解*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class MyAnnotation

/*接受参数的注解*/
annotation class MyAnnotation2(val name : String)

/*接受多个参数的注解*/
annotation class MyAnnotation3(vararg val name : String)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Kotlin中的注解目标:

由于在Kotlin中的单个申明往往对应了多个Java声明,例如,一个Property对应了一个Field和Getter和Setter,为了使标注更为精确,Kotlin中还允许使用点目标。
点目标的语法为 @目标:注解名 例如:@get:MyAnnotation。

class Country{
    /*对属性的getter使用注解*/
    @get:MyAnnotation2("Editable")
    var name : String = ""

    /*对属性使用注解*/
    @MyAnnotation2("Editable")
    var pop : Int = 0

    /*对属性的Setter使用注解*/
    @MyAnnotation3("Editable","Can be below 0")
    @set:MyAnnotation3
    var coin : Int = 0

    /*对生成的Field使用注解*/
    /*具有setter的属性一般会自动生成backing field(后备字段)*/
    @field:MyAnnotation2("")
    var army : Int = 1000
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在Kotlin中支持以下点目标:
property:代表kotlin中的属性,不能被Java的注解所应用
field:为属性生成的字段(包括后备字段)
get:属性的getter
set:属性的setter
receiver:扩展函数/属性的接收者
param:构造函数的参数
setparam:属性setter的参数
delegate:委托属性存储委托实例的字段
file:在文件中声明的顶层函数与类
如果你希望你的注解使用类作为类型参数的话,你可以定义为:
annotation class MyClassAnnotation(val clazz : KClass<out Any>)
注意必须out Any,否则泛型参数无法协变导致你只能使用Any类作为参数.

 @MyClassAnnotation(NewsAdapter::class)
    fun nothingleft()
    {
        
    }
  • 1
  • 2
  • 3
  • 4
  • 5

KClass是Kotlin中与Java中Class类对应的类,用于Kotlin中的反射。
如果要使用泛型类作为注解参数,则需要通过星形投影.

 annotation class ListAnnotation(val clazz : KClass<out List<*>>)
    
    /*把List作为注解参数*/
    @ListAnnotation(List::class)
    fun sum()
    {
        
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

星形投影表示你不知道关于泛型实参的任何信息,比如在上面的例子中,你只知道List本身,而不知道它具体的泛型实参
由此可见,在Kotlin中应用注解的语法和Java很相似,但Kotlin中你注解的目标要比Java更广。

四. Android中的Annotation:

参考自鸿洋大神的公众号文章:https://mp.weixin.qq.com/s/o9LmdsFk-IdFpMEYTG2HNQ

准备工作:

默认情况下,Android注解包没有包含在framework中,它独立为一个单独的包
添加依赖:

dependencies {
    compile 'com.android.support:support-annotations:22.2.0'
}
  • 1
  • 2
  • 3

如果已经引入了appcompat则没有必要再次引用support-annotations,因为appcompat默认包含了对其引用.

替代枚举:

当我们想要做一些值得限定实现枚举的效果,通常是

  • 定义几个常量用于限定
  • 从上面的常量选取值进行使用
public static final int COLOR_RED = 0;
public static final int COLOR_GREEN = 1;
public static final int COLOR_YELLOW = 2;

public void setColor(int color) {
    //some code here
}
//调用
setColor(COLOR_RED)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

弊端:

  • setColor(COLOR_RED)与setColor(0)效果一样,而后者可读性很差,但却可以正常运行。
  • setColor方法可以接受枚举之外的值,比如setColor(3),这种情况下程序可能出问题。

一个相对较优的解决方法就是使用Java中的Enum.使用枚举实现的效果如下:

// ColorEnum.java
public enum ColorEmun {
    RED,
    GREEN,
    YELLOW
}

public void setColorEnum(ColorEmun colorEnum) {
    //some code here
}

setColorEnum(ColorEmun.GREEN);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Enum也并非最佳,Enum因为其相比方案一的常量来说,占用内存相对大很多而受到曾经被Google列为不建议使用,为此Google特意引入了一些相关的注解来替代枚举.
Android中新引入的替代枚举的注解有IntDef和StringDef。

  • 声明必要的int常量
  • 声明一个注解为LightColors
  • 使用@IntDef修饰LightColors,参数设置为待枚举的集合
  • 使用@Retention(RetentionPolicy.SOURCE)指定注解仅存在与源码中,不加入到class文件中
//以IntDef为例
public class Colors {
    @IntDef({RED, GREEN, YELLOW})
    @Retention(RetentionPolicy.SOURCE)
    public @interface LightColors{}

    public static final int RED = 0;
    public static final int GREEN = 1;
    public static final int YELLOW = 2;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Null相关注解:

在安卓开发中,常见如下2个Null注解。他们可以修饰成员属性、方法参数、方法的返回值。

@Nullable 注解的元素可以是Null
@NonNull 注解的元素不能是Null
  • 1
  • 2
//代码示例
@Nullable
private String obtainReferrerFromIntent(@NonNull Intent intent) {
    return intent.getStringExtra("apps_referrer");
}
  • 1
  • 2
  • 3
  • 4
  • 5

NonNull检测生效的条件:显式传入null、在调用方法之前已经判断了参数为null时。

setReferrer(null);//提示警告

//不提示警告
String referrer = getIntent().getStringExtra("apps_referrer");
setReferrer(referrer);

//提示警告
String referrer = getIntent().getStringExtra("apps_referrer");
if (referrer == null) {
    setReferrer(referrer);
}

private void setReferrer(@NonNull String referrer) {
    //some code here
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

区间范围注解:

Android中的IntRange和FloatRange是两个用来限定区间范围的注解。

float currentProgress;

public void setCurrentProgress(@FloatRange(from=0.0f, to=1.0f) float progress) {
    currentProgress = progress;
}
  • 1
  • 2
  • 3
  • 4
  • 5

如果我们传入非法的值,如setCurrentProgress(11);
会报错:Value must be >=0.0 and <= 1.0(was 11)

长度以及数组大小限制:

限制字符串的长度:

private void setKey(@Size(6) String key) {
}
  • 1
  • 2

限定数组集合的大小:

private void setData(@Size(max = 1) String[] data) {
}
setData(new String[]{"b", "a"});//error occurs
  • 1
  • 2
  • 3

限定特殊的数组长度,比如3的倍数:

private void setItemData(@Size(multiple = 3) String[] data) {
}
  • 1
  • 2

权限相关:

在Android中,有很多场景都需要使用权限,无论是Marshmallow之前还是之后的动态权限管理.都需要在manifest中进行声明,如果忘记了,则会导致程序崩溃. 好在有一个注解能辅助我们避免这个问题.使用RequiresPermission注解即可.

@RequiresPermission(Manifest.permission.SET_WALLPAPER)
    public void changeWallpaper(Bitmap bitmap) throws IOException {
}
  • 1
  • 2
  • 3

资源注解:

使用资源相关的注解修饰了参数,就能很大程度上避免错误的情况.

public String getStringById(@StringRes  int stringResId) {
    return getResources().getString(stringResId);
}
  • 1
  • 2
  • 3

在Android中资源注解如下所示

AnimRes
AnimatorRes
AnyRes
ArrayRes
AttrRes
BoolRes
ColorRes
DimenRes
DrawableRes
FractionRes
IdRes
IntegerRes
InterpolatorRes
LayoutRes
MenuRes
PluralsRes
RawRes
StringRes
StyleRes
StyleableRes
TransitionRes
XmlRes

Color值限定:

在较早的TextView的setTextColor是这样实现的.

public void setTextColor(int color) {
    mTextColor = ColorStateList.valueOf(color);
    updateTextColors();
}
  • 1
  • 2
  • 3
  • 4

上面的方法在调用时常常会出现这种情况

myTextView.setTextColor(R.color.colorAccent);
  • 1

如果传递过去的参数为color的资源id就会出现颜色取错误的问题,这个问题在过去还是比较严重的.好在ColorInt出现了,改变了这一问题.

public void setTextColor(@ColorInt int color) {
    mTextColor = ColorStateList.valueOf(color);
    updateTextColors();
}
  • 1
  • 2
  • 3
  • 4

当我们再次传入Color资源值时,就会得到错误的提示.

CheckResult:

关于返回结果的注解,用来注解方法,如果一个方法得到了结果,却没有使用这个结果,就会有错误出现,一旦出现这种错误,就说明你没有正确使用该方法。

@CheckResult
public String trim(String s) {
    return s.trim();
}
  • 1
  • 2
  • 3
  • 4

线程相关:

Android中提供了四个与线程相关的注解

@UiThread,通常可以等同于主线程,标注方法需要在UIThread执行,比如View类就使用这个注解
@MainThread 主线程,经常启动后创建的第一个线程
@WorkerThread 工作者线程,一般为一些后台的线程,比如AsyncTask里面的doInBackground就是这样的.
@BinderThread 注解方法必须要在BinderThread线程中执行,一般使用较少.

new AsyncTask<Void, Void, Void>() {
        //doInBackground is already annotated with @WorkerThread
        @Override
        protected Void doInBackground(Void... params) {
            return null;
            updateViews();//error
        }
    };

@UiThread
public void updateViews() {
    Log.i(LOGTAG, "updateViews ThreadInfo=" + Thread.currentThread());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

CallSuper:

重写的方法必须要调用super方法.
使用这个注解,我们可以强制方法在重写时必须调用父类的方法 比如Application的onCreate,onConfigurationChanged等.

Keep:

在Android编译生成APK的环节,我们通常需要设置minifyEnabled为true实现下面的两个效果

  • 混淆代码
  • 删除没有用的代码
    但是出于某一些目的,我们需要不混淆某部分代码或者不删除某处代码,除了配置复杂的Proguard文件之外,我们还可以使用@Keep注解 .
@Keep
public static int getBitmapWidth(Bitmap bitmap) {
    return bitmap.getWidth();
}
  • 1
  • 2
  • 3
  • 4

ButterKnife与Otto:

  1. ButterKnife是一个用来绑定View,资源和回调的提高效率的工具.作者为Jake Wharton. ButterKnife的好处:
  • 使用BindView替代繁琐的findViewById和类型转换
  • 使用OnClick注解方法来替换显式声明的匿名内部类
  • 使用BindString,BindBool,BindDrawable等注解实现资源获取
  1. Otto Bus 是一个专为Android改装的Event Bus,在很多项目中都有应用.由Square开源共享.
    关于两者的工作原理可自行查阅官网。

反射之前,我们需要对Java虚拟机中的内存模型进行一点简单的了解…
关于Java和Kotlin中的反射,可参考:https://www.jianshu.com/p/5adf5f1c49e8

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/580897
推荐阅读
相关标签
  

闽ICP备14008679号