泛型使用类型参数,指示元素类型。
泛型设计意味着编写的代码可以对多种不同类型的对象重用
1、泛型类
- 使用T代表类型变量,用尖括号括起来
public class Pair<T>
{
private T first;
private T second;
public Pair() { first = null; second = null; }
public Pair(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
- 这个T可以使用具体的类型变量来进行实例化,可以是Pair<String>也可以是Pair<Integer>,这个例子就是使用String进行实例化
class ArrayAlg
{
/**
* Gets the minimum and maximum of an array of strings.
* @param a an array of strings
* @return a pair with the min and max value, or null if a is null or empty
*/
public static Pair<String> minmax(String[] a)
{
if (a == null || a.length == 0) return null;
String min = a[0];
String max = a[0];
for (int i = 1; i < a.length; i++)
{
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<>(min, max);
}
}
2、泛型方法
class ArrayAlg{
public static <T> T getMiddle(T... a){
return a[a.length/2];
}
}
- 这里面有很多T,<T>是指这个方法方法是个泛型方法,方法名前的T是指返回是T,T... a是指传入参数也可以是泛型
https://www.cnblogs.com/iyangyuan/archive/2013/04/09/3011274.html
https://segmentfault.com/a/1190000014120746
public <T> void show(T t) {
System.out.println(t);
}
public static void main(String[] args) {
//创建对象
ObjectTool tool = new ObjectTool();
//调用方法,传入的参数是什么类型,返回值就是什么类型
tool.show("hello");
tool.show(12);
}
3、类型变量限定
泛型非常的灵活,有时需要对类型变量加以约束否则会出很多问题。
- 这个例子中没办法保证a这个数组都有compareTo这个方法,这个方法只有实现了Comparable接口的类才有。
class ArrayAlg{
public static <T> T min(T[] a) {
if (a == null || a.length == 0) return null;
T smallest = a[0];
for (int i = 1; i < a.length; i++)
if (smallest.compareTo(a[i]) > 0) smallest = a[i];
return smallest;
}
}
- 因此可以进行类型的限制
public static <T extends Comparable> T min(T[] a)
这样就可以限制类型是实现了Comparable接口的。这种情况下 - 如果有多个类型限制,使用&分隔,<T extends Comparable & Serializable>
class ArrayAlg
{
/**
Gets the minimum and maximum of an array of objects of type T.
@param a an array of objects of type T
@return a pair with the min and max value, or null if a is
null or empty
*/
public static <T extends Comparable> Pair<T> minmax(T[] a)
{
if (a == null || a.length == 0) return null;
T min = a[0];
T max = a[0];
for (int i = 1; i < a.length; i++)
{
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<>(min, max);
}
}
4、类型擦除
- 无论何时定义一个泛型类型,都会自动提供一个相应的原始类型。这个原始类型名字是去掉类型参数后的泛型类型名。
- 类型变量会被擦除,并替换为限定类型(就是extends的那个),如果没有限定,就是Object,Pair<T>就变成这样,这个就是原始类型
public class Pair
{
private Object first;
private Object second;
public Pair() { first = null; second = null; }
public Pair(Object first, Object second) { this.first = first; this.second = second; }
public Object getFirst() { return first; }
public Object getSecond() { return second; }
public void setFirst(Object newValue) { first = newValue; }
public void setSecond(Object newValue) { second = newValue; }
}
- <T extends Comparable & Serializable>,这种,会把T替换成Comparable,限定里面谁写前面替换成谁
- 擦除后,做返回的时候,就会进行一个强制转换
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst(); //这里会有从Object到Employee的转换
5、转换泛型方法、桥方法
class DateInterval extends Pair<LocalDate> {
public void setSecond(LocalDate second) {
if (second.compareTo(getFirst()) >= 0) super.setSecond(second);
}
}
- 类型擦除之后会变成下面这样,但是这个类还会有个方法,
public void setSecond(Object second) {...}
- 因为如果出现多态
Pair<LocalDate> pair = new DateInterval()
,调用pair.setSecond()
,实质上不会调用setSecond(LocalDate second)
,这个其实不是多态里重写出来的方法,是个完全新的方法。那重写的方法其实就是public void setSecond(Object second) {setSecond((LocalDate) second);}
,这个是自动生成的,被叫做桥方法。
可以看出,这个桥方法实际上就是对超类中sayHello(Obejct)的重写。这样做的原因是,当程序员在子类中写下以下这段代码的时候,本意是对超类中的同名方法进行重写,但因为超类发生了类型擦除,所以实际上并没有重写成功,因此加入了桥方法的机制来避免类型擦除与多态发生冲突。
class DateInterval extends Pair{
public void setSecond(LocalDate second) {...}
}
- 那为什么有两个返回类型不一样,但是参数一样的方法存在于同一个类,是因为
JVM是用返回值+方法名+参数的方式来计算函数签名的
6、泛型的局限与限制
- 不能使用基本类型实例化类型参数
- 运行时类型查询只适用于原始类型,Pair<String> Pair<Employee> 用getClass()调,结果是一样的
- 不能创建参数化类型的数组 new Pair<String>[10],擦除之后,Object都能存,会出错
- public static <T> void addAll(T... ts) 这样的参数,实际上是创建了一个泛型数组,会得到警告
- 不能在构造器中使用类型变量
public Pair() {first = new T(); second = new T();}
,可以写成这样
public static <T> Pair<T> makePair(Supplier<T> constr){
return new Pair<>(constr.get(), constr.get());
}
- 也不能够构造泛型数组,比如下面这个,如果一个String类型的minmax,最后从Comparable转String会报错
public static <T extends Comparable> T[] minmax(T... a) {
var result = new Comparable[2];
return (T[]) result
}
- 泛型类的静态上下文中类型变量无效
- 不能抛出或捕获泛型类的实例
7、泛型类型的继承规则


-
var managerBuddies = new Pair<Manager>(ceo, cfo); Pair<Employee> employeeBuddies = managerBuddies
是不行的 - 但是总是可以将参数化类型转换为一个原始类型
var managerBuddies = new Pair<Manager>(ceo, cfo); Pair rawBuddies = managerBuddies
是可以的
8、通配符
子类限定
- Pair<? extends Employee>,这里用的?号
-
? extends Apple相当于规定了读取的下界,只能够用Apple或其父类来进行读取;同时规定了引用对象的上界,只能引用Apple及其子类,这样父类对象一定能引用子类对象,但是不能做set,因为没有下限编译器不知道具体传什么参数
子类限定
public static void printBuddies(Pair<? extends Employee> p ) {
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.print(first.getName() + second.getName());
}
// 之前提到过Pair<Employee> employeeBuddies = managerBuddies是不行的
// 现在Pair<? extends Employee> employeeBuddies = managerBuddies是可以的,但是是不能set的
? extends Employee getFirst()
void setFirst(? extends Employee) // 这里的?不能匹配,不会传递任何特定的类型
超类限定
- 通配符限定和类型变量限定很相似,但是可以进行超类限定
- ? super Apple 相当于规定了写入的上界,只能够往里面写Apple或Apple子类;同时规定了对象的下界,只能是Apple的父类
- 因为都是引用的父类对象,那把Apple及其子类加进去引用肯定没问题,但是没法获取(get),因为没有一个上界可以覆盖所有,除非用Object
import java.util.ArrayList;
import java.util.List;
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class fxtest {
public static void main(String[] args) {
List<? super Apple> superList1 = new ArrayList<Jonathan>(); // error
List<? super Apple> superList2 = new ArrayList<Apple>();
List<? super Apple> superList3 = new ArrayList<Fruit>();
List<? super Apple> superList4 = new ArrayList<Object>();
superList2.add(new Jonathan());
superList2.add(new Apple());
superList2.add(new Fruit()); //error
superList3.add(new Fruit()); //error
superList4.add(new Fruit()); //error
List<? extends Apple> extendsList1 = new ArrayList<Jonathan>();
List<? extends Apple> extendsList2 = new ArrayList<Apple>();
List<? extends Apple> extendsList3 = new ArrayList<Fruit>(); //error
Jonathan a1 = extendsList1.get(0); //error
Apple a2 = extendsList1.get(0);
Fruit a3 = extendsList1.get(0);
}
}

- 泛型使得你可以使用任意一种类型进行类、方法实现,通配符可以使得引用对象是任意的
- T是类型变量修饰 ?是引用变量修饰 https://www.cnblogs.com/jpfss/p/9929045.html
https://blog.csdn.net/yzpbright/article/details/105181476
https://www.cnblogs.com/mzzcy/p/7231892.html
网友评论