写点什么

Java 高手速成 | Java 集合类泛类型

作者:TiAmo
  • 2023-01-09
    江苏
  • 本文字数:5786 字

    阅读完需:约 19 分钟

Java高手速成 | Java集合类泛类型

 Java 高手是这样炼成的。

01、Java 集合类包括哪些?

作为学习集合类泛类型的预备知识,图 1 列出了 Java 集合类继承图。要学会集合类泛类型,除了懂得集合类外,大家也需

要了解继承的工作原理。图中虚线表示 Collection 是一个接口。


02、什么是集合类泛类型?

泛类型最初发表于 JDK5.0,主要应用于声明和定义 Java 的集合类。首先看一个具体例子:


List<?> bList; //声明一个任何类型元素的集合
复制代码

我们称<?>为泛类型。具体地讲为无通界泛类型,简称无界泛型,英文称之为 Wildcard。

List<?>bList;
复制代码

只是声明了一个名为 bList 的 List 集合。在编写 bList 的具体应用代码时,我们必须用具体的类数据, 如 Integer, Double, String 等等,来替换或者指定具体的 bList 元素类型。例如:

List<?> bList; //声明一个任何类型元素的集合List<Integer> iList = new ArrayList<Integer>(); //创建一个Integer集合iList.add(8); //增添元素,实例变量赋值iList.add(88);bList = new ArrayList<Integer>(iList); //bList也可以是Integer元素的ArrayList集合
复制代码

以上例子只是 Java 泛类型的一个简单例子。从以上例子我们可以看到:

1. 泛类型的应用改变了 Java 语法表示和代码编写传统。

2. 泛类型具有更广泛的声明和定义集合类的功能。

3. 使得对集合类的表示更加复杂和多样化。

03、泛类型的发展历史

泛类型并不是 Java 独有的,它最先在 C/C++中应用。泛类型实际上是以类型模式,即 templates 方式抽象定义数据类型。与 templates 不同的是,Java 中的泛类型不允许是基本变量类型,也不允许将泛类型应用到静态变量和静态初始化程序块中。如下是 Java 中使用泛类型的典型例子:

class SomeClass<T> {  T value;  SomeClass(T value) {    this.value = value;  }...}
复制代码

以上代码定义了一个泛类型 someClass<T>。尖括号为泛类型标识符。尖括号中可以是任何合法名,如 T,表示类型参数。通常简写为大写字母。在创建对象时,必须用可实例化的类,或称参数化类型替换,如:

SomeClass<Integer> obj = new SomeClass<Integer>(100);
复制代码

在应用参数化类型作替换时,也必须使用尖括号,并在尖括号中应用具体的数据类型,例如 Integer。这里,创建了一个具有 Integer 参数化类型的、SomeClass 的对象 obj。注意,参数化类型不允许是基本变量类型,否则为非法语句。

回到 Java 的集合类,我们可以利用以上技术用集合类的根接口 Collection 以泛类型格式定义如下:

