java使用泛型后消除泛型_如何以及何时使用泛型

java使用泛型后消除泛型

本文是我们名为“ 高级Java ”的学院课程的一部分。

本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的旅程! 在这里查看 !

目录

1.简介 2.泛型和接口 3.泛型和类 4.泛型和方法 5.仿制药的局限性 6.泛型,通配符和有界类型 7.泛型和类型推断 8.泛型和注释 9.访问泛型类型参数 10.何时使用泛型 11.下一步 12.下载源代码

1.简介

泛型的概念表示对类型的抽象(C ++开发人员将其称为模板)。 这是一个非常强大的概念(很久以前就出现了),它允许开发抽象算法和数据结构,并提供具体类型以供以后使用。 有趣的是,泛型在Java的早期版本中不存在,并且仅在Java 5版本中才添加。 从那以后,可以说,泛型彻底改变了Java程序的编写方式,提供了更强的类型保证,并使代码更加安全。

在本节中,我们将从接口,类和方法开始介绍泛型的用法。 提供了很多好处,但是泛型确实引入了一些局限性和副作用,我们也将介绍这些局限性和副作用。

2.泛型和接口

与常规接口相反,要定义通用接口,只需提供应使用其进行参数化的类型即可。 例如:

package com.javacodegeeks.advanced.generics;public interface GenericInterfaceOneType< T > {void performAction( final T action );
}

GenericInterfaceOneType用单一类型T参数化,可以由接口声明立即使用。 该接口可以使用多种类型进行参数化,例如:

package com.javacodegeeks.advanced.generics;public interface GenericInterfaceSeveralTypes< T, R > {R performAction( final T action );
}

每当任何类想要实现该接口时,它都可以选择提供确切的类型替换,例如ClassImplementingGenericInterface类提供String作为通用接口的类型参数T

