Java中泛型学习之细节篇

目录
  • 简介
  • 正文
    • 什么是类型参数
    • 为啥要有泛型
    • 泛型的演变史
    • 类型擦除
    • 泛型的应用场景
    • 通配符限定
    • 动态类型安全检查
  • 总结

简介

泛型的作用就是把类型参数化,也就是我们常说的类型参数

平时我们接触的普通方法的参数,比如public void fun(String s);参数的类型是String,是固定的

现在泛型的作用就是再将String定义为可变的参数,即定义一个类型参数T,比如public static <T> void fun(T t);这时参数的类型就是T的类型,是不固定的

从上面的String和T来看,泛型有着浓浓的多态的味道,但实际上泛型跟多态还是有区别的

从本质上来讲,多态是Java中的一个特性,一个概念,泛型是真实存在的一种类型;

正文中大部分示例都是以集合中的泛型为例来做介绍,因为用的比较多,大家都熟悉

正文

什么是类型参数

类型参数就是参数的类型,它接受类作为实际的值

白话一点来说,就是你可以把类型参数看作形参,把实际传入的类看作实参

比如:ArrayList<E>中的类型参数E看做形参, ArrayList<String>中的类String看做实参

如果你学过工厂设计模式,那么就可以把这里的ArrayList<E>看做一个工厂类,然后你需要什么类型的ArrayList,就传入对应的类型参数即可

  • 比如,传入Integer则为ArrayList<Integer>类
  • 比如,传入String则为ArrayList<String>类

为啥要有泛型

主要是为了提高代码可读性和安全性

具体的要从泛型的演变史说起

泛型的演变史

从广义上来说,泛型很早就有了,只是隐式存在的;

比如List list = new ArrayList(); //等价于List<Object> list = new ArrayList<>();

但是这个时候的泛型是很脆弱的,可读性和安全性都很差(这个时期的集合相对于数组来说,优势还不是很大)

首先,填充数据时,没有类型检查,那就有可能把Cat放到Dog集合中

其次,取出时,需要类型转换,如果你很幸运的把对象放错了集合(有可能是故意的),那么运行时就会报错转换异常(但是编译却可以通过)

不过到了JDK1.5,出现了真正意义上的泛型(类型参数,用尖括号<>表示);

比如List<E>集合类,其中的E就是泛型的类型参数,因为集合中都是存的元素Element,所以用E字母替代(类似还有T,S,K-key,V-value);

这个时候,程序的健壮性就提高了,可读性和安全性也都很高,看一眼就知道放进去的是个啥东西(这个时期的集合相对于数组来说,优势就很明显了

现在拿List<Dog> list = new ArrayList<>();来举例说明

首先,填充数据时,编译器自己会进行类型检查,防止将Cat放入Dog中

其次,取出数据时,不需要我们手动进行类型转换,编译器自己会进行类型转换

细心的你可能发现了,既然有了泛型,那我放进去的是Dog,取出的不应该也是Dog吗?为啥编译器还要类型转换呢?

这里就引出类型擦除的概念

类型擦除

什么是类型擦除?

类型擦除指的是,你在给类型参数<T>赋值时,编译器会将实参类型擦除为Object(这里假设没有限定符,限定符下面会讲到)

所以这里我们要明白一个东西:虚拟机中没有泛型类型对象的概念,在它眼里所有对象都是普通对象

比如下面的代码

擦除前

public class EraseDemo<T> {
   private T t;
   public static void main(String[] args) {
       
  }
   public T getT(){
       return t;
  }
   public void setT(T t){
       this.t = t;
  }
}

擦除后

public class EraseDemo {
   private Object t;
   public static void main(String[] args) {
       
  }
   public Object getT(){
       return t;
  }
   public void setT(Object t){
       this.t = t;
  }
}

可以看到,T都变成了Object

泛型类被擦除后的类型,我们一般叫它原始类型(raw type),比如EraseDemo<T>擦除后的原始类型就是EraseDemo

相应的,如果你有两个数组列表,ArrayList<String>和ArrayList<Integer> ,编译器也会把两者都擦除为ArrayList

你可以通过代码来测试一下

ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass());// 这里会打印true

