泛型体系实战
泛型技术快速上手什么是泛型Generics定义在定义类、接口和方法时通过特定格式声明类型参数使用时再指定具体类型。作用让类、接口、方法可适配多种数据类型实现复用与编译时类型安全。特性JDK 5.0 引入的新特性支持编译时类型检查避免运行时类型转换异常。为什么要用泛型解决类型转换问题无泛型时集合默认存储Object类型取出时必须强制类型转换。若插入错误类型对象运行时会抛出ClassCastException。编译时类型校验泛型在编译期检查类型避免插入非法类型提升代码安全性。代码可读性与简洁性消除强制类型转换代码更清晰维护成本更低。组件复用封装通用组件如FIFOCache支持多种数据类型提升抽象能力。泛型的分类与命名规范分类泛型可用于类、接口、方法对应分为泛型类public class 类名 T, E, ... { ... }泛型接口interface 接口名 T, E, ... { 方法名(T t); }泛型方法修饰符 T, E, ... 返回值类型 方法名(T t) { ... }常用泛型字母命名字母含义适用场景TType任意类型通用类型占位EElement集合元素集合中元素类型KKey键Key-Value 结构中的 KeyVValue值Key-Value 结构中的 Value泛型的语法格式泛型类// 定义泛型类 public class GenericClassT { private T data; public T getData() { return data; } public void setData(T data) { this.data data; } } // 使用泛型类 GenericClassString strObj new GenericClass(); strObj.setData(xdclass); String value strObj.getData(); // 无需强制转换泛型接口// 定义泛型接口 public interface GenericInterfaceE { E getElement(); } // 实现泛型接口 public class ImplE implements GenericInterfaceE { Override public E getElement() { return null; } }泛型方法// 定义泛型方法 public T T genericMethod(T param) { return param; } // 调用泛型方法 String result genericMethod(hello); Integer num genericMethod(123);核心价值总结JDK 源码基石JDK 源码、中间件源码如 Spring、MyBatis中大量使用泛型掌握它是阅读源码的基础。提升抽象能力泛型让组件更通用、封装性更强是编写高质量框架级代码的关键。工程实践必备高并发、海量数据场景下的通用组件如缓存、工具类均依赖泛型实现。代码示例泛型实现 FIFOCacheimport java.util.LinkedList; import java.util.Queue; // 泛型版FIFOCache public class FIFOCacheK, V { private final int capacity; private final QueueK keyQueue; private final MapK, V cacheMap; public FIFOCache(int capacity) { this.capacity capacity; this.keyQueue new LinkedList(); this.cacheMap new HashMap(); } // 放入元素 public void put(K key, V value) { if (keyQueue.size() capacity) { K oldestKey keyQueue.poll(); cacheMap.remove(oldestKey); } keyQueue.offer(key); cacheMap.put(key, value); } // 获取元素 public V get(K key) { return cacheMap.get(key); } }使用FIFOCacheString, Integer cache new FIFOCache(3); cache.put(A, 1); cache.put(B, 2); System.out.println(cache.get(A)); // 输出1泛型类和案例实战 泛型类核心要点基本规则类型限制泛型类型必须是引用类型如String、Integer不能使用基本数据类型如int、double。语法格式在类名后添加内部填写类型参数多个参数用逗号分隔public class 类名 泛型类型1, 泛型类型2 { private 泛型类型1 变量名; public 泛型类型1 方法名() { ... } public 返回值 方法名(泛型类型2 t) { ... } }使用方式JDK 1.7 支持菱形语法类名具体数据类型 对象名 new 类Name();关键注意事项未指定类型时泛型类若未指定类型默认是Object类型会失去编译时类型检查。逻辑类型 vs 实际类型从逻辑上看ListString和ListInteger是不同类型但在 JVM 中实际是同一个类型受类型擦除影响。创建限制Java 允许声明泛型对象 / 数组引用但禁止直接创建泛型对象和泛型数组如new T()、new T[5]。类型擦除机制核心原理Java 泛型是伪泛型编译后会执行类型擦除所有泛型类型都会被擦除为Object类型或边界类型。因此new T()等价于new Object()无法确定具体类型编译器直接禁止此类操作。泛型数组同理直接创建会导致类型不安全所以被编译器禁止。案例实战示例java运行// 定义泛型类 public class GenericBoxT { private T data; public T getData() { return data; } public void setData(T data) { this.data data; } } // 使用泛型类JDK 1.7 菱形语法 public class Main { public static void main(String[] args) { GenericBoxString box new GenericBox(); box.setData(Hello Generics); String content box.getData(); // 无需强制类型转换 System.out.println(content); } } 总结泛型类是实现类型安全复用的核心工具通过编译时检查避免运行时类型转换异常同时要注意类型擦除带来的限制不能直接创建泛型对象 / 数组。泛型派生类和泛型接口实战 泛型类继承规则子类是泛型类父类与子类的泛型类型必须保持一致子类若有多个泛型参数必须包含父类的泛型类型。示例// 父类泛型类 public class ParentT { private T value; public T getValue() { return value; } public void setValue(T value) { this.value value; } } // 子类泛型类包含父类泛型T额外增加E、F class ChildT, E, F extends ParentT { // ... } // 子类泛型类与父类泛型完全一致 public class ChildT extends ParentT { Override public T getValue() { return super.getValue(); } }子类不是泛型类必须明确指定父类的泛型类型否则父类泛型默认擦除为Object。示例// 正确明确指定父类泛型为String class Child extends ParentString { // ... } // 不推荐未指定泛型父类泛型默认擦除为Object public class Child extends Parent { Override public Object getValue() { return super.getValue(); } } 泛型接口规则基本格式interface 接口名 泛型类型1, ... { 泛型类型 方法名(); // ... }实现类是泛型类接口与实现类的泛型类型必须一致实现类若有多个泛型参数必须包含接口的泛型类型。示例interface MyInterfaceT { T get(); } // 实现类是泛型类包含接口泛型T class MyImplT, E implements MyInterfaceT { Override public T get() { return null; } }实现类不是泛型类必须明确指定接口的泛型类型。示例class MyImpl implements MyInterfaceString { Override public String get() { return hello; } } 核心总结泛型类继承子类泛型需包含父类泛型子类非泛型则需明确父类泛型类型。泛型接口实现规则与泛型类继承完全一致核心是保持泛型类型的传递与明确性。类型擦除影响未指定泛型时默认擦除为Object会失去编译时类型检查应尽量避免。泛型方法和案例实战 泛型方法核心知识点定义与格式本质在调用方法时才指定具体类型与所在类的泛型相互独立。语法格式必须在修饰符与返回值类型之间声明T,E,...才是泛型方法修饰符 T,E,... 返回值类型 方法名(参数列表) { // 方法体 }误区提醒泛型类里返回T的普通方法如public T pop()不是泛型方法只是使用了类泛型的成员方法。独立性与作用域泛型方法的类型参数与类泛型完全独立同名也互不影响。声明的泛型类型仅在方法参数列表和方法体内有效。示例public class CustomArrayStackT { // 泛型方法E与类泛型T无关 public E E getRandomElement(ListE list) { Random random new Random(); return list.get(random.nextInt(list.size())); } }静态方法限制使用类泛型的成员方法不能定义为static因为类泛型依赖实例静态方法无实例。真正的泛型方法可以定义为static类型参数在调用时确定不依赖实例。错误示例// ❌ 错误使用类泛型T的静态方法 public static T pop() { ... }正确示例// ✅ 正确泛型方法可以是静态的 public static E E getRandomElement(ListE list) { ... }多参数与可变参数泛型方法支持多个类型参数也可搭配可变参数使用public static E,F,K void print(F f, K k, E... arr) { System.out.println(f.getClass()); System.out.println(k.getClass()); for(E e : arr) { System.out.println(e); } } // 调用示例 print(1, 小滴课堂, 12, 32, 4, 2, 1342, 3); 总结判断标准修饰符与返回值之间有T,...才是泛型方法。独立性泛型方法的类型与类泛型无关可单独使用。静态限制只有真正的泛型方法才能定义为static使用类泛型的方法不行。泛型通配符 和案例实战 泛型通配符核心知识点定义Java 泛型通配符是解决泛型之间引用传递问题的特殊语法让泛型能更灵活地接受未知类型数据。分类与语法通配符类型语法格式含义通用通配符List?表示类型参数可以是任何类型? 可看作所有泛型类型的父类上界通配符List? extends E类型参数必须是 E 或 E 的子类包括实现了接口 E 的类下界通配符List? super E类型参数必须是 E 或 E 的超类⚠️ 注意通配符是实参不是形参不能直接用于类 / 接口的泛型声明如class CustomCollection?语法不规范正确用法是在变量 / 参数中使用。一个泛型只能指定上界或下界不能同时指定两者。关键细节上界通配符extends不仅限于继承父类的子类也包括实现了接口 E的类。示例List? extends Number可接受ListInteger、ListDouble等。下界通配符super示例List? super String可接受ListString、ListCharSequence、ListObject等。通用通配符?代表未知类型只能读取不能写入因为无法确定具体类型。常见误区修正类 / 接口声明时不能直接使用?正确写法是声明泛型形参如class CustomCollectionT通配符主要用于方法参数、变量引用场景。extends关键字不代表 “继承类”而是 “上限约束”接口实现类也满足。 总结泛型通配符让泛型的使用更灵活?接受任意类型只读安全。? extends E接受 E 及子类偏向读取场景。? super E接受 E 及超类偏向写入场景。泛型通配符上限和下限 固定上界通配符? extends E语法? extends E其中E是上边界。约束只能接受E或E的子类类型包括实现了接口E的类。示例// 只能接受 Number 或其子类Integer、Double 等 public static void printUp(NumberCollection? extends Number collection) { Number value collection.getValue(); System.out.println(value); }错误案例传入String类型会编译报错因为String不是Number的子类。注意extends不局限于类继承实现接口的类也满足约束。 固定下界通配符? super E语法? super E其中E是下边界。约束只能接受E或E的超类类型。示例// 只能接受 Integer 或其父类Number、Object 等 public static void printDown(NumberCollection? super Integer collection) { Object value collection.getValue(); System.out.println(value); }测试案例NumberCollectionLong传入会报错Long不是Integer或其父类。NumberCollectionInteger、NumberCollectionNumber可以正常传入。⚠️ 核心规则不可同时指定上下边界一个泛型只能指定extends上界或super下界不能两者同时使用。类型安全上界通配符偏向读取读取到的类型至少是E安全。下界通配符偏向写入写入E或其子类是安全的读取时类型会被擦除为Object。 总结对比通配符约束范围适用场景? extends EE 及子类读取数据保证至少是 E 类型? super EE 及超类写入数据保证能存入 E 类型泛型类型擦除 什么是泛型类型擦除背景泛型是 JDK 1.5 引入的特性为了兼容旧版本代码编译后泛型信息会被删除只保留原始类型。时机代码编译完成后、进入 JVM 运行前泛型类型信息被擦除。作用范围类泛型、接口泛型、方法泛型都会被擦除。 类型擦除的分类无限制类型擦除当泛型没有指定边界如T,K时擦除后全部变为Object类型。示例public class GenericT,K { private T age; private K name; }擦除后age和name的类型都变成Object反射打印结果为age, 类型Object name, 类型Object有限制类型擦除当泛型指定了上边界如T extends Number擦除后变为上边界类型这里是Number。示例public class GenericT extends Number, K { private T age; private K name; }擦除后age类型变为Numbername仍为Object反射打印结果为age, 类型Number name, 类型Object⚠️ 核心结论Java 泛型是伪泛型运行时不存在泛型类型只有原始类型。无边界泛型 → 擦除为Object有上边界泛型 → 擦除为上边界类型。类型擦除是泛型兼容旧代码的关键也是 “不能创建泛型对象 / 数组” 等限制的根源。 总结对比擦除类型泛型定义擦除后类型无限制TObject有限制T extends NumberNumber面试题-如何创建泛型数组和获取全部数组 泛型数组的核心痛点与解决方案背景痛点Java 中不能直接创建泛型数组如new T[size]原因是类型擦除编译后泛型信息被擦除JVM 无法确定数组的具体类型Object[]无法强制转换为T[]。直接使用Object[]存储返回时强制类型转换会抛出ClassCastException。✅ 解决方案一反射创建泛型数组推荐通过java.lang.reflect.Array.newInstance(Class? componentType, int length)反射创建指定类型的数组避开直接强制转换的陷阱。完整实现代码import java.lang.reflect.Array; public class GenericArrayT { private T[] array; // 构造器传入类类型ClassT和容量通过反射创建泛型数组 public GenericArray(ClassT cls, int capacity) { // 核心反射创建对应类型的数组并强制类型转换为T[] array (T[]) Array.newInstance(cls, capacity); } // 存入元素 public void put(int index, T item) { array[index] item; } // 获取元素 public T get(int index) { return array[index]; } // 获取原生泛型数组 public T[] getAll() { return array; } // 测试入口 public static void main(String[] args) { // 创建String类型的泛型数组容量3 GenericArrayString genericArray new GenericArray(String.class, 3); genericArray.put(0, 小滴课堂); genericArray.put(1, 架构太课); String value genericArray.get(0); System.out.println(value); // 输出小滴课堂 String[] array1 genericArray.getAll(); System.out.println(array1); // 输出正确的String数组不会报错 } }❌ 方案二直接使用 Object [] 的问题如果直接使用Object[]存储虽然能编译通过但返回数组时会抛出类型转换异常。错误示例代码public class GenericsArrayT { private Object[] array; public GenericsArray(int size) { array new Object[size]; } public void put(int index, T item) { array[index] item; } public T get(int index) { return (T) array[index]; // 单个元素可以安全强转 } // 错误无法将Object[]直接转换为T[]运行时抛出ClassCastException public T[] getAll() { return (T[]) array; } public static void main(String[] args) { GenericsArrayString ga new GenericsArray(3); ga.put(0, springcloud); String value ga.get(0); System.out.println(value); // 以下代码运行报错Cannot cast Object[] to String[] // String[] all ga.getAll(); } } 源码级解析ArrayList 的 toArray 实现JDK 集合框架如ArrayList也是通过反射 数组类型拷贝来实现泛型数组的转换核心逻辑如下核心方法链toArray()返回Object[]public Object[] toArray() { return Arrays.copyOf(elementData, size); }toArray(T[] a)泛型方法通过copyOf创建指定类型数组public T T[] toArray(T[] a) { return (T[]) Arrays.copyOf(elementData, size, a.getClass()); }Arrays.copyOf()底层最终调用Array.newInstance创建真实类型数组public static T,U T[] copyOf(U[] original, int newLength, Class? extends T[] newType) { T[] copy ((Object)newType (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } 核心总结方案实现方式优缺点反射方案Array.newInstance(cls, capacity)能创建真实类型数组安全返回 T[]推荐使用Object [] 方案直接创建 Object[]无法直接返回类型化数组强制转换会报错JDK 集合底层复用 Array.newInstance框架级实现保证类型安全 面试必问为什么 Java 不允许直接创建泛型数组因为类型擦除编译后T会被擦除为Object或上界类型。运行时数组会被强制转换为原始类型Object[]无法保证类型安全例如ListString[]存储了ListInteger运行时无法检测。所以 Java 编译器禁止直接创建泛型数组而推荐使用反射方案。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2413998.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!