泛型的探究

- 泛型的本质是类型参数化。

- 允许在定义类、接口、方法时使用类型形参,当使用时指定具体类型。

- 所有使用该泛型参数的地方都被统一化,保证类型一致。如果未指定具体类型,默认是object类型。集合体系中的所有类都增加了泛型,泛型也主要用在集合。

· 泛型定义在类上

在创建的时候指定对应的类型,使用的时候,就能使用对应的类型,而不用强转

public static void main(String[] args) {
    // 创建ObjectTool对象并指定元素类型为String
    ObjectTool<String> stringTool = new ObjectTool<>();
    stringTool.setObj("muse");
    System.out.println(stringTool.getObj());
    // 创建ObjectTool对象并指定元素类型为Integer
    ObjectTool<Integer> integerTool = new ObjectTool<>();
    // integerTool.setObj("aa"); // 编译报错
    integerTool.setObj(10);
    System.out.println(integerTool.getObj());
}

/**
 * 构建可以存储任何类型对象的工具类
 */
static class ObjectTool<T> {
    private T obj;
    public T getObj() {
        return obj;
    }
    public void setObj(T obj) {
        this.obj = obj;
    }
}

·泛型定义在方法上

调用方法时指定对应的类型,传入什么类型的参数,就会被转换成什么类型。

public static void main(String[] args) {
    //创建对象
    ObjectTool tool = new ObjectTool();
    // 调用方法,传入的参数是什么类型,T就是什么类型
    tool.show("hello");
    tool.show(12);
    tool.show(12.5f);
}
static class ObjectTool {
    //定义泛型方法
    public <T> void show(T t) {
        System.out.println(t);
    }
}

·泛型定义在接口上

子类实现接口时,可以指定泛型,也可以不指定泛型,如下所示:

/**
 * 把泛型定义在接口上
 */
interface Inter<T> {
    void show(T t);
}

/**
 * 实现一:子类明确泛型类的类型参数变量
 * 在这种情况下,如果使用这个子类,传入非String类型编译就会报错
 */
class InterImpl1 implements Inter<String> {
    @Override
    public void show(String s) {
        System.out.println(s);
    }
}

/**
 * 实现二:子类不明确泛型类的类型参数变量,实现类也要定义出T的类型
 */
class InterImpl2<T> implements Inter<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}

·通配符之间的区别:object、T t、?

相同点:object T ?都可以指代任何类型,使用场景不同

不同点:

1.object使用的时候,如果要获取到原来的类型,需要进行强转。

2.T t :使用不需要强转,可以使用在集合中,object不可以使用在集合中,

        例: List<String>传入List<Object>就会编译报错,传入List<T>与List<?> 就没问题

        一般使用最多的也是T进行通配

public <T> void test3(List<T> list) {
    list.add(list.get(0));
    for (int i = 0; i < list.size(); i++) {
        System.out.print(list.get(i) + " ");
    }
    System.out.println();
}

3.?:定义了不能直接使用,会编译报错,T不会编译报错,可以直接使用,例:

public void test(List<?> list) {
    // list.add(list.get(0)); // 编译错误
    for (int i = 0; i < list.size(); i++) {
        System.out.print(list.get(i) + " ");
    }
    System.out.println();
}

·泛型的上限和下限

上限:

        格式:类型名称 <? extends 类> 对象名称

        意义:只能接受该类型及其子类

下限:

        格式:类型名称 <? super 类> 对象名称

        意义:只能接受该类型及其父类

·PECS(Producer Extends Consumer Super)

<? extends T>不允许存储,只能往外取

<? super T>可以存储,但往外取只能使用object接收

extends知道存储类型肯定是T的子类,所以读取的时候,可以使用T或T的父类接收,但是为了保证类型的安全是禁止往里写入任何数据;

super知道存储类型肯定是T的父类,所以写入的时候,可以写入T及T的子类,但是读取的时候必须使用Object接收。

