我的编程空间,编程开发者的网络收藏夹
学习永远不晚

怎样理解Java的泛型及实现

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

怎样理解Java的泛型及实现

本篇文章为大家展示了怎样理解Java的泛型及实现,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

泛型基础

泛型是对Java语言类型系统的一种扩展,有点类似于C++的模板,可以把类型参数看作是使用参数化类型时指定的类型的一个占位符。引入泛型,是对Java语言一个较大的功能增强,带来了很多的好处:

  1. 类型安全。类型错误现在在编译期间就被捕获到了,而不是在运行时当作java.lang.ClassCastException展示出来,将类型检查从运行时挪到编译时有助于开发者更容易找到错误,并提高程序的可靠性

  2. 消除了代码中许多的强制类型转换,增强了代码的可读性

  3. 为较大的优化带来了可能

泛型是什么并不会对一个对象实例是什么类型的造成影响,所以,通过改变泛型的方式试图定义不同的重载方法是不可以的。剩下的内容我不会对泛型的使用做过多的讲述,泛型的通配符等知识请自行查阅。

在进入下面的论述之前我想先问几个问题:

  • 定义一个泛型类到底会生成几个类,比如ArrayList<T>到底有几个类

  • 定义一个泛型方法最终会有几个方法在class文件中

  • 为什么泛型参数不能是基本类型呢

  • ArrayList<Integer>是一个类吗

  • ArrayList<Integer>和List<Integer>和ArrayList<Number>和List<Number>是什么关系呢,这几个类型的引用能相互赋值吗

类型擦除

正确理解泛型概念的首要前提是理解类型擦除(type erasure)。  Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。

很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:

  • 泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。

  • 静态变量是被泛型类的所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是  MyClass.myStaticVar。不管是通过new MyClass<String>还是new  MyClass<Integer>创建的对象,都是共享一个静态变量。

  • 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>和MyException<Integer>的。对于JVM来说,它们都是  MyException类型的。也就无法执行与异常对应的catch语句。

类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。比如T  get()方法声明就变成了Object get();List<String>就变成了List。

泛型的实现原理

因为种种原因,Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型,这样虽然不会有类型膨胀(C++模板令人困扰的难题)的问题,但是也引起了许多新的问题。所以,Sun对这些问题作出了许多限制,避免我们犯各种错误。

保证类型安全

首先***个是泛型所宣称的类型安全,既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的。那类型检查是针对谁的呢,让我们先看一个例子。

ArrayList<String> arrayList1=new ArrayList(); // 正确,只能放入String ArrayList arrayList2=new ArrayList<String>(); // 可以放入任意Object

这样是没有错误的,不过会有个编译时警告。不过在***种情况,可以实现与  完全使用泛型参数一样的效果,第二种则完全没效果。因为,本来类型检查就是编译时完成的。new  ArrayList()只是在内存中开辟一个存储空间,可以存储任何的类型对象。而真正涉及类型检查的是它的引用,因为我们是使用它引用arrayList1  来调用它的方法,比如说调用add()方法。所以arrayList1引用能完成泛型类型的检查。 而引用arrayList2没有使用泛型,所以不行。

类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。

实现自动类型转换

因为类型擦除的问题,所以所有的泛型类型变量***都会被替换为原始类型。这样就引起了一个问题,既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换呢?

public class Test {       public static void main(String[] args) {           ArrayList<Date> list=new ArrayList<Date>();           list.add(new Date());           Date myDate=list.get(0);     }       }

编译器生成的class文件中会在你调用泛型方法完成之后返回调用点之前加上类型转换的操作,比如上文的get函数,就是在get方法完成后,jump回原本的赋值操作的指令位置之前加入了强制转换,转换的类型由编译器推导。

泛型中的继承关系

先看一个例子:

class DateInter extends A<Date> {       @Override       public void setValue(Date value) {           super.setValue(value);       }       @Override       public Date getValue() {           return super.getValue();       }   }

先来分析setValue方法,父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。

public void setValue(java.util.Date);  //我们重写的setValue方法       Code:          0: aload_0          1: aload_1          2: invokespecial #16                // invoke A setValue :(Ljava/lang/Object;)V          5: return      public java.util.Date getValue();    //我们重写的getValue方法       Code:          0: aload_0          1: invokespecial #23                 // A.getValue   :()Ljava/lang/Object;          4: checkcast     #26                       7: areturn      public java.lang.Object getValue();     //编译时由编译器生成的方法       Code:          0: aload_0          1: invokevirtual #28                 // Method getValue:() 去调用我们重写的getValue方法   ;          4: areturn      public void setValue(java.lang.Object);   //编译时由编译器生成的方法       Code:          0: aload_0          1: aload_1          2: checkcast     #26                         5: invokevirtual #30                 // Method setValue;   去调用我们重写的setValue方法   )V          8: return

