十二、集合(5)

本章概要

  • for-in 和迭代器
    • 适配器方法惯用法
  • 本章小结
    • 简单集合分类

for-in和迭代器

到目前为止,for-in 语法主要用于数组,但它也适用于任何 Collection 对象。实际上在使用 ArrayList 时,已经看到了一些使用它的示例,下面是它的通用性的证明:

import java.util.*;public class ForInCollections {public static void main(String[] args) {Collection<String> cs = new LinkedList<>();Collections.addAll(cs,"Take the long way home".split(" "));for (String s : cs) {System.out.print("'" + s + "' ");}}
}

在这里插入图片描述

由于 cs 是一个 Collection ,因此该代码展示了使用 for-in 是所有 Collection 对象的特征。

这样做的原因是 Java 5 引入了一个名为 Iterable 的接口,该接口包含一个能够生成 Iteratoriterator() 方法。for-in 使用此 Iterable 接口来遍历序列。因此,如果创建了任何实现了 Iterable 的类,都可以将它用于 for-in 语句中:

import java.util.*;public class IterableClass implements Iterable<String> {protected String[] words = ("And that is how we know the Earth to be banana-shaped.").split(" ");@Overridepublic Iterator<String> iterator() {return new Iterator<String>() {private int index = 0;@Overridepublic boolean hasNext() {return index < words.length;}@Overridepublic String next() {return words[index++];}@Overridepublic void remove() { // Not implementedthrow new UnsupportedOperationException();}};}public static void main(String[] args) {for (String s : new IterableClass()) {System.out.print(s + " ");}}
}

在这里插入图片描述

iterator() 返回的是实现了 Iterator 的匿名内部类的实例,该匿名内部类可以遍历数组中的每个单词。在主方法中,可以看到 IterableClass 确实可以用于 for-in 语句。

在 Java 5 中,许多类都是 Iterable ,主要包括所有的 Collection 类(但不包括各种 Maps )。 例如,下面的代码可以显示所有的操作系统环境变量:

import java.util.*;public class EnvironmentVariables {public static void main(String[] args) {for (Map.Entry entry : System.getenv().entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}}
}

在这里插入图片描述

System.getenv() 返回一个 MapentrySet() 产生一个由 Map.Entry 的元素构成的 Set ,并且这个 Set 是一个 Iterable ,因此它可以用于 for-in 循环。

for-in 语句适用于数组或者其它任何 Iterable ,但这并不代表数组一定是 Iterable ,也不会发生任何自动装箱:

import java.util.*;public class ArrayIsNotIterable {static <T> void test(Iterable<T> ib) {for (T t : ib) {System.out.print(t + " ");}}public static void main(String[] args) {test(Arrays.asList(1, 2, 3));String[] strings = {"A", "B", "C"};// An array works in for-in, but it's not Iterable://- test(strings);// You must explicitly convert it to an Iterable:test(Arrays.asList(strings));}
}

在这里插入图片描述

尝试将数组作为一个 Iterable 参数传递会导致失败。这说明不存在任何从数组到 Iterable 的自动转换;必须手工执行这种转换。

适配器方法惯用法

如果现在有一个 Iterable 类,你想要添加一种或多种在 for-in 语句中使用这个类的方法,应该怎么做呢?例如,你希望可以选择正向还是反向遍历一个单词列表。如果直接继承这个类,并重写 iterator() 方法,则只能替换现有的方法,而不能实现遍历顺序的选择。

一种解决方案是所谓_适配器方法_(Adapter Method)的惯用法。“适配器”部分来自于设计模式,因为必须要提供特定的接口来满足 for-in 语句。如果已经有一个接口并且需要另一个接口时,则编写适配器就可以解决这个问题。
在这里,若希望在默认的正向迭代器的基础上,添加产生反向迭代器的能力,因此不能使用重写,相反,而是添加了一个能够生成 Iterable 对象的方法,该对象可以用于 for-in 语句。这使得我们可以提供多种使用 for-in 语句的方式:

import java.util.*;class ReversibleArrayList<T> extends ArrayList<T> {ReversibleArrayList(Collection<T> c) {super(c);}public Iterable<T> reversed() {return new Iterable<T>() {@Overridepublic Iterator<T> iterator() {return new Iterator<T>() {int current = size() - 1;@Overridepublic boolean hasNext() {return current > -1;}@Overridepublic T next() {return get(current--);}@Overridepublic void remove() { // Not implementedthrow new UnsupportedOperationException();}};}};}
}public class AdapterMethodIdiom {public static void main(String[] args) {ReversibleArrayList<String> ral =new ReversibleArrayList<String>(Arrays.asList("To be or not to be".split(" ")));// Grabs the ordinary iterator via iterator():for (String s : ral) {System.out.print(s + " ");}System.out.println();// Hand it the Iterable of your choicefor (String s : ral.reversed()) {System.out.print(s + " ");}}
}

在这里插入图片描述

在主方法中,如果直接将 ral 对象放在 for-in 语句中,则会得到(默认的)正向迭代器。但是如果在该对象上调用 reversed() 方法,它会产生不同的行为。

通过使用这种方式,可以在 IterableClass.java 示例中添加两种适配器方法:

MultiIterableClass.java

import java.util.*;public class MultiIterableClass extends IterableClass {public Iterable<String> reversed() {return new Iterable<String>() {@Overridepublic Iterator<String> iterator() {return new Iterator<String>() {int current = words.length - 1;@Overridepublic boolean hasNext() {return current > -1;}@Overridepublic String next() {return words[current--];}@Overridepublic void remove() { // Not implementedthrow new UnsupportedOperationException();}};}};}public Iterable<String> randomized() {return new Iterable<String>() {@Overridepublic Iterator<String> iterator() {List<String> shuffled =new ArrayList<String>(Arrays.asList(words));Collections.shuffle(shuffled, new Random(47));return shuffled.iterator();}};}public static void main(String[] args) {MultiIterableClass mic = new MultiIterableClass();for (String s : mic.reversed()) {System.out.print(s + " ");}System.out.println();for (String s : mic.randomized()) {System.out.print(s + " ");}System.out.println();for (String s : mic) {System.out.print(s + " ");}}
}

IterableClass.java

import java.util.Iterator;public class IterableClass implements Iterable<String> {protected String[] words = ("And that is how we know the Earth to be banana-shaped.").split(" ");@Overridepublic Iterator<String> iterator() {return new Iterator<String>() {private int index = 0;@Overridepublic boolean hasNext() {return index < words.length;}@Overridepublic String next() {return words[index++];}@Overridepublic void remove() { // Not implementedthrow new UnsupportedOperationException();}};}public static void main(String[] args) {for (String s : new IterableClass()) {System.out.print(s + " ");}}
}

在这里插入图片描述

注意,第二个方法 random() 没有创建它自己的 Iterator ,而是直接返回被打乱的 List 中的 Iterator

从输出中可以看到, Collections.shuffle() 方法不会影响到原始数组,而只是打乱了 shuffled 中的引用。之所以这样,是因为 randomized() 方法用一个 ArrayListArrays.asList() 的结果包装了起来。如果这个由 Arrays.asList() 生成的 List 被直接打乱,那么它将修改底层数组,如下所示:

import java.util.*;public class ModifyingArraysAsList {public static void main(String[] args) {Random rand = new Random(47);Integer[] ia = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};List<Integer> list1 =new ArrayList<>(Arrays.asList(ia));System.out.println("Before shuffling: " + list1);Collections.shuffle(list1, rand);System.out.println("After shuffling: " + list1);System.out.println("array: " + Arrays.toString(ia));List<Integer> list2 = Arrays.asList(ia);System.out.println("Before shuffling: " + list2);Collections.shuffle(list2, rand);System.out.println("After shuffling: " + list2);System.out.println("array: " + Arrays.toString(ia));}
}

在这里插入图片描述

在第一种情况下, Arrays.asList() 的输出被传递给了 ArrayList 的构造器,这将创建一个引用 ia 的元素的 ArrayList ,因此打乱这些引用不会修改该数组。但是,如果直接使用 Arrays.asList(ia) 的结果,这种打乱就会修改 ia 的顺序。重要的是要注意 Arrays.asList() 生成一个 List 对象,该对象使用底层数组作为其物理实现。如果对 List 对象做了任何修改,又不想让原始数组被修改,那么就应该在另一个集合中创建一个副本。

本章小结

Java 提供了许多保存对象的方法:

  1. 数组将数字索引与对象相关联。它保存类型明确的对象,因此在查找对象时不必对结果做类型转换。它可以是多维的,可以保存基本类型的数据。虽然可以在运行时创建数组,但是一旦创建数组,就无法更改数组的大小。
  2. Collection 保存单一的元素,而 Map 包含相关联的键值对。使用 Java 泛型,可以指定集合中保存的对象的类型,因此不能将错误类型的对象放入集合中,并且在从集合中获取元素时,不必进行类型转换。各种 Collection 和各种 Map 都可以在你向其中添加更多的元素时,自动调整其尺寸大小。集合不能保存基本类型,但自动装箱机制会负责执行基本类型和集合中保存的包装类型之间的双向转换。
  3. 像数组一样, List 也将数字索引与对象相关联,因此,数组和 List 都是有序集合。
  4. 如果要执行大量的随机访问,则使用 ArrayList ,如果要经常从表中间插入或删除元素,则应该使用 LinkedList
  5. 队列和堆栈的行为是通过 LinkedList 提供的。
  6. Map 是一种将对象(而非数字)与对象相关联的设计。 HashMap 专为快速访问而设计,而 TreeMap 保持键始终处于排序状态,所以没有 HashMap 快。 LinkedHashMap 按插入顺序保存其元素,但使用散列提供快速访问的能力。
  7. Set 不接受重复元素。 HashSet 提供最快的查询速度,而 TreeSet 保持元素处于排序状态。 LinkedHashSet 按插入顺序保存其元素,但使用散列提供快速访问的能力。
  8. 不要在新代码中使用遗留类 VectorHashtableStack

浏览一下Java集合的简图(不包含抽象类或遗留组件)会很有帮助。这里仅包括在一般情况下会碰到的接口和类。(译者注:下图为原著PDF中的截图,可能由于未知原因存在问题。这里可参考译者绘制版)

在这里插入图片描述

简单集合分类

可以看到,实际上只有四个基本的集合组件: MapListSetQueue ,它们各有两到三个实现版本(Queuejava.util.concurrent 实现未包含在此图中)。最常使用的集合用黑色粗线线框表示。

虚线框表示接口,实线框表示普通的(具体的)类。带有空心箭头的虚线表示特定的类实现了一个接口。实心箭头表示某个类可以生成箭头指向的类的对象。例如,任何 Collection 都可以生成 IteratorList 可以生成 ListIterator (也能生成普通的 Iterator ,因为 List 继承自 Collection )。

下面的示例展示了各种不同的类在方法上的差异。实际代码来自泛型章节,在这里只是调用它来产生输出。程序的输出还展示了在每个类或接口中所实现的接口:

CollectionDifferences.java

public class CollectionDifferences {public static void main(String[] args) {CollectionMethodDifferences.main(args);}
}

CollectionMethodDifferences.java

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;public class CollectionMethodDifferences {static Set<String> methodSet(Class<?> type) {return Arrays.stream(type.getMethods()).map(Method::getName).collect(Collectors.toCollection(TreeSet::new));}static void interfaces(Class<?> type) {System.out.print("Interfaces in " + type.getSimpleName() + ": ");System.out.println(Arrays.stream(type.getInterfaces()).map(Class::getSimpleName).collect(Collectors.toList()));}static Set<String> object = methodSet(Object.class);static {object.add("clone");}static voiddifference(Class<?> superset, Class<?> subset) {System.out.print(superset.getSimpleName() +" extends " + subset.getSimpleName() +", adds: ");Set<String> comp = Sets.difference(methodSet(superset), methodSet(subset));comp.removeAll(object); // Ignore 'Object' methodsSystem.out.println(comp);interfaces(superset);}public static void main(String[] args) {System.out.println("Collection: " +methodSet(Collection.class));interfaces(Collection.class);difference(Set.class, Collection.class);difference(HashSet.class, Set.class);difference(LinkedHashSet.class, HashSet.class);difference(TreeSet.class, Set.class);difference(List.class, Collection.class);difference(ArrayList.class, List.class);difference(LinkedList.class, List.class);difference(Queue.class, Collection.class);difference(PriorityQueue.class, Queue.class);System.out.println("Map: " + methodSet(Map.class));difference(HashMap.class, Map.class);difference(LinkedHashMap.class, HashMap.class);difference(SortedMap.class, Map.class);difference(TreeMap.class, Map.class);}
}

Sets.java

import java.util.HashSet;
import java.util.Set;public class Sets {public static <T> Set<T> union(Set<T> a, Set<T> b) {Set<T> result = new HashSet<>(a);result.addAll(b);return result;}public static <T>Set<T> intersection(Set<T> a, Set<T> b) {Set<T> result = new HashSet<>(a);result.retainAll(b);return result;}// Subtract subset from superset:public static <T> Set<T>difference(Set<T> superset, Set<T> subset) {Set<T> result = new HashSet<>(superset);result.removeAll(subset);return result;}// Reflexive--everything not in the intersection:public static <T> Set<T> complement(Set<T> a, Set<T> b) {return difference(union(a, b), intersection(a, b));}
}

在这里插入图片描述

TreeSet 之外的所有 Set 都具有与 Collection 完全相同的接口。ListCollection 存在着明显的不同,尽管 List 所要求的方法都在 Collection 中。另一方面,在 Queue 接口中的方法是独立的,在创建具有 Queue 功能的实现时,不需要使用 Collection 方法。最后, MapCollection 之间唯一的交集是 Map 可以使用 entrySet()values() 方法来产生 Collection

请注意,标记接口 java.util.RandomAccess 附加到了 ArrayList 上,但不附加到 LinkedList 上。这为根据特定 List 动态改变其行为的算法提供了信息。

从面向对象的继承层次结构来看,这种组织结构确实有些奇怪。但是,当了解了 java.util 中更多的有关集合的内容后,就会发现除了继承结构有点奇怪外,还有更多的问题。集合类库一直以来都是设计难题——解决这些问题涉及到要去满足经常彼此之间互为牵制的各方面需求。所以要做好准备,在各处做出妥协。

尽管存在这些问题,但 Java 集合仍是在日常工作中使用的基本工具,它可以使程序更简洁、更强大、更有效。

下面是译者绘制的 Java 集合框架简图,黄色为接口,绿色为抽象类,蓝色为具体类。虚线箭头表示实现关系,实线箭头表示继承关系。

在这里插入图片描述

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】泛型编程 | 函数模板 | 类模板

一、泛型编程 泛型编程是啥&#xff1f; 编写一种一般化的、可通用的算法出来&#xff0c;是代码复用的一种手段。 类似写一个模板出来&#xff0c;不同的情况&#xff0c;我们都可以往这个模板上去套。 举个例子&#xff1a; void Swap(int& a, int& b) {int tmp …

如何在Robosuite中导入自建的物体模型

Robosuite是基于mujoco封装的机器人操作库&#xff0c;而mujoco的碰撞检测是基于凸壳&#xff0c;也就是geom必须是凸的&#xff0c;如果是凹的话&#xff0c;凹的部分会有一个透明的屏障而无法与其他物体有碰撞。但是实际场景很多物体都不是凸的&#xff0c;比如杯子的口是凹进…

六招带你认识炫酷的新的室内工业艺术风格

就在设计师们以为他们已经见识过所有可能的风格和样式组合时&#xff0c;工业风出现了。这种原始与精致的开创性组合从大都市的仓库和工厂改建而来&#xff0c;对现代装饰产生了巨大的影响。 工业风格的起源 工业风格住宅和工业风格公寓的起源 由于大城市缺乏经济适用房&…

模拟实现字符串函数和内存函数

模拟实现字符串函数和内存函数 函数介绍部分模拟实现strlenstrcpy,strcat,strcmpstrncpy,strncat,strncmpstrstr,strtokstrerror 字符分类函数内存函数memcpy,memmove,memset,memcmp 求字符串长度(strlen)长度不受限制的字符串函数(strcpy,strcat,strcmp)长度受限制的字符串函数…

科学中的人工智能:量子、原子和连续体技术概述

人工智能&#xff08;AI&#xff09;的进步正在推动自然科学领域的一种新的发现范式。如今&#xff0c;AI已经开始通过改进、加速和促进我们对各种空间和时间尺度上自然现象的理解来推动自然科学的发展&#xff0c;催生了一个被称为AI for science&#xff08;AI4Science&#…

Vue中的生命周期钩子

生命周期钩子 :::warning 注意 所有生命周期钩子的 this 上下文将自动绑定至实例中&#xff0c;因此你可以利用 this 访问 props、data、computed 和 methods 等选项内的数据/函数。这意味着你不应该使用箭头函数来定义一个生命周期方法&#xff0c;因为箭头函数中没有 this&a…

牛客:小美的01串翻转

小美的01串翻转 #include<iostream> #include<cstring> #include<string> #include<vector>using namespace std; typedef long long ll; const int N 1100; string s; ll res 0;int main() {cin>>s;int n s.size();vector<vector<in…

第三方服务提权

nfs挂载原理 目标机器192.168.17.138 开启2049 nfs端口 查看目标开放的文件夹 showmount -e 192.168.17.138 回显&#xff1a;/home/peter * 说明可挂载/home/peter的所有目录 使用WinSCP链接靶机192.168.17.138 更改名字为 authorized_keys 靶机 赋值权限 攻击机 nfs挂载提…

2023最新UI工作室官网个人主页源码/背景音乐/随机壁纸/一言

2023最新UI工作室官网个人主页源码/支持背景音乐/随机壁纸/一言 功能介绍&#xff1a; 载入动画 站点简介 Hitokoto 一言 日期及时间 实时天气 时光进度条 音乐播放器 移动端适配 打开文件&#xff1b;index.html和setting.json修改替换你的相关信息&a…

Unity的GPUSkinning进一步介绍

大家好&#xff0c;我是阿赵。   在几年前&#xff0c;我曾经写过一篇介绍GPUSkinning的文章&#xff0c;这么多年之后&#xff0c;还是看到不停有朋友在翻看这篇旧文章。今天上去GitHub看了一下&#xff0c;GPUSkinning这个开源的插件已经很久没有更新过了&#xff0c;还是停…

虹科方案 | HK-NEOs系列带来先进的磁带自动化解决方案

一、HK-NEOs 系列自动磁带库 通常只有在昂贵的企业解决方案中才能找到的高级功能&#xff0c;我们的入门级磁带自动化产品就能够具备。使用 HK-NEOs 系列自动化磁带库&#xff0c;可以获得远程管理、可拆卸盒式磁带卷、可升级磁带驱动器、条形码阅读器等更多功能。 但这还不是…

JavaScript逻辑题:输出1000之内的所有完数。所谓完数指的是:如果一个数恰好等于它的所有因子之和,这个数就称为完数。

// 定义函数function judgeNum(){// 定义数组存储完数let arr []// for循环1000以内的所有数for(let i 1;i<1000;i){// 定义sum存储一个数的因子之和let sum 0;// 内层循环一个数的因子for(let j 1;j<i;j){if(i % j 0){sum j;}}// 如果一个数和它的因子之和相等&am…

FPGA-结合协议时序实现UART收发器(六):仿真模块SIM_uart_drive_TB

FPGA-结合协议时序实现UART收发器&#xff08;六&#xff09;&#xff1a;仿真模块SIM_uart_drive_TB 仿真模块SIM_uart_drive_TB&#xff0c;仿真实现。 vivado联合modelsim进行仿真。 文章目录 FPGA-结合协议时序实现UART收发器&#xff08;六&#xff09;&#xff1a;仿真模…

JavaScript逻辑题:牙膏2元 牙刷5元 牙膏盒15元 请问正好花完100元 有多少情况?

// 定义牙膏 牙刷 牙膏盒分别的价格 let toothpaste 0;let toothbrush 0;let toothpastebox 0;// 定义sum用来存储几种情况let sum 0;//第一层循环 循环牙膏买多少for (let i 0; i < 20; i){toothpaste 5 * i;// 二层循环 循环牙刷的数量for (let j 0; j < 50; j…

合伙企业是什么?

合伙企业可能大家听说也较多&#xff0c;但是到底什么是合伙企业&#xff0c;可能就没那么清楚了。看完今日的内容&#xff0c;你就会知道原来这就是合伙企业啊。 一、什么是合伙企业&#xff1f; 根据《中华人民共和国合伙企业法》&#xff0c;合伙企业是由两个或两个以上的自…

黑马JVM总结(六)

&#xff08;1&#xff09;常量池 方法区的组成中都由一个叫做运行时常量池的部分&#xff0c;内部包含一个叫做StringTable的东西 反编译二进制字节码&#xff1a; 类的基本信息&#xff1a; 常量池&#xff1a; 方法定义&#xff1a; 构造方法 main方法 &#xff1a;方法中…

手动开发-实现SpringMVC底层机制--小试牛刀

文章目录 前端控制器Controller注解RequestMapping注解自定义容器LingWebApplicationContext设计handlerList完成分发请求Service注解和AutoWired注解RequestParam注解完整代码 在这里说的底层机制的实现主要是指&#xff1a;前端控制器、Controller、Service注入容器、对象自动…

go并发处理业务

引言 实际上&#xff0c;在服务端程序开发和爬虫程序开发时&#xff0c;我们的大多数业务都是IO密集型业务&#xff0c;什么是IO密集型业务&#xff0c;通俗地说就是CPU运行时间只占整个业务执行时间的一小部分&#xff0c;而剩余的大部分时间都在等待IO操作。 IO操作包括htt…

反编译小程序详细教程,处理各种异常报错

文章目录 一、准备工作 &#xff08;一&#xff09;安装Nodejs &#xff08;二&#xff09;解密和逆向工具 二、小程序缓存文件解密 &#xff08;一&#xff09;定位小程序缓存路径 &#xff08;二&#xff09;源码解密 &#xff08;三&#xff09;源码反编译 三、小结 四、异常…

Go 异常处理

代码在执行的过程中可能因为一些逻辑上的问题而出现错误 func test1(a, b int) int {result : a / breturn result } func main() {resut : test1(10, 0)fmt.Println(resut) }panic: runtime error: integer divide by zero goroutine 1 [running]: …