前言

​ 我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的。通透理解泛型是学好基础里面中非常重要的。于是,我对《Java编程思想》这本书中泛型章节进行了研读。可惜遗憾的是,自己没有太多的经验,有些东西看了几次也是有点懵。只能以后有机会,再进行学习了。但是自己也理解了挺多的。下面就是自己对于泛型的理解与感悟。如有不对,望指出。

概念

由来: Java 一开始设计之初是没有泛型这个特性的,直到jdk 1.5中引入了这个特性。Java 的泛型是由擦除来实现的。要知道擦除是什么?往下看。

概念:一般的类和方法,只能使用具体的类型;要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。泛型实现了参数化类型的概念,使代码应用于多个类型。泛型在编程语言中出现时,其最初的目的是希望类和方法具有广泛的表达能力。

简单泛型

​ 有很多原因促成泛型的出现,其中最重要的一个原因就是为了创造容器类。我们暂时不指定类型,而是稍后再决定具体使用什么类型。要达到这个目的,需要使用类型参数,用尖括号括住,放在类名的后面。然后使用这个类的时候,再用实际的类型替换此类型参数。在下面例子中,T就是类型参数。代码如下:

  1. public class Holder<T> {
  2. private T a;
  3. public Holder(T a) {
  4. this.a = a;
  5. }
  6. public void set(T a) {
  7. this.a = a;
  8. }
  9. public T get(){
  10. return a;
  11. }
  12. public static void main(String[] args) {
  13. Holder<String> h = new Holder<>();
  14. h3.set("hello");
  15. String a = h3.get();
  16. }
  17. }
  18. class Automobile{}

​ 但是往往很多的源码中一些通用的类都是有多个泛型参数,譬如 java.util.function.BiFunction 中就有三个类型参数 T,U,R 。

接口泛型

​ 泛型在接口上的运用是非常多的,例如迭代器(Iterable)中的 Iterator 。

  1. public interface Itreator<T>{
  2. // 判断是否还有元素
  3. boolean hasNext();
  4. // 洗一个元素
  5. E next();
  6. ....
  7. }

​ 这个是我们都很常用的吧,其实接口使用泛型和类使用泛型没什么区别。

泛型方法

​ 泛型方法,在返回参数类型前面添加泛型参数列表,由<>括着

eg:

  1. // 泛型方法 两个泛型参数,T,R 返回类型为 R
  2. public <T, R> R test(T t, R r){
  3. return r;
  4. }

​ 泛型方法使得该方法独立于类而产生变化。在需要编写泛型代码的时候,基本的的指导原则是:无论何时,只要你能做到,就尽量使用泛型方法。意思是如果使用泛型方法可以代替整个类的泛型化,那就用泛型方法,因为它可以使事情更加清楚明白。另外对于static的方法而言,无法访问泛型类的类型参数,所以如果static方法需要使用泛化能力,就必须使其成为泛型方法。

泛型的擦除

​ 在看 《Java编程思想》 中泛型章节中 ’擦除的神秘之处‘ 这一小节的时候,看的我特别的晕晕乎乎的,然后再往下面看时就越来越混了。 特别是看到’边界‘,’通配符‘ 这块了就有点懵了。首先看下什么是擦除。在泛型代码内部,无法获得有关泛型参数类型的信息。 Java 的泛型是由擦拭来实现的,这意味着当你使用泛型的时,任何具体的类型都被擦除了,你唯一知道的就是你在使用一个对象。由于 Java 一开始没有引入 泛型这个特性,在为了兼容 JDK 老版本的情况下。擦除是 Java 泛型实现的一种折中。 因此你在运行时 List<String>List<Integer> 是一样的,注意是在运行时,但是在编译时,List<String> 表示这个 String 类型的 List 容器, List<Integer> 表示这个时 Integer 类型的List容器。 举个例子,例子来源于 Java编程思想

  1. #include <iostream>
  2. using namespace std;
  3. temple<class T> class Manipulator{
  4. T obj;
  5. public
  6. Manipulator(T x){obj = x;}
  7. void manipylate() {obj.f();}
  8. };
  9. class HasF{
  10. public:
  11. void f(){cout<<"HasF::f()"<< endl;}
  12. };
  13. int main(){
  14. HasF hf;
  15. Manipulator<HasF> manipulator(hf);
  16. manipulator.manipylate();
  17. }

输出 HasF:f()

​ Manipulator 类存储了一个类型T的对象,在 manipylate 方法里,他在 obj上调用了方法 f() ; 它是怎么知道 f() 是类型参数T 中有的方法呢? 当你实例化这个模板的时,c++编译器将进行检查,如果他在 Manipulator<HasF> 被实例化的这刻,它看到HasF如果拥有这个方法 f(), 就编译通过,否则不通过。 这个代码就算不会 c++ 的人应该也看的懂吧。我们看下 Java 的版本:

  1. public class HasF{
  2. public void f(){
  3. System.out.println("HasF::f()");
  4. }
  5. }
  6. class Manipulator<T>{
  7. private T obj;
  8. public Manipulator(T obj){
  9. this.obj = obj;
  10. }
  11. public void manipulator(){
  12. //错误: 这个是不可以调用 f() 这个方法的。
  13. // obj.f();
  14. }
  15. public static void main(String[] args){
  16. HasF hf = new HasF();
  17. Manipulator<HasF> manipulator = new Manipulator<>(hf);
  18. manipulator.manipulator();
  19. }
  20. }

