简介

泛型是JDK 5引入的概念,泛型的引入主要是为了保证java中类型的安全性,有点像C++中的模板。

但是Java为了保证向下兼容性,它的泛型全部都是在编译期间实现的。编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码。这种就叫做类型擦除。编译器在编译的过程中执行类型检查来保证类型安全,但是在随后的字节码生成之前将其擦除。

这样就会带来让人困惑的结果。本文将会详细讲解泛型在java中的使用,以避免进入误区。

泛型和协变

有关协变和逆变的详细说明可以参考:

深入理解协变和逆变

这里我再总结一下,协变和逆变只有在类型声明中的类型参数里才有意义,对参数化的方法没有意义,因为该标记影响的是子类继承行为,而方法没有子类。

当然java中没有显示的表示参数类型是协变还是逆变。

协变意思是如果有两个类 A<T> 和 A<C>, 其中C是T的子类,那么我们可以用A<C>来替代A<T>。

逆变就是相反的关系。

Java中数组就是协变的,比如Integer是Number的子类,那么Integer[]也是 Number[]的子类,我们可以在需要 Number[] 的时候传入 Integer[]。

接下来我们考虑泛型的情况,List<Number> 是不是 List<Integer>的父类呢?很遗憾,并不是。

我们得出这样一个结论:泛型不是协变的。

为什么呢?我们举个例子:

List<Integer> integerList = new ArrayList<>();
List<Number> numberList = integerList; // compile error
numberList.add(new Float(1.111));

假如integerList可以赋值给numberList,那么numberList可以添加任意Number类型,比如Float,这样就违背了泛型的初衷,向Integer list中添加了Float。所以上面的操作是不被允许的。

刚刚我们讲到Array是协变的,如果在Array中带入泛型,则会发生编译错误。比如new List<String>[10]是不合法的,但是 new List<?>[10]是可以的。因为在泛型中?表示的是未知类型。


List<?>[] list1 = new List<?>[10]; List<String>[] list2 = new List<String>[10]; //compile error

泛型在使用中会遇到的问题

因为类型擦除的原因,List<String>和List<Integer>在运行是都会被当做成为List。所以我们在使用泛型时候的一些操作会遇到问题。

假如我们有一个泛型的类,类中有一个方法,方法的参数是泛型,我们想在这个方法中对泛型参数进行一个拷贝操作。

public class CustUser<T> {

    public void useT(T param){
T copy = new T(param); // compile error
}
}

上面操作会编译失败,因为我们并不知道T是什么,也不知道T到底有没有相应的构造函数。

直接clone T是没有办法了,如果我们想copy一个Set,set中的类型是未定义的该怎么做呢?

    public void useTSet(Set<?> set){
Set<?> copy1 = new HashSet<?>(set); // compile error
Set<?> copy2 = new HashSet<>(set);
Set<?> copy3 = new HashSet<Object>(set);
}

可以看到?是不能直接用于实例化的。但是我们可以用下面的两种方式代替。

再看看Array的使用:

    public void useArray(){
T[] typeArray1= new T[20]; //compile error
T[] typeArray2=(T[]) new Object[20];
T[] typeArray3 = (T[]) Array.newInstance(String.class, 20);
}

同样的,T是不能直接用于实例化的,但是我们可以用下面两种方式代替。

类型擦除要注意的事项

因为类型擦除的原因,我们在接口实现中,实现同一个接口的两个不同类型是无意义的:

public class someClass implements Comparable<Number>, Comparable<String> { ... } // no

因为在编译过后的字节码看来,两个Comparable是一样的。

同样的,我们使用T来做类型强制转换也是没有意义的:

public <T> T cast(T t, Object o) { return (T) o; }

因为编译器并不知道这个强制转换是对还是错。

总结

本文讨论了泛型在java中使用中可能会存在的问题,希望大家能够喜欢。

本文的例子https://github.com/ddean2009/learn-java-collections

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/java-generics-in-deep/

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

