您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

时间:2021-08-13 11:37:21  来源:CSDN  作者:Java学长

JAVA里有个神奇的存在,注解,就是那个天天@别人的家伙,它到底是何方神圣啊?

请先给二当家的来个一键三连,然后接着耐心地读下去吧,多谢。

什么是注解

从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。

内置的注解

二当家的发现在 IDE 中如果创建一个类并实现一个接口之后,那些实现接口的方法上面会自动帮我添加 @Override 的标记。

而这个标记就是注解,像@Override这样JDK内置的注解还有好几个呢,他们都在java.lang包下面,我们分别看看。

@Override

当子类重写父类方法时,子类可以加上这个注解,那这有什么什么用?这可以确保子类确实重写了父类的方法,避免出现低级错误。


 

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 


 

开始不知道有什么用,直到有一天,我把接口里的这个方法删除了,结果就编译不通过了。


 

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 


下面是直接在命令行编译的结果。这回我知道这个标记的作用了,它可以告诉编译器和阅读源码的人这个方法是覆盖或实现超类型的方法。
 

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

@Deprecated

这个注解用于表示某个程序元素类,方法等已过时,当其他程序使用已过时的类,方法时编译器会给出警告(删除线,这个见了不少了吧)。

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

@SuppressWarnings
抑制警告。

package com.secondgod.annotation;

import java.util.ArrayList;
import java.util.List;

public class Test {

    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        List          objList = intList;
        objList.add("二当家的");
    }
}

在命令行启用建议警告的编译(命令形如 javac -d . -Xlint Test.java ,下文再提到启用建议警告的编译,不再赘述解释)。

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

代码稍作修改,则可以消除编译警告。

package com.secondgod.annotation;

import java.util.ArrayList;
import java.util.List;

public class Test {

    @SuppressWarnings({"unchecked", "rawtypes"})
    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        List          objList = intList;
        objList.add("二当家的");
    }
}

@SafeVarargs

在声明具有模糊类型(比如:泛型)的可变参数的构造函数或方法时,Java编译器会报unchecked警告。鉴于这些情况,如果程序员断定声明的构造函数和方法的主体不会对其varargs参数执行潜在的不安全的操作,可使用@SafeVarargs进行标记,这样的话,Java编译器就不会报unchecked警告。

package com.secondgod.annotation;

import java.util.List;

public class Test {
    public final void test(List<String>... stringLists) {

    }
}

在命令行启用建议警告的编译。

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

代码稍作修改,则可以消除编译警告。

package com.secondgod.annotation;

import java.util.List;

public class Test {
    @SafeVarargs
    public final void test(List<String>... stringLists) {

    }
}

@FunctionalInterface

函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。
这个注解有什么用?这个注解保证这个接口只有一个抽象方法,注意这个只能修饰接口。

没有抽象方法的接口

package com.secondgod.annotation;

@FunctionalInterface
public interface Test {

}

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

有超过一个抽象方法的接口

package com.secondgod.annotation;

@FunctionalInterface
public interface Test {
    String test1();

