泛型与集合

泛型与集合

直接开冲

泛型

本篇博客主要参考码出高效-Java开发手册

泛型的基本概念

泛型的定义和本质
  1. 泛型指的是可以将类型作为参数进行传递,其本质上就是类型参数化,泛型只会存在于编译期,在运行期会进行参数擦除。关于泛型的擦除,如果泛型没有设置类型上限,则泛型会转化为Object类型,如果设置上限则会转化为设置的上限。最后再进行类型的强转,转化为我们需要的类型。

  2. 除了基本类型之外,其他的都可以都可以作为泛型。泛型可以定义在类、接口、方法中,编译器通过尖括号和尖括号中的字母来解析泛型,有些字母是约定俗成的:

    • E代表Element,表示集合中的元素。
    • T代表the Type of object,表示某个类
    • K代表key,V代表value,用于键值对元素
      下面代码选自码出高效:
    public class GeniecTestDemo<T> {
    /**
     * @param type0
     * @param type1
     * @param <String>
     * @param <T>
     * @param <Alibaba>
     * @return
     */
    public static <String, T, Alibaba> String getString(String type0, Alibaba type1) {
        return type0;
    }
    
    public static void main(String[] args) {
        Integer integer = Integer.valueOf("123");
        Long aLong = Long.valueOf("232324");
        //返回的值也是第一个传入的类型的泛型
        Integer string = getString(integer, aLong);
    
    }
    }
    

针对本段代码的说明:

  • 此处的String不是java.lang.String中的String,而是自己定义的泛型,和泛型T、Alibaba效果一样
  • 类名后的泛型T和方法中的泛型T互相不影响,相互独立
  • 尖括号的位置在类名之后或者在方法返回值之前
  • 泛型在定义处只有执行Object方法的能力,即在get方法内部无法使用type0.intValue和type1.longValue方法完成
泛型的优势:
- 避免类型的强制转换
- 在编译期内进行更强的类型检查
- 增强代码的复用

泛型的使用

泛型定义位置
  • 泛型定义的位置:

    1. 泛型定义在方法上:在调用方法时,可以使用任何类型的参数;泛型定义必须放在返回值前,修饰词后。
    2. 泛型定义在类上,创建该类对象时,明确这个对象使用的参数类型。
  • 静态方法能否使用泛型

    1. 静态方法不能使用类上定义的泛型,因为泛型是需要通过对象来明确的,静态方法不需要对象,所以无法用该泛型。
    2. 如果静态方法要使用泛型,只能将泛型定义在方法上。
泛型的使用细节

、<?>、Object与List、List<?>、List
解决这个问题首先弄清楚这三位是什么,以及在JDK源码中的应用,第一个是泛型类型参数T,第二个是泛型通配符,第三个是Object类型.
经典问题,到底我选哪个

类型参数和Object类的区别

其实在泛型没推出来之前,在集合容器中使用Object类很频繁,泛型推出来之后给我们带来的是使用的便利,具体的优势如下:

  1. 类型安全:放入什么、取出什么,不用担心抛出ClassCastException
  2. 提升可读性:编码阶段显示知道泛型集合、泛型方法等处理对象的类型是什么
  3. 提高代码的重用性
    看起来有点眼熟是不是?这不就是为什么要是用泛型吗?是的。
泛型通配符
泛型通配符含义

泛型通配符含义:?代表的是未知类型,所以涉及到?的操作,一定与具体类型无关。

泛型通配符的形式
  • <?> 无限通配符 一般搭配容器使用,只有读的能力,没有写的能力
  • <? extends T> 定义了泛型**上限**,限定了泛型的类型只能是T或者是T的子类,只有读的能力,没有写的能力
  • <? super T> 定义了泛型的**下限**,泛型的参数只能是T或者是T的父类,有读取的能力和部分写的能力
泛型通配符?和泛型参数T

关于这个问题,这个老师讲的比较透彻,本文也是吸取了部分他的内容。

https://blog.csdn.net/sinat_32023305/article/details/83215751

