泛型

概述

泛型的本质是类型参数化,解决不确定具体对象类型的问题。在面向对象编程语言中,允许程序员在强类型校验下定义某些可变部分,以达到代码复用的目的。

Java在引入泛型前,表示可变类型,往往存在类型安全的风险。

泛型可以定义在类、接口、方法中,编译器通过识别尖括号和尖括号内的字母来解析泛型。在泛型定义时,约定俗成的符号包括:E代表Element,用于集合中的元素;T代表the type of object,表示某个类;K代表Key、V代表Value,用于键值对元素。

public class GenericDefinitionDemo<T> {
    static <String, T, Htzw> String get(String string, Htzw htzw){
        return string;
    }
    public static void main(String[] args){
        Integer first = 324;
        Long second = 456L;
        Integer result = get(first,second);
        System.out.println(result);
    }
}

以上代码编译正确且能够正常运行,get()是一个泛型方法,first并非是java.lang.String类型,而是泛型标识<String>,second指代Htzw。get()中其他没有被用到的泛型符号并不会导致编译出错,类名后的T与尖括号内的T相同也是合法的。在实际应用中,不会出现这样的定义方式,只是为了加深理解:

  • 尖括号里的每个元素都指代一种未知类型。String出现在尖括号里,他就不是java.lang.String,而仅仅是一个代号。类名后方定义的泛型<T>和get()前方定义的<T>是两个指代,可以完全不同,互不影响。
  • 尖括号的位置非常讲究,必须在类名之后或方法返回值之前;
  • 泛型在定义处只具备执行Object方法的能力;
  • 对于编译之后的字节码指令,其实没有这些花头花脑的方法签名,充分说明了泛型只是一种编写代码时的语法检查。在使用泛型元素时,会执行强制类型转换。
