好,我们聊一聊泛型。泛型允许在不指定类、接口方、法的具体类型下,通过一个抽象的类型参数动态设定。
就是说我事先不知道你要传入什么类型的参数,我先拿一个对象去代替你的参数写在我的方法里。等到编译时系统会自动将这个对象转换成你给的参数去执行,保证了代码的安全性和可读性。
泛型中的这个对象我们通常用具体的大写字母来表示,可以任意写,但有常见约束 :
T (Type):表示任意类
E (Element):表示集合中的元素类型
K (Key):表示键值对中的键
V (Value):表示键值对中的值
N (Number):表示数值类型
我们用具体实例来做展示,我们先创建一个类名叫Gen的类,传入泛型参数。再创建一个Test文件,里面写我们的main方法。
想要传入泛型到类里,只需在类名后面加一个尖括号,里面填写想要表示的泛型参数即可。既class Gen<T>{} 这样我们就拿到了泛型的参数。当我们想创建和T类型的方法或属性时,就可以直接将类型用T替换就可以了。
T会在定义对象时也就是在编译时就确定了,所以当传入类型不符合时会报错。而且当一个类中需要多个泛型时,可以在尖括号里填写多个泛型,并用","隔开。
如何调用泛型类呢?只需要Gen<> 对象名 = new Gen<>(); 在尖括号里填写引用类型即可。作为初学者经常犯的错误就是会在尖括号里传基本数据类型,而泛型是不支持基本数据类型的,所以会报错。
有小伙伴可能早就发现了,我们使用ArraySet、ArrayList、HachMap时传入的参数都是基本数据类型的包装类。没错,这里就用到的是泛型,所以必须传入引用数据类型。
给大家出道题:在Gen类中创建一个private修饰的泛型数组和set方法,在Test文件中调用set方法分别创建一个String类型和一个Integer类型的数组并打印出结果。
在Gen中创建泛型ArrayList:
java
//创建泛型数组
private ArrayList<T> arr = new ArrayList<T>();
//简写形式(建议使用)
private ArrayList<T> arr2 = new ArrayList<>();
创建set方法:
java
//创建set方法给arr属性中添加T类型元素
public void setArr(T va) {
arr.add(va);
}
创建get方法和print方法获取和打印arr中元素:
java
//创建get方法
public ArrayList<T> getArr(){
ArrayList<T> arr2 = new ArrayList<T>();
for(T va:arr) {
arr2.add(va);
}
return arr2;
}
//创建打印方法
public void printAll(ArrayList<T> arr) {
for(T va:arr) {
System.out.println(va);
}
}
好,我们试试方法能不能用。先在main方法里创建一个Gen类对象,再在数组中添加几个元素:(这里需要注意,如果我们没有在尖括号里添加参数,那么会默认泛型为Object)
java
//创建对象并填入引用类型
Gen<String> gen1 = new Gen<>();
Gen<Integer> gen2 = new Gen<>();
//使用set方法填入内容
gen1.setArr("小明");
gen1.setArr("小芳");
gen1.setArr("小红");
gen1.setArr("小鹏");
gen1.setArr("小越");
gen2.setArr(1);
gen2.setArr(2);
gen2.setArr(3);
gen2.setArr(4);
gen2.setArr(5);
获取并打印:
java
//获取并打印元素
gen1.printAll(gen1.getArr());
gen2.printAll(gen2.getArr());
结果正如我们想的那样输出了两个存放不同类型的数组。
我们再说说泛型的通配符,泛型的通配符常用用于方法的参数声明以及返回声明,以增强方法对不同类型和参数的兼容性。其实就是让方法不必绑定一个具体的泛型,而是接受或返回一个泛型类型的任意成员,从而提供代码的灵活性和复用性。
我们说无法直接创建通配符类型的对象,必须传入一个"具体类型的对象",使其方法中用通配符声明的参数类型。这个"具体类型对象"可以是我们自己写的类,也可以是java提供的类。就比如说接下来我要讲的例子中就是用ArrayList作为具体对象传入方法。
再强调一下,通配符仅能用于"定义类型"的场景,而不能用于"创建具体类型"或"声明泛型占位符"。(<T>不能写成<?>)
三种通配符类型:
1.<?>:无界通配符,支持任意泛型类型
2.<? extends A>:上界通配符,规定了泛型的上界,支持A以及A的子类
3.<? super A>:下界通配符,规定了泛型的下界,支持A以及A的父类
我们举个例子:我们在Test类里创建一个main方法,同时另外创建三个具有相互继承关系的类。
java
public class Test {
public static void main(String[] args) {
}
}
class A{
}
class B extends A{
}
class C extends B{
}
我们在Test中写入三种方法,分别调用不同的通配符类型作为参数。
java
public static void printArrAll(ArrayList<?>arr) {
//相当于<? extends Object>
for(Object obj:arr) {
System.out.println(obj);
}
}
public static void printArrExtends(ArrayList<? extends B>arr) {
//上界通配符,上界是B
for(Object obj:arr) {
System.out.println(obj);
}
}
public static void printArrSuper(ArrayList<? super B>arr) {
//下界通配符,下界是B
for(Object obj:arr) {
System.out.println(obj);
}
}
分别使用Object、A、B、C、作为引用类型创建ArrayList对象。
java
//创建不同类型的ArrayList对象,验证泛型上下界通配符
ArrayList<Object>arr1 = new ArrayList<>();
ArrayList<A>arr2 = new ArrayList<>();
ArrayList<B>arr3 = new ArrayList<>();
ArrayList<C>arr4 = new ArrayList<>();
使用三种不同的方法分别调用我们创建的对象。我们知道泛型在编译时会被确定,所以可以根据报错情况判断是否能使用此引用类型。
java
//调用无界通配符:
printArrAll(arr1);
printArrAll(arr2);
printArrAll(arr3);
printArrAll(arr4);
//调用上界通配符
printArrExtends(arr1);
printArrExtends(arr2);
printArrExtends(arr3);
printArrExtends(arr4);
//调用下界通配符
printArrSuper(arr1);
printArrSuper(arr2);
printArrSuper(arr3);
printArrSuper(arr4);

正如我们所设想的一样,上界通配符无法调用父类引用类型。下界通配符无法调用子类引用类型,而无界通配符可以调用所有的引用类型包括最上级Object引用类型。