Java泛型总结

转载自 Java泛型总结

Java泛型是JDK5引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用的时候使用具体的类型来替换。泛型最主要的应用是在JDK5中的新集合类框架中。对于泛型概念的引入,开发社区的观点是褒贬不一。从好的方面上说,泛型的引入可以解决之前的集合类框架在使用过程中通常会出现的运行时刻类型错误,因为编译器可以在编译时刻就发现很多明显的错误。从不好的方面说,为了保证与旧版本的兼容性,Java泛型的实现上还存在着不够优雅的地方。

类型擦除

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

通过如下代码片段感受类型擦除:

ArrayList<String> a1 = new ArrayList<>();
ArrayList<Integer> a2 = new ArrayList<>();Class c1 = a1.getClass();
Class c2 = a2.getClass();System.out.println(c1.equals(c2));//true
System.out.println(c1.getName());//java.util.ArrayList
System.out.println(c2.getName());//java.util.ArrayList

此时,程序输出true,这就是类型擦除造成的。因为不管是ArrayList<String>还是ArrayList<Integer>,都会在编译期被编译器擦除成ArrayList。编译器这么做的原因归根结底还是为了兼容JDK5前未使用泛型的代码,因此不得不让编译器擦除有关类型信息的部分,这样生成的代码其实就是类型无关的。

List<Integer> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));//[E]
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));//[K,V]

我们期望的是返回泛型参数的类型,结果返回的仅仅是参数的占位符。

public static <T> T[] makeArray(){return new T[10];//编译期报错:不能创建泛型类型的数组
}

因为T仅仅是个占位符,并不具有真实的类型信息。为了解决这个问题,可以利用反射:

public static <T> T[] makeArray(Class<T> clazz){return (T[]) Array.newInstance(clazz, 10);
}

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

  • 泛型类并没有自己独有的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。接下来就可能需要生成一些桥接方法(bridge method),这是由于擦除了类型之后的类可能缺少某些必须的方法。比如考虑下面的代码:

class MyString implements Comparable<String> {public int compareTo(String str) {return 0;}}

当类型信息被擦除之后,上述类的声明变成了class MyString implements Comparable。但是这样类MyString就会有编译错误,因为没有实现接口Comparable声明的compareTo(Object)方法。这个时候就由编译器来动态生成这个方法。

实例分析

了解类型擦除机制之后,就会明白编译器承担了全部的类型检查工作。编译器禁止某些泛型的使用方式,正是为了确保类型的安全性。以List<Object>和List<String>为例来具体分析:

public void inspect(List<Object> list) {for(Object obj : list) {System.out.println(obj);}list.add(1);    //这个操作在当前方法的上下文是合法的
}public void test() {List<String> strs = new ArrayList<String>();inspect(strs);    //编译错误
}

这段代码中,inspect方法接受List<Object>作为参数,当在test方法中试图传入List<String>的时候,会出现编译错误。假设这样的做法是允许的,那么在inspect方法中就可以通过list.add(1)来向集合中添加一个数字。这样在test方法看来,其声明为List<String>的集合中被添加了一个Integer类型的对象,这显然是违反类型安全原则的,在某个时候肯定会抛出ClassCastException。因此,编译器禁止这样的行为。

通配符与上下界

在使用泛型类的时候,既可以指定一个具体的类型,如List<String>就声明了具体的类型是String;也可以用通配符?来表示未知类型,如List<?>就声明了List中包含的元素类型是未知的。通配符所代表的其实是一组类型,但具体的类型是未知的。List<?>所声明的就是所有的类型都是可以的。但是List<?>并不等同于List<Object>。List<Object>实际上确定了List中包含的是Object及其子类,在使用的时候可以通过Object来进行引用。而List<?>则表示其中所包含的元素类型是不确定。其中可能包含的是String,也可能是Integer。如果它包含了String的话,往里面添加Integer类型的元素就是错误的。正因为类型未知,就不能通过new ArrayList<?>()的方法来创建一个新的ArrayList对象。因为编译器无法知道具体的类型是什么。但是对于List<?>中的元素总是可以用Object来引用的,因为虽然类型未知,但肯定是Object及其子类。考虑下面的代码:

public void wildcard(List<?> list) {list.add(1);    //编译错误
}

如上所示,试图对一个带通配符的泛型类进行操作的时候,总是会出现编译错误。其原因在于通配符所表示的类型是未知的。

因为对于List<?>中的元素只能用Object来引用,在有些情况下不是很方便。在这些情况下,可以使用上下界来限制未知类型的范围。如List<? extends Number>说明List中包含的是Number及其子类。而List<? super Number>则说明List中包含的是Number及其父类。当引入了上界时候,在使用类型的时候就可以使用上界类中定义的方法。比如访问List<? extends Number>的时候,就可以使用Number类的intValue等方法。

类型系统

在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<?>的子类型。

开发自己的泛型类

