Java中的泛型
Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
原文链接:全网最完整Java学习笔记
基本介绍
泛型,即参数化类型。泛型的出现是为了统一集合当中的数据类型。可在编译阶段约束操作的数据类型,并进行检查
参数化类型:在方法定义时,将方法签名中的形参数据类型设置为参数(可称之为类型参数:尖括号 <> 中的泛型标识,用于指代任何数据类型),调用该方法时再从外部传入一个具体的数据类型和变量。
泛型的本质
将类、接口和方法中具体的类型参数化,并且提供了编译时类型安全检测机制。通过使用泛型,可以避免使用Object类导致的类型转换错误和减少了代码的冗余。泛型使用过程中,数据类型被设置为一个参数,使用时从外部传入一个数据类型;而一旦传入了具体的数据类型后,传入变量(实参)的数据类型若不匹配,编译器就会直接报错。这种参数化类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
细节:不能写基本数据类型;指定泛型具体类型后,传递数据时可传该类型和其子类类型;若不写泛型,默认是Object。
使用场景:定义类、方法、接口的时候,若类型不确定,可定义泛型;若类型不确定,但知道继承体系,可用泛型通配符 ?
注意:泛型不具备继承性,但数据具备继承性
泛型的标志
尖括号<>是泛型的标志,例如ArrayList<E>就是一个泛型,<E>将实际的集合元素类型参数化了,这样我们使用时可以指定new ArrayList<String>,将它指定:
1  | // ArrayList<E>是标准的类泛型,在使用时指定这个“E”具体是什么  | 
若不用泛型,而用public class ArrayList<Object>{}方式声明ArrayList,就可往集合里存所有类型的参数,编译不报错,但可读性很差,不知道它具体应存哪些类型,存的类型非业务所需类型时,编译期间不报错,直到生产环境运行时报错,就会出现不好的影响。
详细介绍:
泛型:将具体的类型参数化,是一种编程范式,提供了编译时类型安全检测机制。
通过使用泛型,可以将数据类型作为参数传递给类、接口或方法,可以在编译时期进行类型检查,避免在运行时期出现类型转换错误。
泛型的范围:泛型接口,泛型类(创建对象时再指定具体类型),泛型方法。
实现方式:以泛型类举例。只需要在类名后面使用尖括号<>将一个符号或多个符号包裹起来,这样在类里面就可以使用该符号代替具体类型了。使用泛型类时,调用者实际传进来什么类型,编译时就会将泛型符号擦除,替换成这个实际类型。
泛型标识:泛型符号可以是任意符号,但我们约定使用T、E、K、V等符号。Java 常见泛型标识及其代表含义如下:
1  | T :代表一般的任何类。  | 
格式
泛型参数类型
我们可以看见,前面 ArrayList
泛型参数类型的惯例:
- **
<E>**:表示元素(Element),通常在集合类中使用。例如,List,Set 。  - **
<T>**:表示类型(Type),通常在一般类型中使用。例如,Box,Comparable 。  - **
<K>和<V>**:分别表示键(Key)和值(Value),通常在映射(Map)类中使用。例如,Map<K, V>,Entry<K, V>。 - **
<N>**:表示数字(Number),在需要表示数字的泛型中使用。 
泛型类
泛型类定义了一个泛型参数,创建对象时给它传入这个参数的实际类型。 格式:
1  | // ArrayList简化版  | 
泛型接口
泛型接口和泛型类类似,也是定义了一个泛型参数。不同的点是,泛型接口在被实现或者被继承时需要指定具体类型。
如果泛型接口的实现类不是泛型:
- 实现泛型接口时,如果没有省略尖括号“<>”,则必须在接口“<>”中指定类型
 - 实现泛型接口时,如果省略了尖括号“<>”,则默认“<>”内是Object类
 
如果泛型接口的实现类是泛型:
- 实现泛型接口时,实现类也必须是泛型类,并且类型与泛型接口保持一致
 
1  | // 接口泛型  | 
代码示例:
1  | // 泛型接口  | 
泛型方法
当在一个方法签名中的返回值前面声明了一个 < T > 时,该方法就被声明为一个泛型方法。
然后返回类型、参数类型都可以用这个
1  | // 方法泛型  | 
示例代码
1  | public class Test {  | 
类型通配符
类型通配符跟泛型参数<T>、<E>等类似,用于表示不确定的类型,不同的点在于:
- 类型参数:用于声明泛型类、泛型接口或泛型方法。声明时是未知类型,使用时擦除成具体的类型(在编译时泛型擦除)。
 - 类型通配符:用于使用泛型时,表示一种未知的类型。
 
类型通配符有三种:
<?>:无限定的通配符。可用来表示任何类型。无限定通配符只能读Object类型的值,只能写null类型的值,其他类型都不能读写。<? extends T>:有上界的通配符。表示继承自T的任何类型,这里上界指的就是T。它通常用于生产者,即返回T。上界通配符只允许读值,不允许写null以外值。<? super T>:有下界的通配符。表示子类是T的任何类型,这里下界指的就是T。它通常用于消费者,即写入T。下界类型通配符只允许写值,不允许读Object以外的值。
无限定类型通配符:<?>
例如List<?>:表示元素类型未知的List,它的元素可以匹配任何类型。无限定通配符只能读Object类型的值,只能写null类型的值,其他类型都不能读写。
1  | public class Test {  | 
上界类型通配符:List<? extends 指定类型>
表示继承自T的任何类型,这里上界指的就是T。它主要用于写入数据的场景。
上界类型通配符只允许读值,不允许写null以外的值。
1  | public static void main(String[] args) {  | 
下界类型通配符:List<? super 指定类型>
表示子类是T的任何类型,这里下界指的就是T。它主要用于读取数据的场景。
下界类型通配符只允许写值,不允许读Object以外的值。
1  | public static void main(String[] args) {  | 
可变参数
(int… a)是将所有int参数封装到a数组里。
注意可变参数要放在后面。例如(int a,int… b)正确,(int… a,int b)会报错
Arrays工具类中有一个静态方法:
public static <T> List<T> asList(T.. a):返回由指定数组支持的固定大小的列表- 返回的集合不能做增删操作,可以做修改操作
 
List接口中有一个静态方法:
public static <E> List<E> of(E.. elements):返回包含任意数量元素的不可变列表- 返回的集合不能做增删改操作
 
Set接口中有一个静态方法:
public static <E>Set<E>of([...elements):返回一个包含任意数量元素的不可变集合- 在给元素的时候,不能给重复的元素
 - 返回的集合不能做增删操作,没有修改的方法
 
知识加油站
泛型的向上转型
泛型类或接口可以向上转型为父类,泛型符号不能向上转型。
泛型向上转型指的是将一个泛型对象转换为其父类类型或者接口类型的过程。这个过程实际上是将泛型对象的类型参数擦除,重新赋值为其父类或接口类型。
1  | // 泛型类或接口可以向上转型:ArrayList<T>可以向上转型为List<T>  | 
泛型的向下转型
向下转型和向上转型类似,指的是将一个父类类型的对象转换为子类类型的过程。这种转换需要进行类型检查,确保转换是安全的。
1  | List<Integer> list = new ArrayList<>();  | 
泛型擦除
泛型擦除:java的泛型是伪泛型,因为java在编译期间,所有的泛型类型都会被擦掉,并转换为普通类型。
泛型擦除的主要目的是为了向低版本兼容,因为Java泛型是在JDK 1.5之后才引入的特性,为了保证旧有的代码能正常运行,Java编译器采用了泛型擦除来兼容之前的代码。
为什么要有泛型,而不是使用Object类
因为泛型是在编译时泛型擦除和替换实际类型的,而使用Object类会很麻烦,需要经常强制转换。
如List集合里,若直接声明存Object类,存的时候可通过多态机制直接向上转型,而取的时候就麻烦了,要强转Object类为String等对象,然后才能访问该对象的成员;而且不知道实际元素到底是String类型还是Integer等其他类型,还要通过i instanceof String判断类型,就更麻烦了。
泛型的优点
- 防止运行时报错:可以在编译时检查类型安全,防止在程序运行期间出现BUG。
 - 隐式转换:所有的强制转换都是自动和隐式的,可以提高代码的重用率。
 
编译时安全检查:
Java在1.5版本中引入了泛型,在没有泛型之前,每次从集合中读取对象都必须进行类型转换,而这么做带来的结果就是:如果有人不小心插入了类型错误的对象,那么在运行时转换处理阶段就会出错。
在提出泛型之后,我们可以告诉编译器集合中接受哪些对象类型。编译器会自动的为你的插入进行转化,并在编译时告知是否插入了类型错误的对象。这使程序变得更加安全更加清楚。





