java-注解&泛型

Java 注解定义与元注解

一、注解定义基础

  • 语法 :使用 @interface 关键字定义注解,格式如下:

    java 复制代码
    public @interface 注解名 {
        参数类型 参数名() default 默认值; // 参数可带默认值(推荐)
    }
    • 参数规则 :参数类似无参数方法,最常用参数建议命名为 value

二、元注解(修饰注解的注解)

  1. @Target

    • 作用:指定注解可应用的源码位置(类型、字段、方法等)。

    • 参数ElementType 枚举值,可单值或数组(如 { TYPE, METHOD })。

    • 示例

      java 复制代码
      @Target(ElementType.METHOD) // 注解仅用于方法
  2. @Retention

    • 作用:定义注解的生命周期(源码期、编译期、运行期)。

    • 参数RetentionPolicy 枚举值:

      • SOURCE:仅源码有效(编译后丢弃)。
      • CLASS:编译到 class 文件(默认值)。
      • RUNTIME:运行期可用(自定义注解常用,需显式声明)。
    • 示例

      java 复制代码
      @Retention(RetentionPolicy.RUNTIME) // 运行期保留注解
  3. @Repeatable

    • 作用:允许注解在同一位置重复使用。

    • 使用:需定义包含注解数组的容器注解,示例:

      java 复制代码
      @Repeatable(Reports.class) // 允许重复@Report注解
      @interface Report { ... }
      @interface Reports { Report[] value(); }
  4. @Inherited

    • 作用 :子类自动继承父类的类型注解(仅针对 @Target(TYPE) 的类注解,接口无效)。

    • 示例

      java 复制代码
      @Inherited // 子类继承@Report注解
      @Target(ElementType.TYPE)
      @interface Report { ... }

三、定义注解步骤

  1. 声明注解 :使用 @interface 定义基础结构。

  2. 添加参数 :定义参数及默认值(推荐为所有参数设置默认值,核心参数命名为 value)。

  3. 配置元注解

    • 必须设置 @Target(指定应用范围)和 @Retention(RetentionPolicy.RUNTIME)(运行期可用)。
    • 可选 @Repeatable(重复注解)和 @Inherited(继承性,按需使用)。

处理注解

一、注解的保留策略

  1. SOURCE :注解仅存在于源代码中,编译期被丢弃,由编译器使用(如@Override),无需手动编写。
  2. CLASS:注解保存到 class 文件中,但 JVM 加载时不读取,主要由底层工具库使用,开发中较少涉及。
  3. RUNTIME :注解在运行期被加载到 JVM,可通过反射读取,是自定义业务逻辑的核心,需手动编写和使用(必须声明@Retention(RetentionPolicy.RUNTIME))。

二、反射 API 读取注解

1. 判断注解是否存在

通过Class/Field/Method/Constructor.isAnnotationPresent(Class<? extends Annotation> annotationClass)判断目标对象是否存在指定注解。

java 复制代码
Person.class.isAnnotationPresent(Report.class); // 判断类是否有@Report注解
2. 获取注解实例

通过Class/Field/Method/Constructor.getAnnotation(Class<? extends Annotation> annotationClass)获取注解实例并读取属性。

java 复制代码
Report report = Person.class.getAnnotation(Report.class);
int type = report.type(); // 获取注解属性
3. 处理方法参数注解

方法参数注解以二维数组形式存储,通过Method.getParameterAnnotations()获取,遍历处理每个参数的注解。

java 复制代码
Annotation[][] annos = method.getParameterAnnotations();
for (int i = 0; i < annos.length; i++) {
    for (Annotation anno : annos[i]) {
        // 判断注解类型并处理
    }
}

三、实战示例:@Range 注解校验 JavaBean 字段

1. 定义 @Range 注解(RUNTIME 保留策略)

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) // 限定用于字段
public @interface Range {
    int min() default 0; // 最小允许值
    int max() default 255; // 最大允许值
}
2. 在 JavaBean 中应用注解
java 复制代码
public class Person {
    @Range(min = 1, max = 20) // 字符串长度需在1-20之间
    public String name;
    
    @Range(max = 10) // 整数需≤10
    public int age;
}

3. 编写校验逻辑(反射实现)

  • 遍历对象字段,获取@Range注解并校验值(字符串长度 / 整数范围)。
  • 处理私有字段时需调用field.setAccessible(true)突破访问限制。
