您的位置:首页 > 编程语言 > Java开发

史上最全java范型讲解

2021-04-26 20:12 162 查看

什么是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());
}

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

如上所述,该方法可以处理一个以上的泛型类型,在这种情况下,必须将所有泛型类型添加到方法签名中,例如,如果我们要修改上述方法以处理类型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

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

入群
,扫码加入我们交流群!

阅读更多文章

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: