Java注解

转自: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

 表示过时弃用。调用该注解标记的元素在编译时会出现警告提醒。
2018-12-26_211310

4.1.2 @Override

 最常见的注解,表示子类要复写父类的方法。

4.1.3 @SuppressWarnings

 表示抑制警告。前面说过调用@Deprecated注解标记的元素在编译时会出现警告提醒,而如果开发者想忽略这种警告,可以在调用语句所在方法前加入@SuppressWarnings注解。
2018-12-26_212613

#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注解。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值