/**
 * 【读取】
 * 如果要从集合中【可读取】类型T的数据,并且【不能写入】,可以使用 ? extends 通配符;(Producer Extends)
 * List<? extends Animal> animals 里面能够存放什么呢?
 * 动物、狗、猫、猪、鸡... 只要是动物,都有可能被存入进animals里。
 */
public static void testPECSextends() {
    List<Dog> dogs = Lists.newArrayList(new Dog());
    List<? extends Animal> animals = dogs;
    /**
     * animals是一个Animal的子类的List,由于Dog是Animal的子类,因此将dogs赋给animals是合法的,但是编译器会阻止将new Cat()加入animals。
     * 因为编译器只知道animals是Animal的某个子类的List,但并不知道究竟是哪个子类,为了类型安全,只好阻止向其中加入任何子类。那么可不可以加入
     * new Animal()呢?很遗憾,也不可以。事实上,不能够往一个使用了? extends的数据结构里写入任何的值。
     */
    // animals.add(new Cat()); // 编译失败
    // animals.add(new Animal()); // 编译失败
    // animals.add(new Dog()); // 编译失败
    /**
     * 由于编译器知道它总是Animal的子类型,但并不知道具体是哪个子类。因此我们总可以从中读取出Animal对象:
     */
    Animal animal = animals.get(0);
    Object obj = animals.get(0);
    // Dog dog = animals.get(0); // 编译失败
}
/**
 * 【写入】
 * 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
 * <p>
 * 如果既要存又要取,那么就不要使用任何通配符。
 */
public static void testPECSsuper() {
    List<Animal> animals = Lists.newArrayList();
    List<? super Dog> dogs = animals;
    /**
     * 这里的animals是一个Animal的超类(父类,superclass)的List。同样地,出于对类型安全的考虑,我们可以加入Dog对象或者其任何子类(如WhiteDog)对象,
     * 但由于编译器并不知道List的内容究竟是Dog的哪个超类,因此不允许加入特定的任何超类型。
     */
    dogs.add(new Dog());
    dogs.add(new WhiteDog());
    // dogs.add(new Animal()); // 编译失败
    // dogs.add(new Cat()); // 编译失败
    // dogs.add(new Object()); // 编译失败
    /**
     * 而当我们读取的时候,编译器在不知道是什么类型的情况下只能返回Object对象,因为Object是任何Java类的最终祖先类。
     */
    Object obj = dogs.get(0);
    // Dog dog = dogs.get(0); // 编译失败
    // Animal animal = dogs.get(0); // 编译失败
}

·类型擦除与桥接方法

在泛型出现之前,jdk编译器没有考虑过会出现泛型这种情况,那么为了兼容性,就需要向下兼容,使用的方法就是类型擦除与自动生成桥接方法。

泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛型信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。

由于类型被擦除了,为了维持多态性,所以编译器就自动生成了桥接方法。

正常泛型使用:

public class AAA<T data>{
    public T t;
    public void setT(T t){
        this.t = data;
    }
}

//指定泛型类型为String
public class aaa extends AAA<String s>{
    @override
    public void setT(String s){
        this.t = s;
    }
}

向下兼容进行类型擦除和自动生成桥接方法之后:

//类型擦除即为在上面正常泛型的基础上将泛型全部抹去,同时将方法中的泛型T改成Object,如下所示
public class AAA{
    public T t;
    public void setT(Object t){
        this.t = data;
    }
}


//因为进行了类型擦除,所以aaa中的setT(String s)就不再override AAA中的setT(Object t)
//此时自动生成桥接方法,在桥接方法中将object强转成String后调用原来的setT(String s)
public class aaa extends AAA{
    public void setT(String s){
        this.t = s;
    }
    @override
    public void setT(Object t){
        setT((String)t);
    }
}

**************此文章只是本人学习过程中的学习笔记,不做其他用途,如果有其他意见,欢迎一起讨论,谢谢,侵删*************************

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值