package com.javacodegeeks.advanced.generics;public class ClassImplementingGenericInterfaceimplements GenericInterfaceOneType< String > {@Overridepublic void performAction( final String action ) {// Implementation here}
}

Java标准库有很多通用接口的示例,主要是在集合库中。 这是很容易声明和使用通用接口,但是我们将讨论界类型(如果要回他们再次泛型,通配符和有界类型 )和通用限制( 仿制药的限制 )。

3.泛型和类

与接口相似,常规类和泛型类之间的区别仅在于类定义中的类型参数。 例如:

package com.javacodegeeks.advanced.generics;public class GenericClassOneType< T > {public void performAction( final T action ) {// Implementation here}
}

请注意,可以使用泛型对任何类( 具体抽象最终 )进行参数化。 一个有趣的细节是,该类可以将(或不可以)将其泛型类型传递给接口和父类,而无需提供确切的类型实例,例如:

package com.javacodegeeks.advanced.generics;public class GenericClassImplementingGenericInterface< T >implements GenericInterfaceOneType< T > {@Overridepublic void performAction( final T action ) {// Implementation here}
}

这是一种非常方便的技术,它允许类在仍然符合接口(或父类)协定的泛型类型上施加附加界限,这将在“ 泛型,通配符和有界类型”部分中看到。

4.泛型和方法

在讨论类和接口时,我们已经在上一节中看到了几个通用方法。 但是,关于它们还有更多的话要说。 方法可以将泛型用作参数声明或返回类型声明的一部分。 例如:

public< T, R > R performAction( final T action ) {final R result = ...;// Implementation herereturn result;
}

对于可以使用泛型类型的方法没有任何限制,它们可以是具体的, 抽象的静态的final的 。 这是几个示例:

protected abstract< T, R > R performAction( final T action );static< T, R > R performActionOn( final Collection< T > action ) {final R result = ...;// Implementation herereturn result;
}

如果方法被声明(或定义)为通用接口或类的一部分,则它们可以(也可以不)使用其所有者的通用类型。 他们可以定义自己的通用类型,也可以将其与类或接口声明中的类型混合使用。 例如:

package com.javacodegeeks.advanced.generics;public class GenericMethods< T > {public< R > R performAction( final T action ) {final R result = ...;// Implementation herereturn result;}public< U, R > R performAnotherAction( final U action ) {final R result = ...;// Implementation herereturn result;}
}

类构造函数也被认为是一种初始化方法,因此,可以使用其类声明的泛型类型,声明自己的泛型类型或仅将两者混合使用(但是它们不能返回值,因此返回类型参数化不适用于构造函数), 例如:

public class GenericMethods< T > {public GenericMethods( final T initialAction ) {// Implementation here}public< J > GenericMethods( final T initialAction, final J nextAction ) {// Implementation here}
}

它看起来非常简单,而且确实如此。 但是,由于使用Java语言实现泛型的方式存在一些限制和副作用,下一部分将解决该问题。

5.仿制药的局限性

不幸的是,泛型是语言的最鲜明的特征之一,它有一些局限性,这主要是由于泛型很晚才引入已经很成熟的语言。 最可能的是,更彻底的实施需要大量的时间和资源,因此需要进行权衡,以便及时提供仿制药。

首先,在泛型中不允许使用原始类型(例如intlongbyte等等)。 这意味着无论何时需要使用原始类型参数化泛型类型时,都必须使用相应的类包装器( IntegerLongByte …)代替。

final List< Long > longs = new ArrayList<>();
final Set< Integer > integers = new HashSet<>();

不仅如此,由于必须在泛型中使用类包装器,因此会导致对原始值进行隐式装箱和拆箱(本教程的第7部分“ 常规编程指南”中将详细介绍此主题),例如:

final List< Long > longs = new ArrayList<>();
longs.add( 0L ); // 'long' is boxed to 'Long'long value = longs.get( 0 ); // 'Long' is unboxed to 'long'
// Do something with value

但是原始类型只是泛型陷阱之一。 另一个更晦涩的是类型擦除。 重要的是要知道泛型仅在编译时存在:Java编译器使用一组复杂的规则来强制有关泛型及其类型参数使用的类型安全,但是所产生的JVM字节码已擦除了所有具体类型(并替换为Object类)。 首先,以下代码无法编译可能令人惊讶:

void sort( Collection< String > strings ) {// Some implementation over strings heres
}void sort( Collection< Number > numbers ) {// Some implementation over numbers here
}

从开发人员的角度来看,这是一个完全有效的代码,但是由于类型擦除,这两种方法的范围缩小到了相同的签名,并导致编译错误(带有奇怪的消息,如“方法的擦除sort(Collection <String> )与另一种方法相同……” ):

void sort( Collection strings )
void sort( Collection numbers )

由类型擦除引起的另一个缺点来自这样一个事实,即不可能以任何有意义的方式使用泛型的类型参数,例如,无法创建类型的新实例,或者获取类型参数的具体类或在类型参数中使用它。 instanceof运算符。 下面显示的示例没有通过编译阶段:

public< T > void action( final T action ) {if( action instanceof T ) {// Do something here}
}public< T > void action( final T action ) {if( T.class.isAssignableFrom( Number.class )  ) {// Do something here}
}

最后,使用泛型的类型参数创建数组实例也是不可能的。 例如,以下代码无法编译(这时出现一条清晰的错误消息“无法创建T的通用数组” ):

public< T > void performAction( final T action ) {T[] actions = new T[ 0 ];
}

尽管有所有这些限制,但泛型仍然非常有用,并带来了很多价值。 在“ 访问泛型类型参数 ”一节中,我们将介绍几种克服Java语言中的泛型实现所施加的一些约束的方法。

6.泛型,通配符和有界类型

到目前为止,我们已经看到了使用具有无限制类型参数的泛型的示例。 泛型的强大功能是将约束(或界限)强加在使用extendssuper关键字对其进行参数化的类型上。

extends关键字将type参数限制为其他某个类的子类或实现一个或多个接口。 例如:

public< T extends InputStream > void read( final T stream ) {// Some implementation here
}

read方法声明中的类型参数T必须是InputStream类的子类。 相同的关键字用于限制接口实现。 例如:

public< T extends Serializable > void store( final T object ) {// Some implementation here
}

方法存储区需要其类型参数T来实现Serializable接口,以便该方法执行所需的操作。 也可以使用其他类型参数作为extends关键字的绑定,例如:

public< T, J extends T > void action( final T initial, final J next ) {// Some implementation here
}

边界不限于单个约束,可以使用&运算符进行组合。 可能指定了多个接口,但仅允许单个类。 类和接口的组合也是可能的,下面显示了两个示例:

public< T extends InputStream & Serializable > void storeToRead( final T stream ) {// Some implementation here
}
public< T extends Serializable & Externalizable & Cloneable > void persist(final T object ) {// Some implementation here
}

在讨论super关键字之前,我们需要熟悉通配符的概念。 如果类型参数与通用类,接口或方法不相关,则可以将其替换为?。 通配符。 例如:

public void store( final Collection< ? extends Serializable > objects ) {// Some implementation here
}

方法store并不真正在乎调用它的类型参数,唯一需要确保每个类型都实现Serializable接口的方法。 或者,如果这不重要,则可以使用无界通配符:

public void store( final Collection< ? > objects ) {// Some implementation here
}

extends相反, super关键字将type参数限制为某个其他类的超类。 例如:

public void interate( final Collection< ? super Integer > objects ) {// Some implementation here
}

通过使用类型上限和下限(具有extendssuper )以及类型通配符,泛型提供了一种微调类型参数要求的方法,或者在某些情况下完全忽略了它们,仍然保留了面向类型的语义。

7.泛型和类型推断

当泛型进入Java语言时,它们消耗了开发人员为满足语言语法规则而必须编写的代码量。 例如:

final Map< String, Collection< String > > map =new HashMap< String, Collection< String > >();for( final Map.Entry< String, Collection< String > > entry: map.entrySet() ) {// Some implementation here
}

Java 7版本通过在编译器中进行更改并引入了新的菱形运算符<>,在某种程度上解决了该问题。 例如:

final Map< String, Collection< String > > map = new HashMap<>();

编译器能够从左侧推断泛型类型参数,并允许在表达式的右侧省略它们。 在使泛型语法不那么冗长方面,这是一个重大进步,但是编译器推断泛型类型参数的能力非常有限。 例如,以下代码无法在Java 7中编译:

public static < T > void performAction( final Collection< T > actions,final Collection< T > defaults ) {// Some implementation here
}final Collection< String > strings = new ArrayList<>();
performAction( strings, Collections.emptyList() );

Java 7编译器无法推断Collections. emptyList ()的type参数Collections. emptyList () Collections. emptyList ()调用,因此需要将其显式传递:

performAction( strings, Collections.< String >emptyList() );

幸运的是,Java 8版本为编译器,尤其是对泛型的类型推断带来了更多增强,因此上面显示的代码片段成功编译,从而使开发人员不必进行不必要的键入。

8.泛型和注释

尽管我们将在本教程的下一部分中讨论注释,但是值得一提的是,在Java 8之前的时代,泛型不允许其类型参数与注释关联。 但是Java 8改变了这一点,现在可以在声明或使用它们的地方注释泛型类型参数。 例如,以下是如何声明泛型方法并在其类型参数上标注注释的方法:

public< @Actionable T > void performAction( final T action ) {// Some implementation here
}

或只是使用泛型类型时应用注释的另一个示例:

final Collection< @NotEmpty String > strings = new ArrayList<>();
// Some implementation here

在本教程的第4部分“ 如何以及何时使用Enums和Annotations”中 ,我们将看几个示例如何使用注释以将某些元数据与泛型类型参数相关联。 本节仅使您感到可以通过注释丰富泛型。

9.访问泛型类型参数

正如您从“ 泛型的限制 ”一节中已经知道的那样,不可能获得泛型类型参数的类。 解决此问题的一个简单技巧是,在需要知道类型参数T的类的地方,需要传递其他参数Class< T > 。 例如:

public< T > void performAction( final T action, final Class< T > clazz ) {// Some implementation here
}

它可能会浪费方法所需的参数量,但经过精心设计,它并不像乍看上去那样糟糕。

在Java中使用泛型时经常会出现的另一个有趣的用例是,确定泛型实例已被参数化的类型的具体类。 它不是那么简单,并且需要包含Java反射API。 我们将在本教程的第11部分中查看完整示例,即反射和动态语言支持,但现在仅提及ParameterizedType实例是对泛型进行反射的中心点。

10.何时使用泛型

尽管有所有限制,但泛型为Java语言增加的价值却是巨大的。 如今,很难想象曾经有一段时间Java没有泛型支持。 应该使用泛型而不是原始类型(用Collection< T >代替Collection ,用Callable< T >代替Callable ……)或Object来保证类型安全,在合同和算法上定义明确的类型约束,并显着简化代码维护和重构。

但是,请注意Java当前泛型实现的局限性,类型擦除以及著名的原始类型隐式装箱和拆箱。 泛型不是解决您可能遇到的所有问题的灵丹妙药,没有什么可以代替精心设计和周到的思考。

查看一些真实的示例并了解泛型如何使Java开发人员的生活更轻松是一个好主意。

示例1 :让我们考虑该方法的典型示例,该方法针对实现某个接口(例如Serializable )的类的实例执行操作并返回该类的修改后的实例。

class SomeClass implements Serializable {
}

如果不使用泛型,则解决方案可能如下所示:

public Serializable performAction( final Serializable instance ) {// Do something herereturn instance;
} final SomeClass instance = new SomeClass();
// Please notice a necessary type cast required
final SomeClass modifiedInstance = ( SomeClass )performAction( instance );

让我们看看泛型如何改进此解决方案:

public< T extends Serializable > T performAction( final T instance ) {// Do something herereturn instance;
}      final SomeClass instance = new SomeClass();
final SomeClass modifiedInstance = performAction( instance );

丑陋的类型转换已不复存在,因为编译器能够推断出正确的类型并证明这些类型已正确使用。

示例2:该方法的示例更为复杂,该示例要求类的实例实现两个接口(例如SerializableRunnable )。

class SomeClass implements Serializable, Runnable {@Overridepublic void run() {// Some implementation}
}

不使用泛型,直接的解决方案是引入中间接口(或将纯Object作为最后的手段),例如:

// The class itself should be modified to use the intermediate interface
// instead of direct implementations
class SomeClass implements SerializableAndRunnable {@Overridepublic void run() {// Some implementation}
} public void performAction( final SerializableAndRunnable instance ) {// Do something here
}

尽管这是一个有效的解决方案,但它并不是最佳选择,并且随着接口数量的增加,它可能会变得非常讨厌和难以管理。 让我们看看泛型如何在这里提供帮助:

public< T extends Serializable & Runnable > void performAction( final T instance ) {// Do something here
}

代码非常简洁明了,不需要任何中间接口或其他技巧。

泛型使代码易于阅读和直接的示例世界真是无穷无尽。 在本教程的下一部分中,通常将使用泛型来演示Java语言的其他功能。

11.下一步

在本节中,我们介绍了Java语言的一个非常与众不同的特性,称为泛型。 我们已经检查了泛型如何通过检查正确的类型(带有边界)是否在各处使用来使代码安全且简洁。 我们还研究了一些泛型限制以及克服这些限制的方法。 在下一节中,我们将讨论枚举和注释。

12.下载源代码

  • 这是关于如何设计类和接口的课程。 您可以在此处下载源代码: advanced-java-part-4

翻译自: https://www.javacodegeeks.com/2015/09/how-and-when-to-use-generics.html

java使用泛型后消除泛型

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

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

相关文章

用C语言写烟花,给心中的那个人看!

点击蓝字关注我们前言程序员不懂浪漫? 大错特错&#xff01;今天就让你们看看什么是程序员的浪漫&#xff01;你向窗外看烟花&#xff0c;我在窗边看你&#xff0c;这时&#xff0c;你比烟花好看的多&#xff0c;你的眼眸倒映满天的烟火&#xff0c;我的瞳孔倒映你温柔的脸庞…

js实现阶乘算法的三种方法

js实现阶乘算法的三种方法// 非递归写法 function f(n) {if (0 n) {return 1;}let res 1;for (let i 1; i < n; i) {res * i;}return res; }// 递归写法 function g(n) {if (0 n) {return 1;}return n*g(n-1); }// 动态规划写法 let dp []; function h(n) {if (n < …

手把手教你做一个线程池--C语言版

点击蓝字关注我们1、线程池原理我们使用线程的时候就去创建一个线程&#xff0c;这样实现起来非常简便&#xff0c;但是就会有一个问题&#xff1a;如果并发的线程数量很多&#xff0c;并且每个线程都是执行一个时间很短的任务就结束了&#xff0c;这样频繁创建线程就会大大降低…

oracle 48小时内_缺血性脑梗死后48小时内使用阿替普酶能够降低脑损伤程度

一项刊登在影响因子7.6杂志Neurology上题为“Effect of IV alteplase on the ischemic brain lesion at 24–48 hours after ischemic stroke”的研究报告中&#xff0c;来自爱丁堡大学的科学家们发现&#xff0c;alteplase与病变可视性的短期进展降低相关。在荟萃分析中&#…

基于按位与的 就散策略_比较散列策略

基于按位与的 就散策略总览 编年史有很多用于哈希的实现&#xff0c;包括City和Murmur。 它也有自己的香草哈希&#xff0c;但是如何测试呢&#xff1f; 什么是香草哈希&#xff1f; Vanilla Hash设计得尽可能简单&#xff0c;并且针对Orthogonal Bits测试进行了优化&#xff…

js实现数组降维算法[不准用Array.prototype.flat的api]

js实现数组降维算法[不准用Array.prototype.flat的api]// target要降维的元素&#xff0c;n降维阶数&#xff0c;newArr存储结果的新数组 function f(target, n 1, newArr []) {if (n < 0) {newArr.push(target);return;}if (!Array.isArray(target)) {newArr.push(target…

python编程能力等级测试_青少年编程能力等级测评-Python编程二级试卷

青少年编程能力等级测评 Python 编程二级试卷 一、单项选择题&#xff08;共 20 题&#xff0c;每题分&#xff0c;共 50 分&#xff09; 1. 运行下方代码段&#xff0c;输出的结果是&#xff08; &#xff09; 。 a() print(type(a)) A &#xff0e;&#xff1b; B &#xff0…

MySQL夺命16问,你能坚持到第几问?

点击蓝字关注我们1、数据库三大范式是什么&#xff1f;第一范式&#xff1a;每个列都不可以再拆分。第二范式&#xff1a;在第一范式的基础上&#xff0c;非主键列完全依赖于主键&#xff0c;而不能是依赖于主键的一部分。第三范式&#xff1a;在第二范式的基础上&#xff0c;非…

美图手机投射功能在哪_在Java 8中进行投射(还有其他功能?)

美图手机投射功能在哪将实例转换为设计不良的类型。 尽管如此&#xff0c;在某些情况下没有其他选择。 从第一天开始&#xff0c;执行此功能就已成为Java的一部分。 我认为Java 8提出了对这种古老技术稍加改进的需求。 静态铸造 Java中最常见的转换方法如下&#xff1a; 静态…

js箭头函数和普通函数区别

js箭头函数和普通函数区别实验环境&#xff1a;nodejs v12.16.1 箭头函数不能作为构造函数&#xff0c;而普通函数可以 箭头函数没有原型&#xff0c;而普通函数有 箭头函数return可以省略语句块。(如果>右边不是语句块&#xff0c;则代表return右边的表达式或对象) 箭…

git 更新_[技术分享T.191212]GitLab使用方法及git命令常见问题(不断更新)

该文章用于记录一些GitLab的使用指南&#xff0c;以及在实际版本控制过程中遇到的问题及解决方法&#xff0c;会尽量及时的更新~GitLab简介&#xff1a;GitLab和GitHub很相似都属于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来…

记一次开发实战-对提供接口的C/C++进行二次开发

点击蓝字关注我们一、需求描述我有一个USB5538的库和头文件&#xff0c;并通过头文件提供了接口&#xff0c;我想把它更改一下&#xff0c;编译成python可调用的模块。二、创建工程及其目录1、创建空项目2、创建目录三、创建文件1、复制文件并添加2、添加新文件并写入四、环境配…

C++是如何实现多态的

C是如何实现多态的结论&#xff1a;C通过虚函数来实现多态的&#xff0c;根本原因是派生类和基类的虚函数表的不同。 构成多态的必要条件有如下3点&#xff1a; 存在继承关系基类存在虚函数&#xff0c;且派生类有相同原型的函数遮蔽它存在基类类型的指针指向派生类对象&…

python回声程序 一行代码_python实现的比较完成的带声音的摩斯码翻译程序的代码...

将写代码过程较好的一些代码段做个珍藏&#xff0c;如下代码段是关于python实现的比较完成的带声音的摩斯码翻译程序的代码&#xff0c;希望能对各朋友有所用处。 import pygame import time import sys CODE {A: .-, B: -..., C: -.-., D: -.., E: ., F: ..-., G…

jooq 生成数据库_jOOQ类型安全数据库查询教程

jooq 生成数据库课程大纲 SQL是用于关系数据库查询的功能强大且表达能力强的语言。 SQL已建立&#xff0c;标准化并且几乎不受其他查询语言的挑战。 但是&#xff0c;在Java生态系统中&#xff0c;自JDBC以来&#xff0c;几乎没有采取任何相关措施来更好地将SQL集成到Java中。 …

C语言实现通讯录附详细代码(动态+静态)

点击蓝字关注我们一、通讯录简介实现一个通讯录&#xff1b;通讯录可以用来存储1000个人的信息&#xff0c;每个人的信息包括&#xff1a;姓名、性别、年龄、电话、住址提供方法&#xff1a;添加联系人信息删除指定联系人信息查找指定联系人信息修改指定联系人信息显示所有联系…

Lua协程Coroutine是什么

Lua协程Coroutine是什么协程和线程不同&#xff1a; 同一时刻&#xff0c;一个多线程程序可以用多个线程同时执行&#xff1b;而协程只能有一个在执行多线程是抢占式的&#xff1b;而协程是非抢占式的&#xff0c;只有协程显示被挂起&#xff0c;才会被挂起 协程和线程的相同…

请使用复选框选择_使用可选是可选的

请使用复选框选择在上周“收藏中的可选内容”的文章发表之后&#xff0c;我不禁要多讲一些关于同一只野兽的事情。 更多细节。 最初由Google Guava引入并后来包含在Java 8软件包中的Optionial类只是包装可选对象的包装器。 从包装对象存在或包装中没有对象的意义上讲&#xff…

python爬去百度图片_python实现爬取百度图片的方法示例

本文实例讲述了python实现爬取百度图片的方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a; import json import itertools import urllib import requests import os import re import sys wordinput("请输入关键字&#xff1a;") path"./ok" …

C++程序的内存分区模型-栈区堆区

点击蓝字关注我们1、栈区&#xff1a;由编译器自动分配释放&#xff0c;存放函数的参数值&#xff0c;局部变量等&#xff08;由编译器管理其“生死”&#xff09;注意事项&#xff1a;不要返回局部变量的地址&#xff0c;栈区开辟的数据由编译器自动释放栈区代码演示&#xff…