java 复制代码
void check(Person person) throws ReflectiveOperationException {
    for (Field field : person.getClass().getFields()) {
        Range range = field.getAnnotation(Range.class);
        if (range != null) {
            Object value = field.get(person);
            if (value instanceof String s) {
                if (s.length() < range.min() || s.length() > range.max()) {
                    throw new IllegalArgumentException("Invalid field: " + field.getName());
                }
            } else if (value instanceof Integer i) {
                if (i < range.min() || i > range.max()) {
                    throw new IllegalArgumentException("Invalid field: " + field.getName());
                }
            }
        }
    }
}

四、核心要点与注意事项

  1. 注解生效前提 :必须声明@Retention(RUNTIME),否则运行时无法读取。

  2. 反射关键作用 :通过ClassField等反射对象的 API 动态获取注解,实现自定义逻辑(如参数校验、框架功能扩展)。

  3. 应用场景

    • 自定义参数校验(如示例中的字段范围检查);
    • 框架集成(如 JUnit 通过@Test识别测试方法,Spring 通过@Autowired实现依赖注入)。
  4. 注意事项

    • 注解本身不影响代码逻辑,需手动编写处理逻辑;
    • 高频调用场景建议缓存注解信息以提升性能。

Java 泛型

一、核心概念

泛型是一种编写模板代码的机制,通过定义类型参数(如T),使类、接口或方法能够适应任意类型,实现代码的通用性和类型安全。

二、传统集合类的问题(以ArrayList为例)

  1. 无泛型时的缺陷

    • 内部使用Object[]数组,存入 / 取出需强制转型(如(String) list.get(0))。
    • 编译期不检查类型,运行时可能抛出ClassCastException(如误将Integer存入String类型的集合)。
  2. 重复代码问题

    • 为不同类型(如StringIntegerPerson)单独编写类,导致代码冗余(如StringArrayListIntegerArrayList)。

三、泛型的解决方案

  1. 定义泛型类

    • 使用类型参数T作为模板,例如:
    java 复制代码
    public class ArrayList<T> {
        private T[] array;
        public void add(T e) { ... }
        public T get(int index) { ... }
    }
  2. 实例化时指定类型

    • 创建具体类型的泛型实例,编译器自动检查类型:
    java 复制代码
    ArrayList<String> strList = new ArrayList<String>(); // 仅允许存入String
    strList.add("Hello"); // 合法
    strList.add(123); // 编译错误(不允许非String类型)
    • 无需强制转型,直接获取指定类型:
    java 复制代码
    String first = strList.get(0); // 直接得到String,无需转型

四、泛型的核心优势

  1. 类型安全

    • 编译期严格检查存入 / 取出的类型,避免运行时类型错误。
  2. 代码复用

    • 编写一次泛型类,可适配所有类型,无需为每种类型单独实现。
  3. 消除强制转型

    • 直接通过类型参数获取目标类型,提升代码简洁性。

五、泛型的继承规则(关键注意点)

  1. 允许向上转型为泛型接口

    • ArrayList<T>可向上转型为List<T>(因为ArrayList<T>实现了List<T>接口):
    java 复制代码
    List<String> list = new ArrayList<String>(); // 合法
  2. 不允许泛型类型的父类转型

    • 禁止ArrayList<Integer>转型为ArrayList<Number>List<Number>,即使IntegerNumber的子类:
    java 复制代码
    ArrayList<Integer> intList = new ArrayList<>();
    ArrayList<Number> numList = intList; // 编译错误!
    • 原因 :若允许转型,通过numList存入FloatNumber的子类)会导致intList中出现非法类型,运行时获取时抛出ClassCastException
    • 结论 :泛型类型T必须严格匹配,不存在继承关系(如ArrayList<Integer>ArrayList<Number>无任何父子关系)。

Java 泛型使用要点

一、泛型基础概念

  1. 类型安全增强

    • 未定义泛型时,ArrayList 默认类型为 Object,需强制转型且存在类型安全隐患(如运行时 ClassCastException)。
    • 定义泛型后(如 List<String>),编译器确保类型一致,无需强制转型,提升代码安全性和可读性。
  2. 类型推断(Type Inference)

    • 编译器可自动推断泛型类型,简化代码书写。
    • 例:List<Number> list = new ArrayList<>();,省略右侧 <Number>,由左侧声明推断类型。