深入理解java的泛型的更多相关文章

  1. 深入理解Java之泛型

    原文出处: absfree 1. Why ——引入泛型机制的原因 假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过 ...

  2. 你是怎么理解java的泛型的?

    解答: 在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知 ...

  3. 万字长文深入理解java中的集合-附PDF下载

    目录 1. 前言 2. List 2.1 fail-safe fail-fast知多少 2.1.1 Fail-fast Iterator 2.1.2 Fail-fast 的原理 2.1.3 Fail- ...

  4. 转:理解Java泛型

    JDK 5.0 中增加的泛型类型,是 Java 语言中类型安全的一次重要改进.但是,对于初次使用泛型类型的用户来说,泛型的某些方面看起来可能不容易明白,甚至非常奇怪.在本月的“Java 理论和实践”中 ...

  5. 夯实Java基础系列13:深入理解Java中的泛型

    目录 泛型概述 一个栗子 特性 泛型的使用方式 泛型类 泛型接口 泛型通配符 泛型方法 泛型方法的基本用法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型方法总结 泛型上下边界 泛型常见面试 ...

  6. Java 干货之深入理解Java泛型

    一般的类和方法,只能使用具体的类型,要么是基本类型,要么是自定义的类.如果要编写可以应用多中类型的代码,这种刻板的限制对代码得束缚会就会很大. ---<Thinking in Java> ...

  7. 如何深入理解Java泛型

    一.泛型的作用与定义 1.1泛型的作用 使用泛型能写出更加灵活通用的代码泛型的设计主要参照了C++的模板,旨在能让人写出更加通用化,更加灵活的代码.模板/泛型代码,就好像做雕塑时的模板,有了模板,需要 ...

  8. 《深入理解Java虚拟机》类文件结构

    上节学习回顾 在上一节当中,主要以自己的工作环境简单地介绍了一下自身的一些调优或者说是故障处理经验.所谓百变不离其宗,这个宗就是我们解决问题的思路了. 本节学习重点 在前面几章,我们宏观地了解了虚拟机 ...

  9. 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...

  10. 如何理解 Java 中的 <T extends Comparable<? super T>>

    Java 中类似 <T extends Comparable<? super T>> 这样的类型参数 (Type Parameter) 在 JDK 中或工具类方法中经常能看到. ...

随机推荐

  1. HttpURLConnection使用分析

    在项目中遇到各种版本的httpClient,所以想了解一下httpClient的实现 首先查看的是JDK1.1中自带的HttpURLConnection,看一下最初的设计是怎么样的 代码分析 使用Ht ...

  2. Jetpack Compose(1) —— Android 全新的 UI 框架

    写在前面 Jetpack Compose 已经不是什么新技术了,Google 早在 2019 年就推出 Jetpack Compose 的首个 alpha 版本,时至今日,相当大比例的国内 Andro ...

  3. 高效的PDF文字提取技术

    无论是行政法规.学术论文还是企业合同,PDF文档为我们提供了一种便捷.稳定的信息传递方式.然而,从PDF文件中提取文本信息对于数据分析.内容编辑等后续处理来说至关重要. PDF文本提取技术是一种可以从 ...

  4. 【LeetCode二叉树#01】二叉树的遍历(递归/迭代)

    二叉树递归遍历 写递归算法时候需要遵循的三个点: 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函 ...

  5. Golang标准库之bytes介绍

    本次主要介绍golang中的标准库bytes,基本上参考了 字节 | bytes .Golang标准库--bytes 文章. bytes库主要包含 5 大部分,即: 常量 变量 函数 Buffer R ...

  6. go grpc流式和非流式的例子

    参考grpc官方:  https://grpc.io/docs/quickstart/go.html 或官方中文翻译: http://doc.oschina.net/grpc?t=60133 安装pr ...

  7. docker部署监控Prometheus+Grafana

    目录 一.Prometheus简介 二.Prometheus基本原理 三.Prometheus架构图 四.Prometheus特性 五.Prometheus组件 六.Prometheus服务发现 七. ...

  8. 树形dp套路

    我们知道dp也就是动态规划的思想就是先解决小问题,通过不断的解决小问题,最终解决大问题.那么能够应用树形dp套路的题目都应该符合一个条件,那就是通过解决每个子树的小问题,最终解决整棵树的大问题. 套路 ...

  9. 【MongoDB详细步骤】(内附源码)

    第01章-MongoDB 1.安装和启动(docker方式) 1.1.拉取镜像 docker pull mongo:4.4.8 1.2.创建和启动容器 docker run -d --restart= ...

  10. springboot+kaptcha生成数学运算验证码和字符验证码

    使用以下代码只需要复制粘贴,修改一处文本生成器路径即可,文中有交代. 1.添加kaptcha依赖 <dependency> <groupId>com.github.penggl ...