看到没有,Java 中有了擦除, Java 编译器不知道 obj 中有没有 f() 这个方法的事情。

泛型的边界

T extends Class

​ Java 中的泛型,在编译时,T代表一种类型,如果没有指定任何的边界,那么他就相当于 Object 。 我们可以通过 extends 关键字,给泛型指定边界。 上面代码我们为了能够调用f(), 我们可以协助泛型类,给定泛型类的边界,告诉编译器必须接受遵循这个边界的类型。这里用 extends 关键字。 将上面代码改成

  1. public class HasF{
  2. public void f(){
  3. System.out.println("HasF::f()");
  4. }
  5. }
  6. class Manipulator<T extends HasF>{
  7. private T obj;
  8. public Manipulator(T obj){
  9. this.obj = obj;
  10. }
  11. public void manipulator(){
  12. obj.f();
  13. }
  14. public static void main(String[] args){
  15. HasF hf = new HasF();
  16. Manipulator<HasF> manipulator = new Manipulator<>(hf);
  17. manipulator.manipulator();
  18. }
  19. }

输出:HasF::f()

这样子在编译的时候就相当告诉编译器 Manipulator 类中 obj 的参数类型 为 HasF 或着它的子类。

? extends T

? 是泛型表达式中的通配符。 ? extends T 表示: T 数据类型或着 T 的子类数据类型。 举个例子

  1. class Vegetables{}
  2. // 白菜
  3. class Cabbage extends Vegetables{}
  4. // 小白菜
  5. class Pakchoi extends Cabbage{}
  6. //大蒜
  7. class Garlic extends Vegetables{}
  8. public class Main{
  9. public static void main(String[] args) {
  10. // 这个是会报错的。
  11. // ArrayList<Vegetables> vegetables = new ArrayList<Cabbage>();
  12. }
  13. }

​ 上面的中 main 方法里面是报错的因为,ArrayList<Vegetables>表示这个ArrayList容器中只能存放蔬菜,new ArrayList<Cabbage>() 表示 这个使一个只能放白菜的容器。这两个容器是不可以相等的,类型不一样。但是我们可以通过 ? extends T 来解决这个问题,代码如下:

  1. public class Main{
  2. public static void main(String[] args) {
  3. ArrayList<? extends Vegetables> vegetables = new ArrayList<Cabbage>();
  4. // 报错 不能添加白菜进去
  5. // vegetables.add(new Cabbage());
  6. }
  7. }

​ 我们可以用 vegetables 表示 new ArrayList<Cabbage>(), 这是向上转型。但是,为什么 vegetables.add(new Cabbage()) ; 会报错,因为 ArrayList<? extends Vegetables> 表示这个 ArrayList 容器中能够存放任何蔬菜。 但是 ArrayList 具体是什么容器,完全不知道,add 的时候,你添加什么东西进去到这个容器中都是不安全的。 这个时候,我们可以用 ? super T 来进行操作,具体往下看。

? super T

? super T 表示: T 数据类型 或着 T的超类数据类型, super 表示超类通配符。 上面代码可以用以下表示

  1. public class Main{
  2. public static void main(String[] args) {
  3. ArrayList<? super Cabbage> cabbages = new ArrayList<Vegetables>();
  4. cabbages.add(new Cabbage());
  5. cabbages.add(new Pakchoi());
  6. // cabbages.add(new Vegetables());
  7. System.out.println(cabbages);
  8. }
  9. }

上面 ArrayList<? super Cabbage> 表示ArrayList这个容器中怎么都可以存放 Cabbage 以及Cabbage子类的数据类型。 cabbages 指向的是 蔬菜的容器类。 add 进去的是白菜以及白菜的子类型数据。这个当然是支持的。

? extends T VS ? super T

? extends T ,? super T 一般用于方法参数列表。


  1. public class Main{
  2. public static void main(String[] args) {
  3. List<Cabbage> cabbages = new ArrayList<>();
  4. cabbages.add(new Cabbage());
  5. cabbages.add(new Cabbage());
  6. cabbages.add(new Cabbage());
  7. extendsTest(cabbages);
  8. List<? super Cabbage> list = superTest(new ArrayList<Vegetables>());
  9. System.out.println(list);
  10. }
  11. public static void extendsTest(List<? extends Vegetables> list){
  12. for (Vegetables t : list){
  13. System.out.println(t);
  14. }
  15. }
  16. public static List<? super Cabbage> superTest(List<? super Cabbage> list){
  17. list.add(new Cabbage());
  18. list.add(new Pakchoi());
  19. return list;
  20. }
  21. }

? extends T 表示消费者 list 然后把里面的数据消费掉。

? super T 表示生产者 传入一个list 然后往里面添加数据,进行其他操作。

总结

