原本今天是想接着写Java8新特性相关的文章的,在啃Stream的源码的时候,发现Stream对泛型的使用简直是牛逼!于是乎,想起我那可怜的Java基础。因此决定对泛型相关的知识点做一个全面的总结。写给自己看,讲给大家听。
什么是泛型
泛型(Generics)是JDK5中引入的一个新特性,泛型提供了编译时安全检查机制。
泛型的本质是把“类型”也变成一种特殊的参数,即参数化类型。
泛型解决了什么痛点
public class IntegerSession {
private Integer start;
private Integer end;
public Integer getStart() {
return start;
}
public void setStart(Integer start) {
this.start = start;
}
public Integer getEnd() {
return end;
}
public void setEnd(Integer end) {
this.end = end;
}
}
public class BigDecimalSession {
private BigDecimal start;
private BigDecimal end;
public BigDecimal getStart() {
return start;
}
public void setStart(BigDecimal start) {
this.start = start;
}
public BigDecimal getEnd() {
return end;
}
public void setEnd(BigDecimal end) {
this.end = end;
}
}
观察上述的代码,IntegerSession,BigDecimalSession有很多共性,除了类名称和处理的类型不同之外,共通性是很高的!如果要对上述的两个类进行一种合并,我们会怎么编程呢!我们往往会通过基类来替代.比如用他们的顶层基类,Object,代码如下
public class ObjectSession {
private Object start;
private Object end;
public Object getStart() {
return start;
}
public void setStart(Object start) {
this.start = start;
}
public Object getEnd() {
return end;
}
public void setEnd(Object end) {
this.end = end;
}
}
我们设置区间为[1,2],然后来看看这三个类对取值的转换处理。如下图
![显式转换问题.jpg](https://img-blog.csdnimg.cn/img_convert/6b8baced08bff04aac85856906bf428f.png)
我们发现虽然引入了Integer/BigDecimal的基类Object想做一个兼容的处理,但是这会导致我们需要对取值做一些强制转换。虽然图片中的代码并不会出现错误,但是要注意到,Object是所有类的基类,如果我们设置的值是String类型的,那么在强制转换的时候就可能会出现类型转换错误,而这种错误只能到了运行期发现!这显然不合理。
![类型转换问题.jpg](https://img-blog.csdnimg.cn/img_convert/5a85afd7b204fac4aeab0d7c1cbcf463.png)
上面提到了一个痛点。程序在运行时可能发生的类型转换异常。接下来我们看看,泛型是如何解决这两个问题。我们引入一个泛型类,Session 。
public class Session<T> {
private T start;
private T end;
public T getStart() {
return start;
}
public void setStart(T start) {
this.start = start;
}
public T getEnd() {
return end;
}
public void setEnd(T end) {
this.end = end;
}
}
![泛型解决强制转换问题.jpg](https://img-blog.csdnimg.cn/img_convert/0dc440283f41d6c27ca73ae54f41a802.png)
intSession指定的泛型参数 即限定了传入的参数类型必须是Integer类型。如果我们设置的数据是“一”,“二”,则显示如下。
![泛型检查参数.jpg](https://img-blog.csdnimg.cn/img_convert/6fba3914b633304a5cf4557c3faeaf27.png)
从上面的例子中,我们可以得出两点,关于使用泛型的意义。
1.泛型能够讲类型参数化,使得代码的设计更加抽象,是一种模板设计的思想体现。
2.泛型提供一个及早发现类型安全问题的机制,即编译时的类型安全检查机制,使得代码更加安全。
常见的定义和使用
泛型类
![泛型类的声明.jpg](https://img-blog.csdnimg.cn/img_convert/9219d30526a0dcbddcb5f0f9593450eb.png)
泛型的声明方式非常简单,如果是声明泛型类,则在类名后加上<T,…省略>。
如果是声明泛型函数,那么则是在返回类型前加上<T,…省略>。凡是声明,一定要用尖括号包裹泛型参数。泛型变量的命名可以是任意的大写字母!但是往往都具有如下的一些规范
E:是Element的缩写,通常用于表示容器类中的存放元素,例如List
,Set
K,V:是Key/Value的缩写,通常用于表达键值对关系的泛型变量
N:是Number的缩写
T:Type,类型
泛型接口
泛型接口是具有泛型变量的interface,和泛型类的声明方式是一样的。在使用上需要注意,如果子类需要沿用泛型变量,则不应该在implement的时候讲泛型接口具体填充好,而是要和泛型接口保持一致的声明。
以我们常用的List 泛型接口为例,我们观察JDK自带的ArrayList 子类和我们自己实现一个List 子类,MyList的区别
public class MyList implements List<String> {
//...省略代码
}
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//...省略代码
}
public static void main(String[] args) {
MyList myList=new MyList();
List<String> arrayList=new ArrayList<String>();
}
通过上述的MyList和ArrayList的比较,我们不难发现我们自己实现的MyList子类,已经不具备泛型的特性了。这是因为在实现泛型接口的时候将泛型变量指定成具体类型String了。而观察ArrayList,它在实现泛型接口的同时,仍然延续了泛型接口变量 ,因此ArrayList是一个泛型类。
同样的,在extends关系中也有可能造成这个情况。如果我们希望子类在继承或者实现接口类的同时,保持泛型特性,不可把泛型具体化
泛型函数
泛型方法使得方法可以接收不同类型的参数。和泛型类的声明是一样的,泛型方法的声明也需要使用尖括号<T,…>的格式。例如我在ArrayList源码中翻阅到的toArray方法,就是一个泛型方法。
//ArrayList中的toArray方法就是一个泛型方法
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
在ArrayList中还有一个方法,是我们常用的get()方法,我们来看看它的源码
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
get方法虽然返回的是泛型类型的数据,但是他并不是泛型方法,它只是ArrayList中的一个普通成员方法,它能够使用T作为返回参数,只不过是因为ArrayList在声明泛型类的时候已经声明过T变量了。
综上可见。泛型变量必须在类层面声明,或者在泛型函数中声明才能使用。泛型函数一定是具备<T,…>的。方法中使用泛型作为接收参数或者返回参数,不一定就是泛型方法!
学习总结
1.泛型(Generics)是JDK5中引入的一个新特性,泛型提供了编译时安全检查机制。泛型的本质是把“类型”也变成一种特殊的参数,即参数化类型。
2.合理使用泛型,能够使得代码更加健壮,更加精简
3.泛型变量的使用必须先经尖括号(<T,…>)的方式进行声明,可以在类层级(泛型类,泛型接口),或者方法层级(泛型方法)上进行声明。
4.使用了泛型变量作为入参或者出参的方法,不一定是泛型方法。