目录
1.初识泛型的应用
—所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。
—定义泛型,可以在编译阶段就检测到类型不匹配的错误,而不是在运行时才发现。
—允许在定义类,接口,方法时使用类型形参(泛型),这个类型形参将在声明变量,创建对象,调用方法时动态的指定,进而让程序具备编译时检查能力,例如,定义了一个List<String>,就不能向其中添加整数。
2.创建自定义泛型类
自定义泛型类,需要在类名后增加“<标识符>”,如下所示
public class SampleClass <T>{...}
关于标识符命名:
<T> 与 <E> 的区别
标识符的字母本身并无强制要求,常见写法有两种:
<T> 是Type单词的首字母,说明传入的是类型
<E> 是Element元素的首字母,代表是集合中的元素
泛型类如何创建呢?
用代码来描述,首先我们创建一个类CollectionUtils,根据创建自定义泛型类方法在类名后增加
<标识符>
import java.util.ArrayList;
import java.util.List;
import java.util.Random; //生成随机数
//实现随机选择List中的元素
public class CollectionUtils <E>{ //类后增加泛型<E>
private List<E> data =new ArrayList(); //自定义泛型类中,与声明的泛型类保持一致
public void add(E element) //向当前List中新增一个元素
{
data.add(element);
}
在以上代码中
ArrayList ( ):
是Java中ArrayList类中的一个默认构造参数
public class CollectionUtils<E>:
定义了一个名为CollectionUtils的类,并引入了一个类型参数E,这使得该类具有通用性,可以处理不同类型的元素。
private List<E> data = new ArrayList();
创建了一个列表data,用于存储类型为 E 的元素。
public void add(E element):
定义了一个方法add,它接受一个类型为 E 的参数element,并将其添加到 data 列表中
这意味着,无论 E 具体是什么类型(比如整数,字符串,自定义类等),CollectionUtils类都能够·处理和存储这种类型的元素,并通过add方法进行添加操作
定义好自定义泛型类后,我们继续在类中写入方法,利用Java中的Random随机数类写一个randomSelect方法,生成随机数
public E randomSelect(){
int idx = new Random().nextInt(data.size());//生成一个0(包含)到data集合元素数量(不包含)之间的随机整数,并将其赋值给变量idx
E ret = data.get(idx);
return ret;
}
在以上代码中
public E randomSelect(){:
定义了名为randomSelect方法,类型与自定义泛型类一致
int idx = new Random().nextInt(data.size()); :
new Random() 创建了一个Random类的对象,用于生成随机数。
nextInt(data.size()): 调用Random对象的nextInt方法,作用是生成指定范围内的随机整数,并将data.size()作为参数传递进去。
data.size()返回的是列表data中元素的数量。
data.get(idx)
获取
data
集合中索引为idx
的元素,并将其赋值给变量ret
,这里的E
表示这个返回的元素的类型是在类定义时指定的泛型类型。
定义完 泛型类和编写好生成随机数方法后,就可以编写主函数添加数据
public static void main(String[] args) {
CollectionUtils<String> utils= new CollectionUtils<>();// 创建了一个用于存储字符串的 CollectionUtils 对象 utils
utils.add("张三");
utils.add("李四");
utils.add("王五");
utils.add("赵六");
String name=utils.randomSelect(); //调用随机数方法,将生成的随机数存储在字符串name里
System.out.println(name); //打印name
}
运行结果
多次运行 发现能产生不同的值,实现了自定义泛型类后生成随机数方法。
自定义泛型类是定义在类级别上的一个泛型,作用于整个类的实例化对象,一旦确定了具体的类型参数,该类的所以方法和成员变量都将使用这个类型,有时候,我们可能只需要将泛型定义在方法体中,不需要定义在整个类别,利用泛型方法,它仅作用于该方法内部,不会影响类的其他部分或其他方法。
3.利用较小范围的泛型方法定义
泛型方法
允许在类没有声明泛型的前提下让方法独立使用泛型进行开发
public <T> List <T> transferToList (T[] array){
}
我们首先在PtMethod类下定义一个泛型方法transferToList
public class PtMethod {
public <T>List<T> transferToList(T[] array) { //定义泛型方法体
List<T> list =new ArrayList<>(); //创建一个空的ArrayList,利用了泛型,这个列表只能存储类型为T的元素
for (T item:array){ //增强for循环遍历输入的数组,类型为T
list.add(item); //将数组中的元素逐个添加到ArrayList中
}
return list;
}
public <T>List<T> transferToList(T[] array) :
表明这是一个泛型方法,接受一个类型为T的数组作为参数,并返回一个包含相同类型元素的List<T>.
<T>:
表示这是一个泛型方法,类型参数T可以在调用方法时确定具体的类型
然后在主函数main方法中创建数组并调用方法
public static void main(String[] args) {
PtMethod ptMethod=new PtMethod(); //创建一个ptMethod类对象,用于调用transferToList方法
String[] array =new String[]{"A","B","C","D","E"};//创建字符串数组
List<String> list=ptMethod.transferToList(array);//调用peMethod对象的transferToList方法,将字符串数组转化为字符串列表。
System.out.println(list);
}
}
上面代码中哪里体现了泛型呢?
首先是我们定义的泛型方法
public <T>List<T> transferToList(T[] array)
<T>表示类型参数,可以在调用这个方法时指定具体类型,例如在main方法中调用这个方法时,传递一个字符串数组,此时T就被推断为String类型,这个方法会返回一个包含String类型元素列表
在方法内部的体现则是
List<T> list =new ArrayList<>();
它创建了一个泛型列表,存储类型为T的元素,在调用泛型方法时,根据传入的数组类型确定T的具体类型,从而保证列表只能存储特定类型元素。例如,当传入字符串数组时,这个列表就只能存储字符串
List<Shape> shapeList = new ArrayList<>;
obj.doSth(shapeList);
4.了解泛型通配符,什么是泛型通配符?
假设我们有一个方法需要处理各种不同类型的集合,例如打印集合中的元素,如果没有泛型通配符,则可以需要为每种类型的集合单独编写一个方法,而利用了泛型通配符则可以用一个方法处理多种类型的集合。因为通配符?表示未知类型
比如我们定义一个方法 <Shape>是泛型
public void doSth(List<Shape> shapeList)
调用时使用Shape类型是允许的
List<Shape>shapeList = new ArrayList<>;
obj.doSth(shapeList);
如果我们在调用过程中使用Shape的子类Circle就会报错
List<Circle>circleList = new ArrayList<>;
obj.doSth(circleList);
因为泛型只支持关于类型的精准匹配,如果是其子类的话,传入doSth方法里也是不支持的,为了解决这样的问题,我们需要增加泛型通配符来进行描述
泛型的匹配规则
为了增加泛型的匹配范围,泛型通配符 <?> 应运而生
<?> 通配符与匹配范围
<?> 代表所有类型均可传入
public void doSth(List <?> shapeList)
我们可以在使用泛型的时候增加一个问号(通配符),意味着在当前泛型里写问号的位置上出现任何类型数据都可以接受
通常搭配extends与super限定范围
extends关键字代表必须传入Shape或者子类才通过检查
public void doSth(List <? extends Shape > shapeList)
表示只要是Shape和它的子类都是可以在泛型中使用的,这也决定了使用泛型类型的上限最高是Shape,不能够再使用其他类
super关键字代表必须传入Rectangle或者其父类才能通过检查
public void doSth(List <? super Rectangle > shapeList)
表示我们在这里传入使用的类型最低是Rectangle自己或它的父类
通过extends和super关键字,我们就可以用来决定传入泛型类型的范围,下面通过代码形式来理解
我们在开发工具中新建一个包,命名为ShapePro,在包中分别新建Shape(父类),Circle类,ShapeUtiles测试类
Shape(父类)
public class Shape {
public void draw(){
}
}
Circle类(继承父类)
public class Circle extends Shape{
public void draw(){
System.out.println("屏幕上画了一个圆");
}
ShapeUtiles类
import java.util.ArrayList;
import java.util.List;
//通过泛型通配符如何限定使用范围
public class ShapeUtiles {
public void drawAll(List<? extends Shape> shapeList){ //定义泛型
for (Shape shape : shapeList){
shape.draw();
}
}
public static void main(String[] args) {
ShapeUtiles utiles =new ShapeUtiles(); //实例化
List<Circle> circleList =new ArrayList<>();//因为定义了泛型方法只要是继承父类Shape的子类都可以使用
utiles.drawAll(circleList);
}
}
public void drawAll(List<? extends Shape> shapeList) :
在类中定义了一个方法drawAll,并自定义了泛型方法,类型为Shape的子类型,利用定义泛型方法,保证了在遍历这个列表时,其中的元素都可以安全调用Shape类中定义的方法,而shapeList是一个参数名称
for (Shape shape : shapeList){ shape.draw();:
遍历shapeList列表中的每一个元素,并将当前元素赋值给shape变量,然后调用当前shape对象的draw方法
在以上代码中,我们可以看到通过定义了泛型通配符限制了泛型类型的范围,处理集合时,只能接受Shape类和它的子类,这样可以确保在处理集合时,只操作特定范围内的对象,避免类型不匹配的错误。
总结
- 泛型提供编译时检查错误
- 自定义泛型类使用<T>声明
- <?>是泛型通配符
- extends决定泛型的上限
- super决定泛型的下限