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

news2024/11/17 12:51:34

 Java高手是这样炼成的。

01、Java集合类包括哪些?

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

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

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

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

//完整程程序见《Java高手是怎样炼成的》资源配套目录Ch14名为WildcardTest.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.8
0.08

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

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

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

abc
xyz

注意  集合类不是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 Item
Test.printList(list); //调用这个静态方法

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

运行结果为:

Name: Java
Quantity: 5
Publisher: ABC Publishing
Name: JSPS
Quantity: 10
Publisher: 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: software
Name: 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  泛类型常用例子和术语解释 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/149780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java-性能分析监控工具

Java监控和管理 Java监控和管理API Java Standard Edition&#xff08;Java SE&#xff09;平台提供的监控和管理技术 - JMX&#xff08;Java Management Extensions&#xff09; 技术。 Java SE 中包含了用于监控和管理的&#xff08;java.lang.management&#xff09;API&…

RabbitMQ 总结一(简介、安装、Demo)

目录 什么是MQ RabbitMQ和netty是什么关系 作用 流量削峰 应用解耦 异步处理 MQ的构成 生产者 交换机 队列 消费者 下载安装 案例Demo producer 第一步&#xff0c; 定义好连接的信息并且拿到连接&#xff0c;一般一个consumer/ producer 和broker只会建立一条连…

mysql之日志

前言 一条数据在更新过程当中&#xff0c;如果中途 mysql crash 了&#xff0c;mysql 是如何保证数据的一致性和持久性的&#xff1f;在这个过程中 mysql 的日志系统起到了至关重要的作用。本文将会介绍 mysql 中的 undo log、redo log 和 bin log 在这其中的作用。 buffer p…

230109-MacOS解决brew安装慢的问题

MacOS解决brew安装慢的问题 原文请移步参考&#xff1a; https://www.zhihu.com/question/46963138 cd "$(brew --repo)" git remote set-url origin https://mirrors.ustc.edu.cn/brew.gitecho export HOMEBREW_BOTTLE_DOMAINhttps://mirrors.ustc.edu.cn/homebrew…

2022年度大赏 | UWA问答精选

UWA每周推送的知识型栏目《厚积薄发 | 技术分享》已经伴随大家走过了304个工作周。精选了2022年十大精彩问答分享给大家&#xff0c;期待2022年UWA问答继续有您的陪伴。 Q1&#xff1a;动态获取URP设置里自定义的RenderFeatures 我们在URP项目中自定义了多个RenderFeatures去实…

2022年度总结,以及2023的全新展望

时光总是在你的不经意间流逝&#xff0c;无法挽留&#xff1b;留得住的是你过去的努力和回忆&#xff0c;它也许充斥着快乐、忧伤、病痛等等。俗话说得好“笑一笑十年少”&#xff0c;那么我希望与快乐随行&#xff0c;让痛苦尘封记忆。让我们总结过去&#xff0c;展望未来&…

Python tkinter -- 第18章 画布控件之矩形

18.2.20 create_rectangle(bbox, **options) 根据限定矩形 bbox&#xff0c;在画布上创建一个矩形。新创建的对象位于显示的最前端。 &#xff08;1&#xff09;bbox&#xff1a;定义要创建对象的边界(x1, y1, x2, y2) &#xff08;2&#xff09;options&#xff1a; 选项的具体…

ICESat数据下载

ICESat数据下载1. ICESat简介2. ICESat数据产品2.1 GLA01数据2.2 GLA14数据3. 数据下载4. 总结1. ICESat简介 2003年美国国家航空航天局NASA ( National Aeronautics and SpaceAdministration)发射冰、云和陆地高程卫星ICESat&#xff0c;其上搭载的地球科学激光测高系统GLAS是…

基于YOLOv5的智能人脸数据标注工具源码,实现人脸数据标注自动化,可导出PASCAL VOC XML、MS COCO JSON

基于YOLOv5的智能人脸数据标注工具&#xff0c;实现人脸数据标注自动化 可自定义人脸检测模型、可导出多种格式标签&#xff0c;包括PASCAL VOC XML、MS COCO JSON、YOLO TXT 下载地址&#xff1a;基于YOLOv5的智能人脸数据标注工具源码&#xff0c;实现人脸数据标注自动化 …

