1. 什么是泛型
泛型字面理解“广泛适用的类型”,其本质是指类型参数化,即可以将类型当作参数传递给一个类或者是方法。
注意: 允许在定义类、接口、方法时使用类型形参,分别称为泛型类、泛型接口、泛型方法,当使用时 指定具体类型。
所有使用该泛型参数的地方都被统一化,保证类型一致。 如果未指定具体类型,默认是Object类型。
集合体系中所有的类都增加了泛型,泛型主要用在集合
2. 为什么要用泛型?(泛型的优势)
- 不需要强转
不使用泛型:
泛型是jdk1.5的新特性。在jdk1.5之前没有泛型时通常采用的是强转类型
例如:有Cache实体类如下
将Cache进行存取值测试,得到具体类型时必须强转static class Cache{ Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
使用泛型://Cache存取String类型强转 Cache cache = new Cache(); cache.setValue("test"); String valueString = (String)cache.getValue(); //Cache存取int类型强转 cache.setValue(123); int valueInt = (int)cache.getValue();
Cache加入泛型约束
Cache进行存取值时,将value 这个属性的类型参数化了,即参数化类型(再也不用手动去强转了)static class Cache<T>{ T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
//将String参数化创建Cache Cache<String> cache = new Cache(); cache.setValue("test"); String valueString = cache.getValue(); //将Integer参数化创建Cache Cache<Integer> cache1 = new Cache(); cache1.setValue(123); Integer valueInt = cache1.getValue();
- 编译时更强大的类型检查
Java 编译器将强类型检查应用于通用代码,并在代码违反类型安全性时发出错误。java在修复编译时错误比修复运行时错误要容易得多。
如图,无法将String类型设置到objectTool中,因为泛型让它只接受 Integer 的类型。
即参数一旦确定好,如果类型不匹配,编译器就不通过。
总结:
- 与Object代替一切类型相比,泛型提供了一种扩展能力,使得数据的类别可以像参数一样由外部传递进来,其更符合面向抽象开发的软件编程宗旨。
- 当具体的类型确定后,泛型又提供了一种类型检测的机制,只有相匹配的数据才能正常的赋值,否则编译器就不通过。其是一种类型安全检测机制,一定程度上提高了软件的安全性防止出现低级的失误。
- 泛型提高了程序代码的可读性,不必要等到运行的时候才去强制转换,在定义或者实例化阶段,因为 Cache这个类型显化的效果,程序员能够一目了然猜测出代码要操作的数据类型。
3. 泛型基础
下面分别从泛型基础的四大模块:泛型类、泛型方法、泛型类派生出的子类(泛型接口)、泛型通配符来理解泛型。
补充: 这里先补充些Java规范中常见的代表类型参数的规范
- T 代表一般的任何类。
- E 代表 Element 的意思,或者 Exception 异常的意思。
- K 代表 Key 的意思。
- V 代表Value 的意思,通常与 K 一起配合使用。
- S 代表 Subtype 的意思。
如果一个类被 的形式定义,那么它就被称为是泛型类。
3.1 泛型类
基于类上做泛型约束,创建对象做类型定义
例:构建一个可以存储任何类型对象的工具类ObjectTool,并约束泛型类。
public class ClassGenericity {
public static void main(String[] args) {
//创建对象并指定元素类型为String
ObjectTool<String> tool = new ObjectTool<>();
tool.setObj("zzm");
System.out.println(tool.getObj());
//创建对象并指定元素类型为Integer
ObjectTool<Integer> objectTool = new ObjectTool<>();
// 如果我在这个对象里传入的是String类型的,它在编译时期就通过不了了.
//objectTool.setObj("test");
objectTool.setObj(10);
System.out.println(objectTool.getObj());
}
/**构建可以存储任何类型对象的工具类*/
static class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
}
3.2 泛型方法
基于方法做泛型约束,方法入参时做类型定义
例:定义如下泛型方法show(T t),调用方法时,传入的参数是什么类型,返回值就是什么类型。
public class MethodGenericity<T> {
public static void main(String[] args) {
//创建对象
ObjectTool tool = new ObjectTool();
//调用方法,传入的参数是什么类型,返回值就是什么类型
tool.show("hello");
tool.show(12);
tool.show(12.5);
}
static class ObjectTool {
//定义泛型方法..
public <T> void show(T t) {
System.out.println(t);
}
}
}
相较于泛型类,泛型方法更灵活(入什么值,就可以直接去存储)
3.3 泛型类派生出的子类(泛型接口)
/**
* 把泛型定义在接口上,方法也对应同样的泛型
*/
interface Inter<T> {
void show(T t);
}
其实现有两种方式:
1)子类明确泛型类的类型参数变量
class InterImpl1 implements Inter<String> {
@Override
public void show(String s) {
System.out.println(s);
}
}
2)子类不明确泛型类的类型参数变量,实现类也要定义出T的类型
class InterImpl2<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
main方法分别测试两个实现:
public static void main(String[] args) {
// 测试第一种情况,只支持String类型
Inter<String> i = new InterImpl1();
i.show("hello");
// 编译错误,因为子类实现时明确了类型为String,所以不支持Integer
// Inter<Integer> ii = new InterImpl1();
// ii.show(1);
// 第二种情况测试,子类不明确泛型类时,相对较灵活
Inter<String> iii = new InterImpl2();
iii.show("100");
Inter<Integer> iii2 = new InterImpl2<>();
iii2.show(123);
}
}
3.4 通配符
- <?> 被称作无限定的通配符。
- <? extends T> 有上限的通配符。表示参数化类型的可能是T 或是 T的子类
- <? super T> 有下限的通配符。表示参数化类型是此类型的超类型(父类型),直至Object
现定义三个List集合,分别约束泛型为Integer、Object、String:
private final static List<Integer> LIST_INTEGER = Arrays.asList(1, 2, 3, 4);
private final static List<Object> LIST_OBJECT = Arrays.asList(1, 2, 3, 4);
private final static List<String> LIST_STRING = Arrays.asList("a", "b", "c", "d");
1)List<Object>
Object类型,可以传入Integer、String等。
但List<Object>只是List<Object>,和List<String>、List<Integer>等没父子级关系
public void test2(List<Object> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
测试
//typeWildcard.test2(LIST_STRING); //编译报错
typeWildcard.test2(LIST_OBJECT);//输出1 2 3 4
2)List<?> 和List <T>
- List<T>是确定的某一个类型,T为Object及子类或自定义实体类都可以。表示List集合中的元素都为T类型;可以进行读写操作如add、remove;因为它的类型是固定的T类型,在编译期 不需要进行任何的转型操作。
- List<?>表示任意未知类型,默认是允许Object及其下的子类,也就是java的所有对象。
是只读类型的,且List<?>读取出的元素都是Object类 型的,需要主动转型。
不能进行增加、修改操作,可以进行删除操作如remove、clear等,因为删除动作与泛型类型无关。
例子说明:
public void test3(List<?> list) {
//list.add(list.get(0)); // 编译错误
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
public <T> void test4(List<T> list) {
for (int i = 0; i < list.size(); i++) {
// list.add(list.get(0)); // 编译阶段ok,但是运行时抛异常,Exception in thread "main" java.lang
// .UnsupportedOperationException
System.out.println(list.get(i));
}
}
test3编译错误解释:
list.get(0)获取到的是Object类型,而再次将list进行add操作时,此时通配符会捕获具体的Object类型,但编译器不叫它Object,而是起个临时的代号,比如”capture“。所以list<?>不能进行add。除了list.add(null)。
(具体深层次原因?待补充)
测试test3传入LIST_INTEGER、LIST_OBJECT、LIST_STRING:
test4运行错误解释:
?待补充
测试test4传入LIST_INTEGER、LIST_OBJECT、LIST_STRING:
3)List <? extends T> 和List <? super T>
例子说明1:
//限制入参的类型,只能传number及其的子类
public void test5(List<? extends Number> list) {
// list.add(list.get(0));//编译报错
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
//限制入参的类型,只能传number及极其的父类
public void test6(List<? super Number> list) {
// list.add(list.get(0));//编译报错
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
test5编译错误解释:
测试test5传入LIST_INTEGER、LIST_OBJECT、LIST_STRING:
typeWildcard.test5(LIST_INTEGER);//1,2,3,4
test6编译错误解释:
测试test6传入LIST_INTEGER、LIST_OBJECT、LIST_STRING:
typeWildcard.test6(LIST_OBJECT); //1,2,3,4
例子说明2:
1. 实体:
Animal
Dog
Cat
方法及测试说明:
/**
* 【读取】
* 如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
*/
public void testPECSextends() {
List<Dog> dogs = Lists.newArrayList();
dogs.add(new Dog());
List<? extends Animal> animals = dogs; //约束的是anmial及其animal的子类
/**
* animals是一个Animal的子类的List,由于Dog是Animal的子类,因此将dogs赋给animals是合法的,但是编译器会阻止将new Cat()加入animals。
* 因为编译器只知道animals是Animal的某个子类的List,但并不知道究竟是哪个子类,为了类型安全,只好阻止向其中加入任何子类。那么可不可以加入
* new Animal()呢?很遗憾,也不可以。事实上,不能够往一个使用了? extends的数据结构里写入任何的值。
*/
// animals.add(new Cat()); // 编译失败
// animals.add(new Animal()); // 编译失败
// animals.add(new Dog()); // 编译失败,编译期只知道animals是一个Animal的子类的List
/**
* 由于编译器知道它总是Fruit的子类型,因此我们总可以从中读取出Animal对象:
*/
Animal animal = animals.get(0);
// Dog dog = animals.get(0); // 编译失败,只知道是Animal极其子类,并不确定是dog,所以编译失败
}
/**
* 【写入】
* 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
* <p>
* 如果既要存又要取,那么就不要使用任何通配符。
*/
public void testPECSsuper() {
List<Animal> animals = Lists.newArrayList();
List<? super Dog> dogs = animals; //可存放dog及其dog的父类
/**
* 这里的animals是一个Animal的超类(父类,superclass)的List。同样地,出于对类型安全的考虑,我们可以加入Dog对象或者其任何子类(如WhiteDog)对象,
* 但由于编译器并不知道List的内容究竟是Dog的哪个超类,因此不允许加入特定的任何超类型。
*/
dogs.add(new Dog());
dogs.add(new WhiteDog());
// dogs.add(new Animal()); // 编译失败
// dogs.add(new Cat()); // 编译失败
// dogs.add(new Object()); // 编译失败
/**
* 而当我们读取的时候,编译器在不知道是什么类型的情况下只能返回Object对象,因为Object是任何Java类的最终祖先类。
*/
Object obj = dogs.get(0);
// Dog dog = dogs.get(0); // 编译失败
// Animal animal = dogs.get(0); // 编译失败
}
4. 类型擦除与桥接方法
4.1 什么是类型擦除与桥接方法
泛型是提供给javac编译器使用的,它用于限定集合的输入类型, 让编译器在源代码级别上,即挡住向集合中插入非法数据。
但编 译器编译完带有泛形的java程序后,生成的class文件中将不再带 有泛型信息,以此使程序运行效率不受到影响,这个过程称之为 “擦除”。
由于类型被擦除了,为了维持多态性,所以编译器就自动生成了 桥接方法。
4.2 例子说明
MyNode 继承Node,并指定了String类型
通常使用泛型时这样写:
public class Node<T> {
public T data;
public void setData(T data) {
this.data = data;
}
}
public class MyNode extends Node<String> {
@Override
public void setData(String data) {
this.data = data;
}
}
没有泛型时需做向下兼容处理:
- 首先进行类型擦除
public class Node {
public Object data;
public void setData(Object data) {
this.data = data;
}
}
public class MyNode extends Node {
public void setData(String data) {
this.data = data;
}
//产生桥接方法
@Override
public void setData(Object data) {
//调用String类型的方法
setData((String)data);
}
}
- 说明:
即保留了最初的泛型的关系setData(String data),继承的关系、Override的关系、类型限制(转为String);当传String类型时,通过桥接方法调用;而传Integer类型时,报类型异常。
自己做笔记的同时也参考几篇大佬的文章(创作不易,贴下原链接):
https://blog.csdn.net/briblue/article/details/76736356
https://www.jianshu.com/p/6733de8fe844
https://cloud.tencent.com/developer/article/1353549