泛型类与一般的Java类基本相同,只是在类和接口定义上多出来了用<>声明的类型参数。一个类可以有多个类型参数,比如MyClass<X, Y, Z>。每个类型参数在声明的时候可以指定上下界。所声明的类型参数在Java类中可以像一般的类型一样作为方法的参数和返回值,或是作为域和局部变量的类型。由于类型擦除机制,类型参数并不能用来创建对象或是作为静态变量的类型。考虑下面的泛型类中的正确和错误的用法。

class ClassTest<X extends Number, Y, Z> {private X x;private static Y y;    //编译错误,不能用在静态变量中public X getFirst() {return x;    //正确用法}public void wrong() {Z z = new Z();    //编译错误,不能创建对象}
}

假设允许类型参数声明为静态属性,那么如下代码将会非常混乱。

public class Computer<T> {private static T os;public Computer(T os) {this.os = os;}public T getOS() {return os;}public static void main(String [] args) {Computer<Linux> c1 = new Computer<>();Computer<MacOS> c2 = new Computer<>();Computer<Windows> c3 = new Computer<>();System.out.println(c1.getOS());System.out.println(c2.getOS());System.out.println(c3.getOS());}
}

因为os为Computer类的静态属性,所以c1,c2,c3这3个Computer实例共享这个属性,那么此时os的类型是什么?因此,不允许声明静态的类型参数属性。

总结

