赞
踩
注解机制:
JDK1.5后,Java引入了一种新的注释机制(Annotation),其一般作为说明信息,与程序的业务逻辑无关。
注解仅仅是一种说明信息,它广泛的应用于一些工具或框架中。
(官方定义)注解Annotation就是Java提供了一种元程序中的元素关联任何信息和任何元数据(metadata)的途径和方法。
@Override
public String toString(){
return "This is String Representation of current object.";
}
上述代码中,重写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";
}
}
上述中,Table就是自定义的一个注解,使用方法:@Table。那么这个@Target其实就是元注解了。
Java自带的元注解如下:
@Target 注解用于什么地方
@Retention 什么时候使用该注解
@Documented 注解是否将包含在JavaDoc中
@Inherited 是否允许子类继承该注解
ElementType.CONSTRUCTOR:用于描述构造器。
ElementType.FIELD:用于描述域、实例变量。
ElementType.LOCAL_VARIABLE:用于描述局部变量。
ElementType.METHOD:用于描述方法。
ElementType.PACKAGE:用于描述包,记录java文件的package信息。
ElementType.PARAMETER:用于描述参数。
ElementType.TYPE:用于描述类、接口(包括注解类型)或enum声明。
ElementType.ANNOTATION_TYPE:另一个注释
RetentionPolicy.SOURCE:在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以他们不会写入字节码。@Override,@SuppressWarnings都属于这类注解。
RetentionPolicy.CLASS:在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。
RetentionPolicy.RUNTIME:始终不丢弃,运行期也保留该注解。因此可用反射机制读取该注解的信息(自定义的注解常使用这种方式)。
本文分为4个版本介绍Annotation:
- Java 中的Annotation.
- Java中自定义注解.
- Kotlin中的Annotation.
- Android中的Annotation.
在源码java.lang.annotation中,有几个比骄重要的类:(可打开IDEA,在里面追朔annotation的源代码)
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
注意:annotationType的返回值。
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 }
这个类是一个枚举类,用来指定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 }
RetentionPolicy正如它名字所示,它代表了Annotation“保留”的策略。CLASS代表编译器将注解存储于类对应的.class文件中,但是在运行时不能通过JVM读取,在Java中这是Annotation的默认行为(在Kotlin中默认行为是RUNTIME)。RUNTIME则代表编译器将注解存储于.class文件中,并且可由反射获取,由于Kotlin的语言特性,它在是Kotlin中的默认策略。
注意:一个完整的Annotation与多个ElementType和一个RetentionPolicy相关联。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation{
}
上述代码定义了一个名为MyAnnotation的注解,它可以用来修饰类/接口/Annotation或者枚举,并将注解存储于类对应的.class文件中。在代码中,可以通过@MyAnnotation来使用它:
@MyAnnotation
class water{
@MyAnnotation//MyAnnotation不能用作于方法
public void flow(){
}
}
定义注解需要注意:
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD})
@interface CursomAnnotation{
}
@Documented标记表示被注解的内容会被收录入javadoc中,只能用在其他的注解上。喜感的是,它在自己的定义中使用了它自己。
@Deprecated 表示所标注的内容不再被建议使用,它可以用在任何地方。
@Inherited 表示它所标注的Annotation将具有继承性,这意味着它所标注的类的子类也将拥有这个标注(子类默认是不继承注解的)。
@SuppressWarnings可以让编译器忽略掉对它所标注的内容的警告
重复注释允许相同注释在声明使用的时候重复使用超过一次,这是Java 8中的新特性。
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(); }
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); } } } } }
结果:
Class Country has MyAnnotation
doublepopHas Annotation: @test.MyAnnotation2()
describeHas Annotation: @test.MyAnnotation2()
以上的例子深刻地表明只有RetentionPolicy.RUNTIME的注解能够通过反射访问。
注解广泛地使用在各种工具(例如Retrofit,GSON等)和框架(JUnit,DataBinding)中。
自定义注解又叫组合注解。
自定义注解格式:
注意:固定格式,只能用public或默认(default)这2个访问权修饰符。定义注解参数的类型,只能为:所有基本数据类型、String类型、Class类型、enum类型、Annotation类型的数组。方法名,就是这个注解的支持的属性名,表示该属性名在不指定时的默认值,可以不要。
自定义注解举例:
@Target(ElementType.TYPE)
public @interface Table{
public String tableName() default "className";
}
自定义注解的2种用法:
自定义注解处理器专门为你自定义的注解实现语义。【自定义注解处理器请查阅Google】
摘选自简书https://www.jianshu.com/p/c25b08a25368?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=qq
大部分相关的类位于包kotlin.annotation中。(同样可以使用IDEA跟踪相关源码)
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 }
在Kotlin中,AnnotationTarget是一个枚举类,它的作用和ElementType类似,表示注解能应用到哪些地方。
其中需要特别注意的是FIELD和PROPERTY,Kotlin的类不能有字段(FIELD)而只有属性(PROPERTY),只有在使用自定义访问器时才可能需要有一个后备字段。另外,由于在Kotlin中的单个申明往往对应了多个Java声明(例如var属性),因此在标注中也特意进行了PROPERTY_GETTER和PROPERTY_SETTER来特意区分它们。
在Kotlin中也可以对文件(FILE)和别名(TYPEALIAS)进行注解。
如果在声明注解时没有特意指明AnnotationTarget,说明这个注解可以用到任何地方(除了泛型、表达式和文件).
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
}
AnnotationRetention和Java中的RetentionPolicy相对应,表示注解的保留策略。在Kotlin中的BINARY和Java中的CLASS对应,都代表无法通过反射访问,但是会被保留在class文件中。
在Kotlin中,默认的保留策略为RUNTIME,这表明我们可以通过反射在程序中获取它。
在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)
由于在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 }
在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()
{
}
KClass是Kotlin中与Java中Class类对应的类,用于Kotlin中的反射。
如果要使用泛型类作为注解参数,则需要通过星形投影.
annotation class ListAnnotation(val clazz : KClass<out List<*>>)
/*把List作为注解参数*/
@ListAnnotation(List::class)
fun sum()
{
}
星形投影表示你不知道关于泛型实参的任何信息,比如在上面的例子中,你只知道List本身,而不知道它具体的泛型实参
由此可见,在Kotlin中应用注解的语法和Java很相似,但Kotlin中你注解的目标要比Java更广。
参考自鸿洋大神的公众号文章:https://mp.weixin.qq.com/s/o9LmdsFk-IdFpMEYTG2HNQ
默认情况下,Android注解包没有包含在framework中,它独立为一个单独的包
添加依赖:
dependencies {
compile 'com.android.support:support-annotations:22.2.0'
}
如果已经引入了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)
弊端:
一个相对较优的解决方法就是使用Java中的Enum.使用枚举实现的效果如下:
// ColorEnum.java
public enum ColorEmun {
RED,
GREEN,
YELLOW
}
public void setColorEnum(ColorEmun colorEnum) {
//some code here
}
setColorEnum(ColorEmun.GREEN);
Enum也并非最佳,Enum因为其相比方案一的常量来说,占用内存相对大很多而受到曾经被Google列为不建议使用,为此Google特意引入了一些相关的注解来替代枚举.
Android中新引入的替代枚举的注解有IntDef和StringDef。
//以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;
}
在安卓开发中,常见如下2个Null注解。他们可以修饰成员属性、方法参数、方法的返回值。
@Nullable 注解的元素可以是Null
@NonNull 注解的元素不能是Null
//代码示例
@Nullable
private String obtainReferrerFromIntent(@NonNull Intent intent) {
return intent.getStringExtra("apps_referrer");
}
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
}
Android中的IntRange和FloatRange是两个用来限定区间范围的注解。
float currentProgress;
public void setCurrentProgress(@FloatRange(from=0.0f, to=1.0f) float progress) {
currentProgress = progress;
}
如果我们传入非法的值,如setCurrentProgress(11);
会报错:Value must be >=0.0 and <= 1.0(was 11)
限制字符串的长度:
private void setKey(@Size(6) String key) {
}
限定数组集合的大小:
private void setData(@Size(max = 1) String[] data) {
}
setData(new String[]{"b", "a"});//error occurs
限定特殊的数组长度,比如3的倍数:
private void setItemData(@Size(multiple = 3) String[] data) {
}
在Android中,有很多场景都需要使用权限,无论是Marshmallow之前还是之后的动态权限管理.都需要在manifest中进行声明,如果忘记了,则会导致程序崩溃. 好在有一个注解能辅助我们避免这个问题.使用RequiresPermission注解即可.
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public void changeWallpaper(Bitmap bitmap) throws IOException {
}
使用资源相关的注解修饰了参数,就能很大程度上避免错误的情况.
public String getStringById(@StringRes int stringResId) {
return getResources().getString(stringResId);
}
在Android中资源注解如下所示
AnimRes
AnimatorRes
AnyRes
ArrayRes
AttrRes
BoolRes
ColorRes
DimenRes
DrawableRes
FractionRes
IdRes
IntegerRes
InterpolatorRes
LayoutRes
MenuRes
PluralsRes
RawRes
StringRes
StyleRes
StyleableRes
TransitionRes
XmlRes
在较早的TextView的setTextColor是这样实现的.
public void setTextColor(int color) {
mTextColor = ColorStateList.valueOf(color);
updateTextColors();
}
上面的方法在调用时常常会出现这种情况
myTextView.setTextColor(R.color.colorAccent);
如果传递过去的参数为color的资源id就会出现颜色取错误的问题,这个问题在过去还是比较严重的.好在ColorInt出现了,改变了这一问题.
public void setTextColor(@ColorInt int color) {
mTextColor = ColorStateList.valueOf(color);
updateTextColors();
}
当我们再次传入Color资源值时,就会得到错误的提示.
关于返回结果的注解,用来注解方法,如果一个方法得到了结果,却没有使用这个结果,就会有错误出现,一旦出现这种错误,就说明你没有正确使用该方法。
@CheckResult
public String trim(String s) {
return s.trim();
}
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());
}
重写的方法必须要调用super方法.
使用这个注解,我们可以强制方法在重写时必须调用父类的方法 比如Application的onCreate,onConfigurationChanged等.
在Android编译生成APK的环节,我们通常需要设置minifyEnabled为true实现下面的两个效果
@Keep
public static int getBitmapWidth(Bitmap bitmap) {
return bitmap.getWidth();
}
反射之前,我们需要对Java虚拟机中的内存模型进行一点简单的了解…
关于Java和Kotlin中的反射,可参考:https://www.jianshu.com/p/5adf5f1c49e8
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。