0

0

java泛型综合详解

黄舟

黄舟

发布时间:2017-03-01 13:13:31

|

1939人浏览过

|

来源于php中文网

原创

在日常生活中,我们经常用到泛型,但是泛型数据有些时候会报一些莫名其妙的错,而且一些通配符等语法、泛型在虚拟机中的真正操作方式也有我们值得研究之处,今天我们就一起来讨论一下泛型。

(一)  创造

java增加泛型之前,当年都是用继承来处理现在用泛型操作的程序的。

ArrayList files = new ArrayList();String filename = (String) files.get(0);

ArrayList<String> files2 = new ArrayList<>();
//后一个尖括号中的内容可以省略,在1.7之后String filename2= files2.get(0);String addname = "addname";

files2.add(addname);
//在add函数调用之时,ArrayList泛型会自动检测add的内容是不是String类型
files2.add(true);
//报错 The method add(String) in the type ArrayList<String> is not applicable for the arguments (boolean)

比如ArrayList类只是维护了一个Object引用的数组,在获取值时需要类型强转(上面前两行),在传入内容的时候没有保证。有了泛型之后这个数据结构变得方便、可读性好且安全,我们可以一下看出ArrayList数组中存入类型为String,而且类似ArrayList的add方法也会自动的关联起String类型的参数,其他类型的值并不能add到列表中。

除此之外,还有一点想要说明的是,上面代码的前两行是没有错误的。因为在ArrayList中,不使用也是可以的,泛型类的限制还是很多的,在需要的情况下,也有要用到ArrayList类的时候。

(二)  泛型类

直接来一个简单的泛型类:

立即学习Java免费学习笔记(深入)”;

public class Pair<T> {    
private T first;    
private T second;    
public Pair() {
        first = null;
        second = null;
    }    public Pair(T first, T second) {        
    this.first = first;        
    this.second = second;
    }    
    public T getFirst() {        
    return first;
    }    
    public void setFirst(T first) {        
    this.first = first;
    }    
    public T getSecond() {        
    return second;
    }    
    public void setSecond(T second) {        
    this.second = second;
    }

}

其实泛型类可以看作是普通类的工厂,将T替换成我们需要的类型即可。那么对于需要两种类型的情况,我们只要写成Pair即可。T和U这两个域分别使用不同的类型。

上面的例子过于简单,也并不能做什么实质性的工作,那么请大家思考这样一个问题,如果我们在上面的泛型类中添加一个函数:

public T min(T[] array) {...}//返回传入数组中的最小值

那么这个时候,我们想要实现这个函数,我们至少要保证一点T类型的对象是可以比较大小的,怎么样去检测这个情况呢?实际上我们可以通过对类型变量T设置限定而解决这个问题。

public <T extends Comparable>T min(T[] array){...}//T如果没有实现compare方法则报错不执行min函数

<t extends comparable></t>的意思大家也能猜出个八九不离十,就是要求T这个类型变量要继承Comparable这个接口。现在泛型的min方法只能被实现了Comparable接口的类(比如String,Date…)的数组调用,其他的类会产生一个编译错误。
相同的,也可以有多个限定:<t extends comparable></t>也是被允许的。逗号来分隔类型变量,&来分隔限定类型。

实际上Comparable本身就是一个泛型接口,为了上面内容能够简单的被理解,我们就装糊涂当作他不是泛型接口。但是要注意的是,其实上面的真正正确的写法是:

(三)  泛型方法

1.普通类中泛型方法

泛型类定义的泛型在整个类中有效。就是说泛型类的对象在明确要操作的具体类型后,所有要操作的类型就已经固定了。比如ArrayList,整个ArrayList的泛型和String类型绑定了,不能修改。但在某些情况下我们需要更多的灵活性,为了让方法可以操作各种不确定类型,可以将泛型定义在方法上。

    public <T> T getMid(T[] array){        
    return array[array.length/2];
    }

比如上面的这个getMid的函数,在返回值T的前面加上<t></t>就使他变成了一个泛型方法。请注意,他是出于一个普通类中的,泛型方法不一定非要存在于泛型类中。下面给出调用泛型方法的例子,当调用一个泛型方法时,编译器会根据后面的类型来推断调用的方法,所以并不需要显示的表示出T是什么类型:

String mid = ArrayAlg.getMid("Jone" , "Q" , "Peter");
//okdouble mid2 = ArrayAlg.getMid(3.14 , 25.9 , 20);
//error,编译器会把20自动打包成Integer类型,而其他打包成Double类型,我们尽量不要让这种错误发生

