泛型详解及其应用

(一)概念

泛型(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 ());
}

输出结果:
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Funnee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值