    String test2();
原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

只有一个抽象方法的接口
编译通过。

package com.secondgod.annotation;

@FunctionalInterface
public interface Test {
    String test();

只有一个抽象方法的抽象类

package com.secondgod.annotation;

@FunctionalInterface
public abstract class Test {
    public abstract String test();
}

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

元注解

我们打开@Override的源码看下,会发现这个注解的定义上本身还有注解,即注解的注解,人们通常叫他们元注解。元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解。

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

JDK内置的元注解都在java.lang.annotation包下面。

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 


@Target
Target 是目标的意思,@Target 指定了注解运用的地方。
 

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

@Documented

用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了 @Documented,就会被 JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。

@Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。

@Repeatable

Repeatable 自然是可重复的意思。Java 8 新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。

@Retention

@Retention 用于描述注解的生命周期,也就是该注解被保留的时间长短。@Retention 注解中的成员变量(value)用来设置保留策略,value 是
java.lang.annotation.RetentionPolicy 枚举类型,RetentionPolicy 有 3 个枚举常量,如下所示。


 

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

@Native

使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。他也在java.lang.annotation包下面,但是我认为他不算是元注解,因为他的使用目标不是注解。

原来 Java 注解只是个标记,没什么本领,一文精通,值得收藏

 

自定义注解

在Spring,Hibernate,Mybatis等框架下都有注解,二当家的也尝试自定义一个注解,先看下@Override的源码吧。

package java.lang;

import java.lang.annotation.*;

/**
 * Indicates that a method declaration is intended to override a
 * method declaration in a supertype. If a method is annotated with
 * this annotation type compilers are required to generate an error
 * message unless at least one of the following conditions hold:
 *
 * <ul><li>
 * The method does override or implement a method declared in a
 * supertype.
 * </li><li>
 * The method has a signature that is override-equivalent to that of
 * any public method declared in {@linkplain Object}.
 * </li></ul>
 *
 * @author  Peter von der Ahé
 * @author  Joshua Bloch
 * @jls 9.6.1.4 @Override
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

我们照葫芦画瓢也自定义一个。

package com.secondgod.annotation;

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

/**
 * 测试注解
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

我们自定义的注解通常都是定义生命周期为 RUNTIME 的。生命周期为 SOURCE 的,需要编译器的配合。生命周期为 CLASS 的,需要虚拟机的配合。只有程序运行时的行为是我们可以自定义的。

好了,去使用一下这个自定义的注解。

package com.secondgod.annotation;

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


/**
 * 测试注解
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
}

/**
 * 对自定义注解的测试
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class Test {
    @MyAnnotation
    private String name;
}

有的注解还带参数,二当家的也加上。

package com.secondgod.annotation;

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


/**
 * 测试注解
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    String name();
}

/**
 * 对自定义注解的测试
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class Test {
    @MyAnnotation(name = "二当家的")
    private String name;
}

使用时候赋值必须写属性名,像@Target等一些注解赋值的时候就不用写属性名,那是因为属性名是“value”,就可以不用写。

package com.secondgod.annotation;

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


/**
 * 测试注解
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    String value();
}

/**
 * 对自定义注解的测试
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class Test {
    @MyAnnotation("二当家的")
    private String name;
}

在注解里定义了属性后,使用的地方必须赋值。 不过可以在注解里定义默认值,使用时就可以不赋值了。

package com.secondgod.annotation;

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


/**
 * 测试注解
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    String value() default "二当家的";
}

/**
 * 对自定义注解的测试
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class Test {
    @MyAnnotation
    private String name;
}

注解里的内容非常的少,显然他的功能并不在里面实现。所以注解就是个标记,本身并没有任何功能。那他的功能如何实现呢?

注解实战

我们用反射和内省配合注解来实现一个小工具,可以自动用环境变量初始化属性。

package com.secondgod.annotation;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * 工具类
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class MyUtils {
    /**
     * 私有构造,不可实例化
     */
    private MyUtils() {}

    /**
     * 用于对属性按照环境变量默认初始化
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface EnvironmentVariables {
        /**
         * 环境变量的key
         * @return
         */
        String value();
    }

    /**
     * 取得类实例,并用环境变量初始化
     *
     * @param clazz
     * @param <T>
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws java.beans.IntrospectionException
     * @throws InvocationTargetException
     */
    public static <T> T getInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException, IntrospectionException, InvocationTargetException {
        T obj = clazz.newInstance();
        setEnvironmentVariables(obj);
        return obj;
    }

    /**
     * 为对象属性按照环境变量赋值
     *
     * @param o
     * @throws IllegalAccessException
     */
    public static void setEnvironmentVariables(Object o) throws IllegalAccessException, IntrospectionException, InvocationTargetException {
        for (Field f : o.getClass().getDeclaredFields()) {
            // 利用反射读取注解
            EnvironmentVariables environmentVariables = f.getDeclaredAnnotation(EnvironmentVariables.class);
            if (environmentVariables != null) {
                // 如果注解存在就读取环境变量
                String value = System.getProperty(environmentVariables.value());
                // 利用内省将值写入
                PropertyDescriptor pd = new PropertyDescriptor(f.getName(), o.getClass());
                pd.getWriteMethod().invoke(o, value);
            }
        }
    }
}

