Java 范型
泛型
泛型是JDK1.5推出的新特性,Java允许定义泛型类、泛型接口和泛型方法,Java API中的部分类和接口也通过泛型进行了修改。其核心价值是能在编译时而不是运行时检测出错误,让程序更可靠。
泛型的核心优势
编译期错误检测
对比JDK1.5前后的Comparable接口定义:
JDK1.5之前
package java.lang;
public interface Comparable{
public int compareTo(Object o);
}
JDK1.5之后
package java.lang;
public interface Comparable<T>{
public int compareTo(T o);
}
其中<T>表示形式泛型类型,使用时需用实际具体类型替换(即泛型实例化),约定用E、T等单个大写字母表示形式泛型类型。
通过两段代码对比泛型的错误检测优势:
A(无泛型)
Comparable c = new Date();
System.out.println(c.compareTo("peppa"));
B(有泛型)
Comparable<Date> c = new Date();
System.out.println(c.compareTo("peppa"));
B代码在编译期即可发现错误,而A代码需运行时才暴露问题。
限制元素类型
指定泛型类型后,集合仅能容纳该类型元素,避免类型混乱:
ArrayList<String> list = new ArrayList<>();
list.add("suzy"); // 合法
// list.add(123); // 编译错误,非字符串类型
无需类型转换
从泛型集合中获取元素时,编译器已明确元素类型,无需手动转换:
ArrayList<Integer> list = new ArrayList<>();
list.add(5); // 自动装箱
list.add(10); // 自动装箱
Integer num = list.get(0); // 无需类型转换
int num1 = list.get(1); // 自动拆箱,无需类型转换
注意事项
泛型类型必须是引用类型,不能使用int、long、char等基本类型直接替换。
泛型使用示例代码
package com.generic;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class GenericTest {
public static void main(String[] args) {
Comparable<String> message = "hello world";
Comparable<Date> date = new Date();
// System.out.println(message.compareTo(date)); // 编译错误,类型不匹配
// 不使用泛型
List list = new ArrayList();
list.add("hello world");
list.add(100);
String s = (String) list.get(0);
int i = (int) list.get(1);
System.out.println(s);
System.out.println(i);
// 使用泛型,不需要类型转换
List<String> list1 = new ArrayList<>();
list1.add("hello world11");
list1.add("hello world22");
String s1 = list1.get(0);
String s2 = list1.get(1);
System.out.println(s1);
System.out.println(s2);
}
}
定义泛型类和接口
可以为类或接口定义泛型,创建对象或声明引用时需指定具体实际类型。
自定义泛型栈示例
package com.generic;
import java.util.ArrayList;
import java.util.List;
/**
* 泛型栈
* @param <T> 形式类型参数,创建对象或声明引用时需指定具体实际类型
* ArrayList<E>: 泛型类,数组实现的线性表
* add(e:E) 添加元素
* remove(index:int) 删除指定索引元素,返回被删除元素
* remove(e:E) 删除指定元素,返回被删除元素
* clear() 清空集合
* get(index:int) 获取指定索引的E类型元素
* set(index:int, e:E) 修改指定索引元素
* size() 获取元素个数
* isEmpty() 判断是否为空
* contains(e:E) 判断是否包含指定元素
*/
public class GenericStack<T> {
// 默认容量为10
private List<T> stack = new ArrayList<>();
/**
* 入栈
* @param item 入栈元素
*/
public void push(T item) {
stack.add(item);
}
/**
* 出栈
* @return 栈顶元素
*/
public T pop() {
if (stack.isEmpty()) {
throw new IllegalStateException("栈为空");
}
return stack.remove(stack.size() - 1);
}
/**
* 获取栈顶元素(不出栈)
* @return 栈顶元素
*/
public T peek() {
if (stack.isEmpty()) {
throw new IllegalStateException("栈为空");
}
return stack.get(stack.size() - 1);
}
/**
* 判断栈是否为空
* @return true:栈为空,false:栈非空
*/
public boolean isEmpty() {
return stack.isEmpty();
}
/**
* 获取栈中元素个数
* @return 元素个数
*/
public int size() {
return stack.size();
}
public static void main(String[] args) {
/*
泛型只接受引用类型,不接受基本数据类型
*/
GenericStack<Integer> stack = new GenericStack<>();
for (int i = 0; i < 10; i++) {
stack.push(i); // 自动装箱为Integer
}
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}
}
核心说明
- 不使用泛型时,可将元素类型设为Object容纳任意对象,但无法在编译期检测类型错误。
- 使用泛型能提升软件的可靠性和可读性,编译期即可暴露类型相关错误。
泛型方法
泛型方法是使用泛型类型定义的方法,可独立于泛型类存在。
泛型方法示例
package com.generic;
/**
* 泛型方法示例
* @author Jing61
*/
public class GenericMethodDemo {
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
printArray(intArray);
String[] stringArray = {"hello", "world", "java"};
printArray(stringArray);
}
/**
* 泛型方法:打印数组元素
* @param t 待打印的数组,类型为泛型T[]
* @param <T> 形式泛型类型
*/
public static <T> void printArray(T[] t) {
System.out.print("[");
for (int i = 0; i < t.length; i++) {
System.out.print(t[i] + (i == t.length - 1 ? "" : " "));
}
System.out.println("]");
}
}
受限泛型
将泛型指定为另一种类型的子类型,称为受限泛型,使用extends关键字声明。
受限泛型示例
package com.generic;
/**
* 受限泛型示例
* @author Jing61
*/
public class BoundedTypeDemo {
public static void main(String[] args) {
// 比较圆形和矩形的面积
System.out.println(equalArea(new Circle(1), new Rectangle(2, 4)));
}
/**
* 比较两个几何对象的面积是否相等
* @param e1 第一个几何对象,类型为T(GeometricObject的子类型)
* @param e2 第二个几何对象,类型为T(GeometricObject的子类型)
* @param <T> 受限泛型,必须是GeometricObject的子类型
* @return true:面积相等,false:面积不相等
*/
public static <T extends GeometricObject> boolean equalArea(T e1, T e2) {
return e1.getArea() == e2.getArea();
}
}
应用:泛型排序
对实现Comparable接口的对象数组进行排序,利用受限泛型限制元素类型:
package com.generic;
import java.util.Arrays;
/**
* 泛型排序:对Comparable对象数组排序
*/
public class GenericSort {
public static void main(String[] args) {
Integer[] intArray = {2, 4, 6, 3, 5};
Double[] doubleArray = {3.14, 2.14, 0.14};
Character[] charArray = {'a', 'c', 'b', '0'};
String[] strArray = {"Hello", "world", "peppa"};
sort(intArray);
sort(doubleArray);
sort(charArray);
sort(strArray);
System.out.println(Arrays.toString(intArray));
System.out.println(Arrays.toString(doubleArray));
System.out.println(Arrays.toString(charArray));
System.out.println(Arrays.toString(strArray));
}
/**
* 泛型排序方法:冒泡排序
* @param array 待排序数组,元素类型需实现Comparable接口
* @param <T> 受限泛型,必须实现Comparable接口
*/
private static <T extends Comparable<T>> void sort(T[] array) {
T currentMin;
int currentMinIndex;
for (int i = 0; i < array.length; i++) {
currentMin = array[i];
currentMinIndex = i;
// 寻找当前最小值
for (int j = i + 1; j < array.length; j++) {
if (currentMin.compareTo(array[j]) > 0) {
currentMin = array[j];
currentMinIndex = j;
}
}
// 交换当前元素与最小值元素
if (currentMinIndex != i) {
array[currentMinIndex] = array[i];
array[i] = currentMin;
}
}
}
}
原始类型和向后兼容
未指定具体类型的泛型类/接口称为原始类型,用于兼容早期Java版本。
原始类型与泛型类型的等价关系
// 原始类型
GenericStack stack = new GenericStack();
// 等价于(默认使用Object作为泛型类型)
GenericStack<Object> stack = new GenericStack<>();
说明
- 原始类型本质是泛型类/接口未指定类型参数的形式。
- 使用原始类型可让泛型代码与早期非泛型代码兼容,但会失去泛型的类型安全检测功能。
通配泛型
通配泛型用于指定泛型类型的范围,解决泛型类型不具备继承性的问题(如GenericStack<Integer>不是GenericStack<Number>的子类型)。
通配泛型的三种形式
| 形式 | 名称 | 说明 |
|---|---|---|
? |
非受限通配 | 等价于? extends Object,表示任意类型 |
? extends T |
受限通配 | 表示T或T的子类型 |
? super T |
下限通配 | 表示T或T的父类型 |
示例代码
public class WildCardNeedDemo {
public static void main(String[] args ) {
GenericStack<Integer> intStack = new GenericStack<Integer>();
intStack.push(1); // 1 is autoboxed into new Integer(1)
intStack.push(2);
intStack.push(-2);
System.out.print("The max number is " + max(intStack));
}
public static double max(GenericStack<Number> stack) {
double max = stack.pop().doubleValue(); // initialize max
while (!stack.isEmpty()) {
double value = stack.pop().doubleValue();
if (value > max)
max = value;
}
return max;
}
}
尽管Integer是Number的子类型,但是GenericStack<Integer>并不是GenericStack<Number>的子类型。为了避免这个问题,可以使用通配泛型类型。上面System.out.print("The max number is " + max(intStack));这一段代码会出现编译错误,因为intStack不是GenericStackpublic static double max(GenericStack<? extends Number> stack)
非受限通配示例
public class AnyWildCardDemo {
public static void main(String[] args) {
GenericStack<Integer> intStack = new GenericStack<>();
intStack.push(1); // 自动装箱为Integer.valueOf(1)
intStack.push(2);
intStack.push(-2);
print(intStack); // 可接收任意类型的GenericStack
}
/**
* 打印栈中元素并清空栈
* @param stack 泛型栈,使用非受限通配?
*/
public static void print(GenericStack<?> stack) {
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " ");
}
}
}
受限通配示例
public class WildCardNeedDemo {
public static void main(String[] args) {
GenericStack<Integer> intStack = new GenericStack<>();
intStack.push(1);
intStack.push(2);
intStack.push(-2);
System.out.print("The max number is " + max(intStack)); // Integer是Number的子类型,可传入
}
/**
* 计算栈中数值的最大值
* @param stack 泛型栈,元素类型为Number或其子类型
* @return 最大值(double类型)
*/
public static double max(GenericStack<? extends Number> stack) {
double max = stack.pop().doubleValue(); // 初始化最大值
while (!stack.isEmpty()) {
double value = stack.pop().doubleValue();
if (value > max) {
max = value;
}
}
return max;
}
}
泛型类型和通配类型关系图

