文章目录
- 一、为什么需要泛型?
- 二、泛型的核心概念
-
- [2.1 定义](#2.1 定义)
- [2.2 核心优势](#2.2 核心优势)
- 三、泛型的核心用法
-
- [3.1 泛型类/接口](#3.1 泛型类/接口)
- [3.2 泛型方法](#3.2 泛型方法)
- [3.3 泛型通配符](#3.3 泛型通配符)
- 四、泛型的核心规则
一、为什么需要泛型?
在泛型出现之前(JDK5之前),Java集合(比如HashMap、ArrayList)只能存储Object类型的对象,会带来两个致命问题:
- 类型不安全:可以往集合里存任意类型的对象,运行时可能抛出类型转换异常。
- 需要手动强制类型转换:取数据时必须手动转换成目标类型,代码繁琐且容易出错
反面例子(无泛型的HashMap):
java
public static void main(String[] args) {
//无泛型的HashMap:默认存object类型
Map map = new HashMap();
//1.可以存任意类型(字符串key + 整数value 、字符串key + 字符串Value)
map.put("年龄",20);
map.put("姓名","张三");
//甚至能存不相关的类型(比如key是整数,Value是数组)
map.put(100,new int[]{1,2,3});
//2.取值时必须手动强转,且容易出错
//正确转换:年龄是整数
int age = (int) map.get("年龄");
System.out.println("年龄:" + age);
//错误转换:姓名是字符串,强转成整数会抛出ClassCastException
//int name = (int) map.get("姓名"); //运行时报错
}
问题总结:
- 编译时无法检查类型错误(比如往存"年龄"的Map里存字符串),只有运行时才会报错
- 每次取值都要强制类型转换,代码冗余且容易出错。
而泛型的核心作用 就是:在编译阶段限制集合/类的类型,保证类型安全,同时避免手动类型转换。
二、泛型的核心概念
2.1 定义
泛型(Generic)是JDK5引入的特性,允许在定义类、接口、方法时,使用"类型参数"(比如)来表示未知类型,使用时再指定具体类型(比如< String,Integer>)。
可以把泛型理解成"类型的占位符":
- 定义时:用<T> <k,v>等符号占位置(T = type,K = key,V = value,只是约定俗称的命名,用其他的字母也可以)
- 使用时:用具体的类型(比如String、Integer)替换占位符。
2.2 核心优势
| 优势 | 说明 |
|---|---|
| 类型安全 | 编译时检查类型,避免运行时ClassCastException |
| 消除强制转换 | 取值时自动匹配类型,无需手动强转 |
| 代码复用 | 一套泛型代码可以适配多种类型,不用为每种类型写重复代码 |
三、泛型的核心用法
泛型的使用场景主要分为3类:泛型类/接口、泛型方法、通配符,其中前两类是新手必须掌握的核心
3.1 泛型类/接口
泛型类的定义格式:class 类名<类型参数> { ... }
泛型接口的定义格式: interface 接口名<类型参数> { ... }
示例1:自定义泛型类
模拟HashMap的核心逻辑,写一个简单的泛型键值对类
java
class MyGenericMap<K,V> {
//成员变量:类型为K和V(泛型类型)
private K key;
private V value;
//构造方法:参数类型为K和V
public MyGenericMap(K key,V value) {
this.key = key;
this.value = value;
}
//普通方法:返回值类型为K/V
public K getKey() {
return key;
}
public V getValue() {
return value;
}
//重写toString,方便打印
@Override
public String toString() {
return key + " = " + value;
}
}
public class Test {
public static void main(String[] args) {
//使用泛型类:指定K = string,V = Integer(具体类型)
MyGenericMap<String,Integer> map1 = new MyGenericMap<>("年龄",20);
//取值时无需强转,直接是String/Integer类型
String key1 = map1.getKey();
Integer value1 = map1.getValue();
System.out.println(map1); //输出:年龄=20
//换一种类型:K = Integer,V = String
MyGenericMap<Integer,String> map2 = new MyGenericMap<>(1001,"张三");
Integer key2 = map2.getKey();
String value2 = map2.getValue();
System.out.println(map2);
//编译类型检查:如果存错类型,编译直接报错(类型安全)
Map<String,Integer> map3 = new HashMap<>();
//map3.put("姓名",20.5); //编译报错,Integer类型不能赋值给Double
}
}
关键说明:
- 定义时<K,V>是"类型参数",代表未知类型;
- 使用时<String,Integer>是"类型实参",指定具体类型
- 编译时会检查类型,比如往Map<String,Integer>里存Double类型,直接编译报错,避免运行时异常。
示例2:泛型集合
以HashMap,ArrayList为例,演示泛型在集合中的使用:
java
public static void main(String[] args) {
//1.泛型ArrayList:限制只能存String类型
List<String> strlist = new ArrayList<>();
strlist.add("张三");
strlist.add("李四");
//strlist.add(123); //编译报错:不能存整数,只能存String
//取值无需强转,直接是String类型
String name = strlist.get(0);
System.out.println("List第一个元素:" + name);
//2.泛型HashMap:限制Key=String,value=Integer
Map<String,Integer> scoreMap = new HashMap<>();
scoreMap.put("语文",90);
scoreMap.put("数学",95);
//scoreMap.put("英语","95"); //编译报错:value必须是Integer
//取值无需强转,直接是Integer类型
int mathScore = scoreMap.get("数学");
System.out.println("数学成绩:" + mathScore);
}
核心总结:
集合使用泛型后,相当于给集合加了"类型过滤器",只能存指定类型的元素,编译时就能发现类型错误,取值也不需要进行强转。
3.2 泛型方法
泛型方法是指方法本身带有类型参数,即使所在的类不是泛型类,也可以定义泛型方法
定义格式:[修饰符]<类型参数>返回值类型 方法名(参数列表) {...}
java
public static <T> List<T> arrayToList(T[] array) {
List<T> list = new ArrayList<>();
for (T element : array) {
list.add(element);
}
return list;
}
//自定义Person类
static class Person{
private String name;
private int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name = ' " + name + " ',age = " + age + "}";
}
}
public static void main(String[] args) {
//测试1:整数数组转List<Integer>
Integer[] intAaay = {1,2,3};
List<Integer> intlist = arrayToList(intAaay);
System.out.println("整数List:" + intlist); //输出【1,2,3】
//测试2:字符串数组转List<String>
String[] strArray = {"a","b","c"};
List<String> strlist = arrayToList(strArray);
System.out.println("字符串List:" + strlist); //输出:【a,b,c】
//测试3:自定义类型数组转List
Person[] people = {new Person("张三",20),new Person("李四",25)};
List<Person> personList = arrayToList(people);
System.out.println("personList:" + personList);
}
关键说明:
- 泛型方法的<T>必须写在static之后,返回值之前(这是语法规则)
- 同一个泛型方法可以适配多种类型(整数、字符串、自定义Person),实现代码复用
- 调用时无需手动指定<T>,java会自动根据传入的参数类型推断(比如传入Integer数组,T自动就是Integer)
3.3 泛型通配符
泛型通配符用于 "灵活匹配泛型类型",解决 "泛型不协变" 的问题(比如 List<String> 不是 List<Object> 的子类)。新手先掌握核心用法:

java
//1.无界通配符:打印任意类型的List
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj + " ");
}
System.out.println();
}
//2.上界通配符:计算数字List的总和(只能是Number及其子类)
public static double sumlist(List<? extends Number> list){
double sum = 0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
public static void main(String[] args) {
//测试无界通配符
List<String> strlist = new ArrayList<>();
strlist.add("a");
strlist.add("b");
printList(strlist); //输出:a b
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
printList(intList); //输出1 2
//测试上界通配符
List<Integer> numlist = new ArrayList<>();
numlist.add(10);
numlist.add(20);
System.out.println("总和:" + sumlist(numlist));//输出:30.0
List<String> stringList = new ArrayList<>();
stringList.add("c");
stringList.add("d");
System.out.println("总和: " + sumlist(stringList)); //编译报错,String不是number的子类,为了保证类型安全,编译报错
}
四、泛型的核心规则
-
泛型只在编译阶段有效(类型擦除)
Java 泛型是 "编译时语法糖",运行时会把泛型类型擦除成 Object(或上界类型)。比如:
- 编译时 List 和 List 是不同类型;
- 运行时两者都是 List 类型,无法通过反射区分。
-
不能用基本类型作为泛型参数
泛型参数只能是引用类型(比如 Integer、String),不能是基本类型(int、char、double)。
❌ 错误:List list = new ArrayList<>();
✅ 正确:List list = new ArrayList<>();
-
泛型类的静态成员不能使用泛型参数
静态成员属于类,而泛型参数属于对象(创建对象时才指定),所以静态方法 / 变量不能用类的泛型参数:
java
class MyClass<T> {
// 错误:静态变量不能用T
// private static T value;
// 正确:如果静态方法需要泛型,定义成泛型方法
public static <E> E getValue(E e) {
return e;
}
}