下面我总结了集合、泛型、数组转集合等一些常见的陷进,认真看完,相信你绝对有所收获。

1、List ,List<?> 与 List<Object> 有区别吗?

说实话,我敢保证很多人是不知道 List, List<?> 与 List<Object> 之间的区别的。

1、我们先来看看 List 与 List<Object>

很多可能觉得 List<Object>的用法与 List 是一样的,例如很多人认为

List<Object> list;

List list;

这两种定义方法是一模一样的,然而他们是不一样的。看下面一段代码

    List<Integer> t1 = new ArrayList<>();
// 编译通过
List t2 = t1;
//编译失败
List<Object> t3 = t1;

t1 可以赋给 t2, 但是 t1 不能赋给 t3,会抛出如下异常

从这里可以看出

List list;

List<Object> list;

是有区别的,List 变量可以接受任何泛型的变量,而 List 则不可以。

2、我们在看看 Lis<?> 有什么需要注意的地方:

看下面一段代码:

    List<Object> t1 = new ArrayList<>();
List<?> t2 = t1;
// 编译通过
t2.remove(0);
t2.clear();
// 编译不通过
t2.add(new Object());

List<?> 是一个泛型,在没有赋值之前,是可以接受任何集合的赋值的,我想这点大家都知道,但是请注意,赋值之后就不能往里面添加元素了,提示如下错误:

所以 List<?> 一般用来作为参数来接受外部的集合,或者返回一个不知道具体元素的集合。

List 与 List<?>, List<Object> 的细微区别知道了吧?

2、<? extends T> 与 <? super T>你真的懂吗?

我们知道泛型 List<T> 只能放置一种类型,如果你采用 List<Object> 来放置多种类型,然后再进行类型强制转换的话,那会失去了泛型的初衷。

为了能够放置多种类型,于是有了 <? extend T> 与 <? super T>,下面先说一些你可能原本就知道的知识:

1、对于 <? extends T> a,a 这个变量可以接受 T 及其 T 子类的集合,上界为 T,并且从 a 取出来的类型都会被强制转换为 T。重点看下面一个例子:

注意:我们先约定 Cat(猫) 继承自 Animal(动物),RedCat(黑猫) 继承自 Cat

    List<Animal> animals = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
List<RedCat> redCats = new ArrayList<>();
// 可以通过编译
List<? extends Cat> extendsCat = redCats;
// 不能通过编译,因为只能接受 Cat 及其子类的集合
extendsCat = animals; // 重点注意:下面三行都不能通过编译
extendsCat.add(new Animal());
extendsCat.add(new Cat());
extendsCat.add(new RedCat());
// 重点注意:可以通过编译
extendsCat.add(null);

注意,<? extends T>最需要注意的是,就是不能向里面添加除null之外的其他所有元素,这个和 List<?> 有点类似。

2、现在说说 <? super T>,它和 <? extends T> 有点相反。对于 <? super T> a,a 这个变量可以接受 T 及其 T 父类的集合,下界为 T,并且从 a 取出来的类型都会被强制转换为 Object。重点看下面一个例子:

    List<Animal> animals = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
List<RedCat> redCats = new ArrayList<>();
// 可以通过编译
List<? super Cat> superCat = animals;
// 不能通过编译,因为只能接受 Cat 及其父类的集合
superCat = redCats; // 重点注意:不能通过编译,只能添加 Cat 及其 Cat 的子类
superCat.add(new Animal());
// 重点注意,可以通过编译
superCat.add(new Cat());
superCat.add(new RedCat());
superCat.add(null);

注意,<? super T>最需要注意的是,在虽然可以接受 T 及其父类的赋值,但是只能向里面添加 T 及其 T 的子类

总结

1、List<? extends T> a ,可以把 a 及其 a 的子类赋给 a,从 a 里取的元素都会被强制转换为 T 类型,不过需要注意的是,不能向 a 添加任何除 null 外是元素

2、List<? super T> a ,可以把 a 及其 a 的父类赋给 a,从 a 里取的元素都会被强制转换为 Object 类型,不过需要注意的是,可以向 a 添加元素,但添加的只能是 T 及其子类元素

3、泛型与重载

