Java自定义注解——1

什么是注解?

百度百科:

定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
①编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
② 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】

通俗一点就是说:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。

再通俗一点来讲,就是给类、接口、方法、属性等。。。贴上一个标签,来新添他们的作用。

注解的定义

注解通过 @interface 关键字进行定义。

public @interface Annotation {
}

使用注解则是在类或者方法等定义的上方添加@注解名。

@Annotation
public class Test {
}

Java注解分类:分为 1 JDK基本注解、2 JDK元注解、3 自定义注解,基本注解和自定义注解都是在元注解的基础上进行的。

元注解

元标签有 @Retention、@Target、@Inherited、@Documented、@Repeatable 5 种。
作用:元注解用于修饰其他的注解。

1、@Retention:定义注解的保留策略

@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) //默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME) //注解会在class字节码文件中存在,在运行时可以通过反射获取到

如果不是特殊情况,大多情况都是使用RUNTIME

2,@Target:指定被修饰的Annotation可以放置的位置(被修饰的目标)
@Target(ElementType.TYPE) //接口、类、枚举
@Target(ElementType.FIELD) //属性
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE) //局部变量
@Target(ElementType.ANNOTATION_TYPE) //注解
@Target(ElementType.PACKAGE) //包

也可以指定多个位置,例如:
@Target({ElementType.METHOD, ElementType.TYPE}),也就是此注解可以在方法和类上面使用,用逗号隔开

3,@Inherited:指定被修饰的Annotation将具有继承性 ,

也就比如A有@Inherited注解,那么继承他的子类也会有他的所有注解。

4,@Documented:它的作用是能够将注解中的元素包含到 Javadoc 中去。

5,@Repeatable:Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。

@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 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。到了这里接下来给大家讲讲注解的属性.

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Documented
public @interface MyAnnotation1 {
    String name();
    int id();
}

上面代码定义了 MyAnnotation1 这个注解中拥有 id 和 name两个属性。在使用的时候,我们应该给它们进行赋值。

@MyAnnotation1(id=3,name="zhangsan")
public class Test {
}

注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Documented
public @interface MyAnnotation1 {
    String name() default "zhangsan";
    int id() default 1;
}

当你设置了默认值后,你调用的时候就可以不用再赋值了。

@MyAnnotation1()
public class Test {
}

另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Documented
public @interface MyAnnotation1 {
    String value() ;
}

调用:
方法1:

@MyAnnotation1("zhangsan")
public class Test {
}

方法2:

@MyAnnotation1(value="zhangsan")
public class Test {
}

调用的这两个方式效果是一样的。

最后,还需要注意的一种情况是一个注解没有任何属性。比如:

public @interface Test3 {}

那么在应用这个注解的时候,括号都可以省略。

@Test3
public class Test {
}

Java 预置的注解

1,@Deprecated
这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。

public class Hero {
    @Deprecated
    public void say(){
        System.out.println("Noting has to say!");
    }
    public void speak(){
        System.out.println("I have a dream!");
    }
}

在这里插入图片描述
2,@Override
这个大家应该很熟悉了,提示子类要复写父类中被 @Override 修饰的方法也就是重写。
3,@SuppressWarnings
阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。
4,@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。

@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!
}

上面的代码中,编译阶段不会报错,但是运行时会抛出 ClassCastException 这个异常,所以它虽然告诉开发者要妥善处理,但是开发者自己还是搞砸了。

Java 官方文档说,未来的版本会授权编译器对这种不安全的操作产生错误警告。

@FunctionalInterface
函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。

函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。
比如

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

我们进行线程开发中常用的 Runnable 就是一个典型的函数式接口,上面源码可以看到它就被 @FunctionalInterface 注解。

可能有人会疑惑,函数式接口标记有什么用,这个原因是函数式接口可以很容易转换为 Lambda 表达式。这是另外的主题了,有兴趣的同学请自己搜索相关知识点学习。

注解与反射

注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}

然后通过 getAnnotation() 方法来获取 Annotation 对象。

public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}

或者是 getAnnotations() 方法:

public Annotation[] getAnnotations() {}

前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。
如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了。比如:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Documented
public @interface MyAnnotation1 {
    String name() default "zhangsan";
    int id() default 1;
}
@TestAnnotation()
public class Test {
    public static void main(String[] args) {
        boolean hasAnnotation = Test.class.isAnnotationPresent(MyAnnotation1.class);
        if ( hasAnnotation ) {
            TestAnnotation testAnnotation = Test.class.getAnnotation(MyAnnotation1.class);
            System.out.println("id:"+testAnnotation.id());
            System.out.println("name:"+testAnnotation.name());
        }
    }
}

结果:

id:1
name:zhangsan

上面的例子中,只是检阅出了注解在类上的注解,其实属性、方法上的注解照样是可以的。同样还是要假手于反射。

@TestAnnotation(msg="hello")
public class Test {
    @Check(value="hi")
    int a;
    @Perform
    public void testMethod(){}
    @SuppressWarnings("deprecation")
    public void test1(){
        Hero hero = new Hero();
        hero.say();
        hero.speak();
    }
    public static void main(String[] args) {
        boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);
        if ( hasAnnotation ) {
            TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);
            //获取类的注解
            System.out.println("id:"+testAnnotation.id());
            System.out.println("msg:"+testAnnotation.msg());
        }
        try {
            Field a = Test.class.getDeclaredField("a");
            a.setAccessible(true);
            //获取一个成员变量上的注解
            Check check = a.getAnnotation(Check.class);
            if ( check != null ) {
                System.out.println("check value:"+check.value());
            }
            Method testMethod = Test.class.getDeclaredMethod("testMethod");
            if ( testMethod != null ) {
                // 获取方法中的注解
                Annotation[] ans = testMethod.getAnnotations();
                for( int i = 0;i < ans.length;i++) {
                    System.out.println("method testMethod annotation:"+ans[i].annotationType().getSimpleName());
                }
            }
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}

结果:

id:-1
msg:hello
check value:hi
method testMethod annotation:Perform

总结:

注解的使用场景

我相信博文讲到这里大家都很熟悉了注解,但是有不少同学肯定会问,注解到底有什么用呢?

对啊注解到底有什么用?

我们不妨将目光放到 Java 官方文档上来。

文章开始的时候,我用标签来类比注解。但标签比喻只是我的手段,而不是目的。为的是让大家在初次学习注解时能够不被那些抽象的新概念搞懵。既然现在,我们已经对注解有所了解,我们不妨再仔细阅读官方最严谨的文档。

注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。

注解有许多用处,主要如下:

提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
值得注意的是,注解不是代码本身的一部分。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值