一、为啥要使用泛型?
以前没有泛型的时候,泛型的设计时通过继承来实现的。ArrayList只维护一个Object引用的数组,存在了两个问题:
- 获取一个值时,必须进行强制类型转换
- 可以向其中添加任何类型的值
而现在,泛型提供了更好的解决方法,使用 {类型参数}:
var files = new ArrayList<String>();
//或
ArrayList<String> files = new ArrayList<>();
二、Java泛型的应用场景
Java泛型分别可以应用在接口、类和方法中,下面将逐个说明:
2.1 泛型类
泛型类相当于普通类的工厂,我们定义一个可以保存一对变量类型为T的泛型类:
package JavaGeneric.GenClass;
/**
* T is the type that we will deal with
*
* @author Aran
* @param <T>
*/
public class Pair<T>{
public T first;
public T second;
public Pair() {
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
此时,就可以进行泛型类的调用了:
package JavaGeneric.GenClass;
/**
* @author Aran
*/
public class Main {
public static void main(String[] args){
Pair<String> pairStr = new Pair<>();
pairStr.setFirst("first");
pairStr.setSecond("second");
Pair<Integer> pairInt = new Pair<>();
pairInt.setFirst(1);
pairInt.setSecond(2);
System.out.println(pairStr.getFirst());
System.out.println(pairInt.getSecond());
}
}
上例中,我们调用类两次,分别传入了{String} 和 {Integer} 两个具体类型,并对pairStr的第一个元素和pairInt的第二个元素进行输出,运行以上示例,得到输出:
first
2
2.2 泛型方法
我们先通过一个例子,直观的感受下:
package JavaGeneric.GenMethod;
import JavaGeneric.GenClass.Pair;
public class ArrayAlg {
//调用Pair<T>,计算一个字符串数组中的最大最小值
public static Pair<String> minAndMax(String[] a){
//如果数组为空或null,则无法比较
if(a == null || a.length == 0){
return null;
}else{
//初始化最大、最小值为字符串数组的第一个元素
String min = a[0];
String max = a[0];
for(String s : a){
if(min.compareTo(s) > 0){
//min比s大,交换
min = s;
}
if(max.compareTo(s) < 0){
//max比s 小,交换
max = s;
}
}
return new Pair<>(min,max);
}
}
//泛型方法
public static <T> T getMiddle(T...a){
return a[a.length / 2 ];
}
}
ArrayAlg 类中,getMiddle是一个 泛型方法,泛型方法的基本介绍如下:
- public 与 返回值之间的 非常重要,可以理解为,这样做之后,它才是个泛型方法
- 泛型类中使用了泛型的成员方法并不是泛型方法
- 表示该方法将使用泛型类型T,此时可以在方法中使用泛型类型T
- 与泛型的定义一样,此处T可以随便写为任意标识,常见的T、E、K、V等形式的参数常用于表示泛型。
而对于 minAndMax ,它只是一个返回了一个泛型类的方法,并不是泛型方法。
我们写一个Main进行调用:
package JavaGeneric.GenMethod;
import JavaGeneric.GenClass.Pair;
/**
* @author Aran
*/
public class Main {
public static void main(String[] args){
//定义一个字符串数组
String[] words = {"Rita","Aran","Lucy","Marray","John"};
Pair<String> minAndMax = ArrayAlg.minAndMax(words);
//输出最大最小值
System.out.println("min = " + minAndMax.getFirst());
System.out.println("max = " + minAndMax.getSecond());
//调用非静态泛型方法
ArrayAlg aa = new ArrayAlg();
aa.printTheFirst(3);
aa.printTheFirst("Aran");
//调用静态泛型方法
String middle = ArrayAlg.<String>getMiddle("John","Wendy","Amy","Bob");
System.out.println("middle = " + middle);
}
}
注意:
//调用静态泛型方法
String middle = ArrayAlg.<String>getMiddle("John","Wendy","Amy","Bob");
在这种情况下(实际上也是大多数情况下),方法调用中可以省略类型参数,因为编译器有足够的信息推断出你想要的方法。它将参数的类型与泛型类型T进行匹配,推断出T一定是String,即可以将调用方式改为:
//调用静态泛型方法
String middle = ArrayAlg.getMiddle("John","Wendy","Amy","Bob");
2.3 泛型接口
对于一个如下的泛型接口:
package JavaGeneric.GenInterface;
public interface Generator<T> {
T next();
}
对于实现该泛型接口,有两种情况:
未传入泛型实参时:
未传入实参,也就是说,虽然我实现了你,但其实我也是个泛型
package JavaGeneric.GenInterface;
public class MoneyGenerator<T> implements Generator<T>{
private T mynext;
@Override
public T next() {
// TODO Auto-generated method stub
return mynext;
}
}
虽然MoneyGenerator实现了Generator,但它本身也是带T的,如果你不带T,编译器会告诉你“Unknown class”,就是说“没见过你这样连T都不带的”。
传入泛型实参时:
package JavaGeneric.GenInterface;
import java.util.Random;
public class GoldGenerator implements Generator<String> {
private String[] myGold = new String[]{"one","two","three"};
@Override
public String next() {
// TODO Auto-generated method stub
Random rand = new Random();
return myGold[rand.nextInt(3)];
// return null;
}
}
调用上面的next方法:
package JavaGeneric.GenInterface;
public class Main {
public static void main(String[] args){
GoldGenerator gold = new GoldGenerator();
System.out.println(gold.next());
}
}
得到结果:
three
总结:
- 泛型的使用方式有三种:类、方法、接口
- 泛型类的局限是每次只能操作一种实参类型
- 泛型方法可以在非泛型类中
- 泛型接口实现时,如果是T,则实现也必须是T
关于这三种方式的使用,以后学习到更深层次的我会及时更新博客,下面篇文章,我们一起学习下,关于java泛型一些小而美的知识(类型变量的限定、泛型中的限制与局限性、通配符等)。