我们先来看一道题,你觉得下面这道题能够编译通过吗?

 public class GernerTypes {
public static void method(List<Integer> list) {
System.out.println("List<Integer> list");
}
public static void method(List<String> list) {
System.out.println("List<String> list");
}
}

答是编译不通过

两个方法的参数不同,为什么会重载不通过呢?

实际上在 Java 的泛型中,泛型只存在于源码中,在编译后的字节码中,泛型已经被替换为原生类型了,并且在相应的地方插入了强制转换的代码。为了方便理解,可以看下面的一段代码例子:

 // 源码
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
System.out.println(list.get(0));
}

编译之后泛型就不存在了,并且在相应的地方插入了强制转换的代码,编译之后,我们反编译的代码如下:

     // 反编译之后的代码
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
System.out.println((Integer)list.get(0));
}

这种 编译之后泛型就不存在了,并且在相应的地方插入了强制转换代码的机制我们也称之为擦除

所以上面的两个方法,看似参数不一样,但是经过编译擦出之后,他们的参数就是一样的了,所以编译不通过。

4、数组与集合相互转换时需要注意的点

1、数组转集合

大家先看一个例子吧,

    public static void main(String[] args) {
String[] arr = {"one", "two", "three"};
// 数组转换成集合
List<String> list = Arrays.asList(arr);
// 向集合添加元素:编译正常,但运行时抛出了异常
list.add("four");
}

向集合添加元素抛出了如下异常:

问题来了,向集合添加元素为啥会抛出异常呢??

我们先来看一下 Arrays.asList(arr) 方法究竟返回了什么?

源码如下:

返回的明明是 ArrayList 啊,为啥就不能添加元素呢??

实际上,此 ArrayList 非彼 ArrayList,这个返回的 ArrayList 实际上是 Arrays 的一个内部类。该内部类也是十分简单,和真实的那个 ArrayList 没得比,部分源码如下:

而且这个假的 ArrayList 是直接 引用原数组的,不然你看它的构造器(第二条画线)

也就是说,ArrayList 内部是直接引用 arr 数组,你对 arr 数组进行改变,也会同时改变到 list 集合。

下面的代码证明这一点

    public static void main(String[] args) {
String[] arr = {"one", "two", "three"};
// 数组转换成集合
List<String> list = Arrays.asList(arr);
// 修改 arr
arr[0] = "0";
//打印看看
System.out.println(list.get(0));
}

打印结果是 “0”。

所以,我们向 list 添加元素肯定失败,因为 arr 数组的长度了 3 ,本来就有 3 个元素了,你在向里面添加第四个元素,肯定是不行的。

所以,在把数组转换为集合的过程中,需要特别注意。

建议大家这样转换比较安全

List<String> list = new ArrayList<>(Arrays.asList(arr));

2、集合转数组

集合转换为数组相对比较不苛刻,我就不拉很多源码来进行分析了,我只简单说下几个需要注意的地方。例如对于下面这个转换:

    // 集合大小为 size
List<String> list = new ArrayList<>();
// 长度为 n 的数组
String[] arr = new String[n];
// 进行转换
list.toArray(arr);

1、如果数组长度比集合小:由于 arr 的长度不够,所以集合里的元素不会赋给 arr,而且自己再重新创建一个新数组反回去。

2、如果数组长度不小于集合:此时 arr 的长度够了,所以集合里的元素直接复制给 arr 数组,不会重新创建一个新的元素。

一览源码:

