什么是泛型
参数化类型
为什么使用泛型
- 适用于不同的数据类型执行相同的代码
- 能在编译阶段就发现不合适的数据类型,并且适用指定的类型不需要强制转换
怎么使用泛型
-
简单应用场景
- 泛型类
public class Person<T> { T t; }- 泛型接口
public interface Eat<F> { void eat(F f); }- 泛型方法
public class SpecialPerson { public <F> void eat(F f) { System.out.println("I`m eating" + f.toString()); } } -
限定类型变量
通常在上述场景的扩展中出现,以泛型类举栗。
首先我们新建三个类,分别是Food、Fruit、Apple,不难看出他们的继承关系
public class Food { }public class Fruit extends Food { }public class Apple extends Fruit { }如何我们想在创建Person类的时候对传入的泛型进行一定限制,就可以这样实现
public class Person<T extends Fruit> { T t; }当我们在创建Person对象的时候发现
Person<Apple> jack = new Person<>(); //编译通过 Person<Food> jully = new Person<>(); //编译不通过即限定了传入的泛型必须继承自Fruit类或者是Fruit类本身
-
通配符类型
通常用于变量和形参的定义中
-
泛型的上界
我们有这样一个类
public class SpecialPerson { List<Fruit> list; public SpecialPerson(List<Fruit> list) { this.list = list; } }我们想在实例化它的时候,让他既可以接收Fruit类型的列表又可以接收Apple类型的列表,该怎么做咧,很简单,我们稍微改造一下
public class SpecialPerson { List<? extends Fruit> list; public SpecialPerson(List<? extends Fruit> list) { this.list = list; } }当当,这样就实现了我们的需求
public static void main(String[] args) { List<Food> foods = new ArrayList<>(); List<Fruit> fruits = new ArrayList<>(); List<Apple> apples = new ArrayList<>(); SpecialPerson jack = new SpecialPerson(foods); //编译不通过 SpecialPerson pack = new SpecialPerson(fruits); //编译通过 SpecialPerson jave = new SpecialPerson(apples); //编译通过 }接下来有意思的来了,我们在SpecialPerson类中添加一些操作
public class SpecialPerson { List<? extends Fruit> list; public SpecialPerson(List<? extends Fruit> list) { this.list = list; Food food = list.get(0); //编译通过 Apple apple = list.get(0); //编译不通过 list.set(0, food); //编译不通过 list.set(0, apple); //编译不通过 } }我们可以发现,通过这种方式定义的列表,在get列表元素时,竟然可以自动转成Food类型而不能转成Apple类型,是不是黑人问号脸,哈哈哈,因为从字面意义上来看,我们应该可以很方便的get到Apple的类型而且应该get不到Food类型,应为Apple继承自Fruit,但是结果偏偏相反,点解咧?
我的理解是这样的,<? extends Fruit>中的extends是指在接收这个参数的时候,需要符合继承自Fruit的规则,接收完毕它的使命就结束了,但是在你拿这个参数来实际使用的就不用想着它了,但是这个参数里面装的都是写什么数据呢,我们可以逆推回去,我们知道里面装的都是Fruit和Fruit的子类,那我们get的时候用来装这些数据的容器肯定得“足够大”吧,不然“溢出来”怎么办,所以我们用一个Food来装数据是不是很安全的呀,是的,应为列表里面“最大”的数据就是Fruit了,一个Fruit肯定也是一个Food嘛;相反如果我们用Apple来装,万一列表里面有个Fruit怎么办,你能保证这个Fruit一定是Apple吗,要是是Orange呢,所以编译通过的地方是合理的。
(这块推荐还是暂时先死记硬背着,因为太喵喵的绕了,以后用的多了,思考的多了说不定就能理解了,嗯,就是这样)
-
泛型的下界
和上界用法很相似,只是将<? extends Fruit>中的extends换成super,即<? super Fruit>,然后功能相反,但需要注意的是,extends中是能get不能set,而在super中是能set不能get
-
泛型的约束和局限性
-
不能实例化类型变量
即无法做到下面这样
T t = new T(); //编译不通过 -
不能在静态域或者静态方法中使用类型变量
因为类型变量在对象创建的时候才得以确定,而静态域和方法在类加载的时候就执行了,当然,假如你的静态方法本身就是一个泛型方法就可以,因为它有嚣张的资本,游勤逮塞
-
泛型擦除
哇哦~看起来好专业的术语,然鹅其实就是你在编译期间看到的类型变量都是假象,在编译器编译时不管你定义了它是什么类型,它都会被擦掉,变成一个可怜巴巴的Object(当然如果你进行了< T extends XXX >这样的限定,就会被擦成XXX),使用会有
public static void main(String[] args) { Person<Fruit> fruitPerson = new Person<>(); Person<Apple> applePerson = new Person<>(); boolean flag = fruitPerson.getClass() == applePerson.getClass(); System.out.println(flag); //true } -
类型变量不可以是8种基本数据类型,类型变量都得是对象
Person<int> person; //编译不通过 -
泛型不支持instanceof语法
if (person instanceof Person<Fruit>) { } //编译不通过 -
可以声明泛型类数组,但不能创建泛型类数组(请看大屏幕)
Person<Fruit>[] people; //编译通过 people = new Person<Fruit>[4]; //编译不通过 people = new Person<>[4]; //编译不通过 people = new Person[4]; //编译通过 -
不能捕获泛型变量类型的对象,变量类型不能派生自Exception/Throwable
不解释,老师说用的不多,就是这么不求上进哇咔咔
好了,本文的内容大致上就是这么多,如果有写的不对写的不好的地方,还请各位大佬不吝赐教