​ 对于 ? extends Class ,? extends T,? super T,不是很理解的,可以自己把例子写一下,然后想一想。Java 泛型的特性在很多开源的框架上是用的非常多的。这快需要深入的理解一下,我想随着敲代码的年限上,应该到了后面会有不一样得理解吧。现在通过书上能够知道,理解得就只有这么多了。

Java中的泛型 --- Java 编程思想的更多相关文章

  1. Java 中的泛型详解-Java编程思想

    Java中的泛型参考了C++的模板,Java的界限是Java泛型的局限. 2.简单泛型 促成泛型出现最引人注目的一个原因就是为了创造容器类. 首先看一个只能持有单个对象的类,这个类可以明确指定其持有的 ...

  2. 第九节:详细讲解Java中的泛型,多线程,网络编程

    前言 大家好,给大家带来详细讲解Java中的泛型,多线程,网络编程的概述,希望你们喜欢 泛型 泛型格式:ArrayList list= new ArrayList(); ArrayList list= ...

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

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

  4. Java中的泛型 (上) - 基本概念和原理

    本节我们主要来介绍泛型的基本概念和原理 后续章节我们会介绍各种容器类,容器类可以说是日常程序开发中天天用到的,没有容器类,难以想象能开发什么真正有用的程序.而容器类是基于泛型的,不理解泛型,我们就难以 ...

  5. 关于Java、Python、Go编程思想的不同

    Go学习笔记 - 关于Java.Python.Go编程思想的不同 看了两周七牛团队翻译的<Go语言程序设计>,基本上领略到了Go语言的魅力.学习一个语言,语法什么的任何人都是很容易学会,难 ...

  6. 第四节:详细讲解Java中的类和面向对象思想

    前言 大家好,给大家带来详细讲解Java中的类和面向对象思想的概述,希望你们喜欢 类和面向对象 在Java中怎样理解对象,创建对象和引用:什么是引用,对于基础学习的同学,要深入了解引用.示例:Stri ...

  7. Java 中的泛型

    泛型的一般意义: 泛型,又叫 参数多态或者类型参数多态.在强类型的编程语言中普遍作用是:加强编译时的类型安全(类型检查),以及减少类型转换的次数. Java 中的 泛型: 编译时进行 类型擦除 生成与 ...

  8. Java基础之Java中的泛型

    1.为什么要使用泛型 这里我们俩看一段代码; List list = new ArrayList(); list.add("CSDN_SEU_Cavin"); list.add(1 ...

  9. java中的泛型2--注意的一些问题和面试题

    前言 这里总结一下泛型中需要注意的一些地方和面试题,通过面试题可以让你掌握的更清楚一些. 泛型相关问题 1.泛型类型引用传递问题 在Java中,像下面形式的引用传递是不允许的: ArrayList&l ...

随机推荐

  1. Swift中 @objc 使用介绍

    在swift 中 如果一个按钮添加点击方法 如果定义为Private  或者 定义为 FilePrivate 那么会在Addtaget方法中找不到私有方法 但是又不想把方法暴露出来,避免外界访问 ,那 ...

  2. xpath 根据根节点找数据

  3. plugin-barcodescanner 报错

    https://github.com/phonegap/phonegap-plugin-barcodescanner/issues/418 ionic cordova platform rm andr ...

  4. Django框架之第二篇

    一.知识点回顾 1.MTV模型 model:模型,和数据库相关的 template:模板,存放html文件,模板语法(目的是将变量如何巧妙的嵌入到HTML页面中). views:视图函数 另加urls ...

  5. 【深度学习】吴恩达网易公开课练习(class1 week3)

    知识点梳理 python工具使用: sklearn: 数据挖掘,数据分析工具,内置logistic回归 matplotlib: 做图工具,可绘制等高线等 绘制散点图: plt.scatter(X[0, ...

  6. Python基础之re模块(正则表达式)

    就其本质而言,正则表达式(或 RE)是一种小型的.高度专业化的编程语言,(在Python中)它内嵌在Python中, 并通过 re 模块实现.正则表达式模式被编译成一系列的字节码,然后由用 C 编写的 ...

  7. laravel 视图

    在实际开发中,除了 API 路由返回指定格式数据对象外,大部分 Web 路由返回的都是视图,以便实现更加复杂的页面交互,我们在前面已经看到过了视图的定义方式: return view('以.分隔的视图 ...

  8. cf909C 线性dp+滚动数组好题!

    一开始一直以为是区间dp.. /* f下面必须有一个s 其余的s可以和任意f进行匹配 所以用线性dp来做 先预处理一下: fffssfsfs==>3 0 1 1 dp[i][j] 表示第i行缩进 ...

  9. 2017-2018-2 20165314实验二《Java面向对象程序设计》实验报告

    实验报告封面 实验一 实验要求 参考 http://www.cnblogs.com/rocedu/p/6371315.html#SECUNITTEST 完成单元测试的学习提交最后三个JUnit测试用例 ...

  10. 20165314 2016-2017-2 《Java程序设计》第3周学习总结

    20165314 2016-2017-2 <Java程序设计>第3周学习总结 教材学习内容总结 类体包含成员变量和域变量 局部变量只在方法内有效 对象的创建以及对象对自己变量和方法通过用. ...