转自:https://blog.csdn.net/briblue/article/details/73824058
1.注解的定义和简单使用
1.1 注解是什么
简单来说:注解可以理解为标签,用于解释说明代码。
注释是解释给人看的,注解是解释给机器看的。
1.2 注解定义
注解通过 @interface 关键字进行定义,它的形式和接口很类似,不过前面多了一个 @ 符号。下面的代码就创建了一个名字为 TestAnnotaion 的注解。
public @interface TestAnnotation {
}
1.3 注解的简单使用
@TestAnnotation
public class Test {
}
你可以简单理解为将 TestAnnotation 这张标签贴到 Test 这个类上。不过,要想注解能够正常工作,还需要介绍一下一个新的概念那就是元注解。
2.元注解
元注解可以理解为注解的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上。元注解有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。
2.1 Retention
注解的保留期。它的取值如下:
- RetentionPolicy.SOURCE 编译前保留。
- RetentionPolicy.CLASS 运行前保留,不会被加载到 JVM 中。
- RetentionPolicy.RUNTIME 一直保留到运行时,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}
2.2 Documented
默认情况注解不会被JavaDoc记录,但是如果该注解被Documented标注,该注解就会出现在JavaDoc中了。
2.3 Target
使用场景,它有以下取值:
- ElementType.ANNOTATION_TYPE 注解的注解
- ElementType.CONSTRUCTOR 构造方法的注解
- ElementType.FIELD 属性的注解
- ElementType.LOCAL_VARIABLE 局部变量的注解
- ElementType.METHOD 方法的注解
- ElementType.PACKAGE 包的注解
- ElementType.PARAMETER 方法参数的注解
- ElementType.TYPE 类型的注解,比如类、接口、枚举
@Target(ElementType.TYPE)
public @interface TestAnnotation {
}
@TestAnnotation
public class Test {
}
2.4 Inherited
Inherited 是继承的意思,但并不是说注解本身可以被继承,而是指注解从父类继承给子类了。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也继承了Test 这个注解。
可以这样理解:
老子非常有钱,所以人们给他贴了一张富豪的标签。
儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。孙子长大了,自然也是富豪。
这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。
2.5 Repeatable
Repeatable 是可重复使用的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。什么样的注解会多次使用呢?通常是注解的值可以同时取多个。
所以Repeatable可以理解为注解数组。
举例来说:举个例子,一个人既是程序员又是产品经理,同时他还是个画家。
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。
我们再看看代码中的相关容器注解:
@interface Persons {
Person[] value();//必须这么写
}
按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组。
如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。
3.注解的属性
3.1 注解属性的声明
注解只有成员变量(属性),没有成员方法。注解属性以“无参方法头”的形式来声明。default后面跟上属性默认值,可以缺省。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id() default -1;
String msg();
}
3.2 注解属性的赋值
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
当然因为id有默认值,也可以这么用:
@TestAnnotation(msg="hello annotation")
那么id就赋值默认值了。如果属性都有默认值,也可以这么简写赋值:
@TestAnnotation()
属性都赋值默认值。
如果注解只有一个属性(假设是msg属性),你可以这么赋值:
@TestAnnotation("hi")
如果注解都使用默认值,你甚至可以这么赋值:
@TestAnnotation
4.Java 中常见的注解
4.1 Java内置注解
4.1.1 @Deprecated
表示过时弃用。调用该注解标记的元素在编译时会出现警告提醒。
4.1.2 @Override
最常见的注解,表示子类要复写父类的方法。
4.1.3 @SuppressWarnings
表示抑制警告。前面说过调用@Deprecated
注解标记的元素在编译时会出现警告提醒,而如果开发者想忽略这种警告,可以在调用语句所在方法前加入@SuppressWarnings
注解。
#4.1.4 @SafeVarargs
使用SafeVarargs来忽略参数类型未检查编译警告,该注解是Java 1.7 引入的。
当使用泛型作为可变参数时,因为可变参数是数组类型,在编译后泛型会被擦出掉,编译时会出现unchecked的警告。
举例:
public class VarargsWaring {
@SafeVarargs
private static List<String> useVarargs(List<String>... args) {
return args.length > 0 ? args[0] : null;
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();//1
//List<String> list = new ArrayList<>();//2
System.out.println(VarargsWaring.useVarargs(list));
}
}
该代码能编译成功,如果没有@SafeVarargs
注解,由于没有进行参数类型检测,编译时就会出现unchecked的警告。如果开发者确定不会出现未进行参数类型检测错误,就可以使用该注解来忽略警告。如果调用代码1,成功运行;如果调用代码2,运行时会出错。
4.1.5 @FunctionalInterface
表示函数式接口,被标记的接口被调用时可以转换成Lambda 表达式。java1.8引入的新特性
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
4.2 Java第三方注解
4.2.1 Spring,SpringMVC,MyBatis注解(了解即可)
4.2.2 Android常见注解(熟悉)
5.Android中常见的注解
5.1 @BindView
@BindView
是Android IOC 框架ButterKnife 中的注解,用于代替findViewById,减少了大量重复的代码。
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_test)
TextView mTv;
@BindView(R.id.button1)
Button mbutton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//mTv=findViewById(R.id.tv_test);
//mbutton=findViewById(R.id.button1);
ButterKnife.bind(this);
}
}
5.2 @TargetApi
使用@TargetApi
,低版本SDK使用高版本ApI(只是让高于minSDK版本的方法编译不报错);
结合分支语句保证运行不出错。下列举例来自《第一行代码》第二版P300:
//此代码段所在project minSDK<19
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//...
// 判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19) {
// 4.4及以上系统使用这个方法处理图片
handleImageOnKitKat(data);
} else {
// 4.4以下系统使用这个方法处理图片
handleImageBeforeKitKat(data);
}
//...
}
@TargetApi(19)
private void handleImageOnKitKat(Intent data) {
//解析封装过的Uri,Android 4.4 API
//...
}
private void handleImageBeforeKitKat(Intent data) {
//Uri没有被封装可以直接获取
//...
}
5.3 @Nullable @NonNull
@Nullable
标记函数参数或者返回值可为空;@NonNull
标记参数或者返回不可为空。
5.4 资源类型注解
@AnimatorRes ----android.R.animator
@AnimRes-----android.R.anim
@ArrayRes----android.R.array
@AttrRes-----android.R.attr
@BoolRes----bool类型
@ColorRes-----android.R.color
@DrawableRes ----android.R.drawable
@IdRes----android.R.id
@IntegerRes----android.R.integer
@LayoutRes----android.R.layout
@MenuRes----android.R.menu
@RawRes----android.R.raw
@StringRes---android.R.string
@StyleableRes----android.R.styleable
@XmlRes---android.R.xml
5.5 线程注解
@UiThread---UI线程
@MainThread---主线程
@WorkerThread---后台线程
@BinderThread---binder线程
5.6 参数范围注解
(1)@Size
用于限制数组、集合、字符串参数长度
@Size(min = 1)---最小长度
@Size(max = 23)---最大长度
@Size(2)---长度为2
@Size(multiple = 2)//长度是2的倍数
(2)@IntRange和@FloatRange
@IntRange
用于限制int和long类型参数范围;
@FloatRange
用于限制float和double类型参数范围;
public void setValue(@IntRange(from=0,to= 10) int value){
//value is from 0 to 10
}
5.7 权限申请提醒注解
如果某个方法需要申请权限,为了让开发者记得去申请权限,可以在该方法前标注@RequiresPermission
注解,如果开发者没有申请权限,编译时会出现Permission Missing警告。
(1) 某方法需要申请一个权限
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public void setWallPaper(Bitmap bit){
}
(2) 某方法需要申请集合中至少一个权限
@RequiresPermission(anyOf = { Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_FINE_LOCATION})
public void setWallPaper(Bitmap bit){
}
(3) 某方法需要申请集合中所有权限
@RequiresPermission(allOf = { Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_FINE_LOCATION})
public void setWallPaper(Bitmap bit){
}
5.8 @Keep
不作混淆(类或方法)注解
5.9 @SerializedName
解决了json解析过程中json字段名与java对象属性名匹配的问题;
举例来解释:
json数据:
{
"id":"1"
"name":"zhangsan"
"pwd":"123456"
"s":"0"
}
java类:
public class User{
private String id;
@SerializedName("name")
private String userName;
@SerializedName("pwd")
private String password;
@SerializedName("s")
private String sex;
}
这样即使json字段名与java对象属性名不一致也可以正常解析了。
6.利用反射提取注解
(1)是否使用了某注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
(2)提取指定注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
(3)提取所有注解
public Annotation[] getAnnotations()
下面举例利用反射提取了类、属性、方法的注解;注意:只有RetentionPolicy.RUNTIME注解才能被提取;
两个自定义注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
String value() default "isLegal";
}
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id() default -1;
String msg() default "Hi";
}
Hero类:
public class Hero {
@Deprecated
public void say(){
System.out.println("Noting has to say!");
}
public void speak(){
System.out.println("I have a dream!");
}
}
测试类:
/**
* 通过反射提取注解的例子,
* 因为是运行时提取注解,所以只有Retention为RUNTIME的注解才能提取出来
*/
@TestAnnotation(msg="hello")
public class GetAnnotationDemo{
@Check("FieldCheck")
private int a;
@SuppressWarnings("deprecation")
@Check("MethodCheck")
public void testHero(){
Hero hero = new Hero();
hero.say();
hero.speak();
}
public static void main(String[] args) {
//提取类的注解
Class clazz=GetAnnotationDemo.class;//通过反射获取Class对象
if (clazz.isAnnotationPresent(TestAnnotation.class)) {//是否有某注解
//提取类的注解
TestAnnotation testAnnotation = (TestAnnotation) clazz.getAnnotation(TestAnnotation.class);
if(testAnnotation!=null){
System.out.println("id:"+testAnnotation.id());
System.out.println("msg:"+testAnnotation.msg());
}
}
//提取属性的注解
Field a = null;
try {
//通过class对象获取属性
a = clazz.getDeclaredField("a");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
a.setAccessible(true);//暴力使属性可访问
//获取一个属性的注解
Check check = a.getDeclaredAnnotation(Check.class);
if ( check != null ) {
System.out.println("The "+a.getName()+" Field's check annotation value is "+check.value());
}
//提取方法的注解
Method method= null;//通过Class对象获取所有方法
try {
method = clazz.getDeclaredMethod("testHero",null);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
method.setAccessible(true);
//RetentionPolicy.SOURCE 所以SuppressWarnings注解无法提取
SuppressWarnings an= method.getDeclaredAnnotation(SuppressWarnings.class);
if(an!=null){
System.out.println("SuppressWarnings value: "+an.value()[0]);
}
//RetentionPolicy.RUNTIME 所以Check注解可以提取
check=method.getDeclaredAnnotation(Check.class);
if(check !=null){
System.out.println("The "+method.getName()+" method's check annotation value is "+check.value());
}
}
}
运行结果:
id:-1
msg:hello
The a Field's check annotation value is FieldCheck
The testHero method's check annotation value is MethodCheck
7.注解的官方定义
文章开始的时候,我用标签来解释注解。但标签比喻只是我的手段,而不是目的。为的是让大家在初次学习注解时能够不被那些抽象的新概念搞懵。既然现在,我们已经对注解有所了解,我们不妨再仔细阅读官方最严谨的文档。
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
注解不是代码本身的一部分,注解无法改变代码本身;注解是给编译器、工具和APT使用的。
8.自定义注解完成某个需求 举例
现在有如下需求:
-对某个代码单元进行测试;
-测试代码单元某些方法测试,某些方法不进行测试;
这些需求可以用注解完成。
自定义注解
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}
要测试的代码单元:
public class CodeUnit {
@TestAnnotation
public void addition(){
System.out.println("1+1="+(1+1));
}
@TestAnnotation
public void subtraction(){
System.out.println("1-1="+(1-1));
}
@TestAnnotation
public void multiplication(){
System.out.println("3 x 5="+ 3*5);
}
@TestAnnotation
public void division(){
System.out.println("6 / 0="+ 6 / 0);
}
public void selfIntroduction(){
System.out.println("CodeUnit has no bug!");
}
}
测试用例:
import java.lang.reflect.Method;
public class ExampleTest {
public static void main(String[] args) {
//测试日志
StringBuilder log = new StringBuilder();
// 记录异常的次数
int errornum = 0;
CodeUnit codeUnit = new CodeUnit();
Class clazz = codeUnit.getClass();
Method[] method = clazz.getDeclaredMethods();
for ( Method m: method ) {
// 只有被 @TestAnnotation 标注过的方法才进行测试
if ( m.isAnnotationPresent( TestAnnotation.class )) {
try {
m.setAccessible(true);
m.invoke(codeUnit, null);
} catch (Exception e) {
// TODO Auto-generated catch block
//e.printStackTrace();
errornum++;
//记录测试过程中,发生的异常的具体信息
log.append(m.getName()+" has error: "+e.getCause().getMessage()+"\n");
//记录测试过程中,发生的异常的名称
log.append("caused by "+e.getCause().getClass().getSimpleName()+"\n");
}
}
}
log.append(clazz.getSimpleName()+" has "+errornum+" errors."+"\n");
// 生成测试报告
System.out.println(log.toString());
}
}
运行结果:
1-1=0
1+1=2
3 x 5=15
division has error: / by zero
caused by ArithmeticException
CodeUnit has 1 errors.
以上可以看出,我们使用自定义注解,做到了过滤处理(测试)代码。
所以,再问我注解什么时候用?我只能告诉你,这取决于你想用它干什么。
9.总结
- 注解可以理解为标签,用于解释说明代码。用
@interface
定义; - 元注解、注解的属性;
- 注解的提取需要借助反射,反射比较慢,所以要谨慎使用注解;
- 注解不是代码,注解无法改变代码;注解是给编译器、工具和APT使用的。
- 熟悉常见的Java注解和Android注解。