泛型就是 参数化类型 ,即我们在定义的时候,将具体的类型进行参数化,在调用或者使用的时候,再传入具体的参数类型,我们可以将泛型用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型在开发过程中经常出现,比如我们一直高频使用的 List
集合,我们可以这么创建一个 ArrayList
集合。
List<String> stringList = new ArrayList<>(); 复制代码
再看一下 List
的定义
public interface List<E> extends Collection<E>{...} 复制代码
我们看到在 List
接口后面加了 <E>
对类型进行参数化,及参数是可变的,需要我们在使用的时候传入实际的参数,这样的好处是什么?
我们可以看看这么一个例子:
当我们对 List
添加泛型后,我们在使用的时候,可以根据需求声明不同类型的实际参数,如果我们传入的String,我们对List集合添加数据的时候,很明显看到当添加的数据不是String类型的时候,编译器会报错,当我们在获取数据的时候,也不需要强制类型转换,会自动返回我们传入实际参数的类型。
如果我们不传入实际类型,会出现什么情况?
我们定义的List集合没有指定明确的参数,在数据添加的时候,可以看到我们可以添加不同类型的参数,编译器也没有报错,在获取数据的时候,返回的是Object类型,所以我们需要对其进行强制转换,而这个过程,很容易就会报 ClassCastException
异常。
所以,我们使用泛型的好处有:
1、适用于多种数据类型执行相同的代码
2、类型安全:我们使用泛型的时候,指定了特定的参数类型,这样对其类型进行限定,可以在编译期间对我们的传入的参数类型进行判断,增加了类型的安全性。
3、取消强制类型转换:我们指定明确的类型参数后,由于在编译阶段就会对类型进行约束,泛型会自动且隐式的给我们做类型转换,转换成我们指定的类型,我们不再需要关心类型的转换。
泛型可以定义在类、接口、方法上,分别被称为泛型类、泛型接口、泛型方法。
3.1、泛型类
通过 <>
将类型变量T(大写字母都可以,不过常用的就是T,E,K,V等等)括起来,放在类名后面,泛型类运行有多个类型变量。
一个类型变量的泛型类:
/** * @author : EvanZch * description: 一个类型变量的泛型类 **/ public class genericClassTest<T> { private T mData; public genericClassTest() { } public T getmData() { return mData; } public void setmData(T mData) { this.mData = mData; } } 复制代码
多个类型变量的泛型类:
/** * @author : EvanZch * description: 两个类型变量的泛型类 **/ public class genericClassTest1<T, K> { // ... } 复制代码
3.2、泛型接口
泛型接口的定义和泛型一致
/** * @author : EvanZch * description: 泛型接口 **/ public interface genericInterface<T> { T getData(); void set(T data); } 复制代码
我们在实现泛型接口可以使用下面两种方式
1、不指定具体的泛型实参
/** * @author : EvanZch * description:实现泛型接口,不指定具体类型 **/ public class genericInterfaceImpl1<T> implements genericInterface<T> { @Override public T getData() { return null; } @Override public void set(T data) { } } // 我们在调用的时候,再传入实际类型 genericInterfaceImpl1<String> interfaceImpl1 = new genericInterfaceImpl1(); 复制代码
2、指定具体的泛型实参
/** * @author : EvanZch * description:实现泛型接口,指定具体类型 **/ public class genericInterfaceImpl2 implements genericInterface<String> { @Override public String getData() { return null; } @Override public void set(String data) { } } // 使用时候,直接创建 genericInterfaceImpl2 genericInterfaceImpl2 = new genericInterfaceImpl2(); 复制代码
3.3、泛型方法
泛型方法,是在调用方法的时候指明泛型的具体类型 ,泛型方法可以在任何地方和任何场景中使用,包括 普通类 和 泛型类 。注意泛型类中定义的普通方法和泛型方法的区别。
我们区别一下普通的方法和泛型方法
我们看到普通方法中也使用了泛型,但是它只是一个普通的方法,只是它的返回值和传入的类型是在前面已经声明过得泛型,所以,这里才可以继续使用 T 这个类型变量。
而下面这个泛型方法,首先通过 <E>
标识了它是一个泛型方法,返回值类型和传入的类型一致,通过泛型进行参数化了。
我们使用泛型的时候,对类型进行参数化,使用的可以传入任意的类型,但是在实际使用过程中,我们可能需要对类型进行限制,在进行类型擦除的时候,会转换成我们限定的类型,比如我们要比较两个字符的大小,需要用到 compareTo
方法
int compareTo(Object o) 或 int compareTo(String anotherString)
返回值是整型,它是先比较对应字符的大小(ASCII码顺序),如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的 差值 ,如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,以此类推,直至比较的字符或被比较的字符有一方结束。
可以看到如果我们直接这么定义,会报错,因为T为任意类型,但是 compareTo
方法定义在 Comparable
接口中,我们需要限定传入的类型必须实现了 Comparable
接口
public interface Comparable<T> { public int compareTo(T o); } 复制代码
我们可以这么写:
这里我们对类型对进行了限定,使用了通配符和指明了上界 ? extends X
,这样就限制了我们传入的参数类型必须是实现了 Comparable
接口,我们在调用的时候,编译器就会进行判断,我们传入的参数是否实现了 Comparable
接口,如果没有就会报错。
Integer 实现了 Comparable 接口 public final class Integer extends Number implements Comparable
上面类型变量的限制中,我们使用了通配符 ?
,通配符的有三种使用方式。
<? extends X>
: 类型的上界限定,参数类型是X的子类。
<? super X>
:类型的下界限定,参数类型是X的超类。
<?>
: 无界限定。
为了说明他们的区别,我们先新建 People
类、 Man
类、 Son
类,其中 Man
继承至 People
, Son
继承至 Man
People
类:
public class People { } 复制代码
Man
类:
public class Man extends People { } 复制代码
Son
类:
public class Son extends Man { } 复制代码
再创建一个泛型类 Test<T>
:
public class Test<T> { private T data; public T getData() { return data; } public void setData(T mData) { this.data = mData; } } 复制代码
查看继承关系:
很明显,我们可以看到 People
类和 Man
类是具有继承关系的,但是 Test<People>
和 Test<Man>
之间却没有任何关系。
我们进行如下操作
可以看到set,get都没问题,因为我们将泛型直接指定为 People
,而 Man
和 Son
都是 people
的子类,所以我们都能 set
进去。
1、`? extends X` : 上界限定通配符
我在再使用 ? extends X
,指明类型的上界限定为 X,表示传入方法的类型参数必须是其本身或子类。
但是对于泛型类来说,如果内部提供了get/set方法,那么set不允许调用,即类型的上界只读。
我们可以看到,我们通过 <? extends People>
指明了类型参数上界为 People
,那我们执行取操作的时候,即调用 get
的时候,编译器知道返回的肯定是个 x(不管是x还是x的子类),但是我们进行设置的时候,编译器只知道我们传入的是 x ,至于具体是 x 的那个子类并不知道,所以没有办法进行set操作。
总结:上不存。
主要用于安全地访问数据,可以访问X及其子类型,并且不能写入非null的数据。
2、`? super X` : 下界限定通配符
使用 ? super X
,指明类型的下界限定为 X,表示传入方法的类型参数必须是其本身或其超类。
但是对于泛型类来说,如果内部提供了get/set方法,那么set只能传入X的子类及其本身,get返回的类型是Object。
? super X 表示类型的下界,类型参数是X的超类(包括X本身),那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?不知道,但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,但是X和X的子类可以安全的转型为X。
总结:下不取
主要用于安全地写入数据,可以写入X及其子类型。
3、`` 无限定通配符
表示类型无限制,和 T 的区别
ArrayList al=new ArrayList(); 指定集合元素只能是T类型
ArrayList al=new ArrayList(); 集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法。
泛型在java语言中的一种语法糖的存在,java中泛型的实现为类型擦除,是一种伪泛型。