public interface Collection<E> {  //定义基本的集合类方法}
复制代码

Collection<E>称为泛类型,<E>表示类型参数。在创建集合时,它可以被具体的参数化类型所代替。如我们在本章开始讨论过的例子:

Collection<String> collect = new ArrayList<String>();
复制代码

类型参数<E>被参数化类型<String>所代替,创建了一个名为 collect 具有字符串类型元素的 ArrayList 集合,或简称字符串集合。由于 Collection 是 ArrayList 的父类,所以可以作为对 ArrayList 的引用。

04、泛类型的广泛应用举例

泛类型不仅用于集合,也可以用来定义类中的实例变量,但这些变量不允许是基本数据类型。如下例在自定义类 GeneItems 时定义了三个泛类型,每个类型参数用逗号分隔。即:

class GeneItems<T1, T2 , T3> {  private T1 firstObj;  private T2 secondObj;  private T3 thirdObj;  public GeneItems(T1 obj1, T2 obj2, T3 obj3) {    firstObj = obj1;    secondObj = obj2;    thirdObj = obj3;  }  public void setFirstObj(T1 obj1) {    firstObj = obj1;  }  public T1 getFirstObj() {    return firstObj;  }  ...}
复制代码

注意,在多类型参数定义中,如果某个类型参数,如 T1,用来定义指定的变量,如 firstObj,除非作类型转换,否则在代码中,不允许再用其他类型参数定义 firstObj。如下代码:

public void setFirstObj(T2 obj1) {  firstObj = obj1;}
复制代码

为非法语句。

如下代码:

Geneitems<String, Integer, Double> items = new GeneItems<String, Integer, Double>("Java", 15, 79.89);
复制代码

创建一个具有三个参数化类型的对象 items,并对其实例变量初始化。在对整数类型和双精度类型变量初始化时将整数型常数 15、双精度常数 79.89 分别作为两个包装类的实例变量。

以上讨论的类型参数被称为实类型参数 concrete type parameters;参数化类型被称为实参数化类型 concrete parameterized types。

在讨论和学会集合和泛类型的具体应用之前,还需要了解如下三个重要的泛类型概念。它们分别是:无界通配符<?>、上界通配符<? extends T>、以及下界通配符<? super T>。我们在前面的举例中初步讨论过无界通配符。下面系统地用实例教会你怎样应用这些通配符进行编程。

05、怎样读懂和应用无界通配符<?>

前面介绍过无界类型(Wildcard),使用<?>用来表示一个实类型参数。因为<?>表示任何类型元素,所以也称无界通配符。这个无界通配符的目的是用问号代替任何类型元素;但这些元素不能是基本变量,如 byte, , char, short, int, float, double, long 以及 boolean。注意在应用无界通配符时只是声明一个具有任何类型元素的集合,并没有创建或者定义它。我们必须使用具体的元素类型来创建或定义这个集合,才可以对这个集合的元素进行各种操作。例如:

List<?> dList; //声明一个任何类型元素的集合 List<Double> dList = new ArrayList<Double>(); //创建一个Double集合dList.add(0.8); //增添元素dList.add(0.08);bList = new ArrayList<Double>(dList); //bList可以具有Double集合
复制代码

同样的道理,bList 可以包括 dList 的所有 Double 元素。

值得注意的是,使用通配符时,不允许直接在集合中增添元素。如:

bList.add(19.22); //非法操作
复制代码

这是因为如果任何类型的元素都可以增添到 bList,则无类型安全的保证。

无界通配符经常作为方法的参数使用,例如:

class TestWildcard <T>{  static void printList(Collection<?> c) {    for(Object obj : c)      System.out.println(obj);  }}
复制代码

在这个例子中,无界通配符<?>表示一个未确定实类型参数。调用这个方法时,这个参数可以是具有任何元素类型的 Collection 成员集合。在循环中,再把这个集合中的元素由 Object 来引用,当属合法应用。接着上面的例子:

TestWildcard.printList(dList); //或者TestWildcard.printList(bList);
复制代码

dList 或 bList 是 Double 元素的集合,是 Collection 的成员集合,可以作为 printList()的合法参数。运行结果为:

0.80.08
复制代码

如下程序演示调用 printList(Collection<?> c)的更多例子:

ArrayList<String> arrayList = new ArrayList<String>(); //String将替换printList()的无界通配符arrayList.add("abc");arrayList.add("xyz");Test.printList(agrrayList); //接受字符串集合
复制代码

arrayList 是字符串集合,也是 Collection 的成员。运行结果为:

abcxyz
复制代码

注意 集合类不是 Object。用 Object 引用集合类对象,如 printList(Object c),属非法。但集合类对象中的元素可以是 Object 对象,可以被 Object 引用,如 for(Object obj : c)为合法。

06、怎样读懂和应用上界通配符<? extends T>

上界通配符(Upper-Bounded Wildcards) 限定元素类型,或实类型参数,必须是包括 T 在内的子类元素,否则为非法。例如:

List<? extends Number> aList;
复制代码

这里,aList 的实类型必须是数据类,如 Integer、Long、Float、Double、BigDecimal 以及 Number,因为它们都是 Number 的子类。如:

aList = new LinkedList<Integer>();
复制代码

以及:

aList = new ArrayList<Double>();
复制代码

都属合法集合创建。而:

aList = new ArrayList<String>();
复制代码

以及:

aList = new LinkedList<Object>();
复制代码

均属非法。因为 String 没有继承 Number,而 Object 则是 Number 的超类。

再讨论一个例子。假设有如下自定义继承类 Item 和 Book2: 

class Item { //超类Item  protected String name;  Item(String name) {    this.name = name;  }  public String toString() {    return "Name: " + name;  }}class Book2 extends Item { //子类Book2  private int quantity;  private String publisher;  public Book2(String name, int quantity, String publisher)  {    super(name);    this.quantity = quantity;    this.publisher = publisher;  }  public String toString() {    return super.toString() + "\nQuantity: " + quantity+ "\npublisher: " + publisher;  }


复制代码