上面提到的限定符是干嘛的?

限定符就是用来限定边界的,如果泛型有设置边界,比如<T extends Animal>,那么擦除时,会擦除到第一个边界Animal类,而不是Object类

下面还是以上面的代码为例,展示下擦除前后的对比

擦除前:

public class EraseDemo<T extends Animal> {
   private T t;
   public static void main(String[] args) {
       
  }
   public T getT(){
       return t;
  }
   public void setT(T t){
       this.t = t;
  }
}

擦除后:

public class EraseDemo {
   private Animal t;
   public static void main(String[] args) {
       
  }
   public Animal getT(){
       return t;
  }
   public void setT(Animal t){
       this.t = t;
  }
}

这里的extends符号是表示继承的意思吗?

不是的,这里的extends只是表示前者是后者的一个子类,可以继承也可以实现

之所以用extends只是因为这个关键词已经内置在Java中了,且比较符合情景

如果自己再造一个关键词,比如sub,可能会使得某些旧代码产生问题(比如使用sub作为变量的代码)

为什么要擦除呢?

这其实不是想不想擦除的问题,而是不得不擦除的问题

因为旧代码是没有泛型概念的,这里的擦除主要是为了兼容旧代码,使得旧代码和新代码可以互相调用

泛型的应用场景

  • 从大的方向来说:

    • 用在类中:叫做泛型类,类名后面紧跟<类型参数>,比如ArrayList<E>
    • 用在方法中:叫做泛型方法,方法的返回值前面添加<类型参数>,比如:public <T> void  fun(T obj)

是不是想到了抽象类和抽象方法?

还是有区别的,抽象类和抽象方法是相互关联的,但是泛型类和泛型方法之间没有联系

  • 集中到类的方向来说:泛型多用在集合类中,比如ArrayList<E>

如果是自定义泛型的话,推荐用泛型方法,原因有二:

  • 脱离泛型类单独使用,使代码更加清晰(不用为了某个小功能而泛化整个类)
  • 泛型类中,静态方法无法使用类型参数;但是静态的泛型方法可以

通配符限定

这里主要介绍<T>, <? extends T>, <? super T>的区别

  • <T>:这个是最常用的,就是普通的类型参数,在调用时传入实际的类来替换T即可,这个实际的类可以是T,也可以是T的子类

比如List<String> list = new ArrayList<>();,这里的String就是实际传入的类,用来替换类型参数T

  • <? extends T>:这个属于通配符限定中的子类型限定,即传入实际的类必须是T或者T子类

乍一看,这个有点像<T>类型参数,都是往里放T或者T的子类;

但是区别还是挺多的,后面会列出

  • <? super T>:这个属于通配符限定中的超类型限定,即传入实际的类必须是T或者T的父类
  • <?>:这个属于无限定通配符,即它也不知道里面该放啥类型,所以干脆就不让你往里添加,只能获取(这一点类似<? extends T>)

下面用表格列出<T>,<? extends T>, <? super T>的几个比较明细的区别

  <T> <? extends T> <? super T>
类型擦除 传入实参时,实参被擦为Object,但是在get时编译器会自动转为T 擦到T 擦到Object
引用对象 不能将引用指向子类型或者父类型的对象,比如:List<Animal> list = new ArrayList<Cat>();//报错 能将引用指向子类型的对象,比如:List<? extends Animal> list = new ArrayList<Cat>(); 能将引用指向父类型的对象,比如:List<? super Cat> list = new ArrayList<Animal>();
添加数据 可以添加数据,T或者T的子类 不能 能,T或者T的子类

下面我们用代码来演示下

类型擦除:

// <T>类型,传入实参时,擦除为Object,但是get时还是实参的类型
List<Animal> list1 = new ArrayList<>();// 合法
list1.add(new Dog());// 合法
Animal animal = list1.get(0); // 这里不需要强转,虽然前面传入实参时被擦除为Object,但是get时编译器内部已经做了强制类型转换