public <T> T[] toArray(T[] a) {
if (a.length < size)
// 重新创建一个数组来返回去
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
// 长度够的话直接复制给 a
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

以上这些陷进相信有不少人是不知道了,我把它总结整理了出来,希望大家看完能够有所收获。

下面我总结了集合、泛型、数组转集合等一些常见的陷进,认真看完,相信你绝对有所收获。

1、List ,List<?> 与 List<Object> 有区别吗?

说实话,我敢保证很多人是不知道 List, List<?> 与 List<Object> 之间的区别的。

1、我们先来看看 List 与 List<Object>

很多可能觉得 List<Object>的用法与 List 是一样的,例如很多人认为

List<Object> list;

List list;

这两种定义方法是一模一样的,然而他们是不一样的。看下面一段代码

    List<Integer> t1 = new ArrayList<>();
// 编译通过
List t2 = t1;
//编译失败
List<Object> t3 = t1;

t1 可以赋给 t2, 但是 t1 不能赋给 t3,会抛出如下异常

从这里可以看出

List list;

List<Object> list;

是有区别的,List 变量可以接受任何泛型的变量,而 List 则不可以。

2、我们在看看 Lis<?> 有什么需要注意的地方:

看下面一段代码:

    List<Object> t1 = new ArrayList<>();
List<?> t2 = t1;
// 编译通过
t2.remove(0);
t2.clear();
// 编译不通过
t2.add(new Object());

List<?> 是一个泛型,在没有赋值之前,是可以接受任何集合的赋值的,我想这点大家都知道,但是请注意,赋值之后就不能往里面添加元素了,提示如下错误:

所以 List<?> 一般用来作为参数来接受外部的集合,或者返回一个不知道具体元素的集合。

List 与 List<?>, List<Object> 的细微区别知道了吧?

2、<? extends T> 与 <? super T>你真的懂吗?

我们知道泛型 List<T> 只能放置一种类型,如果你采用 List<Object> 来放置多种类型,然后再进行类型强制转换的话,那会失去了泛型的初衷。

为了能够放置多种类型,于是有了 <? extend T> 与 <? super T>,下面先说一些你可能原本就知道的知识:

1、对于 <? extends T> a,a 这个变量可以接受 T 及其 T 子类的集合,上界为 T,并且从 a 取出来的类型都会被强制转换为 T。重点看下面一个例子:

注意:我们先约定 Cat(猫) 继承自 Animal(动物),RedCat(黑猫) 继承自 Cat

    List<Animal> animals = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
List<RedCat> redCats = new ArrayList<>();
// 可以通过编译
List<? extends Cat> extendsCat = redCats;
// 不能通过编译,因为只能接受 Cat 及其子类的集合
extendsCat = animals; // 重点注意:下面三行都不能通过编译
extendsCat.add(new Animal());
extendsCat.add(new Cat());
extendsCat.add(new RedCat());
// 重点注意:可以通过编译
extendsCat.add(null);

注意,<? extends T>最需要注意的是,就是不能向里面添加除null之外的其他所有元素,这个和 List<?> 有点类似。

2、现在说说 <? super T>,它和 <? extends T> 有点相反。对于 <? super T> a,a 这个变量可以接受 T 及其 T 父类的集合,下界为 T,并且从 a 取出来的类型都会被强制转换为 Object。重点看下面一个例子:

    List<Animal> animals = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
List<RedCat> redCats = new ArrayList<>();
// 可以通过编译
List<? super Cat> superCat = animals;
// 不能通过编译,因为只能接受 Cat 及其父类的集合
superCat = redCats; // 重点注意:不能通过编译,只能添加 Cat 及其 Cat 的子类
superCat.add(new Animal());
// 重点注意,可以通过编译
superCat.add(new Cat());
superCat.add(new RedCat());
superCat.add(null);

注意,<? super T>最需要注意的是,在虽然可以接受 T 及其父类的赋值,但是只能向里面添加 T 及其 T 的子类

总结

1、List<? extends T> a ,可以把 a 及其 a 的子类赋给 a,从 a 里取的元素都会被强制转换为 T 类型,不过需要注意的是,不能向 a 添加任何除 null 外是元素

2、List<? super T> a ,可以把 a 及其 a 的父类赋给 a,从 a 里取的元素都会被强制转换为 Object 类型,不过需要注意的是,可以向 a 添加元素,但添加的只能是 T 及其子类元素

3、泛型与重载

我们先来看一道题,你觉得下面这道题能够编译通过吗?

 public class GernerTypes {
public static void method(List<Integer> list) {
System.out.println("List<Integer> list");
}
public static void method(List<String> list) {
System.out.println("List<String> list");
}
}

答是编译不通过

两个方法的参数不同,为什么会重载不通过呢?

实际上在 Java 的泛型中,泛型只存在于源码中,在编译后的字节码中,泛型已经被替换为原生类型了,并且在相应的地方插入了强制转换的代码。为了方便理解,可以看下面的一段代码例子:

 // 源码
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
System.out.println(list.get(0));
}