智能黑白图像自动上色——C++实现

前言 《Colorful Image Colorization》是加里福利亚大学Richard Zhang发表在ECCV 2016上的文章&#xff0c;论文主要解决的问题是给灰度图的自动着色&#xff0c;算法并不是为恢复灰度图的真实颜色&#xff0c;而是用灰度图中物体的纹理、语义等信息作为线索&#xff0c;来预测…

Gradle学习笔记01

一、Gradle视频介绍新一代构建工具Gradle&#xff0c;提到项目自动化构建工具&#xff0c;首先提到的是Maven。现在谈谈Gradle与Maven之间的差异&#xff1a;同样作为项目自动化构建工具&#xff0c;Maven更侧重于项目jar包的管理&#xff0c;而Gradle侧重于项目的构建&#xf…

卷积层、卷积层里的填充和步幅

多层感知机正式总结卷积层之前&#xff0c;先把上次多层感知机落下的一点内容补一补。几个概念&#xff1a;训练数据集&#xff1a;训练模型参数。验证数据集&#xff1a;选择模型超参数。训练误差&#xff1a;模型在训练数据上的误差。泛化误差&#xff1a;模型在新数据上的误…

Linux常用命令——lftp命令

在线Linux命令查询工具 lftp 优秀的文件客户端程序 补充说明 lftp命令是一款优秀的文件客户端程序&#xff0c;它支持ftp、SETP、HTTP和FTPs等多种文件传输协议。lftp支持tab自动补全&#xff0c;记不得命令双击tab键&#xff0c;就可以看到可能的选项了。 语法 lftp(选项…

基于PCAP搭建软HUB实现物联网在线调试

完整工程可从以下地址签出&#xff1a; https://gitcode.net/coloreaglestdio/pcaphub.git 1.需求场景 在调试嵌入式物联设备时&#xff0c;尤其是在多个以太网物联设备交错通信的情况下&#xff0c;很难通过在捉襟见肘的嵌入式系统上进行数据记录与调试。如果设备连接的是一…

基于Kintex-7 FPGA的核心板电路设计

1. 引言Field Programmable GateArray&#xff08;简称&#xff0c;FPGA&#xff09;于1985年由XILINX创始人之一Ross Freeman发明&#xff0c;第一颗FPGA芯片XC2064为XILINX所发明&#xff0c;FPGA一经发明&#xff0c;后续的发展速度之快&#xff0c;超出大多数人的想象&…

系分 - 案例分析 - 需求获取

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录系分 - 案例分析 - 需求获取需求获取方法典型例题题目描述参考答案系分 - 案例分析 - 需求获取 需求获取方法 用户访谈 工作内容要点准备访谈步骤&#xff1a;1 确定访谈目的。2 确定访谈哪些用户。3 准…

PHP 文件上传

通过 PHP&#xff0c;可以把文件上传到服务器。 本章节实例在 test 项目下完成&#xff0c;目录结构为&#xff1a; test |-----upload # 文件上传的目录 |-----form.html # 表单文件 |-----upload_file.php # php 上传代码 源码下载 创建一个文件上…

获取Git权限的三种方式

获取Git权限的三种方式写在最前1. HTTPS配合用户名和密码访问Git1.1 获取当前项目的用户和密码1.2 通过临时用户获取Git权限2. HTTPS配合token访问Git2.1 创建token2.2 HTTPS配合token获取Git权限3. SSH访问Git3.1 生成SSH公钥和私钥3.2 使用SSH获取Git权限写在最前 本文以为Az…

力扣(LeetCode)375. 猜数字大小 II(2023.01.08)

我们正在玩一个猜数游戏&#xff0c;游戏规则如下&#xff1a; 我从 1 到 n 之间选择一个数字。 你来猜我选了哪个数字。 如果你猜到正确的数字&#xff0c;就会 赢得游戏 。 如果你猜错了&#xff0c;那么我会告诉你&#xff0c;我选的数字比你的 更大或者更小 &#xff0c;并…

接口测试——postman和Jemter

接口测试——postman和Jemterpostmanpostman工作原理postman入门postman的基础用法postman的高级用法使用postman管理测试用例批量执行测试用例postman断言环境变量和全局变量postman关联postman请求前置脚本postman参数化及生成测试报告参数化与数据驱动postman生成测试报告je…