// <? extends T> 子类型的通配符限定,擦除到T(整个过程不再变)
List<? extends Animal> list2 = list1;// 合法
Animal animal2 = list2.get(0); // 这里不需要强转,因为只擦除到T(即Animal)

// <? super T> 超类型的通配符限定,擦除到Object
List<? super Animal> list3 = list1; // 合法
Animal animal3 = (Animal)list3.get(0); // 需要手动强制,因为被擦除到Object

将引用指向子类型或父类型的对象:

// <T>类型,不能指向子类型或父类型
List<Animal> list = new ArrayList<Dog>();// 报错:需要的是List<Animal>,提供的是ArrayList<Dog>

// <? extends T> 子类型的通配符限定,指向子类型
List<? extends Animal> list2 = new ArrayList<Dog>();// 合法

// <? super T> 超类型的通配符限定,指向父类型
List<? super Dog> list3 = new ArrayList<Animal>(); // 合法

添加数据

// <T>类型,可以添加T或者T的子类型
List<Animal> list1 = new ArrayList<>();
list.add(new Dog());// 合法

// <? extends T> 子类型的通配符限定,不能添加元素
List<? extends Animal> list2 = new ArrayList<Dog>();// 正确
list2.add(new Dog()); // 报错:不能往里添加元素

// <? super T> 超类型的通配符限定,可以添加T或者T的子类型
List<? super Dog> list3 = new ArrayList<Animal>();
list3.add(new Dog()); // 合法,可以添加T类型的元素
list3.add(new Animal());//报错,不能添加父类型的元素

下面针对上面的测试结果进行解惑

先从<T>的报错开始吧

为啥<T>类型的引用不能指向子类型,比如 List<Animal> list = new ArrayList<Dog>();

首先说明一点,Animal和Dog虽然是父子关系(Dog继承Animal),但是List<Animal> 和 List<Dog>之间是没有任何关系的(有点像Java和Javascript)

他们之间的关系如下图

之所以这样设计,主要是为了类型安全的考虑

下面用代码演示,假设可以将List<Animal>指向子类List<Dog>

List<Animal> list = new ArrayList<Dog>();// 假设这里不报错
list.add(new Cat()); //这里把猫放到狗里面了

第二行可以看到,很明显,把猫放到狗里面是不对的,这就又回到了泛型真正出现之前的时期了(没有泛型,集合存取数据时不安全)

那为啥<? extends T>就能指向子类型呢?比如List<? extends Animal> list = new ArrayList<Dog>();

说的浅一点,原因是:这个通配符限定出现的目的就是为了解决上面的不能指向子类的问题

当然,这个原因说了跟没说一样。下面开始正经点解释吧

因为这个通配符限定不允许插入任何数据,所以当你指向子类型时,这个list就只能存放指向的那个集合里的数据了,而不能再往里添加;

自然的也就类型安全了,只能访问,不能添加

为什么<? extends T>不允许插入数据呢?

其实这个的原因跟上面的修改引用对象是相辅相成的,合起来就是为了保证泛型的类型安全性

考虑下面的代码

List<Animal> list = new ArrayList<>();
list.add(new Cat());
list.add(new Dog());
Dog d = (Dog) list.get(0); // 报错,转换异常

可以看到,插入的子类很混乱,导致提取时转型容易出错(这是泛型<T>的一个弊端,当然我们写的时候多用点心可能就不会这个问题)

但是有了<? extends T>之后,就不一样了

首先你可以通过修改引用的对象来使得list指向不同的Animal子类

其次你添加数据,不能直接添加,但是可以通过指向的Animal子类对象来添加

这样就保证了类型的安全性

代码如下:

// 定义一个Dog集合
List<Dog> listDog = new ArrayList<>();
listDog.add(new Dog());

// 让<? extends Animal>通配符限定的泛型 指向上面的Dog集合
List<? extends Animal> list2 = listDog;
// 这时如果想往里添加数据,只需要操作listDog即可,它可以保证类型安全
listDog.add(new Dog());
// 如果自己去添加,就会报错
list2.add(new Dog());// 报错

<? extends T>一般用在形参,这样我们需要哪个子类型,只需要传入对应子类的泛型对象就可以了,从而实现泛型中的多态