二、泛型使用场景

  1. 集合类应用

    • 定义强类型集合 :通过 <T> 指定元素类型,如 List<String>List<Number>,确保添加元素类型一致。
    • 避免编译器警告 :未指定泛型时(如 List list = new ArrayList();),编译器提示未经检查的操作警告。
  2. 泛型接口实现

    • Comparable<T> 接口 :定义对象比较逻辑,实现该接口的类可用于排序(如 Arrays.sort())。

      • 例:自定义 Person 类实现 Comparable<Person>,重写 compareTo 方法,支持按 namescore 排序。
    • 实现泛型接口时需明确具体类型(如 Comparable<Person>),否则导致类型不匹配异常(如 ClassCastException)。

三、核心代码示例

  1. 泛型集合基础用法

    java 复制代码
    // 无泛型:需强制转型,存在类型风险
    List list = new ArrayList();
    list.add("Hello");
    String first = (String) list.get(0); // 强制转型
    
    // 强类型泛型:类型安全,无需转型
    List<String> list = new ArrayList<>(); // 类型推断省略右侧泛型
    list.add("Hello");
    String first = list.get(0); // 直接获取强类型元素
  2. 泛型接口实现示例

    java 复制代码
    class Person implements Comparable<Person> {
        String name;
        int score;
        // 按 name 排序
        public int compareTo(Person other) {
            return this.name.compareTo(other.name);
        }
    }
    // 使用 Arrays.sort 排序 Person 数组
    Person[] ps = { ... };
    Arrays.sort(ps); // 基于 compareTo 逻辑排序

编写泛型

一、核心内容概述

本文介绍如何编写 Java 泛型类,包括基础步骤、静态方法处理、多泛型类型定义等关键知识点,适用于需要自定义泛型类的场景(如集合类扩展)。

二、编写泛型类的步骤

  1. 从具体类型开始

    先按特定类型(如String)编写类,定义字段、构造方法和方法。

    java 复制代码
    public class Pair {
        private String first;
        private String last;
        // 构造方法、getter等
    }
  2. 替换为泛型类型<T>

    将所有特定类型替换为泛型类型T,并在类名后声明<T>

    java 复制代码
    public class Pair<T> {
        private T first;
        private T last;
        public Pair(T first, T last) { ... }
        public T getFirst() { ... }
        // 其他方法使用T作为返回/参数类型
    }

三、静态方法与泛型

  • 禁止直接使用类泛型<T>

    静态方法属于类级别,无法引用实例级泛型T,直接使用会导致编译错误。
    错误示例

    java 复制代码
    public static Pair<T> create(T first, T last) { ... } // 编译错误
  • 定义独立泛型方法

    在静态方法前单独声明泛型类型(如<K>),与类泛型区分。
    正确示例

    java 复制代码
    public static <K> Pair<K> create(K first, K last) {
        return new Pair<K>(first, last);
    }

四、多个泛型类型

  • 定义多泛型类

    通过<T, K>声明多种泛型类型,用于存储不同类型的对象。

    java 复制代码
    public class Pair<T, K> {
        private T first;
        private K last;
        public Pair(T first, K last) { ... }
        public T getFirst() { ... }
        public K getLast() { ... }
    }
  • 使用场景

    类似 Java 标准库Map<K, V>,分别定义键(K)和值(V)的类型。
    示例

    java 复制代码
    Pair<String, Integer> p = new Pair<>("test", 123);

Java 泛型擦拭法(Type Erasure)详解

一、擦拭法核心概念

  • 定义 :Java 泛型的实现方式,编译器在编译时处理泛型,虚拟机运行时 "看不到" 泛型,所有泛型类型T会被擦除为Object(或其边界类型)。

  • 本质

    • 编译器将泛型类 / 方法中的T视为Object,并在必要时插入安全强制转型。
    • 运行时,泛型类型信息消失,仅保留原始类型(如Pair<T>编译后为Pair,字段和方法参数为Object)。

二、擦拭法的具体表现

  1. 编译前后对比

    • 编译器视角 :泛型类Pair<T>包含类型参数T,如T firstT getFirst()
    • 虚拟机视角T被擦除为Object,实际代码为Object firstObject getFirst(),使用时自动转型(如(String)p.getFirst())。
  2. 示例代码

    java 复制代码
    // 编译器代码(泛型)
    Pair<String> p = new Pair<>("Hello", "World");
    String first = p.getFirst(); // 自动转型
    
    // 虚拟机执行代码(无泛型)
    Pair p = new Pair("Hello", "World");
    String first = (String) p.getFirst(); // 手动转型(编译器隐式插入)

三、擦拭法的局限

  1. T不能是基本类型

    • 原因:T被擦除为Object,而Object无法直接存储基本类型(如int)。
    • 示例:Pair<int>编译错误,需使用包装类型Pair<Integer>
  2. 无法获取带泛型的Class

    • 所有泛型实例的getClass()返回同一个原始类型的Class

    • 示例:

      java 复制代码
      Pair<String> p1 = new Pair<>("a", "b");
      Pair<Integer> p2 = new Pair<>(1, 2);
      System.out.println(p1.getClass() == p2.getClass()); // true(均为Pair.class)
  3. 无法判断泛型类型实例

    • 编译时禁止使用instanceof判断泛型类型(如if (p instanceof Pair<String>)),因运行时无泛型信息。
  4. 不能直接实例化T类型

    • 禁止new T(),因擦除后为new Object(),类型不安全。

    • 解决方案:通过反射传入Class<T>参数实现实例化:

      java 复制代码
      public Pair(Class<T> clazz) throws InstantiationException, IllegalAccessException {
          first = clazz.newInstance(); // 借助Class实例创建T对象
      }

四、泛型方法与覆写问题

  • 方法覆写冲突 :若泛型方法擦除后与Object方法签名冲突(如equals(T t)擦除为equals(Object t)),编译器会报错。

    • 错误示例:

      java 复制代码
      public class Pair<T> {
          public boolean equals(T t) { ... } // 编译错误,与Object.equals(Object)冲突
      }
    • 解决:改用不同方法名(如same(T t))。

五、泛型继承与类型获取

  • 子类继承泛型父类 :子类可固定父类的泛型类型(如IntPair extends Pair<Integer>),此时子类中T被具体化(如Integer)。

  • 通过反射获取父类泛型类型

    • 使用ParameterizedType接口获取父类的实际类型参数:

      java 复制代码
      Type superClass = IntPair.class.getGenericSuperclass();
      if (superClass instanceof ParameterizedType) {
          Type[] typeArgs = ((ParameterizedType) superClass).getActualTypeArguments();
          Class<?> type = (Class<?>) typeArgs[0]; // 获取父类泛型类型(如Integer)
      }

extends 通配符

一、核心概念:上界通配符(Upper Bounds Wildcards)

  • 定义 :使用<? extends 父类>形式的泛型通配符,限定泛型类型为指定父类或其子类(包括父类本身)。
  • 作用 :解决泛型类型的继承问题。例如,Pair<Integer>不是Pair<Number>的子类,无法直接传递给参数为Pair<Number>的方法,通过Pair<? extends Number>可允许所有Number及其子类的Pair类型传入。

二、核心特性与规则

  1. 只读不写原则

    • 允许读操作 :通过<? extends 父类>获取的对象,其方法返回值可安全赋值给父类引用(如Number x = p.getFirst();),因为编译器确保返回值是父类或其子类。
    • 禁止写操作 :无法向<? extends 父类>类型的对象写入任何非null值(除null外)。例如,p.setFirst(new Integer(1));会编译错误,因为实际类型可能是Double等其他子类,写入Integer会破坏类型安全。
  2. 与泛型类定义的区别

    • 方法参数通配符<? extends Number>):临时限定方法接收的泛型类型,不改变原有泛型类的定义,仅在方法作用域内生效。
    • 泛型类限定<T extends Number>):定义泛型类时直接限定T的类型范围,实例化时只能使用Number及其子类(如Pair<Integer>合法,Pair<String>非法)。

