JavaSE| 泛型
泛型
泛型:对后续所有操作的类型做约束,对后续操作起作用,对之前的不起作用; 对类型进行约束;
父 ----> 子,从范围上,父范围小,子范围大;把范围小的给范围大的,
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了“类型形参”,这个类型形参将在声明变量、创建对象时确定,即传入实际的类型,称为“类型实参”。
我们把这个“参数化的类型”称为泛型(Generic)。我们可以为任何类和接口增加泛型声明,并不是只有集合类才可以使用泛型声明。
ArrayList<E>的<E>就是“类型形参”,ArrayList<String>的<String>就是“类型实参”,String类型就是用来确定E的类型用的。
为了区别,我们可以将int max(int a, int b)中a,b称为数据形参,将 int max = max(3,6);中3,6称为数据实参。
泛型形参的命名一般使用单个的大写字母,如果有多个类型形参,那么使用逗号分隔,例如:Map<K,V>。
常见字母(见名知意):
例如定义学生类的成绩score不止一个类型:
...
public static void main(String[] args) {
Student<Integer> stu = new Student(1, "kris", 99);
Student<String> stu1 = new Student(2, "smile", "优秀");
Student<Double> stu2 = new Student(5, "aa", 89.9);
}
}
class Student<T>{
private int id;
private String name;
private T score;
.....
}
可以在定义接口、类时指定类型形参,类型形参在整个接口或类体中可以当成类型使用,几乎所有可使用其他普通类型的地方都可以使用这种类型形参,例如:属性类型、方法的形参类型、方法返回值类型等。
但是泛型类或泛型接口上的泛型形参,不能用于声明静态变量,也不能用在静态方法中,那是因为静态成员的初始化是随着类的初始化而初始化的,此时泛型实参无法指定,那么泛型形参的类型就不确定。
当创建带泛型声明的类时,为该类定义构造器时,构造器名还是原来的类名,不需要增加泛型声明。例如:Student<T>类定义的构造器依然是Student(),而不是Student<T>,但调用构造器时却可以使用
Student<T>的形式,而且应该为T类型形参传入实际的类型实参。
泛型实参的要求
首先泛型实参必须是引用数据类型,不能是基本数据类型,可以指定为包装类型,因为集合中只能存储对象。
什么时候指定泛型实参
(1)在用泛型类、接口声明变量时
(2)在继承泛型类或实现泛型接口时,如果子类不延续使用该泛型,那么必须明确指定实际类型。此时子类不再是泛型类了
(3)在创建泛型类对象时
public class EmployeeManager {
private ArrayList<Employee> lis;
public static void test(ArrayList<String> lis){
ArrayList<String> list = new ArrayList<String>();
ArrayList<String> li = new ArrayList<>();//JDK1.7之后支持如下简化写法:
}
}
//继承泛型类
class MyArrayList extends ArrayList<String>{//此处ArrayList<String>就不能写成ArrayList<E>
//因为MyArrayList不再是泛型类了,因此E必须给出具体的类型
}
//实现泛型接口
class Employee implements Comparable<Employee>{
@Override
public int compareTo(Employee o) {
// TODO Auto-generated method stub
return 0;
}
}
继承泛型类、实现泛型接口时,想要继续保留父类、父接口的泛型,必须在父类、父接口和子类、子接口中都要保留泛型。
设定泛型形参的上限
如果泛型形参没有设定上限,那么泛型实参可以是任意引用数据类型。如果泛型形参设定了上限(例如:T extends 父类上限),那么只能指定为该父类本身或其各子类类型。
在一种更极端的情况下,程序需要为形参设定多个上限(至多有一个父类上限,可以有多个接口上限)表明该类型形参必须是其父类的子类(包括是父类本身也行),并且实现多个上限接口。
与类同时继承父类、实现接口类似的是:为类型形参指定多个上限时,所有的接口上限必须位于类上限之后。也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位。
class Student<T extends Number & java.io.Serializable> {
//...省略其他代码
}
....
Student<Integer> stu = new Student(1, "kris", 99);
//Student<String> stu1 = new Student(2, "smile", "优秀");//以下代码编译报错,因为String不是Number的子类
Student<Double> stu2 = new Student(5, "aa", 89.9);
ArrayList<Integer> list = new ArrayList<>();
class Student<T extends Number>{
private int id;
private String name;
private T score;
public Student(int id, String name, T score) {
super();
this.id = id;
this.name = name;
this.score = score;
....
}
定义泛型方法
在定义类、接口时可以使用类型形参,在该类的方法和属性定义、接口的方法定义中,这些类型形参可被当成普通类型来用。但是,在另外一些情况下,
(1)如果我们定义类、接口时没有使用类型形参,但是某个方法定义时,想要自己定义类型形参;
(2)另外我们之前说类和接口上的类型形参是不能用于静态方法中,那么当某个静态方法想要定义类型形参。那么,JDK1.5之后,还提供了泛型方法的支持。
* 二、泛型方法
* 1、什么时候使用泛型方法
* (1)因为刚才说,泛型类或泛型接口上的泛型形参是不能用于静态成员的,
* 那么当静态方法需要用到泛型时,只能用泛型方法。
*
* (2)如果泛型类或泛型接口上的泛型形参,但是对于某个方法来说,不适用,或这个类或接口本身不是泛型类和泛型接口,
* 这个方法想要单独声明泛型,那么也得用方法,这个方法可以是非静态的
*
* 2、如何声明
* 【修饰符】 <泛型形参列表> 返回值类型 方法名(方法的形参列表) {
* }
* 泛型方法中声明的泛型形参,只能用在当前方法中。
*
* 需要实现这样的一个方法,该方法负责将一个数组的所有元素添加到一个Collection集合中
*
* 3、泛型方法的泛型形参是什么时候指定的
* 调用时
*
* 4、可以给泛型方法的泛型形参指定上限
* 【修饰符】 <泛型形参 extends 父类上限> 返回值类型 方法名(方法的形参列表) {
* }
*
* 需求:接收一个集合(里面都是图形),打印所有图形的面积
ArrayList<String> list = new ArrayList<String>();
..
public <T extends Graphic> void print(ArrayList<T> list){
for (T t : graphics) {
System.out.println(t.getArea());
}
}
String[] array = {"Hello", "java"};
Collection<String> c = new ArrayList<String>();
//Object[] arr = array;//这样子是多态引用是可以的
//Collection<Object> coll = c;//但右边ArrayList<String>,左边是不接收的;报错
fromArrayToCollection(array, c);
//编译报错
public static void fromArrayToCollection(Object[]a, Collection<Object> c){
for(Object object : a){
c.add(object);
}
}
//可以这样子写;//该方法负责将一个数组的所有元素添加到一个Collection集合中
public static <T> void copy(T[] arr, Collection<T> coll){
for (int i = 0; i < arr.length; i++) {
coll.add(arr[i]);
}
}
泛型擦除
* 1、泛型的擦除:
* 当我们使用泛型类或泛型接口时,如果没有指定泛型的实参,那么它会出现泛型擦除的现象,
* 如果泛型形参有上限,就按照第一个上限处理,如果没有上限,就按照Object处理。
*
* 2、泛型类指定为不同泛型实参时,运行时是同一种类型
*
* 3、instanceof后面不支持泛型类;// 由于系统中并不会真正生成泛型类
*
* 4、泛型类不能创建数组; ArrayList<String>[] array = new ArrayList<String>[5]; //编译错误; this.arr = new T[length];
*
* 5、try..catch的catch中不能使用泛型

public class TestErase { public static void main(String[] args) { ArrayList list = new ArrayList<String>();//ArrayList<String>被转换为了ArrayList list.add("kris"); list.add("smile"); for (Object object : list) { System.out.println(object); } Object object = list.get(1); //泛型被擦除,按照默认上限Object处理 Student s = new Student("kk", 99); //Student<Number> Number score = s.getScore();//泛型被擦除,按照第一个上限Number处理 } } class Student<T extends Number>{ private String name; private T score; public Student(String name, T score) { super(); this.name = name; this.score = score; } public T getScore() { return score; } }
public static void main(String[] args) { ArrayList<String> list1 = new ArrayList<>(); ArrayList<Integer> list2 = new ArrayList<>(); System.out.println(list1.getClass());//class java.util.ArrayList System.out.println(list2.getClass());//class java.util.ArrayList System.out.println(list1.getClass() == list2.getClass());//true ArrayList<Object> list = new ArrayList<String>(); //错误的 /*ArrayList<String>不是ArrayList<Object>的子类, 因为他们的运行时类型都是ArrayList,因此不允许如下赋值操作。 这点和数组不同,因为数组是要生成新的Class对象的,String[]仍然是Object[]的子类,因此允许如下赋值操作。 Object[] arr = new String[5];*/ }
通配符
* 通配符:?
* 1、类型通配符只能用于方法中,不能用在泛型类或泛型接口的声明上。
* 2、形式:
* (1) <?> 代表的是任意类型
* (2)<? extends 上限Upper> 代表的是Upper的类型或它的子类
* (3)<? super 下限Lower> 代表的是Lower的类或它的父类
当我们声明一个方法时,某个形参的类型是一个泛型类或泛型接口类型,但是在声明方法时,又不确定该泛型实际类型,我们可以考虑使用类型通配符。
//使用类型通配符
public static void test(List<?> c){ //List c这种形式使用List接口时没有传入实际类型参数,这将引起泛型警告。
for (int i = 0; i < c.size(); i++) {//List<Object> c这种太局限了,调用时,只能传List<Object>,List<Object>不能接收List<String>等其他集合
System.out.println(c.get(i));
}
}
//声明一个泛型方法,需要声明泛型形参T。
public static <T> void test2(List<T> c){
for (int i = 0; i < c.size(); i++) {
System.out.println(c.get(i));
}
}
通配符的List仅表示它可以接受指定了任意泛型实参的List,并不能把元素加入其中,例如如下代码将会引起编译错误:
public static void test(List<?> c, String str){
c.add(str);
}
不知道上面程序中c集合里元素的类型,所以不能向其中添加对象,除了null对象,因为它是所有引用数据类型的实例。
test2方法带泛型的List,表示该集合的元素类型是T,因此允许T系列的对象加入其中,例如如下代码是可行的:
public static <T> void test(List<T> c, T t){
c.add(t);
}
如果不涉及添加元素到带泛型的集合中,那么两种方式都可以,如果涉及到添加元素到带泛型的集合中,使用类型通配符<?>的不支持。
当直接使用List<?>这种形式时,即表明这个List集合接收泛型实参指定为任意类型的List。
有时候我们只希望接收某些类型的List。
例如:一个图形的抽象父类Graphic,两个子类Circle和Rectangle。接下来我们想定义一个方法,可以打印不同图形的面积。
但是,List<Graphic>的形参只能接收List<Graphic>的实参,如果想要接收List<Circle>,List<Rectangle>的集合,可以使用List<?>。
但是这样有两个问题,一个是List<?>可以接收任意类型,不仅仅图形,第二个是需要强制类型转换。为了解决这个问题,Java允许设定通配符的上限:<? extends Type>,这个通配符表示它必须是Type本身,或是Type的子类。
public static void printArea(List<? extends Graphic> graphic){
for (Graphic g : graphic) {
System.out.println(g.getArea());
}
}
/*与前面的完全相同,因为我们不知道这个受限制的通配符的具体类型,所以我们不能把Graphic对象或其子类对象加入这个泛型集合中。
如果要需要将Graphic对象或其子类对象加入这个泛型集合,那么就只能用泛型方法了,*/
public static void printArea(List<? extends Graphic> graphics){
graphics.add(new Circle());//编译错误,因为不知道?的具体类型,也可能是Rectangle
}
返回的T是Object,也就是说,程序在复制集合元素的过程中,丢失了src集合元素的类型String。
对于上面的copy方法,可以这样理解两个集合参数之间的依赖关系:不管src集合元素的类型是什么,只要dest集合元素类型与src的元素类相同或是它的父类即可。为了表示这种约束关系,Java允许设定通配符的下限:<? super Type>,这个通配符表示它必须是Type本身或是Type的父类。
public static void main(String[] args) {
ArrayList<String> src = new ArrayList<String>();
src.add("kris"); //src -- >dest
ArrayList<Object> dest = new ArrayList<Object>();
String last = copy(dest, src);
System.out.println(last);
}
/* 实现将src集合里元素复制到dest集合中的功能,因为dest集合需要接受src的所有元素,
所以dest集合元素的类型应该是src集合元素的父类。
为了表示两个参数之间的类型依赖,考虑同时使用通配符、泛型形参类实现该方法。*/
/* public static <T> void copy (Collection<T> dest, Collection <? extends T> src){
for (T t : src) {
dest.add(t);
}
}*/
public static <T> T copy (Collection<? super T> dest, Collection <? extends T> src){
T last = null;
for (T t : src) {
dest.add(t);
}
return last;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人