服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 年后跑路第一战,从 Java 泛型学起!

年后跑路第一战,从 Java 泛型学起!

2021-11-03 22:27爱写Bug的麦洛麦洛 Java教程

本文章是对 Java 中泛型的快速介绍,包含泛型背后的目标以及使用泛型如何提高我们代码的质量。

年后跑路第一战,从 Java 泛型学起!

概述

大家好,我是麦洛,今天来复习一下泛型。JDK 5.0 引入了 Java 泛型,允许设计者详细地描述变量和方法的类型要如何变化,使得代码具有更好的可读性。本文章是对 Java 中泛型的快速介绍,包含泛型背后的目标以及使用泛型如何提高我们代码的质量。

为什么要引入泛型?

在没有泛型的背景下,让我们想象一个场景,我们要在 Java 中创建一个List来存储Integer。

代码如下:

  1. List list = new LinkedList(); 
  2. list.add(new Integer(1));  
  3. Integer i = list.iterator().next(); 

果不其然,IDEA会直接提醒需要强制转换。

年后跑路第一战,从 Java 泛型学起!

我们对代码进行修改,如下所示:

  1. Integer i = (Integer) list.iterator.next(); 

在没有泛型的前提下,定义的List可以保存任何对象,当我们遍历时候,根据上下文进行判断,只能保证它是一个Object,所以需要我们显示转换。

我们知道List中的数据类型是Integer,可以直接强制转换,如果我们不知道或者强制转换时候写错类型,就会导致报错,一场灾难就这样发生了。

这时候,就有人想了,我能不能在使用List时候就指定保存的类型,编译阶段来帮我保证类型的正确性,那就可以完全避免让人讨厌的强制转换,所以,泛型就因运而生了。

让我们修改前面代码片段的第一行:

  1. List<Integer> list = new LinkedList<>(); 

通过添加包含类型的菱形运算符 <>,我们将List能保存的类型限制到只有Integer类型,编译器可以在编译时强制执行类型。

泛型方法

对于泛型方法,我们可以用不同类型的参数调用它们。编译器将确保我们使用的任何类型的正确性。

泛型方法属性:

  • 泛型方法在方法声明的返回类型之前有一个类型参数(包含类型的菱形运算符)。
  • 类型参数可以是有界的(我们将在本文后面解释边界)。
  • 泛型方法可以在方法签名中具有用逗号分隔的不同类型参数。
  • 泛型方法的方法体就像普通方法一样。

