AI智能
改变未来

史上最全java范型讲解


什么是java的泛型?

Java泛型是在JDK 5.0中引入的,引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

泛型基础

让我们想象一个场景,我们想要在Java中创建一个列表来存储Integer;我们很容易写:

Listlist=newLinkedList();
list.add(newInteger(1));
Integeri=list.iterator().next();

令人惊讶的是,编译器会抱怨最后一行。它不知道返回什么数据类型。编译器将需要显式强制转换:

Integeri=(Integer)list.iterator.next();

没有可以保证列表的返回类型为Integer。此时列表可以容纳任何对象。我们只知道我们通过检查上下文来检索列表。在查看类型时,它只能保证它是一个Object,因此需要进行显式强制转换以确保该类型是安全的。

这种转换可能很烦人,我们知道此列表中的数据类型是Integer。演员表也使我们的代码混乱。如果程序员在显式转换中犯了错误,则可能导致与类型相关的运行时错误。

如果程序员可以表达使用特定类型的意图,而编译器可以确保这种类型的正确性,则容易得多。这是泛型背后的核心思想。

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

List<Integer>list=newLinkedList<>();

通过添加包含该类型的菱形运算符<>,我们仅将此列表的特殊化范围缩小为Integer类型,即,我们指定将在列表内保留的类型。编译器可以在编译时强制执行类型。在小型程序中,这看似微不足道,但是,在大型程序中,这可以增加强大的鲁棒性,并使程序更易于阅读。

泛型方法

泛型方法是用单个方法声明编写的那些方法,并且可以使用不同类型的参数来调用。编译器将确保使用任何一种类型的正确性。这些是泛型方法的一些属性:

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

定义将数组转换为列表的通用方法的示例:

public<T>List<T>fromArrayToList(T[]a){
returnArrays.stream(a).collect(Collectors.toList());
}

在前面的例子中,在方法签名意味着该方法将被处理的通用型Ť。即使该方法返回void,这也是必需的。

如上所述,该方法可以处理一个以上的泛型类型,在这种情况下,必须将所有泛型类型添加到方法签名中,例如,如果我们要修改上述方法以处理类型T和类型G,应该这样写:

publicstatic<T,G>List<G>fromArrayToList(T[]a,Function<T,G>mapperFunction){
returnArrays.stream(a)
.map(mapperFunction)
.collect(Collectors.toList());
}

我们传递了一个函数,该函数将具有T类型的元素的数组转换为具有G类型的元素的列表。一个示例是将Integer转换为其String表示形式:

@Test
publicvoidgivenArrayOfIntegers_thanListOfStringReturnedOK(){
Integer[]intArray={1,2,3,4,5};
List<String>stringList
=Generics.fromArrayToList(intArray,Object::toString);

assertThat(stringList,hasItems(\"1\",\"2\",\"3\",\"4\",\"5\"));
}

泛型边界

  • 上边界

当泛型T给定形如 的A类型到A类型任何子类的限制域,可以匹配任何在此限制域中的类型,此种表示叫上边界通配符

上边界

上边界不能往里存,只能往外取,请看下面例子:

List<?extendsList>lists2=newArrayList<>();
//编译器正确
Listlist1=lists2.get(0);
//编译器报错
lists2.add(newLinkedList());
  • 下边界

当泛型T给定形如 的A类型到A类型任何父类的限制域,可以匹配任何在此限制域中的类型,此种表示叫下边界通配符。

下边界

上边界只能往里存,不能往外取,请看下面例子:

List<?superList>lists=newArrayList<>();
//编译器正确
lists.add(newArrayList());
//编译器正确
lists.add(newLinkedList());
//编译器报错
Listlist=lists.get(0);

通配符 <? extends A>

通配符由Java中的问号表示。”,它们用于指代未知类型。通配符在使用泛型时特别有用。

publicstaticvoidpaintAllBuildings(List<?extendsBuilding>buildings){
...
}

泛型擦除

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

类型擦除会删除所有类型参数,并用其边界或对象(如果类型参数是无界的)替换它。因此,编译后的字节码仅包含常规类,接口和方法,从而确保不会产生任何新类型。在编译时,正确的转换也适用于Object类型。

下面是类型擦除的一个示例:

public<T>List<T>genericMethod(List<T>list){
returnlist.stream().collect(Collectors.toList());
}

使用类型擦除时,无界类型T被Object替换,如下所示:

//擦除后的方法只剩下Object
publicList<Object>withErasure(List<Object>list){
returnlist.stream().collect(Collectors.toList());
}

//whichinpracticeresultsin
publicListwithErasure(Listlist){
returnlist.stream().collect(Collectors.toList());
}

如果类型是有界的,那么在编译时,该类型将被有界的代替:

public<TextendsBuilding>voidgenericMethod(Tt){
...
}

编译后会改变:

publicvoidgenericMethod(Buildingt){
...
}

END

泛型是Java语言的强大功能,它使我们的开发工作更加简单,并且不易出错。泛型在编译时强制类型正确,最重要的是,使用泛型是不会给我们的应用程序造成任何额外的开销。

End

欢迎关注公众号!公众号回复:

入群

,扫码加入我们交流群!

阅读更多文章

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 史上最全java范型讲解