本文纲要
- 泛型概述
不写泛型的弊端
泛型的好处 - 泛型类的使用
以ArrayList为例
源码解析 - 自定义泛型类
定义格式
案例:Box类 - 泛型方法的使用
ArrayList中的toArray方法
空参 vs 带参 - 自定义泛型方法
定义格式
案例:批量添加元素 - 泛型接口
定义格式
两种使用方式 - 通配符
无界通配符<?>
上界限定<? extends 类型>
下界限定<? super 类型>
示例项目结构
本文所有代码均位于 mygenericity 项目中,包结构如下:
t
mygenericity/src/com/wb/
├── genericitysummarize/
│ ├── GenericitySummarize.java
│ └── GenericityClass.java
├── genericityclass/
│ ├── Box.java
│ └── MyGenericityClass.java
├── genericitymethod/
│ ├── GenericityMethod1.java
│ └── GenericityMethod2.java
├── genericityinterface/
│ └── GenericityInterface.java
└── genericityglobbing/
└── genericityglobbing1.java
泛型概述
在创建集合时,如果不指定泛型,集合中元素的类型会被当作 Object,虽然数据本身可以正常存储,但在取出时会丢失具体类型信息。
不写泛型的弊端演示:
java
public class GenericitySummarize {
public static void main(String[] args) {
ArrayList list = new ArrayList(); // 没有泛型
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add(123); // 添加Integer也没问题
Iterator it = list.iterator();
while (it.hasNext()) {
// 取出时是Object,必须强转
String next = (String) it.next(); // 当取到123时会抛出ClassCastException
int len = next.length();
System.out.println(len);
}
}
}
问题总结:
集合中可以随意添加不同类型数据(都是 Object)。
取出时必须强转,如果类型不一致,会在运行时抛出 ClassCastException。
代码冗余,所有需要强转的地方都得自己处理。
泛型的好处:
- 将运行时的错误提前到编译期(添加元素时就会类型检查)。
- 避免了强制类型转换,代码更简洁安全。
泛型类的使用
Java中已定义好的泛型类最典型的代表就是 ·ArrayList·。我们可以查看源码发现 ·ArrayList· 的声明:
java
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ... }
这里的 <E> 代表一个未知的类型,会在创建对象时被确定。
java
public class GenericityClass {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(); // 此时 E 被明确为 String
list.add("hello");
String s = list.get(0); // 直接得到String,无需强转
}
}
ArrayList 中的 add(E e) 和 get(int index): E 等方法里的 E 都会随着创建对象时的具体类型参数而变化。这样设计让集合类可以适应任意数据类型,同时保证类型安全。
自定义泛型类
定义格式
java
修饰符 class 类名<泛型标识> { ... }
泛型标识可以是任意符合变量命名规则的字母,常见的有 E(Element)、T(Type)、K(Key)、V(Value)等。
案例:Box类
定义一个万能盒子,可以存放任意类型的元素:
java
// Box.java
public class Box<E> {
private E element;
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
}
使用方式:
java
// MyGenericityClass.java
public class MyGenericityClass {
public static void main(String[] args) {
// 存放字符串
Box<String> box1 = new Box<>();
box1.setElement("给小丽的土味情话");
String element1 = box1.getElement();
System.out.println(element1);
// 存放整数
Box<Integer> box2 = new Box<>();
box2.setElement(19);
Integer element2 = box2.getElement();
System.out.println(element2);
}
}
在创建 Box 对象时,指定的类型会传递给类中的 E,从而使 element 和相应的 get/set 方法拥有具体的类型。
泛型方法的使用
Java内置的 ArrayList 就提供了泛型方法的典型应用:toArray。
java
public class GenericityMethod1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("给小花同学的土味情话");
list.add("给小丽同学的土味情话");
list.add("给小路同学的土味情话");
// 空参:返回Object[]
Object[] objects = list.toArray();
System.out.println(Arrays.toString(objects));
// 带参数的泛型方法:返回指定类型的数组
String[] strings = list.toArray(new String[list.size()]);
System.out.println(Arrays.toString(strings));
}
}
空参的 toArray() 返回的是 Object[],想使用字符串方法仍需强转。
带参的 toArray(T[] a) 会根据传入的数组类型返回相同类型的数组,避免了强转。
这里的 T 就是泛型方法中定义的类型参数。
自定义泛型方法
定义格式
java
修饰符 <泛型标识> 返回值类型 方法名(参数列表) { ... }
泛型标识必须声明在返回值之前,否则编译器会认为它是一个具体的类名。
案例:批量添加元素
定义一个可以批量添加四个元素到集合中并返回集合的方法:
java
public class GenericityMethod2 {
public static void main(String[] args) {
ArrayList<String> list1 = addElement(new ArrayList<String>(), "a", "b", "c", "d");
System.out.println(list1);
ArrayList<Integer> list2 = addElement(new ArrayList<Integer>(), 1, 2, 3, 4);
System.out.println(list2);
}
// 泛型方法:类型T在调用时确定
public static <T> ArrayList<T> addElement(ArrayList<T> list, T t1, T t2, T t3, T t4) {
list.add(t1);
list.add(t2);
list.add(t3);
list.add(t4);
return list;
}
}
调用 addElement 时,根据传入的集合和元素类型自动推导出 T,无需手动指定。
泛型接口
定义格式
java
修饰符 interface 接口名<泛型标识> { ... }
两种使用方式
假设有如下泛型接口:
java
interface Genericity<E> {
void method(E e);
}
方式一:实现类仍然不确定类型(延续泛型)
java
class GenericityImpl1<E> implements Genericity<E> {
@Override
public void method(E e) {
System.out.println(e);
}
}
// 使用
GenericityImpl1<String> genericity = new GenericityImpl1<>();
genericity.method("小丽给我的土味情话");
GenericityImpl1 本身也是一个泛型类,创建对象时才确定类型。
方式二:实现类直接指定具体类型
java
class GenericityImpl2 implements Genericity<Integer> {
@Override
public void method(Integer integer) {
System.out.println(integer);
}
}
// 使用
GenericityImpl2 genericityImpl2 = new GenericityImpl2();
genericityImpl2.method(19);
此时 GenericityImpl2 是一个普通类,类型被固定为 Integer。
通配符
通配符 ? 表示未知类型,常用于接收各种泛型集合,但不能添加元素(null除外)
1 ) 无界通配符 <?>
java
public static void printList(ArrayList<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
2 ) 上界限定 <? extends 类型>
表示可以接收该类型及其所有子类。例如 <? extends Number> 可以接收 Number 或 Integer、Double 等
3 ) 下界限定 <? super 类型>
表示可以接收该类型及其所有父类。例如 <? super Number> 可以接收 Number 或 Object。
完整案例
考虑继承关系:
#mermaid-svg-JhJJyHv1i8pzhomS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JhJJyHv1i8pzhomS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JhJJyHv1i8pzhomS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JhJJyHv1i8pzhomS .error-icon{fill:#552222;}#mermaid-svg-JhJJyHv1i8pzhomS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JhJJyHv1i8pzhomS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JhJJyHv1i8pzhomS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JhJJyHv1i8pzhomS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JhJJyHv1i8pzhomS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JhJJyHv1i8pzhomS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JhJJyHv1i8pzhomS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JhJJyHv1i8pzhomS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JhJJyHv1i8pzhomS .marker.cross{stroke:#333333;}#mermaid-svg-JhJJyHv1i8pzhomS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JhJJyHv1i8pzhomS p{margin:0;}#mermaid-svg-JhJJyHv1i8pzhomS g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-JhJJyHv1i8pzhomS g.classGroup text .title{font-weight:bolder;}#mermaid-svg-JhJJyHv1i8pzhomS .cluster-label text{fill:#333;}#mermaid-svg-JhJJyHv1i8pzhomS .cluster-label span{color:#333;}#mermaid-svg-JhJJyHv1i8pzhomS .cluster-label span p{background-color:transparent;}#mermaid-svg-JhJJyHv1i8pzhomS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JhJJyHv1i8pzhomS .cluster text{fill:#333;}#mermaid-svg-JhJJyHv1i8pzhomS .cluster span{color:#333;}#mermaid-svg-JhJJyHv1i8pzhomS .nodeLabel,#mermaid-svg-JhJJyHv1i8pzhomS .edgeLabel{color:#131300;}#mermaid-svg-JhJJyHv1i8pzhomS .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-JhJJyHv1i8pzhomS .label text{fill:#131300;}#mermaid-svg-JhJJyHv1i8pzhomS .labelBkg{background:#ECECFF;}#mermaid-svg-JhJJyHv1i8pzhomS .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-JhJJyHv1i8pzhomS .classTitle{font-weight:bolder;}#mermaid-svg-JhJJyHv1i8pzhomS .node rect,#mermaid-svg-JhJJyHv1i8pzhomS .node circle,#mermaid-svg-JhJJyHv1i8pzhomS .node ellipse,#mermaid-svg-JhJJyHv1i8pzhomS .node polygon,#mermaid-svg-JhJJyHv1i8pzhomS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JhJJyHv1i8pzhomS .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS g.clickable{cursor:pointer;}#mermaid-svg-JhJJyHv1i8pzhomS g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-JhJJyHv1i8pzhomS g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-JhJJyHv1i8pzhomS .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-JhJJyHv1i8pzhomS .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-JhJJyHv1i8pzhomS .dashed-line{stroke-dasharray:3;}#mermaid-svg-JhJJyHv1i8pzhomS .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-JhJJyHv1i8pzhomS #compositionStart,#mermaid-svg-JhJJyHv1i8pzhomS .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS #compositionEnd,#mermaid-svg-JhJJyHv1i8pzhomS .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS #dependencyStart,#mermaid-svg-JhJJyHv1i8pzhomS .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS #dependencyStart,#mermaid-svg-JhJJyHv1i8pzhomS .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS #extensionStart,#mermaid-svg-JhJJyHv1i8pzhomS .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS #extensionEnd,#mermaid-svg-JhJJyHv1i8pzhomS .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS #aggregationStart,#mermaid-svg-JhJJyHv1i8pzhomS .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS #aggregationEnd,#mermaid-svg-JhJJyHv1i8pzhomS .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS #lollipopStart,#mermaid-svg-JhJJyHv1i8pzhomS .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS #lollipopEnd,#mermaid-svg-JhJJyHv1i8pzhomS .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-JhJJyHv1i8pzhomS .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-JhJJyHv1i8pzhomS .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JhJJyHv1i8pzhomS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JhJJyHv1i8pzhomS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JhJJyHv1i8pzhomS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Object
Number
Integer
代码演示:
java
public class genericityglobbing1 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<Number> list2 = new ArrayList<>();
ArrayList<Object> list3 = new ArrayList<>();
// 上界通配符:接收 Number 及其子类
method1(list1); // OK,Integer是Number的子类
method1(list2); // OK
// method1(list3); // 编译错误,Object不是Number的子类
// 下界通配符:接收 Number 及其父类
method2(list2); // OK
method2(list3); // OK,Object是Number的父类
// method2(list1); // 编译错误,Integer不是Number的父类
}
// 上界:只能是 Number 或 Number 的子类
public static void method1(ArrayList<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
// 下界:只能是 Number 或 Number 的父类
public static void method2(ArrayList<? super Number> list) {
// 这里只能以Object类型访问,因为父类最低是Number,最高是Object
for (Object obj : list) {
System.out.println(obj);
}
}
}
记忆要点:
? extends Number:规定了上边界,最多到 Number,所以只能取(当作 Number),不能随便添加。
? super Number:规定了下边界,至少是 Number,可以添加 Number 及其子类,但取出时只能当作 Object。
总结
泛型是Java提供的一种编译时类型安全检测机制,合理使用泛型可以写出更健壮、更优雅的代码。掌握泛型类、泛型方法、泛型接口以及通配符的用法,是Java基础的重要内容。