选什么,老师把好处都说了
全都要的爪巴

  1. 首先是两者可以互换的地方
  • List<?> mlist、List mlistT
  • List<? extends A> mlist、List mlistT
    在这两个地方中通配符?和泛型参数T是一样的
  1. 两者的区别
  • List<?>表示不确定的类型,代表范围内任意类型,List表示确定的类型(一旦在创建对象或者方法调用时该类型即是已经确定),代表范围内所有类型
    [图片上传失败…(image-c86b0e-1597131632906)]
    从使用者角度出发,通配符代表的泛型是不确定的,即使在方法中调用或者在初始化对象时将该泛型确定之后,对于该泛型也只能做读取、判空等操作,但是不能增加元素;
    这边需要对读取做两个说明,
  1. 当返回值需要和泛型相关时,就不能使用。
  2. 不能对数据进行增加操作增加的操作,
  • 只有泛型通配符?才支持super关键字,泛型T不支持super关键字,无法对泛型的下限进行限定

  • 通配符中数据不能增加,原因在前文已经说明了。泛型参数T可读取、可修改。泛型参数T在类型实例化的时候,泛型的类型就已经确定。

  1. 两者的使用
  • 通配符的优势在于代码简洁,可读性好,因而对于数据我们如果只需要读取,并且返回值对该泛型没有任何依赖时,选择使用?,否则使用泛型参数T。
    在JDK源码中,这部分代码在集合类Collections中表现的很明显
 public static int indexOfSubList(List<?> source, List<?> target) {
     .....
 }

该方法作用是查找集合的某一子集在该集合中的位置坐标,对数据不需要进行修改、增加的操作,因而选择通配符没有选择泛型参数

  • 在日常的使用过程中,两者往往是配合使用,比如日常使用频率比较高的Collections的排序
   public static <T> void sort(List<T> list, Comparator<? super T> c) 
List<?> List List

这个问题出自码出高效,书中也是做了解答。
泛型未推出时定义List的方式
完整代码图片
对于非泛型集合List,可以赋值给任意泛型集合,所以图中的几个集合的定义没有问题。

  1. 首先是List和List
    看起来没有区别不是吗?
    你说的不对
    两者都可以添加任意元素(Java中Object类是所有类的父类),但是在赋值时就会出现问题了。
    请看下面的代码
    这段代码有两个问题
 public static void main(String[] args) {
        //List和List<Object>的区别
        List<Object> objects = new ArrayList<>();
        List mLists = new ArrayList();
        List<Long> longLists=objects;
        List<Long> longLists1=mLists;

        //数组和集合的协变性,
        ArrayList<Integer> integers = new ArrayList<>();
        integers.add(1234);
        List<Object> Objects1=integers;

        Integer[] integers1 = {1};
        Object [] mObjects1=integers1;

    }

image
这段代码有两个问题:

  1. 将Object类型的集合赋值给Long型的很显然是不可以的,因为List中对集合的泛型类型作了限制,只能接受Object类型的参数,将Object类型的集合赋值给Integer类型的集合显然是不符合规范
  2. 按照数组可以协变,那么集合的泛型是否可以协变呢,关于协变,可以参照这个大佬的博客

https://blog.csdn.net/magi1201/article/details/45127487

答案是不可以。
数组的协变性(covariant)是指如果类Base是类Sub的基类,那么Base[]就是Sub[]的基类。而泛型是不可变的,List不会是List的基类,更不会是它的子类。
对照代码部分就是integer类型的集合是不可以赋值给Object类型的,在编译阶段就会报错。

  1. 关于List<?>则相对简单,List<?> 不支持添加任何元素,可以remove和clear元素,List<?> 作为参数接受外部的集合,或者返回不知道具体类型的集合
List<? extends T> 和 List<? super T>区别

List只能放置一种类型,为放置解决多种受泛型约束的类型,出现了List<?extends T>和List<? super T>这两种

  1. 首先我们看一下结论,List<? extends T>的特点如下:
  • 可赋值给T及T的子类集合
  • 除了null之外,任何元素不得添加进<? extends T>中
    综合以上特点,我们可知List<? extends T>适合消费元素为主的场景,取出元素反而能加上限
  1. List<? super T>的特点:
  • 可赋值给T及T的父类
  • 可添加T及T的父类,下界为T
  • 取出功能受限,只能取出Object类型的对象
    综合以上特点,List<? super T>适合生产元素为主的场景,取出元素会发生类型擦除。
    下面看示例代码:
    image
    这段代码可以看到有明显的编译错误:
  1. 赋值的问题
    List<? extends Cat> 可接受Cat及Cat子类集合的赋值
    List<? super Cat> 可接受Cat及Cat父类集合的赋值(object类的集合也可以),
    在赋值的两个操作中有编译报错源于此
  2. 放入新元素
    List<? extends Cat> 任何类型对象均不可放入,extendsCatList的三行报错缘于此
    List<? super Cat> 只可添加Cat及Cat的子类。
  3. 取出元素
    List<? extends Cat>取出元素自动向上转型为Cat
    List<? super Cat>取出元素会发生类型丢失,只能返回object对象

本文中有任何错误,各位大佬在评论区域更正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值