在使用Java泛型的时候可以遵循一些基本的原则,从而避免一些常见的问题。

  • 在代码中避免泛型类和原始类型的混用。比如List<String>和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。

  • 在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。

  • 泛型类最好不要同数组一块儿使用。只能创建new List<?>[10]这样的数组,无法创建new List<String>[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。

参考

  • InfoQ:http://www.infoq.com/cn/articles/cf-java-generics

  • Java泛型:类型擦除:http://findingsea.github.io/2015/10/09/java-generic-type-erasure/


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/329231.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用 Visual Studio Code 在 macOS 上创建首个 ASP.NET Core 应用程序

原文&#xff1a;Your First ASP.NET Core Application on a Mac Using Visual Studio Code作者&#xff1a;Daniel Roth、Steve Smith 以及 Rick Anderson翻译&#xff1a;赵志刚校对&#xff1a;何镇汐、刘怡(AlexLEWIS) 本节将展示如何在 macOS 平台上创建首个 ASP.NET Core…

linux安装mysql遇到的问题_Linux下安装MySQL5.7及遇到的问题解决方法

一、下载地址本文安装的版本&#xff1a;或者使用wget下载&#xff1a;[rootlocalhost opt]# wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.32-el7-x86_64.tar.gz[rootlocalhost opt]# tar -xvf mysql-5.7.32-el7-x86_64.tar.gz二、检查是否已经安装过mysql[…

Java 8 新特性:扩展注解(类型注解和重复注解)

转载自 Java 8 新特性&#xff1a;扩展注解&#xff08;类型注解和重复注解&#xff09;&#xff08;注&#xff1a;先回顾下JDK1.5版本出现的注解 &#xff0c;然后再解释JDK 8的注解 更新内容。&#xff09; 一.注解&#xff08;JDK1.5&#xff09; 1.注解&#xff08;&…

框架写mysql插入为空_学习springMVC框架配置遇到的问题-数据写入不进数据库时的处理办法...

Idea简单SpringMVC框架配置前边已经介绍过了Struts在Idea上的配置,相对于Struts来说,我觉得SpringMVC有更多的优势,首先Struts是需要对action进行配置,页面发送不同的请求,就需要配置不同的acti ...hibernate学习之一 框架配置hibernate 框架 1.hibernate框架应用在javaee三层结…

“.Net 社区虚拟大会”(dotnetConf) 2016 Day 3 Keynote: Scott Hanselman

美国时间 6月7日--9日&#xff0c;为期三天的微软.NET社区虚拟大会正式在 Channel9 上召开&#xff0c;美国时间6.9 是第三天&#xff0c; Scott Hanselman 做Keynote。今天主题围绕的是.NET OpenSource 展开&#xff0c;Hanselman通过PowerBI分析了.NET社区这两年的发展&#…

Java注释@interface的用法

转载自 Java注释interface的用法java用 interface Annotation{ } 定义一个注解 Annotation&#xff0c;一个注解是一个类 Override&#xff0c;Deprecated&#xff0c;SuppressWarnings为常见的3个注解。注解相当于一种标记&#xff0c;在程序中加上了注解就等于为程序加上了某…

RPC的发展历史

服务器通讯原理就是一台socket服务器A,另一台socket客户端B,现在如果要通讯的话直接以流方式写入或读出。 这样能实现通讯&#xff0c;但有个问题。如何知道更多信息&#xff1f;比如需要发送流大小&#xff0c;编码&#xff0c;Ip等。 这样就有了协议&#xff0c;协议就是规范…

类的继承python 简明_[简明python教程]学习笔记2014-05-04

今天学习的内容&#xff1a;1.面向对象编程的概念1)面向对象的三个基本特征&#xff1a;封装、继承、多态2)类和对象是面向对象编程的2个主要方面。类使用class关键字创建。类的域和方法被列在一个缩进块中。2.类[rootreed 0504]# cat simpleclass.py#!/usr/bin/pythonclass Pe…

子类可以继承到父类上的注解吗

转载自 子类可以继承到父类上的注解吗?不了解注解基础知识的请先看《JDK 5 Annotation\注解\注释\自定义注解》子类可以继承到父类上的注解吗&#xff1f; 我们知道在编写自定义注解时&#xff0c;可以通过指定Inherited注解&#xff0c;指明自定义注解是否可以被继承。但实现…

java aop 实例_Spring aop 简单示例

简单的记录一下spring aop的一个示例基于两种配置方式&#xff1a;基于xml配置基于注解配置这个例子是模拟对数据库的更改操作添加事物其实并没有添加&#xff0c;只是简单的输出了一下记录首先看下整个例子的目录图全部代码就不贴了&#xff0c;数目有点多&#xff0c;不过很简…

用 Visual Studio 和 ASP.NET Core MVC 创建首个 Web API

原文&#xff1a;Building Your First Web API with ASP.NET Core MVC and Visual Studio作者&#xff1a;Mike Wasson 和 Rick Anderson翻译&#xff1a;谢炀(kiler)校对&#xff1a;何镇汐、刘怡(AlexLEWIS) HTTP 协议不仅仅提供网页服务。它也是一个构建公开服务和数据 API …

java抽取pdf_java 抽取 word,pdf 的四种武器

转自:https://www.ibm.com/developerworks/cn/java/l-java-tips/ 感谢作者发布的文章用 jacob其实 jacob 是一个 bridage&#xff0c;连接 java 和 com 或者 win32 函数的一个中间件&#xff0c;jacob 并不能直接抽取 word,excel 等文件&#xff0c;需要自己写 dll 哦&…

SuperSocket与Netty之实现protobuf协议,包括服务端和客户端

今天准备给大家介绍一个c#服务器框架&#xff08;SuperSocket&#xff09;和一个c#客户端框架&#xff08;SuperSocket.ClientEngine&#xff09;。这两个框架的作者是园区里面的江大渔。 首先感谢他的无私开源贡献。之所以要写这个文章是因为群里经常有人问这个客户端框架要如…

Java 注解指导手册 – 终极向导

转载自 Java 注解指导手册 – 终极向导译文出处&#xff1a; Toien Liu 原文出处&#xff1a;Dani Buiza编者的话&#xff1a;注解是java的一个主要特性且每个java开发者都应该知道如何使用它。我们已经在Java Code Geeks提供了丰富的教程, 如Creating Your Own Java Annota…

ajax调用后台java类_ajax调用java后台方法是什么

ajax是一种基于 JavaScript和HTTP请求(HTTP requests)&#xff0c;广泛应用在浏览器的网页开发技术。那么&#xff0c;ajax调用java后台方法是什么?var http_requestfalse;function sendRequest(method,url,content,processResponse){http_requestfalse;if(window.XMLHttpRequ…

用 Visual Studio 发布一个 Azure 云 Web 应用程序

原文&#xff1a;Getting Started 作者&#xff1a;Rick Anderson 翻译&#xff1a;谢炀(Kiler) 校对&#xff1a;孟帅洋(书缘)、刘怡(AlexLEWIS)、何镇汐 设置开发环境 安装最新版本的 Azure SDK for Visual Studio 2015。如果你之前没有装过 Visual Studio 2015&#xff0c;S…

Java中的注解是如何工作的

转载自 Java中的注解是如何工作的&#xff1f;自Java5.0版本引入注解之后&#xff0c;它就成为了Java平台中非常重要的一部分。开发过程中&#xff0c;我们也时常在应用代码中会看到诸如Override&#xff0c;Deprecated这样的注解。这篇文章中&#xff0c;我将向大家讲述到底什…

impdp导入mysql_Oracle数据库的impdp导入操作以及dba_directories使用方法

Oracle数据库的impdp导入操作以及dba_directories使用方法今天从同事那里拿到了导出的dmp文件&#xff0c;当导入时发现了很多问题&#xff0c;记下来以免以后忘记&#xff0c;以下是本人的操作过程&#xff1a;1.首先是创建一个文件夹dump&#xff0c;用来存放dmp文件&#xf…

ASP.NET Core MVC 与 Visual Studio 入门

原文&#xff1a;Getting started with ASP.NET Core MVC and Visual Studio作者&#xff1a;Rick Anderson翻译&#xff1a;娄宇(Lyrics)校对&#xff1a;刘怡(AlexLEWIS)、夏申斌 、张硕(Apple) 这篇教程将告诉你如何使用 Visual Studio 2015 构建一个 ASP.NET Core MVC Web …

深入理解Java:注解(Annotation)自定义注解入门

转载自 深入理解Java&#xff1a;注解&#xff08;Annotation&#xff09;自定义注解入门要深入学习注解&#xff0c;我们就必须能定义自己的注解&#xff0c;并使用注解&#xff0c;在定义自己的注解之前&#xff0c;我们就必须要了解Java为我们提供的元注解和相关定义注解的语…