2.泛型类中泛型方法

在普通类中,我们可以通过上述泛型方法来灵活调用不同的类型参数。在泛型类中,我们需要这么灵活的泛型方法么?我认为是不需要的。我们为泛型类提供了类型参数,实际上我们很少会在一个类型中用到其他的类型,如果一定要用到,我们一般采用Pair这样多个类型参数的方法,或者干脆再定义一个要使用类型的泛型类。
那么泛型方法在泛型类中有什么用呢?我们可以在泛型类中用泛型方法来为泛型变量做限定,比如我们在上面提到的 <t extends comparable></t>T方法只能被有Comparable接口的T使用,否则会出错。

@SuppressWarnings("hiding")
public static <T extends Comparable<?> & Serializable> T min(T[] array) {...}

(四)  虚拟机中的泛型

1.虚拟机中没有泛型,只有普通的类和方法

java虚拟机中没有泛型类型对象——所有的对象都属于普通类,那么虚拟机是怎么用普通类来模拟出泛型的效果呢?
只要定义了一个泛型类,虚拟机都会自动的提供一个原始类型。原始类型的名字就是删除类型参数的泛型类的名字。擦除类型变量,并替换为第一个限定类型(如果没有限定则用Object来替换)。
比如,我们在一开始给出的简单泛型类Pair在虚拟机中会变成如下的情况:

public class Pair {    
private Object first;    
private Object second;    
public Pair() {
        first = null;
        second = null;
    }    
    public Pair(Object first, Object second) {        
    this.first = first;        
    this.second = second;
    }    
    public Object getFirst() {        
    return first;
    }    
    public void setFirst(Object first) {        
    this.first = first;
    }    
    public Object getSecond() {        
    return second;
    }    
    public void setSecond(Object second) {        
    this.second = second;
    }

}

这就是擦除了类型变量,并且将没有限制的T换成Object之后的情况,如果有限定类型,比如,我们的泛型类是Pair按照替换为第一个限定类型的规定,替换后的情况如下:

public class Pair {    
private Compararble first;    
private Compararble second;    
public Pair() {
        first = null;
        second = null;
    }    
    public Pair(Compararble first, Compararble second) {        
    this.first = first;        
    this.second = second;
    }    
    public Compararble getFirst() {        
    return first;
    }    
    public void setFirst(Compararble first) {        
    this.first = first;
    }    
    public Compararble getSecond() {        
    return second;
    }    
    public void setSecond(Compararble second) {        
    this.second = second;
    }

}

那么既然类型都被擦除了,类型参数也被替换了,怎么起到泛型的效果呢?当程序调用泛型方法时,如果返回值是类型参数(就是返回值是T),那么编译器会插入强制类型转换:

Pair<String> a = ...;
String b = a.getFirst();//编译器强制将a.getFirst()的Object类型的结果转换为String类型

编译器将上述过程翻译位两条虚拟机指令:
- 对原始方法Pair.getFirst的调用。
- 将返回的Object类型强制转换为String类型。

2.翻译泛型方法,桥方法被使用以保持多态性

对于类型擦出也出现在泛型方法中,但是泛型方法中的擦除带来很多问题:

//----------------类A擦除前----------------------class A extends Pair<Date>{
    public void setSecond(Date second){...}
}//----------------类A擦除后----------------------class A extends Pair{
    public void setSecond(Date second){...}
}

从上面的代码中,我们可以看出,类A重写了Pair中的setSecond方法,看似合情合理,但是一旦擦除之后就发生了问题。将Pair擦除后,原函数变为:

public Class Pair{
    public void setSecond(Object second){...}
}

突然发现,父类Pair中的setSecond方法参数变为Object,和子类中的参数不同。擦除之后,父类的setSecond方法和子类的setSecond方法完全变为两个不一样的方法!这就没有了重写之说,那么接着考虑下面的语句:

A a = new A();Pair<Date> pair = a;pair.setSecond(new Date());
//当pair去调用setSecond函数时,有两个不一样的setSecond函数可以被调用,一个是父类中参数为Object的,一个是子类中参数为Date的。

第三行的调用出现了两种情况,这绝对不是我们想要的结果,我们开始的时候只是重写了父类中的setSecond方法,但是现在有两个不同的setSecond方法可以被使用,而且这时编译器不知道要去调用哪个。
为了防止这种情况的发生,编译器会在A类(子类)中生成一个桥方法