并且,还有一点也许会有疑问,子类中的方法  Object   getValue()和Date getValue()是同  时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。

我们再看一个经常出现的例子。

class A {     Object get(){         return new Object();     } }  class B extends A {     @Override     Integer get() {         return new Integer(1);     } }    public static void main(String[] args){     A a = new B();     B b = (B) a;     A c = new A();     a.get();     b.get();     c.get();   }

反编译之后的结果

17: invokespecial #5                  // Method com/suemi/network/test/A."<init>":()V       20: astore_3       21: aload_1       22: invokevirtual #6                  // Method com/suemi/network/test/A.get:()Ljava/lang/Object;       25: pop       26: aload_2       27: invokevirtual #7                  // Method com/suemi/network/test/B.get:()Ljava/lang/Integer;       30: pop       31: aload_3       32: invokevirtual #6                  // Method com/suemi/network/test/A.get:()Ljava/lang/Object;

实际上当我们使用父类引用调用子类的get时,先调用的是JVM生成的那个覆盖方法,在桥接方法再调用自己写的方法实现。

泛型参数的继承关系

在Java中,大家比较熟悉的是通过继承机制而产生的类型体系结构。比如String继承自Object。根据Liskov替换原则,子类是可以替换父类的。当需要Object类的引用的时候,如果传入一个String对象是没有任何问题的。但是反过来的话,即用父类的引用替换子类引用的时候,就需要进行强制类型转换。编译器并不能保证运行时刻这种转换一定是合法的。这种自动的子类替换父类的类型转换机制,对于数组也是适用的。   String[]可以替换Object[]。但是泛型的引入,对于这个类型系统产生了一定的影响。正如前面提到的List<String>是不能替换掉List<Object>的。

引入泛型之后的类型系统增加了两个维度:一个是类型参数自身的继承体系结构,另外一个是泛型类或接口自身的继承体系结构。***个指的是对于  List<String>和List<Object>这样的情况,类型参数String是继承自Object的。而第二种指的是  List接口继承自Collection接口。对于这个类型系统,有如下的一些规则:

相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。即List<String>可以赋给Collection<String>   类型的引用,List<String>可以替换Collection<String>。这种情况也适用于带有上下界的类型声明。 当泛型类的类型声明中使用了通配符的时候, 这种替换的判断可以在两个维度上分别展开。如对Collection<? extends  Number>来说,用来替换他的引用可以在Collection这个维度上展开,即List<? extends  Number>和Set<? extends  Number>等;也可以在Number这个层次上展开,即Collection<Double>和  Collection<Integer>等。如此循环下去,ArrayList<Long>和  HashSet<Double>等也都可以替换Collection<? extends Number>。

如果泛型类中包含多个类型参数,则对于每个类型参数分别应用上面的规则。理解了上面的规则之后,就可以很容易的修正实例分析中给出的代码了。只需要把List<Object>改成List<?>即可。List<String>可以替换List<?>的子类型,因此传递参数时不会发生错误。

个人认为这里对上面这种情形使用子类型这种说法来形容这种关系是不当的,因为List<String>等本质上来说不能算作类型,只是对List类型加上了编译器检查约束,也就不存在子类型这种说法。只能用是否在赋值时能够进行类型转换来说明。

泛型使用中的注意点

运行时型别查询
// 错误,为类型擦除之后,ArrayList<String>只剩下原始类型,泛型信息String不存在了,无法进行判断 if( arrayList instanceof ArrayList<String>)   if( arrayList instanceof ArrayList<?>)    // 正确
异常中使用泛型的问题
  • 不能抛出也不能捕获泛型类的对象。事实上,泛型类扩展Throwable都不合法。为什么不能扩展Throwable,因为异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。类型信息被擦除后,那么多个使用不同泛型参数地方的catch都变为原始类型Object,那么也就是说,多个地方的catch变的一模一样,这自然不被允许。

  • 不能再catch子句中使用泛型变量。

