java注解全网最细

引言

在java编程中,注解(Annotation)是一种元数据,它提供了关于程序代码的额外信息。注解不直接影响程序的执行,但可以在运行时提供有关程序的信息,或者让编译器执行额外的检查。

下面笔者通过循序渐进的方式一步步介绍注解的相关内容,帮助大家消化吸收知识点。

一、何谓java注解

Java注解又称Java标注,是在 JDK5 时引入的新特性,注解(也被称为元数据)。
Java注解它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。

Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。

二、java注解分类

1. 内置注解(Built-in Annotations):

这些注解是Java标准库(java.lang包或相关包)中预先定义的,用于特定的编程目的。例如:

  • @Override:表示方法重写父类的方法。
  • @Deprecated:标记过时的方法或类,编译器会发出警告。
  • @SuppressWarnings:抑制特定的编译器警告。
  • @FunctionalInterface:标识一个函数式接口,即只有一个抽象方法的接口。
  • @SafeVarargs:表示方法的安全可变参数列表,避免泛型警告。

@Override示例

/**
 * override注解
 * @author hulei
 * @date 2024/5/10 13:52
 */

public class OverrideAnnotations {

    static class BaseUser implements UserInterface {
        @Override
        public void method() {
            new BaseUser().method();
        }
    }

    interface UserInterface {
        void method();
    }
}

这个注解没什么好说的,一般用在子类覆写父类的方法上,比较简单基础的注解。上图代码表示的是:BaseUser 类实现了 UserInterface 接口,并重写了method()方法,方法上标识了 @Override注解。不一定非要是实现一个接口,继承一个普通类或者抽象类,重写父类的方法也可。

在Java中,如果子类重写父类的方法但不使用@Override注解,会有以下几点需要注意:

  1. 编译器提示
    如果你没有使用@Override,但实际上是重写了父类方法,某些IDE(如Eclipse, IntelliJ IDEA)会在方法上显示警告,提示你可能遗漏了@Override注解。虽然这不是强制性的,但添加它有助于提高代码的可读性和清晰度。

  2. 编译错误
    如果方法签名(包括方法名、参数列表和返回类型)与父类方法不完全匹配,编译器不会报错,因为你实际上并没有重写方法。这可能导致意外的行为,因为你可能以为你在调用子类的方法,但实际上调用了父类的方法。

  3. 方法覆盖的确认
    使用@Override可以确保编译器在编译时检查你是否真正重写了父类的方法。如果签名不匹配,编译器会报错,防止因意外的非重写而导致的问题。

  4. 代码可读性
    添加@Override注解使代码更易读,因为它清楚地表明该方法是用于重写父类方法的。

  5. 未来修改的保护
    如果父类的签名在未来发生变化,而你没有更新子类的方法签名,没有@Override的子类方法将不再重写父类方法。而如果有@Override,编译器会报错,提醒你需要更新子类的方法。

因此,尽管不是必须的,但推荐在重写父类方法时使用@Override注解,以确保代码的正确性和一致性。

@Deprecated示例

package com.datastructures;

/**
 * Deprecated注解
 * @author hulei
 * @date 2024/5/10 14:06
 */


public class DeprecatedAnnotation {
    public static void main(String[] args) {
        DeprecatedAnnotation deprecatedAnnotation = new DeprecatedAnnotation();
        deprecatedAnnotation.method();
    }

    @Deprecated
    public void method() {
        System.out.println("DeprecatedAnnotation.method");
    }
}

在这里插入图片描述

调用一个过时的方法,大部分编译器比如IntelliJ IDEA会给出警告信息,不推荐使用。像我们在开发过程中使用很多的第三方库或者框架包括jdk自身的大量类库时,可能早期提供的方法或函数有缺陷,但是又被大量的开发者使用,所以不能删除,这些第三方库的作者就在过时的方法加上这个注解,api调用者在调用这个过时方法就会收到提示,从而查看源码,根据作者的注释指引调用新的更加安全的方法。

@SuppressWarnings示例

package com.datastructures;
import java.util.ArrayList;
import java.util.List;


/**
 * @author hulei
 * @date 2024/5/10 14:31
 */


