使用场景

泛型主要用在“容器”类中,因为容器这种特殊的类,无法在编写代码时确定里面到底要存放何种类型的元素。

钻石语法

泛型在Java 5被引入,使用时需要这样指定类型参数:

Map<String> data = new HashMap<String>();

到了Java 7时,使用泛型只需在左侧<>内写上类型参数,右侧尖括号不用写:

Map<String> data = new HashMap<>();

这种写法被称为钻石语法,可以让你少敲几次键盘(当然在IDE的辅助下,这并不是什么大问题)

泛型方法

记住一种准则:优先使用泛型方法而非泛型类,因为泛型方法比泛型类更清晰易懂。

要定义一个泛型方法,需要将泛型参数列表放置在返回类型之前:

public <T> void hello(T data) {
    System.out.println(data);
}

类型擦除

所有的泛型在被编译后,都会被擦除为Object类型,所以在方法体中并不能调用对应类型的方法。如果想要在方法体中使用泛型类对应的方法,需要指定泛型边界。

泛型边界

单个边界

看一段示例代码:

class Test<T extends List<String>> {
    public void test(T data){
        data.add("hello");
    }
}

注意<T extends List<String>>,这表示泛型T的类型必须是实现了`List这个接口的类,所以才能在test()方法体中使用Listadd()方法。

多重边界

我们知道,在 Java 是单继承、多实现的,那么泛型的边界也是如此。泛型边界可以指定一个类和多个接口,语法如下:

class Test<T extends ArrayList<String> & Map<String,String>>{
    public void test(T data){
        data.add("hello");
        data.put("key","value");
    }
}

注意<T extends ArrayList<String> & Map<String,String>>这里,表示泛型T的类型需要继承 ArrayList,并且实现 Map 接口,这样才能在方法体重既使用 ArrayList 的 add() 方法,又使用 Map 的 put() 方法。

注意:多重边界的书写顺序必须是类放在前面,接口放在后面,如:

通配符

extends 上界通配符

通配符可以用于向上转型的场景,看这样一个例子

List<List<String>> a = new ArrayList<>();
List<? extends List<String>> b = new ArrayList<>();
List<ArrayList<String>> c = new ArrayList<>();
a = c; // 无法向上转型
b = c; // 可以向上转型

这里变量b用了通配符,所以c可以向上转型为b,但无法向上转型为a

super 下界通配符

extends 用于规定上界,而 super 用于规定下界,如下:

List<? super ArrayList<String>> a = new ArrayList<>();
List<List<String>> b = new ArrayList<>();
List<LinkedList<String>> c = new ArrayList<>();
a = b; // 可以向上转型
a = c; // 无法向上转型

<? super ArrayList<String>>表明只接受ArrayList及其父类类型

无界通配符

无界通配符语法如下:

List<?> data = new ArrayList<>();

无界通配符表达的含义是要使用泛型,但暂时不知道这个泛型的具体类型是什么。

这和List<Object>似乎很相似,从某些层面上来说,我认为大部分情况下二者并无太大差别。

具体的使用场景我现在无法想到,希望以后遇到了再回到这里填坑。

最后

读这一章的目的是因为对泛型不是特别了解,同时现在在接触lin-cms-springboot,遇到一个message使用泛型的问题,我提出了异议,团队也表示要重新商讨,所以看一下这章内容以便让自己更了解泛型。

读完后的体会:

泛型的目的是让代码更加泛化,让写出的代码通用性更强,其实现的就是让类型参数化。但是由于 Java 的历史原因,在最初设计时,并未将泛型引入,直到 Java 5 才引入泛型。所以采用了一种名为类型擦除的方式来实现泛型,也就导致了 Java 的泛型相较于其他语言(如C++)并不是那么泛化。

而使用泛型的场景,我觉得最重要的就是容器类场景,因为这类场景无法在编写代码时确定其元素类型,所以使用泛型非常合适。至于为什么用Object不好,这或许也是 Java 引入泛型的另一个原因。首先,在 Java 5 之前,Java 的集合类就是使用 Object 实现的,在当时,我认为会有如下问题:

  • 因为是 Object ,所以任何元素都能往集合中放,如果不小心放了不同类型的元素,那么只有等到运行时才能发现问题。

而引入泛型后,只要在使用集合类时写明需要存储的是何种元素,如果存入了不同类型的元素,那么在编译期就会检查出错误,不会把错误带到运行期。而且语义上也更加的清晰明了。

Last modification:August 25th, 2020 at 10:25 am