什么是java的泛型?
Java泛型是在JDK 5.0中引入的,引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
泛型基础
让我们想象一个场景,我们想要在Java中创建一个列表来存储Integer;我们很容易写:
List list = new LinkedList();
list.add(new Integer(1));
Integer i = list.iterator().next();
令人惊讶的是,编译器会抱怨最后一行。它不知道返回什么数据类型。编译器将需要显式强制转换:
Integer i = (Integer) list.iterator.next();
没有可以保证列表的返回类型为Integer。此时列表可以容纳任何对象。我们只知道我们通过检查上下文来检索列表。在查看类型时,它只能保证它是一个Object,因此需要进行显式强制转换以确保该类型是安全的。
这种转换可能很烦人,我们知道此列表中的数据类型是Integer。演员表也使我们的代码混乱。如果程序员在显式转换中犯了错误,则可能导致与类型相关的运行时错误。
如果程序员可以表达使用特定类型的意图,而编译器可以确保这种类型的正确性,则容易得多。这是泛型背后的核心思想。
让我们将前面的代码片段的第一行修改为:
List<Integer> list = new LinkedList<>();
通过添加包含该类型的菱形运算符<>,我们仅将此列表的特殊化范围缩小为Integer类型,即,我们指定将在列表内保留的类型。编译器可以在编译时强制执行类型。在小型程序中,这看似微不足道,但是,在大型程序中,这可以增加强大的鲁棒性,并使程序更易于阅读。
泛型方法
泛型方法是用单个方法声明编写的那些方法,并且可以使用不同类型的参数来调用。编译器将确保使用任何一种类型的正确性。这些是泛型方法的一些属性:
泛型方法在方法声明的返回类型之前具有类型参数(包含该类型的菱形运算符) 类型参数可以是有界的(边界将在本文后面进行解释) 泛型方法可以在方法签名中使用逗号分隔不同类型的参数 通用方法的方法主体就像普通方法一样
定义将数组转换为列表的通用方法的示例:
public <T> List<T> fromArrayToList(T[] a) {
return Arrays.stream(a).collect(Collectors.toList());
}
在前面的例子中,
如上所述,该方法可以处理一个以上的泛型类型,在这种情况下,必须将所有泛型类型添加到方法签名中,例如,如果我们要修改上述方法以处理类型T和类型G,应该这样写:
public static <T, G> List<G> fromArrayToList(T[] a, Function<T, G> mapperFunction) {
return Arrays.stream(a)
.map(mapperFunction)
.collect(Collectors.toList());
}
我们传递了一个函数,该函数将具有T类型的元素的数组转换为具有G类型的元素的列表。一个示例是将Integer转换为其String表示形式:
@Test
public void givenArrayOfIntegers_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<? extends List> lists2 = new ArrayList<>();
// 编译器正确
List list1=lists2.get(0);
// 编译器报错
lists2.add(new LinkedList());
下边界
当泛型T给定形如 的A类型到A类型任何父类的限制域,可以匹配任何在此限制域中的类型,此种表示叫下边界通配符。
上边界只能往里存,不能往外取,请看下面例子:
List<? super List> lists = new ArrayList<>();
// 编译器正确
lists.add(new ArrayList());
// 编译器正确
lists.add(new LinkedList());
// 编译器报错
List list = lists.get(0);
通配符 <? extends A>
通配符由Java中的问号表示。”,它们用于指代未知类型。通配符在使用泛型时特别有用。
public static void paintAllBuildings(List<? extends Building> buildings) {
...
}
泛型擦除
泛型已添加到Java中,以确保类型安全并确保泛型不会在运行时引起开销,编译器在编译时对泛型应用了一个称为类型擦除的过程。
类型擦除会删除所有类型参数,并用其边界或对象(如果类型参数是无界的)替换它。因此,编译后的字节码仅包含常规类,接口和方法,从而确保不会产生任何新类型。在编译时,正确的转换也适用于Object类型。
下面是类型擦除的一个示例:
public <T> List<T> genericMethod(List<T> list) {
return list.stream().collect(Collectors.toList());
}
使用类型擦除时,无界类型T被Object替换,如下所示:
// 擦除后的方法只剩下Object
public List<Object> withErasure(List<Object> list) {
return list.stream().collect(Collectors.toList());
}
// which in practice results in
public List withErasure(List list) {
return list.stream().collect(Collectors.toList());
}
如果类型是有界的,那么在编译时,该类型将被有界的代替:
public <T extends Building> void genericMethod(T t) {
...
}
编译后会改变:
public void genericMethod(Building t) {
...
}
END
泛型是Java语言的强大功能,它使我们的开发工作更加简单,并且不易出错。泛型在编译时强制类型正确,最重要的是,使用泛型是不会给我们的应用程序造成任何额外的开销。
End
欢迎关注公众号! 公众号回复:
入群
,扫码加入我们交流群!