我们定义一个类用来表示虚拟机的信息,并且用注解标注属性字段。

package com.secondgod.annotation;

import java.text.MessageFormat;

/**
 * 虚拟机信息
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class VmInfo {
    @MyUtils.EnvironmentVariables(value = "java.vm.version")
    private String version;
    @MyUtils.EnvironmentVariables(value = "java.vm.vendor")
    private String vendor;
    @MyUtils.EnvironmentVariables(value = "java.vm.name")
    private String name;
    @MyUtils.EnvironmentVariables(value = "java.vm.specification.name")
    private String specName;
    @MyUtils.EnvironmentVariables(value = "java.vm.specification.vendor")
    private String specVendor;
    @MyUtils.EnvironmentVariables(value = "java.vm.specification.version")
    private String specVersion;
    @MyUtils.EnvironmentVariables(value = "java.vm.info")
    private String info;

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getVendor() {
        return vendor;
    }

    public void setVendor(String vendor) {
        this.vendor = vendor;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSpecName() {
        return specName;
    }

    public void setSpecName(String specName) {
        this.specName = specName;
    }

    public String getSpecVendor() {
        return specVendor;
    }

    public void setSpecVendor(String specVendor) {
        this.specVendor = specVendor;
    }

    public String getSpecVersion() {
        return specVersion;
    }

    public void setSpecVersion(String specVersion) {
        this.specVersion = specVersion;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }

    public String toString() {
        return MessageFormat.format("version={0},vendor={1},name={2},specName={3},specVendor={4},specVersion={5},info={6}", version, vendor, name, specName, specVendor, specVendor, info);
    }
}

好了,来测试一下我们的工具吧。

package com.secondgod.annotation;

import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;

/**
 * 对自定义注解的测试
 *
 * @author 二当家的白帽子 https://le-yi.blog.csdn.net/
 */
public class Test {

    public static void main(String[] args) throws IntrospectionException, InvocationTargetException, IllegalAccessException, InstantiationException {
        VmInfo info1 = new VmInfo();
        System.out.println("普通方式实例化后:" + info1);
        VmInfo info2 = MyUtils.getInstance(VmInfo.class);
        System.out.println("使用我们的小工具实例化后:" + info2);
    }
}

内容比较多,图没有截全,但是很明显可以看出我们的小工具符合我们的预期想法,很Nice。

尾声

在Spring,Hibernate,Mybatis等框架中都有自定义注解,常常用来作为除配置文件之外的另一种配置选择。他们各有利弊,应该根据实际情况选择。配置文件修改重启程序即可,而注解修改显然只能重新编译。但是二当家的觉得配置直接放在使用的地方非常方便,可读性也更好,所以除非是有发布后修改的需求,否则二当家都喜欢用注解。不过用注解就会比较分散,不如配置文件集中。哎,大家仁者见仁,智者见智吧。
日更不易记得点个关注@Java学长支持一下小编哦

原文
:https://blog.csdn.net/leyi520/article/details/118994416?spm=1001.2014.3001.5501



Tags:Java 注解   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
java里有个神奇的存在,注解,就是那个天天@别人的家伙,它到底是何方神圣啊?请先给二当家的来个一键三连,然后接着耐心地读下去吧,多谢。什么是注解从JDK5开始,Java增加对元数据的支...【详细内容】
2021-08-13  Tags: Java 注解  点击:(81)  评论:(0)  加入收藏
▌简易百科推荐
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(11)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(12)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(10)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(10)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(14)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(17)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(21)  评论:(0)  加入收藏
一、概述观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监听者,类似监听器的存在,一旦被观察/监听的目标发生的情况,就会被监听者发现,这么想来目标发生情况到观察...【详细内容】
2021-12-13  唯一浩哥    Tags:Java   点击:(16)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条