(一)概念
泛型(Generics,通用的类型)的本质是为了参数化类型(通过泛型指定的不同类型来控制形参具体限制的类型)。
Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。
(二)泛型的作用
-
使用泛型能写出更加灵活通用的代码,使代码重用更容易实现
有时项目中存在对大量的重复代码,这些重复代码中仅仅类型是不同的,泛型能很好的解决这个问题。(通过继承一个使用泛型的通用父类) -
泛型将代码安全性检查提前到编译期
使用泛型后,能让编译器在编译的时候借助传入的类型参数检查对容器的插入,获取操作是否合法,从而将运行时ClassCastException转移到编译时。 -
泛型能够省去类型强制转换
在JDK1.5之前,Java容器都是通过将类型向上转型为Object类型来实现的,因此在从容器中取出来的时候需要手动的强制转换。加入泛型后,由于编译器知道了具体的类型,因此编译期会自动进行强制转换,使得代码更加简洁优雅。
(三)泛型类、泛型接口、泛型方法
在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
可以用<T>、<K,V>、<T extends Number>等进行泛型的声明。
//泛型类
public class GenericClass<T>{
T test(){}
}
//泛型接口
public interface GenericInterface<T>{
T test(){}
}
public class ConcreteGenerator implements GenericInterface<String> {
@Override
public String test() {
return "1";
}
}
//泛型方法
public <T> T genericMethod(Class<T> tClass){
T instance = tClass.newInstance();
return instance;
}
3.1)泛型方法与可变参数
public class MyTest {
public <T> void printMsg( T... args){
for(T t : args){
System.out.println("可变参数泛型测试,t is " + t);
}
}
@Test
public void printTest(){
printMsg("111",222,"a", 13.14);
}
}
(四)泛型通配符
List对比数组,数组能够协变,而List无法协变。
class A{}
class B extends A{}
A[] array = new B[10];//编译通过
List<A> list = new ArrayList<B>();//编译失败
泛型的通配符用来解决子、父类的容器无法协同的现象。(即父类的容器无法存放子类的对象,反之亦然)
泛型的通配符大致分为三种:
频繁往外读取内容的,适合用上界Extends。
经常往里插入的,适合用下界Super。
①. 无边界的通配符(Unbounded Wildcards), 就是<?>, 比如List<?>.
无边界的通配符的主要作用就是让泛型能够接受未知类型的数据.。
示例代码:
public class MyTest {
//通配符
private void wildcard(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
//add方法只能添加null,若不进行强制转换,get方法只能取出Object,否则会编译报错
list.add(null);
Object o = list.get(0);//aa 11
}
@Test
public void testWildcard() {
List<String> stringList = new ArrayList<>();
stringList.add("aa");
stringList.add("bb");
wildcard(stringList);
List<Integer> integerList = new ArrayList<>();
integerList.add(11);
integerList.add(22);
wildcard(integerList);
}
}
输出结果:
结论:
String的List和Integer的List都能被通配符
List<?> list
接受,并打印,List<?> list
其效果与List list
一致。
List<?> list
声明的list的add方法只能添加null,添加其他类型会编译不通过;
若不进行强制转换,get方法只能取出Object,否则会编译报错。
②. 固定上边界的通配符(Upper Bounded Wildcards):
使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据. 要声明使用该类通配符, 采用<? extends E>的形式, 这里的E就是该泛型的上边界. 注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类.。
2.1)在类、接口中的使用
public interface GenericInteface<T extends Number>{
T getT();
}
public class Generic<T extends Number> implements GenericInteface{
private T t;
public Generic(T t) {
this.t= t;
}
public T getT(){
return t;
}
}
public class MyTest {
@Test
public void testWildcardExtendClass(){
Generic<Integer> gc = new Generic(1);
System.out.println(gc.getT());
Generic<Double> gcDou = new Generic(1.34);
System.out.println(gcDou.getT());
}
}
输出结果:
2.2)在方法中的使用
public class MyTest {
private void wildcardExtend(List<? extends Number> list){
//add方法不能使用(只能添加null),若不进行强制转换,get方法能取出Number及Object
list.add(null);
Number obj = list.get(0);
System.out.println(obj);
for (Object o : list) {
System.out.println(o);
}
}
@Test
public void testWildcardExtend(){
List<Double> doubleList = new ArrayList<>();
doubleList.add(1.23);
doubleList.add(2.23);
wildcardExtend(doubleList);
List<Integer> integerList = new ArrayList<>();
integerList.add(11);
integerList.add(22);
wildcardExtend(integerList);
}
}
输出结果:
结论:
Number及其子类的List都能被通配符
List<? extends Number> list
接受,但其他类的List会编译报错。
List<? extends Number> list
声明的list的add方法只能添加null(因为list能接受Number的子类,jdk无法确定具体哪一个子类型,故无法添加);
但是get的时候是可以得到一个Number, 也就是上边界类型的数据, 因为不管存入什么数据类型都是Number的子类型, 得到这些就是一个父类引用指向子类对象(多态).
③. 固定下边界的通配符(Lower Bounded Wildcards):
使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据. 要声明使用该类通配符, 采用<? super E>的形式, 这里的E就是该泛型的下边界. 注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。
3.1)在类、接口中的使用
public interface GenericInteface<T super Integer>{
T getT();
}
public class Generic<T super Integer> implements GenericInteface{
private T t;
public Generic(T t) {
this.t= t;
}
public T getT(){
return t;
}
}
public class MyTest {
@Test
public void testWildcardExtendClass(){
Generic<Integer> gc = new Generic(1);
System.out.println(gc.getT());
Generic<Number> gcDou = new Generic(1.34);
System.out.println(gcDou.getT());
}
}
输出结果:
3.2)在方法中的使用
public class MyTest{
public void wildcardSuper(List<? super Integer> list){
//add方法能添加null,Integer及其子类,若不进行强制转换,get方法只能取出Object
list.add(new Integer(1));
for (Object o : list) {
System.out.println(o);
}
}
@Test
public void testWildcardSuper(){
List<Number> numberList = new ArrayList<>();
numberList.add(1.23);
numberList.add(2.23);
wildcardSuper(numberList);
List<Integer> integerList = new ArrayList<>();
integerList.add(11);
integerList.add(22);
wildcardSuper(integerList);
}
}
输出结果:
结论:
Integer及其父类的List都能被通配符
List<? super Interger> list
接受,但其他类的List会编译报错。
参考下列代码:父类的容器是可以添加子类的对象的
List<Number> numbers = new ArrayList<> ();
numbers.add (new Integer (1));//编译通过
List<? super Interger> list
声明的list的add方法能添加null或是Integer及其子类的数据(假设此时是Number的list,add一个Integer是可以的(其实可以看做是一个父类引用指向子类对象)),添加其他类型会编译不通过;
但是get的时候是可以得到一个Object, 因为我们所传入的类都是Integer的类或其父类, 所传入的数据类型可能是Integer到Object之间的任何类型, 这是无法预料的, 也就无法接收.。
(五)泛型的应用
应用场景: Spring环境下通用控制器BaseController
日常开发中有很多通用的代码除了类型不同,其他的方法都一致,这时候我们可以采用封装一个通用父类。
基础类准备:Student,School
public class Student {
private String name;
private int age;
/**getters and setters**/
}
public class School {
private String name;
/**getters and setters**/
public School(String name){
this.name = name;
}
public School(){}
}
BaseController:
public abstract class BaseController<T,V>{
private T entity;
private V entityTwo;
public BaseController() throws Exception{
//获取父类的泛型类型
ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass ();
//获取真实类型,即 T,V 的真实类型
Class<T> clazz = (Class<T>) type.getActualTypeArguments()[0];
Class<V> clazzTwo = (Class<V>) type.getActualTypeArguments()[1];
//无参构造函数实例化
this.entity = clazz.newInstance();
//有参构造函数实例化
this.entityTwo = clazzTwo.getConstructor (String.class).newInstance ("中心小学");
}
protected T getEntity(){
return this.entity;
}
protected V getEntityTwo(){
return this.entityTwo;
}
}
StudentController:
public class StudentController extends BaseController<Student, School> {
public StudentController() throws Exception {
super();
}
}
测试:
@Test
public void testStudent() throws Exception{
StudentController studentController = new StudentController ();
System.out.println (studentController.getEntity ());
System.out.println (studentController.getEntityTwo ().getName ());
}
输出结果: