JAVA的泛型
为什么引入泛型
有两个作用:
- 适用于多种数据类型执行相同的代码(代码复用)
- 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
- 消除强制类型转换
- 兼容性与类型擦除
- 更灵活的类型关系控制
代码复用
如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法,如下:
private int add(int a, int b) {System.out.println(a + "+" + b + "=" + (a + b));return a + b;
}private float add(float a, float b) {System.out.println(a + "+" + b + "=" + (a + b));return a + b;
}private double add(double a, double b) {System.out.println(a + "+" + b + "=" + (a + b));return a + b;
}
通过泛型,我们可以复用为一个方法,如下:
private <T extends Number> double add(T a, T b) {System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));return a.doubleValue() + b.doubleValue();
}
类型安全
泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
- 在泛型出现之前,Java 集合类(如 List、Map)默认使用 Object 类型存储元素,开发者需要手动强制类型转换,这可能导致运行时错误(如 ClassCastException)
- 通过泛型,编译器能检测类型错误,确保集合中元素的类型一致
List<String> list = new ArrayList<>(); list.add("Hello"); list.add(100); // 编译报错!直接阻止非法类型
消除强制类型转换
// 无泛型
List list = new ArrayList();
list.add("Java");
String str = (String) list.get(0); // 需要强制转换// 有泛型
List<String> list = new ArrayList<>();
list.add("Java");
String str = list.get(0); // 自动推断类型
兼容性与类型擦除
Java 泛型通过**类型擦除(Type Erasure)**实现,编译后泛型信息会被擦除,替换为原始类型(如 Object)或边界类型。
优点:
- 保持向后兼容性:旧版本 JVM 能运行泛型代码(编译后的字节码与非泛型代码兼容)。
- 避免性能损失:无需为泛型生成额外运行时类型信息。
类型擦除的副作用:
- 无法直接获取泛型的运行时类型(如 T.class)。
- 泛型类型不能是基本数据类型(需使用包装类,如 List)
更灵活的类型关系控制
泛型支持更灵活的类型关系控制:
<? extends T>(上界通配符):接受 T 或其子类。
<? super T>(下界通配符):接受 T 或其父类。
// 生产者使用 extends(Producer-Extends)
public void processList(List<? extends Number> list) {for (Number num : list) { ... }
}// 消费者使用 super(Consumer-Super)
public void addNumbers(List<? super Integer> list) {list.add(100);
}
泛型用法
泛型类
有两种常见用法:
- 类中只有一个泛型属性
- 类中有多个泛型属性
类中只有一个泛型属性
class Point<T>{ // 此处可以随便写标识符号,T是type的简称 private T var ; // var的类型由T指定,即:由外部指定 public T getVar(){ // 返回值的类型由外部决定 return var ; } public void setVar(T var){ // 设置的类型也由外部决定 this.var = var ; }
}
public class GenericsDemo06{ public static void main(String args[]){ Point<String> p = new Point<String>() ; // 里面的var类型为String类型 p.setVar("it") ; // 设置字符串 System.out.println(p.getVar().length()) ; // 取得字符串的长度 }
}
类中有多个泛型属性
class Notepad<K,V>{ // 此处指定了两个泛型类型 private K key ; // 此变量的类型由外部决定 private V value ; // 此变量的类型由外部决定 public K getKey(){ return this.key ; } public V getValue(){ return this.value ; } public void setKey(K key){ this.key = key ; } public void setValue(V value){ this.value = value ; }
}
public class GenericsDemo09{ public static void main(String args[]){ Notepad<String,Integer> t = null ; // 定义两个泛型类型的对象 t = new Notepad<String,Integer>() ; // 里面的key为String,value为Integer t.setKey("abc") ; // 设置第一个内容 t.setValue(20) ; // 设置第二个内容 System.out.print("姓名;" + t.getKey()) ; // 取得信息 System.out.print(",年龄;" + t.getValue()) ; // 取得信息 }
}
泛型接口
// 定义一个泛型接口:数据处理接口
public interface DataProcessor<T> {void process(T data);T getResult();
}
示例如下:
通用数据存储接口:定义一个泛型接口用于存储和检索不同类型的数据。
// 泛型存储接口
public interface Storage<T> {void save(T item);T retrieve(String id);
}// 实现类:文件存储
public class FileStorage<T> implements Storage<T> {@Overridepublic void save(T item) {// 将对象序列化到文件}@Overridepublic T retrieve(String id) {// 从文件反序列化对象return null;}
}// 使用示例
Storage<String> stringStorage = new FileStorage<>();
stringStorage.save("Hello World");Storage<User> userStorage = new FileStorage<>();
userStorage.save(new User("Alice"));
泛型接口的边界控制:通过 extends 或 super 约束泛型类型,增强安全性。
示例:限定类型范围
// 限制泛型类型必须是 Number 的子类
public interface Calculator<T extends Number> {T add(T a, T b);
}// 实现类
public class IntegerCalculator implements Calculator<Integer> {@Overridepublic Integer add(Integer a, Integer b) {return a + b;}
}
泛型方法
泛型方法,是在调用方法的时候指明泛型的具体类型
public <T> T getObject(Class<T> class)
示例:限制类型为数值类型
public <T extends Number> double sum(T a, T b) {return a.doubleValue() + b.doubleValue();
}// 使用
double result1 = sum(10, 20); // 30.0
double result2 = sum(3.14, 2.718); // 5.858
其他
获取Java集合中泛型的Class对象