消除泛型和对泛型的限制
泛型通过类型消除实现,编译器在编译时使用泛型信息检测类型安全,编译后会擦除泛型信息,使泛型代码兼容遗留的原始类型代码。
类型消除的具体表现
泛型集合的类型消除
泛型代码
ArrayList<String> list = new ArrayList<>();
list.add(“peppa”);
String name = list.get(0);
编译后转换的原始类型代码
ArrayList list = new ArrayList();
list.add(“peppa”);
String name = (String)list.get(0);
泛型方法的类型消除
泛型代码
public static <E> void print(E[] list){
for(int i = 0; i < list.length; i++)
System.out.print(list[i] + “ “);
System.out.println();
}
编译后转换的原始类型代码
public static void print(Object[] list){
for(int i = 0; i < list.length; i++)
System.out.print(list[i] + “ “);
System.out.println();
}
受限泛型的类型消除
受限泛型方法
public static <E extends Geometric> boolean equalArea(E o1,E o2){
return o1.getArea() == o2.getArea();
}
编译后转换的方法
public static boolean equalArea(Geometric o1, Geometric o2){
return o1.getArea() == o2.getArea();
}
泛型的核心特性
- 泛型类的所有实例在运行时共享同一个类,无论实际类型参数是什么。
ArrayList<String> list1 = new ArrayList<>(); ArrayList<Integer> list2 = new ArrayList<>(); System.out.println(list1 instanceof ArrayList); // true System.out.println(list2 instanceof ArrayList); // true // System.out.println(list1 instanceof ArrayList<String>); // 编译错误,运行时无泛型类型信息
泛型的使用限制
限制1:不能使用new T()
运行时泛型类型T已被擦除,无法实例化泛型类型的对象。
限制2:不能使用new T[]
可通过创建Object数组并强制转换为T[]规避,但会产生免检编译警告:
// E为泛型类型
E[] elements = (E[])new Object[capacity]; // 允许,有编译警告
// GenericStack<String>[] stack = new GenericStack<String>[10]; // 编译错误
GenericStack<String>[] stack = (GenericStack<String>[])new GenericStack[10]; // 允许,有编译警告
限制3:静态上下文中不能使用类的泛型参数
泛型类的静态成员(静态方法、静态变量、静态初始化块)被所有实例共享,而泛型参数是实例级别的,因此静态上下文中无法引用类的泛型参数。
限制4:异常类不能是泛型的
泛型类型在运行时被擦除,无法用于异常类的定义(异常处理需要运行时的类型信息)。

浙公网安备 33010602011771号