<? super T>为啥可以插入呢?

两个原因

  • 它只能插入T或者T的子类
  • 它的下限是T

也就是说你随便插入,我已经限制了你插入的类型为T或者T的子类

那么我在查询时,就可以放心的转为T或者T的父类

代码如下:

List<? super Dog> listDog = new ArrayList<>();
listDog.add(new Dog());
listDog.add(new Cat()); // 报错
listDog.add(new Animal()); // 报错
Dog dog = (Dog) listDog.get(0); // 内部被擦除为Object,所以要手动强转

为啥<T>获取时,编译器会自动强转转换,到了这里<? super T>,就要手动转换了呢?

这个可能是因为编译器也不确定你的要返回的T的父类是什么类型,所以干脆留给你自己来处理了

但是如果你把这个listDog指向一个父类的泛型对象,然后又在父类的泛型对象中,插入其他类型,那可就乱了(又回到<T>的问题了,要自己多注意)

比如:

List<Animal> list = new ArrayList<>();
list.add(new Cat()); // 加了Cat
// 指向Animal
List<? super Dog> listDog = list;
listDog.add(new Dog());
list.add(new Cat()); // 报错
list.add(new Animal()); // 报错

Dog dog = (Dog) listDog.get(0); //报错:转换异常Cat-》Dog

所以建议<? super T>在添加数据的时候,尽量集中在一个地方,不要多个地方添加,像上面的,要么都在<? super Dog>里添加数据,要么都在<Animal>中添加

动态类型安全检查

这个主要是为了跟旧代码兼容,对旧代码进行的一种类型安全检查,防止将Cat插入Dog集合中这种错误

这种检查是发生在编译阶段,这样就可以提早发现问题

对应的类为Collections工具类,方法如下图

代码如下

// 动态类型安全检查,在与旧代码兼容时,防止将Dog放到Cat集合中类似的问题

// === 检查之前 ===
List list = new ArrayList<Integer>();
// 添加不报错
list.add("a");
list.add(1);
// 只有用的时候,才会报错
Integer i = (Integer) list.get(0); // 这里运行时报错

// === 检查之后 ===
List list2 = Collections.checkedList(new ArrayList<>(), Integer.class);
// 插入时就会报错
list2.add("a"); // 这里编译时就报错,提前发现错误
list2.add(1);

总结

泛型的作用:

  • 提高类型安全性:预防各种类型转换问题
  • 增加程序可读性:所见即所得,看得到放进去的是啥,也知道会取出啥
  • 提高代码重用性:多种同类型的数据(比如Animal下的Dog,Cat)可以集合到一处来处理,从而调高代码重用性

类型擦除:

泛型T在传入实参时,实参的类型会被擦除为限定类型(即<? extends T>中的T),如果没有限定类型,则默认为Object

通配符限定:

  • <? extends T>:子类型的通配符限定,以查询为主,比如消费者集合场景
  • <? super T>:超类型的通配符限定,以添加为主,比如生产者集合场景