public class SuppressWarningsAnnotation {

    @SuppressWarnings("all")
    public static void addItems(String item){
        List items = new ArrayList();
        items.add(item);
    }

    public static void main(String[] args) {
        addItems("item");
    }
}

如果不加@SuppressWarnings注解,则会出现如下提示
在这里插入图片描述
看着很不舒服,都是一些无关紧要的提示,比如类型检查操作的警告,装箱、拆箱操作时候的警告等等。
加了 @SuppressWarnings(“all”) 这个注解,告警信息就没有了,抑制类所有类型的告警信息,清清爽爽,这对强迫症患者极为友好。

在这里插入图片描述
就算是加了过时注解的方法,加了@SuppressWarnings(“all”),也会把过时告警信息隐蔽掉。
在这里插入图片描述

我们一般常用的是如下三种

  1. @SuppressWarnings(“unchecked”) :抑制单类型的警告
  2. @SuppressWarnings(value={“unchecked”, “rawtypes”}) :抑制多类型的警告
  3. @SuppressWarnings(“all”) :抑制所有类型的警告

抑制警告的关键字对照表

关键字用途描述
allto suppress all warnings抑制所有警告
boxingto suppress warnings relative to boxing/unboxing operations抑制装箱、拆箱操作时候的警告
castto suppress warnings relative to cast operations抑制映射相关的警告
dep-annto suppress warnings relative to deprecated annotation抑制启用注释的警告
deprecationto suppress warnings relative to deprecation抑制过期方法警告
fallthroughto suppress warnings relative to missing breaks in switch statements抑制确在switch中缺失breaks的警告
finallyto suppress warnings relative to finally block that don’t return抑制finally模块没有返回的警告
hidingto suppress warnings relative to locals that hide variable抑制相对于隐藏变量的局部的警告
incomplete-switchto suppress warnings relative to missing entries in a switch statement (enum case)忽略没有完整的switch语句
nlsto suppress warnings relative to non-nls string literals忽略非nls格式的字符
nullto suppress warnings relative to null analysis忽略对null的操作
rawtypesto suppress warnings relative to un-specific types when using generics on class params使用generics时忽略没有指定相应的类型
restrictionto suppress warnings relative to usage of discouraged or forbidden references抑制禁止引用的使用相关的警告
serialto suppress warnings relative to missing serialVersionUID field for a serializable class忽略在serializable类中没有声明serialVersionUID变量
static-accessto suppress warnings relative to incorrect static access抑制不正确的静态访问方式警告
synthetic-accessto suppress warnings relative to unoptimized access from inner classes抑制子类没有按最优方法访问内部类的警告
uncheckedto suppress warnings relative to unchecked operations抑制没有进行类型检查操作的警告
unqualified-field-accessto suppress warnings relative to field access unqualified抑制没有权限访问的域的警告
unusedto suppress warnings relative to unused code抑制没被使用过的代码的警告

@FunctionalInterface示例

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

这里就拿JDK官方的Function函数式接口为例,注释被我删除了
打上@FunctionalInterface注解的接口,就可以使用java8提供的lamda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的)。
如下代码调用实例
在这里插入图片描述
表示给定一个入参,经过一定的逻辑处理,返回一个出参结果
还有BiFunction,给定两个入参,返回一个出参结果,
也可以自定义,多个入参,比如笔者自定义的 ThreeBiFunction就是三个入参,一个出参
在这里插入图片描述

@SafeVarargs示例

package com.datastructures;

import java.util.List;
import java.util.Optional;

/**
 * 注解:SafeVarargs示例
 */
public class SafeVarargsAnnotations {

    @SafeVarargs
    static void function(List<String>... stringLists) {
    }

    abstract static class BaseUser implements UserInterface {

        @SafeVarargs
        final <T> void gamma(T... ts) {
        }

        @Override
        @SafeVarargs
        public final void method(Optional<Object>... optionals) {
            UserInterface.super.method(optionals);
        }
    }

    interface UserInterface {

        default void method(Optional<Object>... optionals) {
        }

        @SafeVarargs
        static <T> void gamma(Class<T>... classes) {
        }

        void method();
    }

}