三、典型场景与示例

  1. 方法参数应用

    • 错误场景 :直接使用Pair<Number>作为参数时,无法接收Pair<Integer>等子类实例。

    • 正确写法 :使用Pair<? extends Number>允许子类传入,同时确保读取安全:

      java

      javascript 复制代码
      static int add(Pair<? extends Number> p) {
          Number first = p.getFirst(); // 合法,返回值为Number或其子类
          // p.setFirst(new Integer(1)); 编译错误,禁止写入
          return first.intValue() + p.getLast().intValue();
      }
  2. Java 集合中的应用

    • 当方法只需读取集合元素(如计算总和),不修改集合时,使用List<? extends 类型>

      java

      sql 复制代码
      int sumOfList(List<? extends Integer> list) {
          int sum = 0;
          for (Integer n : list) sum += n; // 合法,读取元素
          // list.add(new Integer(1)); 编译错误,禁止添加
          return sum;
      }

四、关键限制与原因

  • 擦拭法影响 :泛型在编译期会被擦拭,<? extends Number>实际类型在运行时无法确定具体子类,因此写入操作可能导致类型不匹配(如向Pair<Double>写入Integer)。
  • 唯一例外 :允许写入null,但会导致后续读取时抛出NullPointerException

Java 泛型中的super通配符详解

一、核心概念:super通配符的作用

  • 定义<? super T>表示泛型类型为TT的父类(包括直接父类和祖先类)。

  • 核心作用 :允许向泛型对象中写入T类型的数据,但限制读取操作(仅能读取为Object类型)。

  • 示例

    java

    javascript 复制代码
    void setSame(Pair<? super Integer> p, Integer n) {
        p.setFirst(n); // 允许写入Integer类型
        Object obj = p.getFirst(); // 仅能读取为Object
    }

二、superextends通配符对比

特性 <? extends T>(上界通配符) <? super T>(下界通配符)
读取操作 允许读取T类型(生产者) 仅允许读取Object类型
写入操作 仅允许写入null 允许写入T及其子类类型(消费者)
典型场景 从集合中读取元素(如src.get() 向集合中写入元素(如dest.add()

三、PECS 原则(Producer Extends Consumer Super)

  • 生产者(Producer) :当需要从泛型对象中读取数据(返回T)时,使用extends通配符(确保类型安全)。
    示例List<? extends T> src(从src读取T)。

  • 消费者(Consumer) :当需要向泛型对象中写入数据(接受T)时,使用super通配符(允许父类类型接收子类数据)。
    示例List<? super T> dest(向dest写入T)。

  • 应用案例 :Java 标准库Collections.copy()方法:

    java

    typescript 复制代码
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        // dest是消费者(写入T),src是生产者(读取T)
    }

四、无限定通配符<?>

  • 定义Pair<?>表示未知类型的泛型,是所有Pair<T>的超类。

  • 限制

    • 不能写入非null数据(类型不确定)。
    • 只能读取为Object类型。
  • 示例

    java

    javascript 复制代码
    void sample(Pair<?> p) {
        Object first = p.getFirst(); // 允许
        p.setFirst(123); // 编译错误(类型不明确)
    }

五、关键规则与最佳实践

  1. 读写分离原则

    • 只读场景用extends,只写场景用super
    • 禁止在同一泛型对象上同时进行读写(避免类型安全问题)。
  2. 类型擦除影响

    • 编译后泛型信息会被擦除,super通配符通过编译器静态检查保证类型安全。
  3. 优先使用具体泛型

    • 无限定通配符<?>很少使用,多数场景可通过定义泛型参数<T>替代。
相关推荐
进阶的DW10 分钟前
新手小白使用VMware创建虚拟机安装Linux
java·linux·运维
oioihoii14 分钟前
C++11 尾随返回类型:从入门到精通
java·开发语言·c++
伍六星31 分钟前
更新Java的环境变量后VScode/cursor里面还是之前的环境变量
java·开发语言·vscode
风象南37 分钟前
SpringBoot实现简易直播
java·spring boot·后端
这里有鱼汤1 小时前
有人说10日低点买入法,赢率高达95%?我不信,于是亲自回测了下…
后端·python
万能程序员-传康Kk1 小时前
智能教育个性化学习平台-java
java·开发语言·学习
落笔画忧愁e1 小时前
扣子Coze飞书多维表插件-列出全部数据表
java·服务器·飞书
鱼儿也有烦恼1 小时前
Elasticsearch最新入门教程
java·elasticsearch·kibana
eternal__day1 小时前
微服务架构下的服务注册与发现:Eureka 深度解析
java·spring cloud·微服务·eureka·架构·maven
一介草民丶1 小时前
Jenkins | Linux环境部署Jenkins与部署java项目
java·linux·jenkins