 假设使用如下具有上界通配符的静态方法:

static void printList(List<? extends Item> c) {    for(Item item : c)       System.out.println(item);    }  }
复制代码

方法 printList(List<? extends Item> c)规定调用这个方法的参数必须是 Book2 以及 Item 元素的集合。

如下代码:

List<Book2> bList = new ArrayList<Book2>();bList.add(new Book2("Java", 5, “ABC Publishing”)); //增添Book2元素bList.add(new Book2("JSPS", 10, “Lulu.com”));List<? extends Item> list = new ArrayList<Book2>(bList); //Book2 extends ItemTest.printList(list); //调用这个静态方法
复制代码

list 是 bList 的拷贝,其元素都是 Book2。

运行结果为:

Name: JavaQuantity: 5Publisher: ABC PublishingName: JSPSQuantity: 10Publisher: Lulu.com
复制代码

如下代码也符合上界通配符<? extends Item>的规定,属合法创建和调用:

List<Item> iList = new LinkedList<Item>();iList.add(new Item("software"));iList.add(new Item("hardware"));Test.printList(iList); //调用这个静态方法,参数为Item的集合iList
复制代码

运行结果为:

Name: softwareName: hardware
复制代码

但如下代码:

List<String> sList = new LinkedList<String>();sList.add("xyz");Test.printList(sList); //编译错误
复制代码

因为 sList 的元素定义为 String,超出上界通配符<? extends Item>的范围,属非法调用。

如果将方法 printList (List<?extends Item>C)中的实类型 Item 修改为泛类型,如:

class TestWildcard <T> {  static <T> void printList(List<? extends T> c) {    for(T item : c)       System.out.println(item);  }}
复制代码

在应用时,泛类型 T 将被任何一个实类型替换,如 Item,Book2,或者 String 替换,那么这个方法也可以接受 sList 作为合法参数。而<? extends Object>则等同于无界通配符<?>,因为所有类都是 Object 的子类。

07、怎样读懂和应用下界通配符<? super T>

下界通配符 (Lower Bounded Wildcards) 限定元素类型,或实类型参数,必须是包括 T 在内的超类元素,否则为非法。例如:

List<? super Integer> aList;
复制代码

则规定集合 aList 必须是 Number,或 Object,或 Integer 类型的集合。你回顾一下,下界通配符正好与上界通配符相反。再例如:

Collection<? super String> sList;
复制代码

规定 sList 必须是 Object 或者 String 类型的集合。

还以上面讨论过的 Item 和 Book2 为例。将方法 printList()修改如下:




class Test3 { static void printList(List<? super Book2> c) {//或Collection <? super Books> c for(Object item : c) System.out.println(item); }}
复制代码

注意,Book2 的超类最终有可能是 Object,必须用 Object 类引用集合 c 中的元素,才属合法。

首先创建并增添新元素到 iList,再调用 Test3 中的 printList()方法:

List<Item> iList = new ArrayList<Item>();iList.add(new Item("software"));iList.add(new Item("hardware"));List<? super Book2> list = new ArrayList<Item>(iList); //list具有iList的Item元素Test3.printList(list); //合法调用
复制代码

List 中的元素都是 Book2 的超类元素 Item,符合下界通配符<? super Book2>的规定,属合法调用。

08、高手怎样应用泛类型

在实际应用中,上、下界通配符经常在一起使用,以便限定集合中的元素类型。例如在 java.util.Collections 类中的搜索方法:

int binarySearch(List<? extends Comparable<? super T>> list, T key)
复制代码

规定了两个参数,第一个参数应用下界和上界通配符,指定 list 中的元素必须是而且实现了 Comparable 的子类,第二个参数指定了搜索键。当然,所有 JDK 提供的类都满足这个条件。但自定义类必须首先实现 Comparable 接口,才可调用这个方法。

下面讨论上、下界通配符的具体应用。假设你编写了一个如下复制方法 copy():

public static <T> void copy(List<? super T> dest, List<? extends T> src) {      for (int i = 0; i < src.size(); i++) {          dest.set(i, src.get(i));  }}
复制代码

指定了两个参数。第一个参数规定 dest 中的元素必须是 T,并包括 T 的超类,而第二个参数规定 src 中的元素必须是 T 并包括 T 的子类。同时满足这个两个条件的参数,或者集合元素,必须具备如下条件:


