彻底理解java中泛型
一、什么是泛型?
泛型,即“类型参数化”,说人话,就是将具体的类型定义成参数,在使用时传递具体类型。
public class Test<T> {public void test1(String params){}public void test2(T params){}
}
泛型类Test中方法test1形参定义为具体类型String,方法test2中形参为泛型,在具体进行传递。
泛型可以使用在类、接口、方法
二、为什么有泛型?
我们都知道java中泛型又称为“伪泛型”,为什么称为伪泛型呢?是因为泛型只存在编译阶段,编译完成之后泛型会被擦除,并没有作用在程序的执行阶段,所以他存在的目的就是“为了规范”。
1、使用集合时规范,保证集合中存入的是同一类型的数据,在取数、类型转换时防止报错,在代码编写阶段发现问题。
2、抽象一类事物的公共行为、属性,使用泛型,提高代码的可复用性。
3、解决了元素不确定性。
三、泛型的使用
1、泛型类、泛型方法
public class Test<T> {public T test2(T params){}
}
2、泛型通配符
比如常见的?,T,K,V 就是通配符,比如其中的T换成A-Z其中任何一个都可以,java中是讲究约定的,就是大家共同约定,某个字符代表什么意思,这样也有利于代码维护。
- ? 表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
2、上界通配符(<? extends T>)、下界通配符(<? super T>)
a、为什么会有这两通配符?
开发人员在使用泛型的时候,犯一些错误很容易根据自己的直觉而。比如一个方法如果接收 List<Object> 作为形式参数,那么如果尝试将一个 List<String> 的对象作为实际参数传进去,却发现无法通过编译。
虽然从直觉上来说,Object 是 String 的父类,这种类型转换应该是合理的。但是实际上这会产生隐含的类型转换问题,因此编译器直接就禁止这样的行为。
b、上界通配符使用
比如有Fruit水果类,和它的派生类Apple
class Fruit {}
class Apple extends Fruit {}
有一个简单的容器:Plate类,盘子里可以放各类水果”,对盘子中水果做“放”和“取”的动作:set( )和get( )方法。
class Plate<T>{private T item;public Plate(T t){item=t;}public void set(T t){item=t;}public T get(){return item;}
}
定义在水果盘中放苹果,就会出现编译报错
问题:水果之间有继承关系,但是放水果的容器之间没有继承关系,所以这个时候我们要解决,容器都能放水果,所以边界通配符就出现了。
public class Test<T> {public static void main(String[] args) {Plate<? extends Fruit> plate = new Plate<>(new Apple());//不能存入任何元素plate.set(new Fruit());plate.set(new Apple());//读取出来的东西只能存放在Fruit或它的父类里Apple apple = plate.get(); //报错Fruit fruit = plate.get();Object obj = plate.get();}
}
上界通配符又称为get原则,是指一个范围内,上边界的一个限制条件,只能get操作,是因为编译器知道容器里面存储的是Fruit及Fruit的子类,所以可以get出里面的元素。
不能add操作,是因为编译器不知道具体那个子类。
C、下界通配符使用
继续使用上面的例子,
//水果
class Fruit {}
//苹果
class Apple extends Fruit {}
//红苹果
class RedApple extends Apple{}
//绿苹果
class GreenApple extends Apple{}
class Plate<T>{private T item;public Plate(T t){item=t;}public void set(T t){item=t;}public T get(){return item;}
}
public class Test<T> {public static void main(String[] args) {Plate<? super Apple> plate = new Plate<>(new Apple());//存入元素正常plate.set(new GreenApple());plate.set(new Apple());Apple apple = plate.get(); //报错Fruit fruit = plate.get();}
}
下界通配符又称为put原则,是指一个范围内,下边界的一个限制条件,只能add操作,是因为编译器知道容器泛型边界是Fruit及Fruit的父类的,所以容器中可以存储Fruit子类。
- <? extends C> 适合大量做获取操作的情景。
- <? super C> 适合大量做添加操作的情景。
特点:<? extends C> 的 add() 被限制,<? super C> 的 get() 被限制。
四、泛型常见面试问题
1、泛型为什么不能使用基本数据类型
泛型在编译阶段会进行泛型擦除,擦除为原始类型,但是object并不是基本数据类型的父类。