这是定义将数组转换为List的泛型方法的示例:

  1. public <T> List<T> fromArrayToList(T[] a) {    
  2.     return Arrays.stream(a).collect(Collectors.toList()); 

方法签名中的表明该方法将处理泛型类型T。即使该方法返回 void,这也是必需的。

如前所述,该方法可以处理多个泛型类型。在这种情况下,我们必须将所有泛型类型添加到方法签名中。

以下是我们如何修改上述方法以处理类型T和类型G:

  1. public static <T, G> List<G> fromArrayToList(T[] a, Function<T, G> mapperFunction) { 
  2.     return Arrays.stream(a) 
  3.       .map(mapperFunction) 
  4.       .collect(Collectors.toList()); 

我们正在传递一个函数,该函数将具有T类型元素的数组转换为具有G类型元素的列表。

一个例子是将Integer转换为它的String表示:

  1. @Test 
  2. public void givenArrayOfIntegers_thanListOfStringReturnedOK() { 
  3.     Integer[] intArray = {1, 2, 3, 4, 5}; 
  4.     List<String> stringList 
  5.       = Generics.fromArrayToList(intArray, Object::toString); 
  6.   
  7.     assertThat(stringList, hasItems("1""2""3""4""5")); 

请注意,Oracle 建议使用大写字母来表示泛型类型,并选择更具描述性的字母来表示正式类型。在 Java 集合中,我们使用T表示类型,K表示键,V表示值。

有界泛型

类型参数可以有界,我们可以限制方法接受的类型。例如,我们可以指定一个方法接受一个类型及其所有子类(上限)或一个类型及其所有超类(下限)。要声明上界类型,我们在类型后使用关键字extends,要声明下界类型,我们在类型后使用关键字super。

例子:

  1. public <T extends Number> List<T> fromArrayToList(T[] a) { 
  2.     ... 

我们在这里使用关键字 extends 表示类型 T 在类的情况下扩展上限或在接口的情况下实现上限。

多重边界

一个类型也可以有多个上限:

如果T扩展的类型之一是一个类(例如Number),我们必须将它放在边界列表中的第一个。否则会导致编译时错误。

在泛型中使用通配符

在Java中,通配符由?表示,我们使用它们来指代未知类型。通配符对泛型特别有用,可以用作参数类型。

首先,我们知道Object是所有 Java 类的超类。但是,Object的集合不是任何集合的超类型。所以,一个List 不是List的超类型,二者直接没有任何关系。

例子:

  1. public static void paintAllBuildings(List<Building> buildings) { 
  2.     buildings.forEach(Building::paint); 

假如现在有一个Building 的子类型,叫House,我们不能将这个方法用于 House 的列表,即使 House 是 Building 的一个子类型。

如果我们需要将此方法与类型 Building 及其所有子类型一起使用,则有界通配符可以发挥作用:

  1. public static void paintAllBuildings(List<? extends Building> buildings) { 
  2.     ... 

现在此方法将适用于类型 Building 及其所有子类型。这称为上限通配符,其中类型 Building 是上限。

我们还可以指定具有下限的通配符,其中未知类型必须是指定类型的超类型。可以使用 super 关键字后跟特定类型来指定下限。例如,表示未知类型,它是 T 的超类(= T 及其所有父类)。

类型擦除

Java 中添加了泛型以确保类型安全。并且为了确保泛型不会在运行时造成开销,编译器在编译时对泛型应用了一个称为类型擦除的过程。

如果类型参数是无界的,则类型擦除会删除所有类型参数并用它们的边界或Object替换它们。这样,编译后的字节码只包含正常的类、接口和方法,确保不会产生新的类型。在编译时也将正确的转换应用于 Object 类型。

这是类型擦除的示例:

  1. public <T> List<T> genericMethod(List<T> list) { 
  2.     return list.stream().collect(Collectors.toList()); 

使用类型擦除,无界类型T被替换为Object:

  1. public List<Object> withErasure(List<Object> list) { 
  2.     return list.stream().collect(Collectors.toList()); 
  3.  
  4.  
  5. public List withErasure(List list) { 
  6.     return list.stream().collect(Collectors.toList()); 

如果类型是有界的,则在编译时该类型将被边界替换:

  1. public <T extends Building> void genericMethod(T t) { 
  2.     ... 

编译后:

  1. public void genericMethod(Building t) { 
  2.     ... 

泛型和原始数据类型

Java 中泛型的一个限制是类型参数不能是基本类型。

例如,以下不能编译:

  1. List<int> list = new ArrayList<>(); 
  2. list.add(17); 

要理解基本类型为什么不起作用,让我们记住泛型是一个编译时特性,这意味着类型参数被删除并且所有泛型类型都实现为类型Object。

我们来看 一个列表的add方法:

  1. List<Integer> list = new ArrayList<>(); 
  2. list.add(17); 

add方法的签名是:

  1. boolean add(E e); 

并将被编译为:

  1. boolean add(Object e); 

因此,类型参数必须可转换为Object。由于基本类型不扩展Object,我们不能将它们用作类型参数。

然而,Java 为原语提供了装箱类型,以及自动装箱和拆箱来解包它们:

  1. Integer a = 17; 
  2. int b = a; 

所以,如果我们想创建一个可以容纳整数的列表,我们可以使用这个包装器:

  1. List<Integer> list = new ArrayList<>(); 
  2. list.add(17); 
  3. int first = list.get(0); 

编译后的代码将等效于以下内容:

  1. List list = new ArrayList<>(); 
  2. list.add(Integer.valueOf(17)); 
  3. int first = ((Integer) list.get(0)).intValue(); 

结论 

Java 泛型是对 Java 语言的强大补充,因为它使程序员的工作更轻松且不易出错。泛型在编译时强制类型正确,最重要的是,可以实现泛型算法而不会对我们的应用程序造成任何额外开销。

原文链接:https://mp.weixin.qq.com/s/CgKrrOY4aQwh_9mn9S7_pw

延伸 · 阅读

精彩推荐
  • Java教程xml与Java对象的转换详解

    xml与Java对象的转换详解

    这篇文章主要介绍了xml与Java对象的转换详解的相关资料,需要的朋友可以参考下...

    Java教程网2942020-09-17
  • Java教程小米推送Java代码

    小米推送Java代码

    今天小编就为大家分享一篇关于小米推送Java代码,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...

    富贵稳中求8032021-07-12
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    这篇文章主要介绍了Java使用SAX解析xml的示例,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程20个非常实用的Java程序代码片段

    20个非常实用的Java程序代码片段

    这篇文章主要为大家分享了20个非常实用的Java程序片段,对java开发项目有所帮助,感兴趣的小伙伴们可以参考一下 ...

    lijiao5352020-04-06
  • Java教程升级IDEA后Lombok不能使用的解决方法

    升级IDEA后Lombok不能使用的解决方法

    最近看到提示IDEA提示升级,寻思已经有好久没有升过级了。升级完毕重启之后,突然发现好多错误,本文就来介绍一下如何解决,感兴趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java8中Stream使用的一个注意事项

    Java8中Stream使用的一个注意事项

    最近在工作中发现了对于集合操作转换的神器,java8新特性 stream,但在使用中遇到了一个非常重要的注意点,所以这篇文章主要给大家介绍了关于Java8中S...

    阿杜7482021-02-04
  • Java教程Java BufferWriter写文件写不进去或缺失数据的解决

    Java BufferWriter写文件写不进去或缺失数据的解决

    这篇文章主要介绍了Java BufferWriter写文件写不进去或缺失数据的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望...

    spcoder14552021-10-18
  • Java教程Java实现抢红包功能

    Java实现抢红包功能

    这篇文章主要为大家详细介绍了Java实现抢红包功能,采用多线程模拟多人同时抢红包,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙...

    littleschemer13532021-05-16