到此这篇关于Java中泛型学习之细节篇的文章就介绍到这了,更多相关Java中泛型细节内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java中的泛型详解

    所谓泛型:就是允许在定义类.接口指定类型形参,这个类型形参在将在声明变量.创建对象时确定(即传入实际的类型参数,也可称为类型实参) 泛型类或接口 "菱形"语法 复制代码 代码如下: //定义   public interface List<E> extends Collection<E>    public class HashMap<K,V> extends AbstractMap<K,V>  implements Map<K,V

  • 基于java中泛型的总结分析

    要我直接说出泛型是个what我还真讲不出来,这里先由一道问题引入: 定义一个坐标点类,要求能保存各种类型的数据,如:整形,浮点型,和字符串类型 既然变量类型起先不确定,那么很容易想到就是用所有类型的父类,也就是Object类来代替 不废话了,用代码来体现 实例1:用Object来实现不确定的数据类型输入 复制代码 代码如下: //这是定义的坐标点类class Point {    private Object x;    private Object y; //用Object来表示不确定的类型 

  • java泛型基本知识及通用方法

    泛型的基本使用 泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 这种参数类型可以用在类.接口和方法的创建中, 分别称为泛型类.泛型接口.泛型方法.  Java语言引入泛型的好处是安全简单. 今天就从以下几个方面介绍一下java的泛型: 基础, 泛型关键字, 泛型方法, 泛型类和接口. 基础: 通过集合的泛型了解泛型的基本使用 public void testBasis(){ List<String> list = new Array

  • Java中的泛型方法详解及简单实例

      java 泛型方法: 泛型是什么意思在这就不多说了,而Java中泛型类的定义也比较简单,例如:public class Test<T>{}.这样就定义了一个泛型类Test,在实例化该类时,必须指明泛型T的具体类型,例如:Test<Object> t = new Test<Object>();,指明泛型T的类型为Object. 但是Java中的泛型方法就比较复杂了. 泛型类,是在实例化类的时候指明泛型的具体类型:泛型方法,是在调用方法的时候指明泛型的具体类型.   定

  • Java泛型<T> T与T的使用方法详解

    泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样. 在集合框架(Collection framework)中泛型的身影随处可见.例如,Map 类允许向一个 Map 类型的实例添加任意类的对象,即使最常见的情况在给定映射(map)中保存一个string键值对. 命名类型参数 对于常见的泛型模式,推荐的泛型类型

  • Java泛型类与泛型方法的定义详解

    本文实例讲述了Java泛型类与泛型方法的定义.分享给大家供大家参考,具体如下: Java泛型类的定义 一 点睛 泛型类定义的语法如下: [访问修饰符] class 类名称 <T> 泛型类的主要作用在于类被实例化后,传入具体的类型参数,对类的成员属性的类型和成员方法的参数类型和返回值类型进行替换. 二 代码 public class Base<T> { T m; Base(T t) { m = t; } public T getM(){ return m; } public void

  • Java 获取泛型的类型实例详解

    Java 获取泛型的类型实例详解 Java 泛型实际上有很多缺陷,比如不能直接获取泛型的类型,不能获取带泛型类等. 以下方式是不正确的: ①.获取带泛型的类的类型 Class lstUClazz = List<User>.class ②获取局部变量泛型的类型 List<User> listUser = new ArrayList<User>(); Type genType = listUser.getClass().getClass().getGenericSuperc

  • Java泛型的用法及T.class的获取过程解析

    这篇文章主要介绍了Java泛型的用法及T.class的获取过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 胡乱总结泛型的四点作用: 第一是泛化,可以拿个T代表任意类型. 但GP是被C++严苛的静态性逼出来的,落到Java.C#这样的花语平原里----所有对象除几个原始类型外都派生于Object,再加上Java的反射功能,Java的Collection库没有范型一样过得好好的. 第二是泛型 + 反射,原本因为Java的泛型拿不到T.cla

  • JAVA利用泛型返回类型不同的对象方法

    有时需要在方法末尾返回类型不同的对象,而return 语句只能返回一个或一组类型一样的对象.此时就需要用到泛型. 首先先解释个概念, 元组:它是将一组对象直接打包存储于其中的一个单一对象,这个容器对象允许读取其中元素,但不能修改. 利用泛型创建元组 public class ReturnTwo<A,B> { public final A first; public final B second; public ReturnTwo(A a,B b) { first = a; second = b

  • Java中泛型学习之细节篇

    目录 简介 正文 什么是类型参数 为啥要有泛型 泛型的演变史 类型擦除 泛型的应用场景 通配符限定 动态类型安全检查 总结 简介 泛型的作用就是把类型参数化,也就是我们常说的类型参数 平时我们接触的普通方法的参数,比如public void fun(String s):参数的类型是String,是固定的 现在泛型的作用就是再将String定义为可变的参数,即定义一个类型参数T,比如public static <T> void fun(T t);这时参数的类型就是T的类型,是不固定的 从上面的S

  • Java中jqGrid 学习笔记整理——进阶篇(二)

    相关阅读: Java中jqGrid 学习笔记整理--进阶篇(一) 本篇开始正式与后台(java语言)进行数据交互,使用的平台为 JDK:java 1.8.0_71 myEclisp 2015 Stable 2.0 Apache Tomcat-8.0.30 Mysql 5.7 Navicat for mysql 11.2.5(mysql数据库管理工具) 一.数据库部分 1.创建数据库 使用Navicat for mysql创建数据库(使用其他工具或直接使用命令行暂不介绍) 2. 2.创建表 双击打

  • Java 中泛型 T 和 ? 的区别详解

    目录 泛型中 T 类型变量 和 ? 通配符 区别 Generic Types 类型变量 用法 2.声明通用的方法 – 泛型方法: 有界类型参数 Wildcards 通配符 1.上界通配符:? extend 上界类型 2.无界通配符:? 3.下界通配符:? super 子类 类型擦除 泛型中 T 类型变量 和 ? 通配符 区别 定义不同 :T 是类型变量,? 是通配符 使用范围不同: ? 通配符用作 参数类型.字段类型.局部变量类型,有时作为返回类型(但请避免这样做) T 用作 声明类的类型参数.

  • Java中泛型的用法总结

    本文实例总结了Java中泛型的用法.分享给大家供大家参考.具体如下: 1 基本使用 public interface List<E> { void add(E); Iterator<E> iterator(); } 2 泛型与子类 Child是Parent的子类,List<Child>却不是List<Parent>的子类. 因此:List<Object> list = new ArrayList<String>()是错误的. 如果上面

  • Java中泛型使用实例详解

    Java中泛型使用 泛型作用: 泛型:集合类添加对象不用强转 反射机制:将泛型固定的类的所有方法和成员全部显示出来 核心代码: ArrayList<Ls> ff=new ArrayList()<Ls>; Ls ls1=new Ls("薯片",5f); ff.add(ls1); Ls cls=ff.get(0);//这里不再需要强转 代码实例: 说明:这是非泛型的代码,集合类中调用对象时需要强转 import java.util.*; public class L

  • Java中泛型通配符的使用方法示例

    本文实例讲述了Java中泛型通配符的使用方法.分享给大家供大家参考,具体如下: 一 点睛 引入通配符可以在泛型实例化时更加灵活地控制,也可以在方法中控制方法的参数. 语法如下: 泛型类名<? extends T> 或 泛型类名<? super T> 或 泛型类名<?> ? extends T:表示T或T的子类 ? super T:表示T或T的父类 ?:表示可以是任意类型 二 通配符在泛型类创建泛型对象中使用 1 代码 class gent<T> { publ

  • Java中泛型总结(推荐)

    Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数. 泛型类 范例:泛型类的基本语法 class MyClass<T> { T value1; } 尖括号 <> 中的 T 被称作是类型参数,用于指代任何类型.实际上这个T你可以任意写,但出于规范的目的,Java还是建议我们用单个大写字母来代表类型参数.常见的如: T 代表

  • 详解Java 中泛型的实现原理

    泛型是 Java 开发中常用的技术,了解泛型的几种形式和实现泛型的基本原理,有助于写出更优质的代码.本文总结了 Java 泛型的三种形式以及泛型实现原理. 泛型 泛型的本质是对类型进行参数化,在代码逻辑不关注具体的数据类型时使用.例如:实现一个通用的排序算法,此时关注的是算法本身,而非排序的对象的类型. 泛型方法 如下定义了一个泛型方法, 声明了一个类型变量,它可以应用于参数,返回值,和方法内的代码逻辑. class GenericMethod{ public <T> T[] sort(T[]

  • Java中泛型的示例详解

    目录 泛型概述 使用泛型的好处 泛型的定义与使用 定义和使用含有泛型的类 含有泛型的方法 含有泛型的接口 泛型通配符 通配符基本使用 通配符高级使用----受限泛型 泛型概述 我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型.当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换. 大家观察下面代码: public class GenericDemo { public static void main(String[] args) {

  • Java中泛型使用的简单方法介绍

    一. 泛型是什么 "泛型",顾名思义,"泛指的类型".我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如List<Integer> iniData = new ArrayList<>().

随机推荐