方法的参数包含可变参数列表时,不加这个@SafeVarargs注解就会有告警信息,比如上面的代码,method方法有可变参数列表,没有加注解,产生类型安全和泛型相关提示
在这里插入图片描述

2.元注解(Meta-Annotations):

元注解是用于注解其他注解的注解,是所有其他注解的基础,它们定义了注解的行为和生命周期。主要包括:

  • @Retention:定义注解的保留策略,可以是SOURCE(只存在于源码中)、CLASS(编译时丢弃,存在于字节码中但不运行时可用)或RUNTIME(运行时可通过反射访问)。
  • @Target:指定注解可以应用于哪些程序元素,如类、方法、字段等。
  • @Documented:指示是否将注解包含在生成的Javadoc中。
  • @Inherited:允许子类继承父类的注解(仅适用于类,不适用于方法或字段)。

@Retention示例

在这里插入图片描述
上面的@SuppressWarnings注解源码,就只有一个 @Retention注解
打上@Retention注解的其他注解,有三个保留策略,上面已经说明。
在这里插入图片描述

@Target示例

如果一个注解上有@Target注解,则@Target注解声明了这个注解可以使用的地方
在这里插入图片描述
比如这个自定义注解,就只能在方法上使用,ElementType.METHOD枚举就是方法声明限制,关于ElementType枚举,可以自行查看里面的枚举信息
当然,后面可以写多个使用场景的枚举声明
在这里插入图片描述

在这里插入图片描述
还有的注解,没有加@Target注解,比如上面的@SuppressWarnings注解。一个注解上没有加使用范围的注解@Targe,那这个注解可以使用在任何能够使用注解的地方。所以 @SuppressWarnings 不包含自己的 @Target 注解,意味着它理论上可以应用于 Java 规范中任何允许注解的地方。然而,它实际上的使用受到限制,尤其是不能在表达式上下文中使用,这是因为其设计目的和 Java 语言规范的限制

  • 设计目的:@SuppressWarnings 的设计初衷是为了告诉编译器在特定的范围(如类、方法、字段等)内忽略特定类型的警告。它是为了简化开发过程,允许开发者在明知某些代码可能引起编译器警告,但确认这些警告不影响程序正确性的情况下,有选择地忽略这些警告。因此,它主要应用于编译单位的较大结构上。

  • 表达式上下文限制:表达式上下文通常涉及更细粒度的操作,如赋值、方法调用、算术运算等。在这些上下文中使用 @SuppressWarnings 不符合其设计逻辑,因为这些地方通常不涉及整体性的类型或结构警告,而是更具体的、即时的操作。如果允许在表达式中使用,不仅会增加语言的复杂性,还可能引发滥用,使得代码难以理解和维护。

  • 类型注解与普通注解的区别:类型注解(自 Java 8 引入)专门设计用于标注类型声明,包括泛型类型参数、返回类型、参数类型等,而 @SuppressWarnings 并不属于这一类别。类型注解可以在某种程度上改变编译器对类型的理解,而 @SuppressWarnings 仅用于指示编译器如何处理警告信息,不改变代码的类型系统或结构。

  • Java 语言规范限制:即使 @SuppressWarnings 没有限定其 @Target,Java 语言规范和编译器实现也决定了哪些注解可以用在哪些上下文中。表达式上下文通常不接受注解,特别是像 @SuppressWarnings 这样旨在影响编译器警告处理的注解,因为这不符合语言的语义和设计哲学。

举例如下:
在这里插入图片描述
在这里插入图片描述
综上所述,@SuppressWarnings 不能在表达式上下文中使用,主要是由于其设计意图、语言规范的限制以及为了保持语言的清晰度和简洁性。

@Documented示例

这个注解不重要,表示是否将注解包含在生成的Javadoc中。加不加完全在于我们自己,只要知道的用途就行了

@Inherited示例

这个注解还是比较重要的,允许子类继承父类的注解(仅适用于类,不适用于方法或字段)。什么意思呢?
一个注解上有@Inherited注解,那么当我们把这个注解打在一个类上时,如果这个类有子类,那么这个子类继承父类的这个注解

package com.datastructures;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * @author hulei
 * @date 2024/5/10 17:01
 */