编译之后泛型就不存在了,并且在相应的地方插入了强制转换的代码,编译之后,我们反编译的代码如下:

     // 反编译之后的代码
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
System.out.println((Integer)list.get(0));
}

这种 编译之后泛型就不存在了,并且在相应的地方插入了强制转换代码的机制我们也称之为擦除

所以上面的两个方法,看似参数不一样,但是经过编译擦出之后,他们的参数就是一样的了,所以编译不通过。

4、数组与集合相互转换时需要注意的点

1、数组转集合

大家先看一个例子吧,

    public static void main(String[] args) {
String[] arr = {"one", "two", "three"};
// 数组转换成集合
List<String> list = Arrays.asList(arr);
// 向集合添加元素:编译正常,但运行时抛出了异常
list.add("four");
}

向集合添加元素抛出了如下异常:

问题来了,向集合添加元素为啥会抛出异常呢??

我们先来看一下 Arrays.asList(arr) 方法究竟返回了什么?

源码如下:

返回的明明是 ArrayList 啊,为啥就不能添加元素呢??

实际上,此 ArrayList 非彼 ArrayList,这个返回的 ArrayList 实际上是 Arrays 的一个内部类。该内部类也是十分简单,和真实的那个 ArrayList 没得比,部分源码如下:

而且这个假的 ArrayList 是直接 引用原数组的,不然你看它的构造器(第二条画线)

也就是说,ArrayList 内部是直接引用 arr 数组,你对 arr 数组进行改变,也会同时改变到 list 集合。

下面的代码证明这一点

    public static void main(String[] args) {
String[] arr = {"one", "two", "three"};
// 数组转换成集合
List<String> list = Arrays.asList(arr);
// 修改 arr
arr[0] = "0";
//打印看看
System.out.println(list.get(0));
}

打印结果是 “0”。

所以,我们向 list 添加元素肯定失败,因为 arr 数组的长度了 3 ,本来就有 3 个元素了,你在向里面添加第四个元素,肯定是不行的。

所以,在把数组转换为集合的过程中,需要特别注意。

建议大家这样转换比较安全

List<String> list = new ArrayList<>(Arrays.asList(arr));

2、集合转数组

集合转换为数组相对比较不苛刻,我就不拉很多源码来进行分析了,我只简单说下几个需要注意的地方。例如对于下面这个转换:

    // 集合大小为 size
List<String> list = new ArrayList<>();
// 长度为 n 的数组
String[] arr = new String[n];
// 进行转换
list.toArray(arr);

1、如果数组长度比集合小:由于 arr 的长度不够,所以集合里的元素不会赋给 arr,而且自己再重新创建一个新数组反回去。

2、如果数组长度不小于集合:此时 arr 的长度够了,所以集合里的元素直接复制给 arr 数组,不会重新创建一个新的元素。

一览源码:

public <T> T[] toArray(T[] a) {
if (a.length < size)
// 重新创建一个数组来返回去
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
// 长度够的话直接复制给 a
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

以上这些陷进相信有不少人是不知道了,我把它总结整理了出来,如果大家看完觉得有收获,不妨点个底部小卡片 + 点赞鼓励我一下?

Java集合与泛型中的几个陷阱,你掉进了几个?的更多相关文章

  1. Java集合与泛型中的陷阱

    List,List<Object>区别 List<Integer> t1 = new ArrayList<>(); // 编译通过 List t2 = t1; // ...

  2. Java集合之泛型的使用

    Java集合之泛型的使用 泛型提供了一种轻便灵活的数据操作,数据的安全性相对提高. 泛型提供了对列表元素的约束条件,比如ArrayList有序链表,可存储任意类型的元素. 此处构建一个ArrayLis ...

  3. 浅入深出之Java集合框架(中)

    Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...

  4. Java集合和泛型

    集合 常用的集合有ArrayList,TreeSet,HashMap,HashSet. ArrayList 最常用的集合,每次插入都在后面追加元素. TreeSet 以有序状态保持并可防止重复.当你需 ...

  5. Java 集合和泛型

    一.集合(Collections) Java使用集合来组织和管理对象. 1.Java的集合类 集合类主要负责保存.盛装和管理对象,因此集合类也被称为容器类. 集合类分为Set.List.Map和Que ...

  6. java——集合、泛型、ArrayList、LinkedList、foreach循环、模拟ktv点歌系统

    集合:一系列特殊的类,这些类可以存储任意类型的对象,长度可变,集合类都在java.util包中. 但是集合记不住对象的类型,当把对象从集合中取出时这个对象的编译类型就变成了Object类型.这样在取元 ...

  7. Java 集合框架_中

    Set接口 特点: [1]Set接口表示一个唯一.无序的容器(和添加顺序无关) Set接口常用实现类有 HashSet [1]HashSet是Set接口的实现类,底层数据结构是哈希表. [2]Hash ...

  8. java List.subList方法中的超级大陷阱

    ArrayList 中 subList 的基本用法: subList(fromIndex:int,toIndex:int):List<E> 返回从fromIndex到toindex-1 的 ...

  9. Java泛型和集合之泛型介绍

    在声明一个接口和类的时候可以使用尖括号带有一个或者多个参数但是当你在声明属于一个接口或者类的变量的时候或者你在创建一个类实例的时候需要提供他们的具体类型.我们来看下下面这个例子 List<Str ...

随机推荐

  1. composer的安装方法

    网上说的方法几乎都不正确,经作者总结,终于知道怎么使用composer的方法.第一,从http://docs.phpcomposer.com/下载安装包:composer.phar 第二,把安装包放在 ...

  2. I/O-----字符输入流

    今天学习了字符流  ,果不其然又和以前的搞混了 package io.day04; import java.io.FileReader; import java.io.FileWriter; impo ...

  3. 「SDOI 2018」战略游戏

    题目大意: 给一个$G=(V,E)$,满足$|V|=n$,$|E|=m$,且保证图联通,有Q个询问,每组询问有s个点,求图中有多少点满足:将其删去后,这s个点中存在一对点集$(a,b)$不联通且删去点 ...

  4. [Usaco2005 dec]Layout 排队布局 差分约束

    填坑- 差分约束一般是搞一个不等式组,求xn-x1的最大最小值什么的,求最大值就转化成xa<=xb+w这样的,然后建图跑最短路(这才是最终约束的),举个例子 x1<=x0+2x2<= ...

  5. HTTP VISUAL HTTP请求可视化工具、HTTP快照工具(公测)

    先啰嗦几句,最近工作比较忙,再加上自己又开设了一个小站(简单点),没时间写博客,都快憋坏了,趁着周末有时间,抓紧来一篇~ HTTP VISUAL是一款HTTP可视化工具,它可以记录HTTP请求,包括请 ...

  6. JAVA中正则表达式总结

    昨天,我的朋友请教我正则表达式.我也好久没有写过正则表达式了,昨天刚好看了下如鹏网创始人杨中科老师关于正则表达式的讲解.使我加深了正则表达式的印像.现我把他总结下: 许多语言,包括Perl.PHP.P ...

  7. nodejs-5.1 ejs模板引擎

    ejs官方文档:https://ejs.bootcss.com/ 1.什么是 EJS? "E" 代表 "effective",即[高效]. EJS 是一套简单的 ...

  8. 大数据小视角2:ORCFile与Parquet,开源圈背后的生意

    上一篇文章聊了聊基于PAX的混合存储结构的RCFile,其实这里笔者还了解一些八卦,RCfile的主力团队都是来自中科院的童鞋在Facebook完成的,算是一个由华人主导的编码项目.但是RCfile仍 ...

  9. kubernetes 微服务西游记(持续更新中...)

    随着微服务架构的流行,迈向云原生的趋势,容器化微服务就成为了持续集成最好的手段,镜像成为了持续交付最好的产物,容器成为了镜像运行最好的环境,kubernetes成了部署容器最好的生态系统和规范.实践出 ...

  10. Vue 进阶之路(六)

    上篇文章我们分析了一下 vue 中的条件渲染,本篇我们说一下 vue 中的列表渲染和 set 方法. <!DOCTYPE html> <html lang="en" ...