  • 集合 dest 和 src 同类。或者,

  • 集合 dest 和 src 具有继承关系。即 src 中的元素必须是 dest 的子类对象。

如下代码演示了符合以上两个条件的典型复制操作:

List<String> str1 = Arrays.asList("abc", "xyz"); //调用Arrays的asList()方法返回一个字符串集合List<String> str2 = Arrays.asList("11");Test.copy(str1, str2); //同类集合复制System.out.println(str1);List<Object> objs = Arrays.<Object>asList(1, 2.89, "three"); //返回一个Object集合List<Integer> ints = Arrays.asList(100); //返回一个Integer集合Test.copy(objs, ints); //Integer是Object的子类,合法复制System.out.println(objs); //返回一个Item集合List<Item> items = Arrays.<Item>asList(new Item("Java"), new Item("JSPS")); //返回一个Book2集合List<Book2> books = Arrays.<Book2>asList(new Book2("J2EE", 100.89, "Bot Publishing"));Test.copy(items, books); //Book2是Item的子类,合法复制System.out.println(items)
复制代码

运行结果为:

[11, xyz]
[100, 2.89, three]
[Name: J2EE price: 110.89 publisher: Bot Publishing, Name: JSPS price: 0.0]
复制代码

为了增添元素方便,例子中应用了 java.util.Arrays 的方法 asList()。这个方法接受一个或者多个元素,返回一个指定类型的 ArrayList 集合。因为 ArrayList 实现 List 接口,我们利用 List 作为引用。可以看到,如果 dest 和 src 都是相同类型集合,程序将执行指定的复制操作。如 String、Integer 或者 src 是 dest 的子类, 如在 Test.copy(items, books)中,books 是 items 的子类。当然,如果 src 是 Integer 的集合,合法的 dest 集合还有 Number 以及 Integer 本身。

表 1 总结了泛类型常用例子和术语解释。表中利用 Collection 和 ArrayList 为例,可以举一反三,推广到任何集合类的应用。

表 1  泛类型常用例子和术语解释 



发布于: 刚刚阅读数: 3
用户头像

TiAmo

关注

有能力爱自己,有余力爱别人! 2022-06-16 加入

CSDN全栈领域优质创作者;阿里云创作者社区专家博主、技术博主、星级博主;华为云享专家;

评论

发布
暂无评论
Java高手速成 | Java集合类泛类型_Java_TiAmo_InfoQ写作社区