public class InheritedAnnotation {


    /**
     * 自定义注解
     */
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @interface InnerAnnotation{
        String value();
    }

    /**
     * 父类,使用了自定义注解
     */
    @InnerAnnotation(value = "父类注解")
    public static class ParentClass {
    }

    /**
     * 子类继承父类
     */
    public static class ChildClass extends ParentClass {
    }


    public static void main(String[] args) {
        Class<?> childClass = ChildClass.class;
        if (childClass.isAnnotationPresent(InnerAnnotation.class)) {
            InnerAnnotation annotation = childClass.getAnnotation(InnerAnnotation.class);
            System.out.println("Value from InnerAnnotation: " + annotation.value());
        } else {
            System.out.println("No InnerAnnotation found.");
        }
    }

}

运行可以看到,子类也获取到了这个注解

在这里插入图片描述
那我们改造下,把注解上的@Inherited注解去掉,再执行看看
在这里插入图片描述
在这里插入图片描述
可以看到子类没有获取到父类的注解了,即没有从父类继承

3.自定义注解(Custom Annotations):

开发者可以使用**@interface**关键字创建自己的注解,根据需求定义注解的行为和用途。自定义注解可以结合元注解来定义其行为,例如通过@Retention和@Target来控制自定义注解的生命周期和应用范围。下面给出几个示例:

1. 用于记录日志的注解

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

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface LogExecution {
    String message() default "";
}

这个注解可以应用于方法,表示在执行该方法前/后需要记录日志。

切面类aop

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

    @Around("@annotation(LogExecution)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String message = joinPoint.getSignature().getAnnotation(LogExecution.class).message();

        long start = System.currentTimeMillis();
        logger.info("Starting method: {}.{} with message: {}", className, methodName, message);

        Object result = joinPoint.proceed(); // 继续执行目标方法

        long elapsedTime = System.currentTimeMillis() - start;
        logger.info("Completed method: {}.{} in {}ms", className, methodName, elapsedTime);

        return result;
    }
}

实际代码调用

@Service
public class SomeService {

    @LogExecution(message = "Executing business logic")
    public String performTask() {
        // 示例业务逻辑
        try {
            Thread.sleep(100); // 模拟耗时操作
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
        return "Task completed";
    }
}

后面的注解,笔者就不再提供aop相关代码了,和日志注解类似,比较简单

2.用于数据验证的注解

比如邮箱格式验证

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Constraint(validatedBy = EmailValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EmailVaild{
    String message() default "邮箱格式不正确";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

3.用于事务管理的注解

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Transactional {
    boolean readOnly() default false;
}

这个注解用于标记一个方法需要在数据库事务中执行,readOnly 参数表示是否为只读事务。

4.用于权限控制的注解

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

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresRole {
    String[] roles() default {};
}

这个注解用于标记一个方法或类需要特定的角色才能访问,roles 参数是角色的数组。

5.用于缓存结果的注解

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheResult {
    long cacheTime() default 60; // 缓存60秒
}

这个注解用于标记一个方法的结果应该被缓存一定时间,cacheTime 参数表示缓存的秒数。

在实际使用中,这些注解通常会与AOP(面向切面编程)框架结合,如Spring AOP,以便在运行时动态地处理注解的逻辑。

三、注解中的属性省略问题

这一节是笔者在学习时遇到的疑问,这里作为记录
​​​​在这里插入图片描述
这个注解有两个属性,value和logical ,@HasPermissions(“system:user:query”)

这种写法会把属性值默认给value,注意必须要有名为value的属性,并且其他属性都有默认值才可以

否则得显示给属性赋值

@HasPermissions(value = {"om:deviceCascade:edit","om:deviceCascade:add"},logical = Logical.OR)

总结如下

  • 如果注解只有一个属性,那么肯定是赋值给该属性。

  • 如果注解有多个属性,而且前提是这多个属性都有默认值,那么你不写注解名赋值,会赋值给名字为“value”这属性或者赋值给多个属性中唯一没有默认值的属性。

  • 如果注解有多个属性,其中有没有设置默认值的属性,那么当你不写属性名进行赋值的时候,是会报错的。

  • 31
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值