造梦阁AI
造梦阁AI

AI小说推文一键成片,你的故事值得被看见

下载
//------------桥方法--------------
public void setSecond(Object second){ setSecond((Date)second); }

这个桥方法,让Object参数的方法去调用Date参数的方法,从而将两个方法合二为一,这个桥方法不是我们自己写的,而是在虚拟机中自动生成的,让代码变得安全,让运行结果变得符合我们的期望。

(五)  通配符 “?”

当两个有关系的类分别作为两个泛型类的类型变量的时候,这两个泛型类是没有关系的。这个时候如果需要涉及到继承规则之类的内容时,那么就需要使用通配符——“?”。

1. 子类型限定和超类型限定(上下界限定)

有的时候两个类间有继承关系,但是分别作为泛型类型变量之后就没了关系,在函数调用和返回值的时候,这种不互通尤为让人头痛。好在通配符的上下界限定类型为我们安全的解决了这个难题。
? super A的意思是“?是A类型的父类型”,? extends A的意思是“?是A类型的子类型”。
既然知道了意思,我们看一下下面这四种用法:

public static void printA (Pair<? super A> p) {...} //ok
public static void printA (Pair<? extends A> p) {...} //error
public static Pair<? extends A> printA () {...} //ok
public static Pair<? super A> printA () {...} //error

为什么四种中有两种有错误呢?
实际上,要牢记这句话:带有超类型限定的通配符可以向泛型对象写入(做参数),带有子类型限定的通配符可以从泛型对象读取(做返回值)。道理这里就不详细讲了,如果有兴趣研究的话可以思考一下,思考的方向无非是继承关系之间的引用转换,只有上面两行ok的方法才是转换安全的。现在我们可以试着去理解上面的>了。

2. 无限定通配符

无限定通配符的用法其实很单一。无限定通配符修饰的返回值只能为Object,而其做参数是不可以的。那么无限定通配符的用处在哪里呢?源于他的可读性好。

public static boolean hasNulls(Pair<?>){...}

上面的代码的意思是对于任何类型的Pair泛型判断是否为空,这样写比用T来表示可读性确实好的多。

(六)  泛型注意事项

1. 不能用基本类型实例化类型参数

八大基本类型务必要使用包装类来实例化,否则泛型参数一擦除我们就傻眼了,怎么把int的数据放到Object里面呢?

2. 不能创建参数化类型数组

参数化的类型数组是不能被创建的。我们完全可以用多个泛型嵌套来避免这种情况的发生,如果创建了泛型数组,擦除之后类型会变成Object[ ],如果有一个类型参数不同的泛型存入这个数组时,因为都被擦除成Object,所以不会报错,导致了错误的发生。需要说明的是,只是不允许创建这些数组,而声明类型为Pair[]的变量是合法的,只不过不能用new Pair[10]初始化这个变量。更确切地表达是:数组类型不能是类型变量,采用通配符的方式是可以允许的

3. 运行时类型查询只适用于原始类型

要记住在虚拟机中,每个对象都有一个特定的非泛型类。所以,所有的类型查询只产生原始类型。比如:

if(a instanceof Pair<String>) //只能测试a是否是任意类型的一个Pair
Pair<String> sp = ...;
sp.getClass(); //获得的也是Pair(原始类型)

4. 不能实例化类型变量

有的时候我们需要将类型变量实例化成它自己本身的类型,但是一定要注意写法,不可以直接实例化类型变量:

public Pair() {    this.first = new T();//错误,类型擦除后T变成Object,new Object()肯定不是想要的
    this.first = T.class.newInstance();//错误,T.class是不合法的写法}

上述的两种写法都是错的,如果一定要这样做的话,也只能在调用泛型类的时候构造出一个方法(构造的方法不在泛型类中,在调用泛型类的普通类中),将你要用的类型作为参数传进去,想办法来实例化。

5. 泛型类的静态上下文中类型变量无效

不能在静态域中引用类型变量,静态方法本来就是和对象无关,他怎么能知道你传进来的是什么类型变量呢?

6. 不能抛出或者捕获泛型类实例

既不能抛出也不能捕获泛型对象。事实上,甚至泛型类扩展Throwable都是不可以的,但是平时我们不去编写泛型类的时候,这一条并不需要注意过多。

7. 继承关系的类分别成为类型参数后无关系

当两个有关系的类分别作为两个泛型类的类型变量的时候,这两个泛型类是没有关系的。这个时候如果需要涉及到继承规则之类的内容时,一定要使用通配符,坚决不要手软。

8. 泛型类的静态方法必须作为泛型方法

泛型类的静态方法因为是静态,所以也不能获得类型变量,在这个时候唯一的解决办法是——所有的静态方法都是泛型方法。

9. 通配符不能作为一种类型使用

? t = p.getA(); //error

所有用“?”来作为类型的写法都是不被允许的,我们需要用T来作为类型参数,有必要的时候可以做一个辅助函数,用T类型来完成工作,用?通配符来做外面的包装,既达到了目的,又提高了可读性。

(七)  总结

泛型为我们提供了许许多多的便利,包装出来的很多泛型类让我们能更快速安全的工作。而泛型的底层实现原理也是很有必要研究一下的,一些需要我们注意的事项和通配符的使用,此类细节也确实有许多值得我们学习之处。

在日常生活中,我们经常用到泛型,但是泛型数据有些时候会报一些莫名其妙的错,而且一些通配符等语法、泛型在虚拟机中的真正操作方式也有我们值得研究之处,今天我们就一起来讨论一下泛型。

 以上就是java泛型综合详解的内容,更多相关内容请关注PHP中文网(www.php.cn)!

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

6

2026.02.28

Golang 工程化架构设计:可维护与可演进系统构建
Golang 工程化架构设计:可维护与可演进系统构建

Go语言工程化架构设计专注于构建高可维护性、可演进的企业级系统。本专题深入探讨Go项目的目录结构设计、模块划分、依赖管理等核心架构原则,涵盖微服务架构、领域驱动设计(DDD)在Go中的实践应用。通过实战案例解析接口抽象、错误处理、配置管理、日志监控等关键工程化技术,帮助开发者掌握构建稳定、可扩展Go应用的最佳实践方法。

6

2026.02.28

Golang 性能分析与运行时机制:构建高性能程序
Golang 性能分析与运行时机制:构建高性能程序

Go语言以其高效的并发模型和优异的性能表现广泛应用于高并发、高性能场景。其运行时机制包括 Goroutine 调度、内存管理、垃圾回收等方面,深入理解这些机制有助于编写更高效稳定的程序。本专题将系统讲解 Golang 的性能分析工具使用、常见性能瓶颈定位及优化策略,并结合实际案例剖析 Go 程序的运行时行为,帮助开发者掌握构建高性能应用的关键技能。

8

2026.02.28

Golang 并发编程模型与工程实践:从语言特性到系统性能
Golang 并发编程模型与工程实践:从语言特性到系统性能

本专题系统讲解 Golang 并发编程模型,从语言级特性出发,深入理解 goroutine、channel 与调度机制。结合工程实践,分析并发设计模式、性能瓶颈与资源控制策略,帮助将并发能力有效转化为稳定、可扩展的系统性能优势。

14

2026.02.27

Golang 高级特性与最佳实践:提升代码艺术
Golang 高级特性与最佳实践:提升代码艺术

本专题深入剖析 Golang 的高级特性与工程级最佳实践,涵盖并发模型、内存管理、接口设计与错误处理策略。通过真实场景与代码对比,引导从“可运行”走向“高质量”,帮助构建高性能、可扩展、易维护的优雅 Go 代码体系。

17

2026.02.27

Golang 测试与调试专题:确保代码可靠性
Golang 测试与调试专题:确保代码可靠性

本专题聚焦 Golang 的测试与调试体系,系统讲解单元测试、表驱动测试、基准测试与覆盖率分析方法,并深入剖析调试工具与常见问题定位思路。通过实践示例,引导建立可验证、可回归的工程习惯,从而持续提升代码可靠性与可维护性。

2

2026.02.27

漫蛙app官网链接入口
漫蛙app官网链接入口

漫蛙App官网提供多条稳定入口,包括 https://manwa.me、https

130

2026.02.27

deepseek在线提问
deepseek在线提问

本合集汇总了DeepSeek在线提问技巧与免登录使用入口,助你快速上手AI对话、写作、分析等功能。阅读专题下面的文章了解更多详细内容。

8

2026.02.27

AO3官网直接进入
AO3官网直接进入

AO3官网最新入口合集,汇总2026年可用官方及镜像链接,助你快速稳定访问Archive of Our Own平台。阅读专题下面的文章了解更多详细内容。

208

2026.02.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4万人学习

C# 教程
C# 教程

共94课时 | 10.4万人学习

Java 教程
Java 教程

共578课时 | 74.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号