public static <T extends Throwable> void doWork(Class<T> t){           try{               ...           }catch(T e){ //编译错误  T->Throwable,下面的永远不会被捕获,所以不被允许             ...           }catch(IndexOutOfBounds e){           }                             }
不允许创建泛型类数组
Pair<String,Integer>[] table = new Pair<String,Integer>[10];// 编译错误 Pair[] table = new Pair[10];// 无编译错误

由于数组必须携带自己元素的类型信息,在类型擦除之后,Pair<String,Integer>数组就变成了Pair<Object,Object>数组,数组只能携带它的元素是Pair这样的信息,但是并不能携带其泛型参数类型的信息,所以也就无法保证table[i]赋值的类型安全。编译器只能禁用这种操作。

泛型类中的静态方法和静态变量

泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数。

public class Test2<T> {         public static T one;   //编译错误         public static  T show(T one){ //编译错误             return null;         }     }

因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。

类型擦除后的冲突
class Pair<T>   {       public boolean equals(T value) {           return null;       }         }

方法重定义了,同时存在两个equals(Object o)。

上述内容就是怎样理解Java的泛型及实现,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

怎样理解Java的泛型及实现

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

怎样理解Java的泛型及实现

本篇文章为大家展示了怎样理解Java的泛型及实现,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。泛型基础泛型是对Java语言类型系统的一种扩展,有点类似于C++的模板,可以把类型参数看作是使用参数化类
2023-06-17

怎样进行介绍Java 泛型的理解与等价实现

这篇文章给大家介绍怎样进行介绍Java 泛型的理解与等价实现,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数
2023-06-17

java 泛型的详解及实例

java 泛型的详解及实例Java在1.5版本中增加了泛型,在没有泛型之前,从集合中读取每一个对象都需要进行强转,如果一不小心插入了类型错误的对象,在运行时就会报错,给日常开发带来了很多不必要的麻烦,比如以下代码:public class
2023-05-31

Java泛型的原理分析是怎样的

这篇文章将为大家详细讲解有关Java泛型的原理分析是怎样的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。一、前言泛型在java中有很重要的地位,在实际开发中用处也很大。二、泛型泛型:是jdk
2023-06-26

Java中的泛型怎么理解

本篇内容介绍了“Java中的泛型怎么理解”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!泛型:是JDK5中引入的特性,可以在编译阶段约束操作的
2023-06-30

怎么理解Java/Scala泛型

本篇内容介绍了“怎么理解Java/Scala泛型”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!泛型(Generics)是强类型编程语言中经常
2023-06-16

怎么解析Java泛型与等价实现

怎么解析Java泛型与等价实现,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。说起泛型,也许有很多人不明白或是不懂。泛型是Java SE 1.5的新特性,泛型的本
2023-06-17

怎么用Java泛型实现类型擦除

本篇内容主要讲解“怎么用Java泛型实现类型擦除”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么用Java泛型实现类型擦除”吧!前言先给大家奉上一道经典的测试题。List l1
2023-06-29

C#中怎么实现泛型处理

C#中怎么实现泛型处理,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。C#泛型处理的问题陈述考虑一种普通的、提供传统 Push() 和 Pop() 方法的数据结构(例如,堆栈)。
2023-06-17

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

Java 获取泛型的类型实例详解Java 泛型实际上有很多缺陷,比如不能直接获取泛型的类型,不能获取带泛型类等。以下方式是不正确的:①.获取带泛型的类的类型Class lstUClazz = List.class
2023-05-31

Java怎么用泛型实现数组排序

这篇文章主要介绍“Java怎么用泛型实现数组排序”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java怎么用泛型实现数组排序”文章能帮助大家解决问题。1. 整数数组的顺序收缩public stati
2023-07-06

java中怎么实现一个泛型算法

java中怎么实现一个泛型算法,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。说明1、有界类型参数是实现泛型算法的关键。2、这个方法实现简单但无法编译,因为大于号的操作符(>
2023-06-20

Java泛型的实现方式是什么

本篇内容主要讲解“Java泛型的实现方式是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java泛型的实现方式是什么”吧!Java 泛型实现方式Java 采用**类型擦除(Type eras
2023-06-16

Go语言泛型的定义及实现方式

题目:Go语言泛型的定义及实现方式随着Go语言在各种领域的应用不断扩大,对于泛型的需求也变得日益迫切。泛型是编程语言中一种非常重要的特性,它可以提高代码的复用性、减少重复的代码量,使代码更加清晰和简洁。在Go语言中,一直以来没有原生支持泛
Go语言泛型的定义及实现方式
2024-03-10

怎么在java中利用ParameterizedType实现一个泛型

本篇文章给大家分享的是有关怎么在java中利用ParameterizedType实现一个泛型,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java有哪些集合类Java中的集合主
2023-06-14

Java中怎么利用泛型实现数组排序

这篇文章将为大家详细讲解有关Java中怎么利用泛型实现数组排序,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。1. 整数数组的顺序收缩public static int seqSearch(i
2023-06-17

JDK5.0新特性的泛型怎么理解

本篇文章给大家分享的是有关JDK5.0新特性的泛型怎么理解,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。没有参数的情况下使用泛型 既然在J2SE 5.0中收集类型已经泛型
2023-06-03

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录