泛型
泛型可以使我们在编译时而不是在运行时检测出错误
泛型是JDK1.5推出的新特性,java允许定义泛型类、泛型接口和泛型方法。Java API中的一些类和接口使用泛型进行了修改。
package java.lang; public interface Comparable{ public int compareTo(Object o); } |
package java.lang; public interface Comparable<T>{ public int compareTo(T o); } |
Jdk1.5之前 jdk1.5
这里的<T>表示形式泛型类型(formal generic type),随后可以用一个实际具体类型(actual concrete type)来替换它。替换泛型类型称为泛型实例化(generic instantiation)。按照约定俗成的惯例,像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代码块中可以在编译期而不是在运行期得到这个错误,因此泛型使程序更加可靠。
再比如:
ArrayList<String> list = new ArrayList<>();
现在,只能向该线性表中添加字符串,例如:list.add(“suzy”),如果试图向其中添加非字符串,就会产生编译错误。
泛型类型必须是引用类型。不能使用int,long或char等基本类型来替换泛型类型。
其次,使用泛型从线性表等中拿取数据时无须类型转换,因为编译器已经知道了这个元素类型。例如:
ArrayList<Integer> list = new ArrayList<>(); list.add(5);//自动装箱 list.add(10);//自动装箱 Integer num = list.get(0);//无须类型转换 int num1 = list.get(1);//无须类型转换,自动拆箱。
定义泛型类和接口
可以为类或者接口定义泛型,使用该类来创建对象(或者声明引用变量)时,须指定具体的实际类型来替换它
import java.util.ArrayList; /** * 定义泛型类:自定义栈 * * @param <T>形式类型参数,使用该类来创建对象(或者声明引用变量)时,须指定具体的实际类型来替换它 */ public class GenericStack<T>{ //一个数组列表,用于存储元素 private ArrayList<T> stack = new ArrayList<>(); /** * 添加一个新的元素到栈顶 * @param ele 要添加的类型元素 */ public void push(T ele) { stack.add(ele); } /** * 返回栈中元素个数 * @return 元素数目 */ public int getSize() { return stack.size(); } /** * 返回并移除栈顶元素(弹栈) * @return 栈顶元素 */ public T pop() { T ele = stack.get(stack.size() - 1); stack.remove(ele); return ele; } /** * 返回栈顶元素 * @return 栈顶元素 */ public T peek() { T ele = stack.get(stack.size() - 1); return ele; } /** * 是否为空栈 * @return 如果栈为空,返回true */ public boolean isEmpty() { return stack.isEmpty(); } }
当使用类来创建对象,或者使用类或接口来声明引用变量时,必须指定具体的类型。
GenericStack<String> stack = new GenericStack<>(); stack.push("peppa"); stack.push("suzy"); stack.push("pedro"); GenericStack<Integer> stack1 = new GenericStack<>(); stack1.push(1020); stack1.push(9527); stack1.push(886);
可以不使用泛型,而将元素类型设置为Object,也可以容纳任何对象类型。但是,使用泛型能够提高软件的可靠性和可读性,因为某些错误能在编译时而不是运行时被检测到。
泛型方法
使用泛型类型来定义泛型方法示例如下:
public class GenericMethodDemo { public static void main(String[] args ) { Integer[] integers = {1, 2, 3, 4, 5}; String[] strings = {"London", "Paris", "New York", "Austin"}; GenericMethodDemo.<Integer>print(integers);//或者print(integers); GenericMethodDemo.<String>print(strings);//或者print(strings);实际类型没有明确指定。编译器自动发现实际类型 } public static <E> void print(E[] list) { for (int i = 0; i < list.length; i++) System.out.print(list[i] + " "); System.out.println(); } }
可以将泛型指定为另外一种类型的子类型。这样的泛型类型称为受限的(bounded)。
public class BoundedTypeDemo { public static void main(String[] args ) { Rectangle rectangle = new Rectangle(2, 2); Circle circle = new Circle(2); System.out.println("Same area? " + equalArea(rectangle, circle)); } public static <E extends GeometricObject> boolean equalArea(E object1, E object2) { return object1.getArea() == object2.getArea(); } }
示例学习:对一个对象数组进行排序
对一个Comparable对象数组进行排序。这些对象是Comparable接口的实例,它们使用compareTo方法进行比较。
import java.util.Arrays; /** * 对一个对象数组进行排序 * */ public class GenericSort { public static void main(String[] args) { Integer[] intArray = {Integer.valueOf(2),Integer.valueOf(4),Integer.valueOf(6),Integer.valueOf(3),Integer.valueOf(5)}; Double[] doubleArray = {Double.valueOf(3.14),Double.valueOf(2.14),Double.valueOf(0.14)}; Character[] charArray = {Character.valueOf('a'),Character.valueOf('c'),Character.valueOf('b'),Character.valueOf('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(strArray)); } 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(); |
等价于
GenericStack<Object> stack = new GenericStack<>(); |
像这样不带类型参数的GenericStack和ArrayList泛型类称为原始类型(raw type),使用原始类型可以向后兼容java的早期版本。
通配泛型
可以使用非受限通配、受限通配或者下限通配来对一个泛型类型指定范围。
为什么要使用通配泛型?首先观察下面这个例子:
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)); } /** Find the maximum in a stack of numbers */ 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>的子类型。为了避免这个问题,可以使用通配泛型类型。通配泛型类型有三种形式----?、? extends T或者? super T,其中T是泛型类型。上面飘红的代码会出现编译错误,因为intStack不是GenericStack<Number>的实例。因此,不能调用max(intStack).
- ?称为非受限通配(unbounded wildcard),它和? extends Object是一样的。
- ? extends T称为受限通配(bounded wildcard),表示T或者T的一个子类型。
- ? super T称为下限通配(lower-bounded wildcard),表示T或者T的一个父类型。
使用下面代码替换掉上面程序瓢蓝代码就可以修复上面的错误。
public static double max(GenericStack<? extends Number> stack)
<? extends Number>表示Number或者Number子类型的通配类型。
public class AnyWildCardDemo { public static void main(String[] args ) { GenericStack<Integer> intStack = new GenericStack<>(); intStack.push(1); // 1自动装箱为 Intege.valueOf(1) intStack.push(2); intStack.push(-2); print(intStack); } /** 打印栈中元素并清空栈 */ public static void print(GenericStack<?> stack) { while (!stack.isEmpty()) { System.out.print(stack.pop() + " "); } } }
上面程序清单在print方法中使用?通配符,打印栈中的对象以及清空栈。<?>是一个通配符,表示任何一种对象类型。它等价于<? extends Object>.如果用GenericStack<Object>替换GenericStack<?>,这样调用print方法就会报错,因为intStack不是GenericStack<Object>的实例。注意:尽管Integer是Object的一个子类型,但是GenericStack<Integer>并不是GenericStack<Object>的子类型。
public class SuperWildCardDemo {
public static void main(String[] args) { GenericStack<String> stack1 = new GenericStack<String>(); GenericStack<Object> stack2 = new GenericStack<Object>(); stack2.push("Java"); stack2.push(2); stack1.push("Sun"); add(stack1, stack2); AnyWildCardDemo.print(stack2); } public static <T> void add(GenericStack<T> stack1,GenericStack<? super T> stack2) { while (!stack1.isEmpty()) stack2.push(stack1.pop()); } }
泛型类型和通配类型之间的关系图
消除泛型和对泛型的限制
编译器可以使用泛型信息,但这些信息在运行时是不可用的。这被称为类型消除。
泛型是使用一种叫做类型消除(擦除)的方法来实现的。编译器使用泛型类型信息来编译代码,但是随后会消除它。因此,泛型信息在运行时是不可用的。这种方法可以使泛型代码向后兼容使用原始类型的遗留代码。
泛型存在于编译时。一旦编译器确认泛型类型是安全使用的,就会将它转换为原始类型。例如:编译器检查左边的代码里泛型是否被正确使用,然后将它翻译成右边的代码
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); |
当编译泛型类、接口和方法时,编译器用Object类型代替泛型类型.如下翻译:
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<>(); |
尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但是,在运行时只有一个ArrayList类会被加载到JVM中。list1和list2都是ArrayList的实例。因此:
Sout:list1 instanceof ArrayList //true Sout:list2 instanceof ArrayList //true |
然而表达式list1 instanceof ArrayList<String>是错误的。由于ArrayList<String>并没有在JVM中存储为单独一个类,所以,在运行时使用它是毫无意义的。
由于泛型类型在运行时被擦除,因此,对于如何使用泛型类型是有一些限制的:
限制1:不能使用new T()
运行时泛型T是不可用的。
限制2:不能使用new T[]
可以通过创建一个Object的数组,然后将它的类型转换为T[]来规避这个限制,
E[] elements = (E[])new Object[capacity];
类型转换到E[]会导致一个免检的编译警告,因为编译器无法确保在运行时类型转换是否能成功,这种类型的编译警告是使用java泛型的不足之处,也是无法避免的。
使用泛型类型创建泛型数组也是不允许的。
GenericStack<String>[] stack = new GenericStack<String>[10];//错误的
可以修改为下面代码来规避,但是会有编译警告
GenericStack<String>[] stack = (GenericStack<String>[])new GenericStack [10];
限制3:在静态上下文中不允许类的参数是泛型类型
由于泛型类的所有实例都有相同的运行时类,所以泛型类的静态变量和方法是被它的所有实例所共享的。因此,在静态方法、数据域或者初始化的语句中,为类引用泛型类型参数是非法的。
限制4:异常类不能是泛型的
示例学习:泛型矩阵类
对于所有矩阵,除了元素类型不同以外,它们的加法和乘法操作都是类似的。因此,可以设计一个父类,不管它们的元素类型是什么,该父类描述所有类型的矩阵共享的通用操作,还可以创建若干个适用于指定矩阵类型的子类。
import com.iweb.generic.exception.MatrixException; /** * 使用泛型来设计用于矩阵运算的类 * @param <T> */ public abstract class GenericMatrix<T extends Number> { protected abstract T add(T num1, T num2); protected abstract T multiply(T num1, T num2); protected abstract T zero(); /** * 矩阵加法 */ public T[][] addMatrix(T[][] matrix1,T[][] matrix2){ if(matrix1.length != matrix2.length || matrix1[0].length != matrix2[0].length) throw new MatrixException("两个矩阵的行列不相等,不能完成加法操作"); @SuppressWarnings("unchecked") T[][] results = (T[][])new Number[matrix1.length][matrix1[0].length]; for(int i = 0; i < results.length;i++) { for(int j = 0; j < results[i].length;j++) { results[i][j] = add(matrix1[i][j], matrix2[i][j]); } } return results; } /** * 将泛型类型T[][]的两个矩阵进行相乘 */ public T[][] multiplyMatrix(T[][] matrix1,T[][] matrix2){ if(matrix1[0].length != matrix2.length) { throw new MatrixException("两个矩阵不符合乘法运算规则"); } @SuppressWarnings("unchecked") T[][] results = (T[][])new Number[matrix1.length][matrix2[0].length]; for(int i = 0; i < results.length; i++) { for(int j = 0; j < results[0].length;j++) { results[i][j] = zero(); for(int k = 0; k < matrix1[0].length;k++) { results[i][j] = add(results[i][j],multiply( matrix1[i][k], matrix2[k][j])); } } } return results; } //显示矩阵、操作以及它们的结果 public static void printResult(Number[][] matrix1,Number[][] matrix2,Number[][] matrix3,char operator) { for(int i = 0; i < matrix1.length; i++) { for(int j = 0; j < matrix1[0].length; j++) System.out.print(" " + matrix1[i][j]); if(i == matrix1.length / 2) System.out.print(" " + operator + " "); else System.out.print(" "); for(int j = 0;j < matrix2[0].length; j++) { System.out.print(" " + matrix2[i][j]); } if(i == matrix1.length / 2) System.out.print(" = "); else System.out.print(" "); for(int j = 0;j < matrix3[0].length; j++) System.out.print(matrix3[i][j] + " "); System.out.println(); } System.out.println(); } }
addMatrix和multiplyMatrix方法在进行操作之前检测矩阵的边界。如果两个矩阵的边界不匹配,那么程序就会抛出一个异常(自定义异常)
public class MatrixException extends RuntimeException{ private static final long serialVersionUID = 4955819099245243429L; public MatrixException(String message) { super(message); } }
两个具体子类之一---整型矩阵
public class IntegerMatrix extends GenericMatrix<Integer>{ @Override protected Integer add(Integer num1, Integer num2) { return num1 + num2; } @Override protected Integer multiply(Integer num1, Integer num2) { return num1 * num2; } @Override protected Integer zero() { return 0; } }
整型矩阵测试类
public class IntegerMatrixTest { public static void main(String[] args) { Integer[][] matrix1 = {{1,2,3},{4,5,6},{1,1,1}}; Integer[][] matrix2 = {{1,1,1},{2,2,2},{0,0,0}}; IntegerMatrix integerMatrix = new IntegerMatrix(); IntegerMatrix.printResult(matrix1, matrix2, integerMatrix.addMatrix(matrix1, matrix2), '+'); IntegerMatrix.printResult(matrix1, matrix2, integerMatrix.multiplyMatrix(matrix1, matrix2), '*'); } }
两个具体子类之一---Rational矩阵(具体Rational类详见面向对象程序设计章节)
public class RationalMatrix extends GenericMatrix<Rational>{ @Override protected Rational add(Rational num1, Rational num2) { return num1.add(num2); } @Override protected Rational multiply(Rational num1, Rational num2) { return num1.multiply(num2); } @Override protected Rational zero() { return new Rational(0, 1); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南