你在人群中看到的每一个耀眼的人,都是踩着刀尖过来的,你如履平地地舒适泰然,当然不配拥有任何光芒。
楔子
要想学会拿起,首先得学会放下。在我们介绍泛型之前,我们先忘记有泛型这个东西,先看个故事:假设你手中有一个杯子,这个杯子既可以用来存放固体(例如:糖),又可以用来存放液体(例如:水),有一天你往这个杯子里放了几颗糖,然后几年后的一天,你打篮球回来发现很渴,发现角落里有这个杯子,你想都不想拿起来喝,发现你吃了过期的糖,惊的你赶紧吐掉,然后说了句卧槽!!!
编这个故事是为了让大家乐呵一下,在乐呵之余,我们想一下,有没有什么办法避免这种悲剧:
上面两种方法提供了不同的解决思路,同时在Java中也对应着这两种解决思路
强转/instanceOf
Java中有各种各样的存放数据的容器:List、Set、Map,我们把这一个个容器看成是水杯,当我们从这里获取数据时,我们为了确保数据类型的准确,我们可以在获取数据的时候确认:
// 利用instanceOf关键字确认
public class ArrayListTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add("1");
for (Object item : list) {
// 确认是string类型,那么输出
if (item instanceof String) {
System.out.println(item + " item type is String" );
}
}
}
}
// 利用强转确认数据类型
public class ArrayListTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add("1");
for (Object item : list) {
// 确认是string类型,那么输出
String itemStr = (String) item;
System.out.println(itemStr + " item type is String" );
}
}
}
可是等到我们每次拿数据的时候再去判断,其实是已经后置了,为什么不在存的时候就去指定类型?这样我们在用的时候就可以放心用了,不做任何的判断,所以,为了优化这种强转的代码,Java提出了泛型机制。
泛型
咱们的“大姑娘”泛型终于出现了,而它的出现一方面为了让强制类型转换的代码具有更好的安全性和可读性,又解决了业务问题(通过在存储的时候就指定存储的数据类型),那么我们接下来来看看泛型是怎么解决这个问题的:
泛型的手段之类型参数
大神总是会把各种理论知识,命名成高大上的概念,让我们有一种肃然起敬的感觉(稍微的吐槽一下下!!!),那么话说回来什么是类型参数呢?
类型参数的土味话:我们知道在面向对象中类(杯子)是用来描述对象(具体一个水杯)的,所以在我们创建水杯这个对象的时候,是把类型(装水)指定了,但是既然是容器,那么就肯定不单单只能装水,所以我们要把一个容器的类型做成可变的,创建出来的时候确定装啥就是啥,就是把容器的类型做成参数化,达到可变的目的。
类型参数的好处
举例:以ArrayList为例,ArrayList有一个类型参数来指示存储元素的类型
public class ArrayListTest {
public static void main(String[] args) {
// 在Java SE 7 及以后的版本中,构造函数中可以省略泛型类型,因为可以从变量的类型推断得出
List<String> fileList = new ArrayList<>();
// 在存储数据的时候,编译器可以很好的利用指定的类型信息,进行类型检查,避免插入错误的对象
//出现编译错误比类在运行时出现类的强制类型转换异常要好的多
fileList.add("测试");
// 在调用get的时候,不需要进行强转,编译器就知道返回值类型是String,而不是Object
String fileName = fileList.get(0);
System.out.println(fileName);
}
}
泛型的使用
巴拉巴拉说了一堆,我们总得先知道泛型是怎么用的吧。嗯,对,那么我们就从类、方法、接口三个不同的东西来讲讲泛型到底是怎么使用的。
泛型的使用-类
顾名思义:泛型类具有就是一个或多个类型变量的类
例:定义一个变量的泛型类
// Pair类引入一个类型变量T,用尖括号括起来,放在类名后。
public class Pair<T> {
/**
* 类定义中的类型变量指定方法的返回类型以及域和局部变量的类型
**/
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
//虽然在方法中使用了泛型,这不是一个泛型方法哈。泛型方法在下面会介绍的
//这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
//所以在这个方法中才可以继续使用 T 这个泛型。
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
}
例:定义多个变量的泛型类
// PairNew类引入一个类型变量T和U,用尖括号括起来,放在类名后
public class PairNew<T, U> {
private T first;
private U second;
public PairNew() {
first = null;
second = null;
}
public T getFirst() {
return first;
}
public U getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(U second) {
this.second = second;
}
}
泛型类的使用
public class PairTest {
public static void main(String[] args) {
// 用具体的类型替换类型变量就可以实例化泛型类型
Pair<String> pair = new Pair<>();
pair.setFirst("1");
pair.setSecond("2");
System.out.println(pair.getFirst());
System.out.println(pair.getSecond());
// 可以创建带有构造器的普通类
Pair<String> pair1 = new Pair<>("1", "2");
pair1.setFirst("1");
pair1.setSecond("2");
System.out.println(pair1.getFirst());
System.out.println(pair1.getSecond());
}
}
来自月球的小贴士:定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
Pair pair = new Pair();
pair.setFirst("1");
pair.setSecond(123);
System.out.println(pair1.getFirst());
System.out.println(pair1.getSecond());
结果:
1(String类型)
123
强烈打call!!!!!!
- 1.泛型的类型参数只能是类类型,不能是简单类型。
- 2.不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错
if(pair instanceof Pair<String>){
}
泛型的使用-方法
泛型除了可以运用在类上,还可以运用在方法上
泛型类是在实例化类的时候指明泛型的具体类型;泛型方法是在调用方法的时候指明泛型的具体类型 。
public class PairMethod {
public static void main(String[] args) {
String[] words = {"Mary", "had", "little", "lamb"};
// 当调用一个泛型方法时,在方法名的尖括号中放入具体的类型
String middle1 = PairMethod.<String>getMiddle(words);
// 但是由于编译器的强大,它已经有足够的信息去推断出所调用的方法的类型,
// 所以一般不用在尖括号指定类型(用String[] 与泛型T[]进行匹配,推断出T一定是String)
String middle = PairMethod.getMiddle(words);
System.out.println(middle1);
System.out.println(middle);
}
/**
* 泛型方法:
* 1、泛型方法既可以定义在普通类中,也可以定义在泛型类中
* 2、类型变量放在修饰符(这里是public static)的后面,返回类型的前面
**/
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
}
强烈打call!!!!!!
- 1.public static 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法
- 2.只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
- 3.T表明该方法将使用泛型类型<T>,此时才可以在方法中使用泛型类型T。
- 4.和泛型类的定义一样,此处<T>可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
上面是在普通类中的泛型方法,我们再来看泛型类中的泛型方法:
public class Pair<T> {
/**
* 第一个
**/
private T first;
/**
* 第二个
**/
private T second;
public <E> void show(E t){
System.out.println(t.toString());
}
public <T> void show_new(T t){
System.out.println(t.toString());
}
}
强烈打call!!!!!!
- 1.在泛型类中声明了一个泛型方法,使用泛型<E>,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
- 2.由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
- 3.在泛型类中声明了一个泛型方法,使用泛型<T>,注意这个T是一种全新的类型,可以与泛型类中声明的<T>不是同一种类型。
泛型的使用-接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:
//定义一个泛型接口
public interface Generator<T> {
public T next();
}
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class GeneratorImpl<T> implements Generator<T>{
* 如果不声明泛型,如:class GeneratorImpl implements Generator<T>,
* 编译器会报错:"Unknown class"
*/
class GeneratorImpl<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
/**
* 传入泛型实参时:
* 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
* 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
*/
public class GeneratorImpl implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
颅内小剧场
截止目前为止呢,我们主要讲了泛型为了解决什么业务场景以及泛型怎么用(类、方法、接口),但是回过头来来看,在杯子存放东西的类型之前我们是不是得保证这个杯子是能用放这些东西的(你让杯子装地球,这不是为难杯子嘛)。 所以我们要对杯子的存储类型做下限制的,保证能存放在杯子中,延伸到泛型中,就是我们要对泛型的类型做限制,下面就进入到泛型的类型限制
泛型的类型限制
public class PairMethod {
public static void main(String[] args) {
Integer[] words = {1, 2, 3, 4};
Integer smallest = PairMethod.min(words);
System.out.println(smallest);
}
public static <T> T min(T[] a) {
if (a == null || a.length == 0) {
return null;
}
T smallest = a[0];
for (int i = 0; i < a.length; i++) {
// 使用这行代码必须要保证T所属的类有compareTo方法
if (smallest.compareTo(a[i]) > 0) {
smallest = a[i];
}
}
return smallest;
}
}
在上述的例子中,我们在使用compareTo方法的时候必须要保证T所属的类有compareTo方法,而解决这个问题的方案就是将T限制为实现了Comparable接口的类。可以通过对类型变量T设置限定(bound)实现这一点:
// 类如果没有实现Comparable接口,就会报编译错误
public static <T extends Comparable> T min(T[] a) {......}
// 重新编写了一个泛型方法minmax。这个方法计算泛型数组的最大值和最小值,并返回Pair<T>
public class PairTest2 {
public static void main(String[] args) {
Pair<Integer> pair = PairTest2.minMax(new Integer[] {1,2,3,4});
System.out.println(pair.getFirst());
System.out.println(pair.getSecond());
}
/**
* 泛型方法
**/
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 = 0; 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);
}
}
类型限制为什么要用extends关键字
其实我看到extends这个关键字的时候还感觉到奇怪,为什么要用extends而不是implement,因为有时需要保证这个类一定实现了某个接口。
我在《java核心技术》这本书中找到了这句话:<T extends Comparable>,表示T应该是绑定类型的子类型(subtype)。T和绑定类型可以是类,也可以是接口。选择关键字extends的原因是更接近子类的概念,并且Java的设计者也不打算在语言中再添加一个新的关键字(例如:sub)
一个泛型变量可以有多个限定
// 限定类型用"&"分开,","用来分隔类型变量<T,k>
public static <T extends Comparable & Serializable> T min(T[] a) {......}
强烈打call!!!!!!
- 在Java的继承中,可以根据需要拥有多个接口超类型,但限定中至多一个类。如果用一个类做限定,它必须是限定列表中的第一个。
颅内小剧场
截止目前我们讲完了泛型在使用上的各种操作(秀就一个字),但是这个T啊E啊到底是啥东西,随便写啥都可以嘛,下面就来盘盘这个T和E
泛型的通配符
我们在定义泛型类,泛型方法,泛型接口的时候,常常要写 T,E,K,V 等等的符号,其实这些都是java中的通配符,相当于一个标志,那么这些通配符都是什么意思呢?
常用的 T,E,K,V,?
本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。
通配符的约定
- ?表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
无界通配符 ?
为什么要把这个通配符单独拎出来呢?因为它和一般的T 啊 E 啊的还是有区别的,为什么称之为无界通配符呢?是因为其他的通配符都代表一个具体的java类型,但是?意思是未知的类型,可以匹配任何类型,所以称之为无界通配符
无界通配符 ? 的使用场景
- 1.不会出现在泛型类的声明上,而多用于使用泛型类或泛型方法
public class GenericTest {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("test1");
age.add(1);
number.add(2);
getData(name);
getData(age);
getData(number);
}
// 在此处使用通配符,则可以传入各种类型的 List 泛型,
public static void getData(List<?> data) {
System.out.println("Test date :" + data.get(0));
}
}
Test data :test1
Test data :1
Test data :2
-
- 上界通配符(子类型通配符)
<? extends ClassType> 该通配符为 ClassType 的所有子类型。
表示任何泛型 ClassType 类型,它的类型参数是 ClassType 的子类,上界通配符可以使用返回值,但是不可以为方法提供参数。
// 赋值 Manager是Employee的子类
Pair<Manager> manager = new Pair<>();
Pair<? extends Employee> wildCardBuddies = manager;
// 成功
? extends Employee getFirst();
// 报错
void setFirst(? extends Employee);
编译器只知道需要某个 Employee 的子类型,但是不知道具体的类型,它拒绝传递任何特定的类型,
毕竟 ?不能用来匹配,不知道要转成什么
使用 getFirst 就不存在这个问题:将 getFirst 的返回值赋值给 Employee 的引用完全合法。
往外取可以转成Employee
所以有了上文中的 – 上界通配符可以使用返回值,但是不可以为方法提供参数。
使用上界通配符意味着我们可以进行读取,但是不能写入。
- 3.超类型限定符(下界通配符)
<? super ClassType> 该通配符为 ClassType 的所有超类型。
表示任何泛型 ClassType 类型,它的类型参数是 ClassType 的超类,与下界通配符恰好相反,可以为方法提供参数,但是不能使用返回值。
// 赋值 Employee是Manager的父类
Pair<Employee> manager = new Pair<>();
Pair<? extends Manager> wildCardBuddies = manager;
// 可以
void setFirst(? super Manager)
// 报错
? super Manager getFirst()
? super Manager 表示为 Manager 或 Manager 的超类型,可进行 set,
对应的 setter 方法的参数可以传进 Manager 或 Manager 的父类型,在写入的时候,可以转成Manager来使用,
在调用 getter 方法获取返回值时,不知道要匹配成什么,所以是不合法的,但是可以将返回值赋值给 Object 对象。
使用下界通配符意味着我们可以写入,不可以读取。
颅内小剧场
怎么用我们是明白了,但是我们得知道泛型的原理呀,把T啊E啊什么的放到编译器,鬼知道怎么给你编译,所以编译器肯定做了各种骚操作,下面我们就来看看,编译器对这些T啊E啊什么的怎么处理
泛型的类型擦除
没错,咱们的编译器就是这么公平,管你是T啊还是E啊还是什么妖魔鬼怪,统统擦除了,既然泛型的使用区分了类和方法(接口),那么擦除的时候也区分了这两种场景。
泛型类的类型擦除
强烈打call!!!!!! 虚拟机是没有泛型类型对象的(鬼知道你这T啊E啊是什么东西,我编译你个头,在我这一视同仁哈)-所有的对象都属于普通类。
从上面打call中可以得出结论,无论你是什么泛型,最后都会转成一个普通的类型,而这个就是所谓的类型擦除
// 无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(就是删除类型参数后的泛型类型名)
// 擦除的是类型变量,并替换为限定类型(无限定的变量用Object)
// T 是一个无限定的变量,所以用Object
public class Pair {
private Object first;
private Object second;
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
public void setSecond(Object second) {
this.second = second;
}
public void setFirst(Object first) {
this.first = first;
}
}
// T extends Number T是一个限定的变量,所以用Number
public class Generic<T extends Number>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
// 原始类型:
public class Generic implement Number{
private Number key;
public Generic(Number key) {
this.key = key;
}
public Number getKey(){
return key;
}
}
强烈打call!!!!!! 还记得类型限定中的打call嘛。如果用一个类做限定,它必须是限定列表中的第一个。所以这里引出一个切换限定(就是限定列表的顺序改巴改巴),这里提出一个建议,为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在限定列表列表的末尾。
截止目前擦是擦除了,也翻译成通用类型了,那么编译器是怎么知道具体的类型的呢?
Pair<Employee> buddies = ...;
Employee buddy = (Employee) buddies.getFirst();
擦除之后,getFirst()的返回类型是返回Object类型。编译器自动插入Employee的强制类型转换。也就是说,编译器把这个方法调用翻译为两条虚拟指令:
- 对原始方法Pair.getFirst的调用
- 将返回的Object类型强制转换为Employee类型。
强烈打call!!!!!! 也就是说,编译器帮我们做了强转这一步操作
还有哪些操作也会让编译器自动发生强转
- 类的变量是公有的,通过类.变量这种形式调用也会发生强转
泛型方法的类型擦除
类型擦除也会出现在泛型方法中,和类的类型擦除相同,示例:
public static <T extends Comparable> T min(T[] a)
擦除之后,只留下了限定类型Comparable:
public static Comparable min(Comparable[] a)
但是由于方法具有多态的特点和泛型方法的类型擦除造成了冲突:
public class DateInterval extends Pair<LocalDate>{
@Override
public void setSecond(LocalDate second) {
if (second.compareTo(getFirst()) > 0 ) {
super.setSecond(second);
}
}
}
// 擦除后
public class DateInterval extends Pair {
@Override
public void setSecond(LocalDate second) {
if (second.compareTo(getFirst()) > 0 ) {
super.setSecond(second);
}
}
// 从Pair继承的setSecond方法
public void setSecond(Object obj) {
......
}
}
强烈打call!!!!!! 但是由于多态的机制,在擦除后,会生成一个不同类型的参数-Object(父类类型擦除后变成Object),但是针对多态,我们应该调用最适合的方法。所以要解决这个问题,就需要编译器在DateInterval类中生成一个桥方法
Datelnterval interval = newDatelnterval(...);
Pair<LocalDate> pair = interval;
pair.setSecond(new Date()) ;
// 桥方法:变量pair已经声明为类型Pair<LocalDate>,并且这个类型只有一个简单的方法叫setSecond,即
// setSecond(Object)。虚拟机用pair引用的对象调用这个方法。这个对象是Datelnterval类型的,
// 因而将会调用Datelnterval.setSecond(Object)方法。这个方法是合成的桥方法。
// 它调用Datelnterval.setSecond(Date),这就调用了最合适的方法
public void setSecond(Object second) { setSecond((Date) second); }
假设在Datelnterval类中,有两个getSecond方法:
LocalDate getSecond() Object getSecond()
这样写是会报编译错误的(具有相同参数列表的两个方法是不违法的)。但是,在虚拟机中,用参数类型和返回类型确定一个方法。因此,编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能够正确的处理这一种情况。
强烈打call!!!!!! 桥方法不仅用于泛型类型。 在一个方法覆盖另一个方法时可以 指定一个更严格的返回类型。
// Objectxlone 和 Employee.clone 方法被说成具有协变的返回类型 (covariant return types。)
// 实际上, Employee 类有两个克隆方法:1、Employee clone(); 2、Object clone();
// 合成的桥方法调用了新定义的方法。
public class Employee implement Cloneable {
public Employee clone() throws CloneNotSupportedException{...}
}
颅内练习
- 1.虚拟机中没有泛型, 只有普通的类和方法。
- 2.所有的类型参数都用它们的限定类型替换。
- 3.桥方法被合成来保持多态。
- 4.为保持类型安全性,必要时插入强制类型转换。
颅内小剧场
在说了泛型怎么用,以及编译器和虚拟机怎么对待泛型之后,我觉得很有必要对泛型的使用做一个友情提醒,因为泛型在使用上有很大的限制(大部分是类型擦除带来的)
泛型使用的善意提醒
- 1.不能用基本类型实例化类型参数:不能用类型参数代替基本类型。因此,没有Pair<double>, 只有Pair<Double>。当然, 其原因是类型擦除。 擦除之后, Pair类含有 Object 类型的域, 而 Object 不能存储double值
- 2.运行时类型查询只适用于原始类型:虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。
// 报错
if (a instanceof Pair<String>)
// 报错
if (a instanceof Pair<T>)
// 报错
// Pair<String> p = (Pair<String>) a;
为提醒这一风险,试图查询一个对象是否属于某个泛型类型时,倘若使用instanceof会得到一个编译器错误
如果使用强制类型转换会得到一个警告。
同样的道理, getClass 方法总是返回原始类型。
Pair<String> stringPair = . .
Pai<Employee> employeePair = . .
// true 都将返回Pair.class
if (stringPair.getClass() == employeePair.getClass())
- 3.不能实例化参数化类型的数组, 例如:
// 报错
Pair<String>[] table = new Pair<String>[10];
这有什么问题呢? 擦除之后,table的类型是Pair[] 可以把它转换为Object[]
Object[] objarray = table;
数组会记住它的元素类型, 如果试图存储其他类型的元素, 就会抛出一个 ArrayStoreException 异常:
// Error component type is Pair
objarray[0] = "Hello";
不过对于泛型类型, 擦除会使这种机制无效。 以下赋值:
objarray[0] = new Pair<Employee>();
能够通过数组存储检査, 不过仍会导致一个类型错误。 出于这个原因, 不允许创建参数
化类型的数组。
需要说明的是, 只是不允许创建这些数组, 而声明类型为 Pair<String>[] 的变量仍是合法
的。不过不能用 new Pair<String>[10] 初始化这个变量。
如果需要收集参数化类型对象, 只有一种安全而有效的方法: 使用 ArrayList:ArrayList<Pair<String>>
- 4.不能实例化类型变量
不能使用像 new T(...,) newT[...] 或 T.class 这样的表达式中的类型变量。
例如,下面的 Pair<T> 构造器就是非法的:
public Pair() { first = new T(); second = new T(); }
- 5.注意擦除后的冲突
当泛型类型被擦除时, 无法创建引发冲突的条件。下面是一个示例。假定像下面这样将 equals 方法添加到 Pair 类中:
public class Pair<T> {
public boolean equals(T value) {
return first.equals(value) && second.equals(value);
}
}
考虑一个 Pair< String>。从概念上讲,它有两个equals方法 :
boolean equals(String) // defined in Pair<T>
// 擦除后生成
boolean equals(Object)
与 Object.equals 方法发生冲突。补救的办法是重新命名引发错误的方法。
泛型规范说明还提到另外一个原则:“ 要想支持擦除的转换, 就需要强行限制一个类或类
型变量不能同时成为两个接口类型的子类, 而这两个接口是同一接口的不同参数化。” 例如,下述代码是非法的:
class Employee implements Coinparab1e<Emp1oyee> { . . . }
// Error
class Manager extends Employee implements Comparable<Hanager>{ . . . }
Manager会实现Comparable<Employee> 和 Comparable< Manager> , 这是同一接口的不同参数化。
这一限制与类型擦除的关系并不十分明确。毕竟, 下列非泛型版本是合法的。
class Employee implements Comparable { . . . }
class Manager extends Employee implements Comparable { . . . }
其原因非常微妙, 有可能与合成的桥方法产生冲突。实现了 C0mpamble<X>的类可以获得一个桥方法:
public int compareTo(Object other) { return compareTo((X) other); }
对于不同类型的 X 不能有两个这样的方法。
总结-扬帆起航
篇幅这么长为难大家了,大家再坚持一下下,通过以下几个问题来回顾一下这篇到底BB了些啥:
- 1.泛型解决的业务问题是什么?
- 2.泛型怎么使用?
- 3.到底什么是通配符?
- 4.怎么限制泛型?
- 5.泛型是怎么被编译器识别的?
- 6.泛型的使用注意点
学了就要用,不然学他干啥呢?你说是吧,所以总结:盘它!!!!祝大家在泛型的路上扬帆起航!!!
点关注,不迷路
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。 我后面会每周都更新常用技术栈相关的文章,非常感谢人才们能看到这里,如果这个文章写得还不错,觉得「小沙弥」我有点东西的话 求点赞👍 求关注❤️ 求分享👥 对暖男我来说真的 非常有用!!!
白嫖不好,创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
迷途小沙弥 | 文 【原创】
如果本篇博客有任何错误,请批评指教,不胜感激 !