INVOKESTATIC com/htzw/demo/GenericDefinitionDemo.get(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
CHECKCAST java/lang/Integer

这就是盛传的类型擦除。CHECKCAST指令在运行时会检查对象实例的类型是否匹配,如果不匹配,则抛出运行时异常ClassCastException。

使用泛型的好处有哪些?

泛型就是在编译期增加了一道检查而已,目的是促使程序员在使用泛型时安全放置和使用数据。

  1. 类型安全。放置的是什么,取出来的自然是什么,不用担心会抛出ClassCastException异常;
  2. 提升可读性。从编码阶段就显式地知道泛型集合、泛型方法等处理的对象类型是什么;
  3. 代码重用。泛型合并了同类型的处理代码,使代码重用度变高

集合与泛型

泛型与集合的联合使用,可以把泛型的功能发挥到极致。

一、List、List<Object>、List<?>三者到的区别是什么?

public class ListNoGeneric {
    public static void main(String[] args){
        // 第一段:泛型出现之前的集合定义方式
        List a1 = new ArrayList();
        a1.add(new Object());
        a1.add(new Integer(1111));
        a1.add(new String("hello htzw"));
        
        // 第二段:把a1引用赋值给a2,a2与a1的区别在是增加了泛型限制<Object>
        List<Object> a2 = a1;
        a2.add(new Object());
        a2.add(new Integer(2222));
        a2.add(new String("hello a2"));
        
        // 第三段:把a1引用赋值给a3,a3与a1的区别是增加了泛型<Integer>
        List<Integer> a3 = a1;
        a3.add(new Integer(3333));
        // 下面两行,不允许增加非Integer类型进入集合
        a3.add(new Object());
        a3.add(new String("hello a3"));
        
        //第四段:把a1引用赋值给a4,a4与a1的区别是增加了通配符
        List<?> a4 = a1;
        a4.remove(0);
        a4.clear();
        // 编译出错,不允许增加任何元素
        a4.add(new Object());
    }
}

第一段说明:在定义List之后,随意的往集合中装入三种不同的对象:Object、Integer和String,遍历没有问题,但是贸然以为里面的元素都是Integer,使用强制转换,则抛出ClassCastException异常。

第二段说明:把a1赋值给a2,a2是List<Object>类型的,也可以再往里面装入三种不同的对象。

第三段说明:由于泛型在JDK5之后才出现,应尽量使用泛型定义,以及使用类、集合、参数等。

如果把a1的定义从List a1修改为List<Object> a1,那么第三段就会编译出错。List<Object>赋值给List<Integer>是不允许的,反过来也不行。

第四段说明:问号在正则表达式中可以匹配任何字符,List<?>称为通配符集合。他可以接受任何类型的集合引用赋值,不能添加任何元素,但可以remove和clear,并非immutable集合。List<?>一般作为参数来接收外部的集合,或者返回一个不知道具体元素类型的集合。

List<T>最大的问题是只能放置一种类型,如果随意转换类型的话,泛型就失去了类型安全的意义了。

二、<? extends T>与<? super T>的使用场景是什么?

在泛型中,如果需要放置多种受泛型约束的类型怎么办呢?为了满足需求,JDK实现了<? extends T>与<? super T>两种语法,但是这两者的区别非常微妙。

简单地说,<? extends T>是Get First,适用于,消费集合元素为主的场景;<? super T>是Put First,适用于,生产集合元素为主的场景。

<? extends T>可以赋值给任何T及T子类的集合,上界为T,取出来的类型带有泛型限制,向上强制转型为T。null可以表示任何类型,所以除了null之外,任何元素都不得添加进<? extends T>集合内;

<? super T>可以复制给任何T及T的父类集合,下界为T。

extends的场景是put功能受限,super的场景是get功能受限。

下面以加菲猫、猫、动物为例,说明extends和super的语法差异:

public class AnimalCatGarfield {
    public static void main(String[] args){
        // 第一段:声明三个依次继承的类的集合:Object>动物>猫>加菲猫
        List<Animal> animal = new ArrayList<Animal>();
        List<Cat> cat = new ArrayList<Cat>();
        List<Garfield> garfield = new ArrayList<Garfield>();
        
        animal.add(new animal());
        cat.add(new Cat());
        garfield.add(new Garfield());
        
        // 第二段:测试赋值操作
        // 下行编译出错,只能赋值cat或者cat的子类的集合
        List<? extends Cat> extendsCatFromAnimal = animal;
        List<? super Cat> superCatFromAnimal = animal;
        
        List<? extends Cat> extendsCatFromCat = cat;
        List<? super Cat> superCatFromCat= cat;
        
        List<? extends Cat> extendsCatFromGarfield = garfield;
        // 下行编译出错。只能赋值Cat或者Cat父类的集合
        List<? super Cat> superCatFromGarfield = garfield;
        
        // 第三段:测试add方法
        // 下面三行中所有<? extends T>都无法进行add操作,编译出错
        extendsCatFromCat.add(new Animal());
        extendsCatFromCat.add(new Cat());
        extendsCatFromCat.add(new Garfield());
        
        // 下面编译出错,只能添加Cat或者Cat的子类集合
        superCatFromCat.add(new Animal());
        superCatFromCat.add(new Cat());
        superCatFromCat.add(new Garfield());
        
        // 第四段:测试get方法
        // 所有的super操作能够返回元素,但是泛型丢失,只能返回Object对象
        
        // 以下extends操作能够返回元素
        Object catExtends2 = extendsCatFromCat.get(0);
        Cat catExtends1 = extendsCatFromCat.get(0);
        // 下行编译出错,虽然Cat集合从Garfield赋值而来,但类型擦除后,是不知道的
        Garfield garfield1 = extendsCatFromGarfield.get(0);       
    }
}

第1段,声明了三个泛型集合,可以理解为三个不同的笼子,List<Animal>住的是动物,List<Cat>住的是猫,List<Garfield>住的是加菲猫。Garfield继承于Cat,Cat继承于Animal。

第2段,以Cat类为核心,因为它有父类也有子类。定义类型限定集合,分别为List<? extends Cat> 和List<? super Cat>。把List<Cat>对象赋值给两者都是可以的。但是把List<Animal>赋值给List<? extends Cat>时编译出错,因为能赋值给<? extends Cat>的类型,只有Cat自己和他的子类集合。List<Garfield>赋值给List<? super Cat>时,也会编译报错。因为能复制给<? super Cat>的类型,只有Cat自己和它的父类。

第三段:所有的List<? extends T>都会编译出错,无法进行add操作,因为除null之外,任何元素都不能被添加进<? extends T>集合内。List<? super Cat>可以往里面增加元素,但只能添加Cat自身及子类对象,假如加入一块石头,则明显违背了Animal大类的性质。

第四段:所有List<? super T>集合可以执行get操作,虽然能够返回元素,但是类型丢失,即只能返回Object对象。List<? extends Cat>可以返回带类型的元素,但只能返回Cat自身及其父类对象,因为子类类型被擦除了。

对于一个笼子,如果只是不断地往外取动物而不是向里放的话,则属于Get First,应采用<? extends T>;相反,如果经常向里放动物的话,则应采用<? super T>,属于Put first。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值