JAVA中的注解和泛型

目录

JAVA注解介绍

概念

注解的本质

4种标准元注解

自定义注解

泛型介绍

泛型的定义

JAVA泛型

[泛型方法( )](#泛型方法( ))

[泛型类( )](#泛型类( ))

类型通配符

类型擦除


JAVA注解介绍

概念

注解是 JDK 5.0 引入的一种元数据机制,用来对代码进行标注。它不会影响程序的实际逻辑,但可被编译器、开发工具、框架或运行时反射机制读取,用于生成代码、配置信息、提供指令等。

简单理解:注解就是"给代码贴标签",再由工具读取标签做相应处理。

注解的本质

Java 中的注解是一个接口,所有注解都是 java.lang.annotation.Annotation 接口的子类型。注解可以添加在类、方法、变量、参数等元素上。

我们可以通过反射获取注解的信息:

复制代码
Class<?> clazz = MyClass.class;
if (clazz.isAnnotationPresent(MyAnnotation.class)) {
    MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
    System.out.println(annotation.value());
}

4种标准元注解

元注解的作用是负责注解其他注解。 Java5.0定义了 4个标准的 meta-annotation类型,它们被用来提供对其它 annotation类型作说明。

@Target ------ 说明注解可以应用于哪些 Java 成员上

复制代码
@Target({ElementType.TYPE, ElementType.METHOD})

常见取值(ElementType):

  • TYPE: 类、接口、枚举

  • FIELD: 成员变量

  • METHOD: 方法

  • PARAMETER: 参数

  • CONSTRUCTOR: 构造方法

  • LOCAL_VARIABLE: 局部变量

  • ANNOTATION_TYPE: 注解

  • PACKAGE: 包

@Retention ------ 指定注解的生命周期

复制代码
@Retention(RetentionPolicy.RUNTIME)

常见取值(RetentionPolicy):

  • SOURCE:仅在源码中保留,编译后丢弃(如@Override

  • CLASS:编译时保留,运行时不可通过反射获取(默认值)

  • RUNTIME:运行时仍可读取(用于框架反射,如 Spring、MyBatis)

@Documented ------ 表示注解是否包含在 Javadoc 中

复制代码
@Documented

@Inherited ------ 注解是否可以被子类继承

复制代码
@Inherited #仅对类有效:如果某个类使用了被 @Inherited 修饰的注解,那么其子类也会继承这个注解。

自定义注解

复制代码
/*
 * 需要登录才可以访问对应的资源
 */
@Target(ElementType.METHOD) // 注解作用于方法上
@Retention(RetentionPolicy.RUNTIME) // 注解在运行时仍然可用
@Documented
public @interface RequireLogin {
    String role() default "user";
}

public class AnnotationTest {
​
    @RequireLogin(role = "admin")
    public void deleteUser() {
        System.out.println("删除用户");
    }
​
    @RequireLogin
    public void queryUser() {
        System.out.println("查询用户");
    }
​
    public void publicMethod() {
        System.out.println("公共方法,不需要登录");
    }
​
    public static void main(String[] args) {
        //获取注解的反射信息
        AnnotationTest test = new AnnotationTest();
        // 获取类的注解
        Class<?> clazz = AnnotationTest.class;
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(RequireLogin.class)) {
                RequireLogin annotation = method.getAnnotation(RequireLogin.class);
                System.out.println("方法: " + method.getName() + ", 角色: " + annotation.role());
            }
        }
    }
}

注解一般需要配置拦截器使用实现对应的业务场景,例如日志、权限等功能实现,也可以配合AOP使用来实现一些特定的场景需求

以下例子是我自己写的一个实现登录日志记录的案例:

注解

复制代码
/**
 * 自定义操作日志注解
 * @author hyh
 */
@Target({ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface LoginLog {
    String businessName() default ""; // 操作名称
}

AOP切面

这里对应数据库操作的类换成自己的

复制代码
/**
 * 登录日志记录切面
 *
 * @author hyh
 * @date 2024-06-012
 */
@Aspect
@Component
public class LoginLogAspect {
    @Autowired
    private HttpServletRequest request;
​
    @Autowired
    private ISysLoginInforService sysLoginInforService;
​
    // 切点定义 只要包含 @LoginLog 注解的方法都会被拦截
    @Pointcut("@annotation(com.hyh.ad.common.annotation.LoginLog)")
    public void operationLogPointCut() {
    }
​
    // 方法正常返回之后执行 afterReturningLogWrite 方法。
    @AfterReturning(pointcut = "operationLogPointCut()", returning = "returnValue")
    public void afterReturningLogWrite(JoinPoint joinPoint, Object returnValue)  {
        handleLog(joinPoint, null, returnValue);
    }
​
    //表示:方法抛出异常后执行 afterThrowingLogWrite 方法。
    @AfterThrowing(pointcut = "operationLogPointCut()", throwing = "e")
    public void afterThrowingLogWrite(JoinPoint joinPoint, Throwable e)  {
        handleLog(joinPoint, e, null);
    }
​
    //这个方法负责构建一个 SysLogininfor 登录日志对象,然后保存到数据库。
    private void handleLog(JoinPoint joinPoint, Throwable e, Object returnValue)  {
        // 创建日志对象
        SysLogininfor sysLogininfor = new SysLogininfor();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        LoginLog log = method.getAnnotation(LoginLog.class);
        String username = "";
​
        Object[] args = joinPoint.getArgs();
​
        ObjectMapper objectMapper = new ObjectMapper();
        String params = "";
        try {
            params = objectMapper.writeValueAsString(args);
        } catch (JsonProcessingException jsonProcessingException) {
            jsonProcessingException.printStackTrace();
        }
​
        // 获取User-Agent
        String userAgentString = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgent.parseUserAgentString(userAgentString);
​
        // 获取操作系统和浏览器信息
        OperatingSystem os = userAgent.getOperatingSystem();
        Browser browser = userAgent.getBrowser();
​
        // 获取参数里面的用户名
        if (args.length > 0 && args[0] instanceof LoginBody) {
            LoginBody loginBody = (LoginBody) args[0];
            username = loginBody.getUsername();
        } else {
            username = "Unknown";
        }
        String ipAddress = IpUtil.getIpAddr(request);
        sysLogininfor.setUserName(username);
        sysLogininfor.setIpaddr(ipAddress);
        sysLogininfor.setLoginLocation("内网IP");  // 或者通过IP地址来判断内外网
        sysLogininfor.setOs(os.getName());
        sysLogininfor.setBrowser(browser.getName());
        //时间加上8小时
        sysLogininfor.setLoginTime(LocalDateTime.now().plusHours(8));
​
        // 设置状态
        if (e != null) {
            sysLogininfor.setStatus("1");
            sysLogininfor.setMsg(e.getMessage()); // 设置错误信息
        } else {
            sysLogininfor.setStatus("0");
            if (returnValue != null) {
                String returnValueString = returnValue.toString(); // 假设 returnValue 是包含 {msg=验证码已过期, code=201} 的对象
                String msg = "";
                // 使用正则表达式提取 msg 字段
                Pattern pattern = Pattern.compile("msg=([^,}]*)");
                Matcher matcher = pattern.matcher(returnValueString);
                if (matcher.find()) {
                    msg = matcher.group(1);
                }
                // 设置 sysLogininfor 的 msg 字段
                sysLogininfor.setMsg(msg);
            } else {
                sysLogininfor.setMsg("Operation completed successfully.");
            }
        }
        try {
            sysLoginInforService.insert(sysLogininfor);
        } catch (Exception exception) {
            exception.printStackTrace();
        }
}

应用的方法

复制代码
/**
     * 用户名密码登录
     * @param loginBody
     * 对象参数包括以下字段:
     * 用户名 名字或者手机号
     * 密码
     * 验证码
     * uuid
     */
    @ApiOperation(value = "通过用户名密码登陆 ")
    @PostMapping("/loginByPwd")
    @LoginLog(businessName = "用户登录") //注解
    public AjaxResult loginByPassword(@RequestBody LoginBody loginBody) {
        return sysUserService.loginByPassword(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());
    }

泛型介绍

泛型的定义

泛型 是 Java 中的一种语法机制,用于在类、接口和方法中参数化类型 ,让你在写代码时不用指定具体的数据类型,而在使用时再指定,从而提高代码的复用性类型安全性可读性

JAVA泛型

泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用 Java 泛型。

泛型方法(<E>)

我们可以编写一个泛型方法,该方法在调用的时候可以接受不同的参数 ,根据传递的参数不同 ,编译器适当的处理每一个不同的调度

复制代码
/*
 * 编写泛型方法
 */
public class FanxingMethodTest {
    //通用泛型方法
    public static <E> void printArray(E[] inputArray) {
        for (E e : inputArray) {
            System.out.println("元素的类型是: " + e.getClass().getName() +
                               ", 值: " + e);
        }
    }
    
    public static void main(String[] args) {
        printArray(new Integer[]{1, 2, 3, 4, 5});
        printArray(new String[]{"Hello", "World", "Generics"});
        printArray(new Double[]{1.1, 2.2, 3.3, 4.4, 5.5});
        printArray(new Character[]{'A', 'B', 'C', 'D', 'E'});
    }
}

泛型类(<T>)

泛型类的 声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明 部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量 ,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

泛型类

复制代码
/*
 * 泛型类
 */
@Data
public class Box<T> {
    private T data;
}

泛型类测试

复制代码
public class BoxTest {
​
    public static void main(String[] args) {
        //字符串类型
        Box<String> stringBox = new Box<String>();
        stringBox.setData("Hello, Generics!");
        System.out.println("String Box contains: " + stringBox.getData());
​
        //整数类型
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.setData(123);
        System.out.println("Integer Box contains: " + integerBox.getData());
    }
}

类型通配符

类型通配符一般是使用 ? 代替具体的类型参数。例如 List 在逻辑上是List,List 等所有 List<具体类型实参>的父类。

类型擦除

Java中的泛型 基本上都是在编译器这个层次来实现的。在生成的 Java字节代码 中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除 。如在代码中定义的 List和 List等类型,在编译之后都会变成 List,JVM 看到的只是 List ,而由泛型附加的类型信息对 JVM 来说是不可见的。类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是 Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。

  • 运行时无法获取泛型类型

复制代码
  List<String> list1 = new ArrayList<>();
  List<Integer> list2 = new ArrayList<>();
  ​System.out.println(list1.getClass() == list2.getClass()); // true
  ​
  • 无法使用 T 创建数组

复制代码
  public class Box<T> {
      T[] array = new T[10]; // ❌ 编译报错
  }
  • 不能用 instanceof 判断泛型类型

复制代码
  if (obj instanceof List<String>) {  // ❌ 编译错误
  }
相关推荐
都叫我大帅哥几秒前
Java线程与线程池:从入门到“避坑”全攻略
java
the_seventh_dog5 分钟前
mybatis和hibernate区别
java·mybatis·hibernate
LiuYaoheng16 分钟前
【JVM】Java类加载机制
java·jvm·笔记·学习
黄雪超20 分钟前
JVM——JVM中的字节码:解码Java跨平台的核心引擎
java·开发语言·jvm
wangfenglei12345624 分钟前
idea不识别lombok---实体类报没有getter方法
java·ide·intellij-idea
烬奇小云27 分钟前
怎么通过 jvmti 去 hook java 层函数
java·开发语言
m0_7482453432 分钟前
SpringAI集成DeepSeek实战
java
程序员岳焱1 小时前
15.Java 泛型编程:类型安全与代码复用
java·后端·编程语言
天天摸鱼的java工程师1 小时前
摸鱼学 Spring:Bean 的生命周期,面试官为啥总揪着问?
java·后端·面试
比特森林探险记1 小时前
MySQL 时间类型与 Java 日期时间类对应关系详解
java·后端