【Java编程思想读后整理(干货版)】第十二章 集合2

简介: 【Java编程思想读后整理(干货版)】第十二章 集合

七、迭代器Iterators

1.为什么要有迭代器

在任何集合中,都必须有某种方式可以插入元素并再次获取它们。

如果从更高层次的角度考虑,会发现这里有个缺点:要使用集合,必须对集合的确切类型编程。

为了解决这个问题,迭代器这个概念被提了出来。


2.什么是迭代器

迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为轻量级对象(lightweight object):创建它的代价小。因此,经常可以看到一些对迭代器有些奇怪的约束。例如,Java 的 Iterator 只能单向移动。这个 Iterator 只能用来:


使用 iterator() 方法要求集合返回一个 Iterator。 Iterator 将准备好返回序列中的第一个元素。

使用 next() 方法获得序列中的下一个元素。

使用 hasNext() 方法检查序列中是否还有元素。

使用 remove() 方法将迭代器最近返回的那个元素删除。

3.如何使用迭代器

代码示例如下:


public static void display(Iterator<Pet> it) {
    while(it.hasNext()) {
      Pet p = it.next();
      System.out.print(p.id() + ":" + p + " ");
    }
    System.out.println();
  }


我们可以使用 Iterable 接口生成上一个示例的更简洁版本(该接口必须要实现iterator()方法,即提供一个Iterator迭代器)


public static void display(Iterator<Pet> it) {
    while(it.hasNext()) {
      Pet p = it.next();
      System.out.print(p.id() + ":" + p + " ");
    }
    System.out.println();
  }


4.ListIterator迭代器

ListIterator 是一个更强大的 Iterator 子类型,它只能由各种 List 类生成。 Iterator 只能向前移动,而 ListIterator 可以双向移动。它可以生成迭代器在列表中指向位置的后一个和前一个元素的索引,并且可以使用 set() 方法替换它访问过的最近一个元素。可以通过调用 listIterator() 方法来生成指向 List 开头的 ListIterator ,还可以通过调用 listIterator(n) 创建一个一开始就指向列表索引号为 n 的元素处的 ListIterator 。


5.for-in和迭代器

for-in 语法主要用于数组,但它也适用于任何 Collection 对象。示例如下:
for(Map.Entry entry: System.getenv().entrySet()) {
      System.out.println(entry.getKey() + ": " +
        entry.getValue());
    }

for-in语法出现于Java5,其原理在于 Java 5 引入了一个名为 Iterable 的接口,该接口包含一个能够生成 Iterator 的 iterator() 方法。for-in 使用此 Iterable 接口来遍历序列。而在 Java 5 中,许多类都是 Iterable ,主要包括所有的 Collection 类(但不包括各种 Maps )。

所以for-in 语句适用于数组或其它任何 Iterable ,但这并不意味着数组肯定也是个 Iterable ,也不会发生任何自动装箱。

PS:这句话的意思是实现了Iterable 接口的类都可以使用for-in语法,但是这并不是说数组本身也实现了Iterable接口,数组能使用for-in语法应该要归功于编译器层次的改变。


适配器方法惯用法

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

一种解决方案是所谓适配器方法(Adapter Method)的惯用法


而解决思路就是另写一个方法返回实现了Iterable接口的对象,实例如下


import java.util.*;
class ReversibleArrayList<T> extends ArrayList<T> {
  ReversibleArrayList(Collection<T> c) {
    super(c);
  }
  public Iterable<T> reversed() {
    return new Iterable<T>() {
      public Iterator<T> iterator() {
        return new Iterator<T>() {
          int current = size() - 1;
          public boolean hasNext() {
            return current > -1;
          }
          public T next() { return get(current--); }
          public void remove() { // Not implemented
            throw 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 choice
    for(String s : ral.reversed())
      System.out.print(s + " ");
  }
}
/* Output:
To be or not to be
be to not or be To
*/


八、集合的使用技巧

1.面向接口编程

在理想情况下,你编写的大部分代码都在与这些接口打交道,并且唯一需要指定所使用的精确类型的地方就是在创建的时候。因此,可以像下面这样创建一个 List :


List<Apple> apples = new ArrayList<>();

请注意, ArrayList 已经被向上转型为了 List ,这与之前示例中的处理方式正好相反。使用接口的目的是,如果想要改变具体实现,只需在创建时修改它就行了。因此,应该创建一个具体类的对象,将其向上转型为对应的接口,然后在其余代码中都是用这个接口。


2.创建集合时尽量写上合适的初始化大小

我们在平常使用过程中,创建对象时,便给ArrayList一个初始化的大小,代码如下:

List arrayList=new ArrayList<>(2);

在可预期的范围内,尽量避免扩容的发生。当然这并不是越大越好,因为过大的初始容量会造成内存的浪费。


3.使用Iterator来连接序列和消费该序列的方法

生成 Iterator 是将序列与消费该序列的方法连接在一起耦合度最小的方式,并且与实现 Collection 相比,它在序列类上所施加的约束也少得多。


4.添加元素组的几个常用方法

在 java.util 包中的 Arrays 和 Collections 类中都有很多实用的方法,可以在一个 Collection 中添加一组元素。


Arrays.asList()

Arrays.asList() 方法接受一个数组或是逗号分隔的元素列表(使用可变参数),并将其转换为 List 对象。


PS:这里看源码我们就能发现Arrays类内部自己实现了个ArrayList类,和util包中的ArrayList有所不同,它就是简单继承实现了AbstractList类,其并没有真正ArrayList的扩容能力,其内部只是根据这个数组去做了一个封装,所以如果改变了这个list的内容,那么输入的数组也会发生改变(如果输入的数组的话)。

q4.png


这里我们还可以使用如下方法:


List<Snow> snow4 = Arrays.<Snow>asList(
       new Light(), new Heavy(), new Slush());

注意 Arrays.asList() 中间的“暗示”(即 ),告诉编译器 Arrays.asList() 生成的结果 List 类型是什么实际目标类型。这称为显式类型参数说明(explicit type argument specification)。


Collections.addAll()

Collections.addAll() 方法接受一个 Collection 对象,以及一个数组或是一个逗号分隔的列表,将其中元素添加到 Collection 中。


PS:和Arrays类类似,Collections类中只包含对集合进行操作或返回集合的静态方法。和我们常说的集合类无关,可以理解为简化集合类操作的工具类。就如jdk里注释所说的:


q3.png

collection.addAll()

用法如下:


Collection<Integer> collection =new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
collection.addAll(Arrays.asList(moreInts));

5.集合的打印

PS:原文这部分内容打印部分讲的比较少,反而各个集合类型的区别占了很大篇幅,这是我认为不合理的地方,为了应和标题,我将补充一下我对于集合打印的理解


集合打印会自动调用集合的toString方法,原因是在调用println方法时实质上会调用String.valueOf(Object x)方法,而该方法实质上就是调用了对象的toString方法。


q2.png

q4.png


而我们打印的时候可以打印出类似数组的样式


q.png

所以我们有理由猜测集合类中一定重写了toString方法,而事实确实如此,可我们在具体诸如ArrayList、LinkedList类中并没有发现被重写的痕迹。原因是该方法已经在AbstractCollection类中被重写了。


q2.png

不知道大家注意到没有,这里有一点很巧妙。他在重写toString方法时调用了相应的迭代器,从而使该方法可以在各个类的抽象类中就被重写而不用去管具体类的具体实现。


本章小结

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


数组将数字索引与对象相关联。它保存类型明确的对象,因此在查找对象时不必对结果做类型转换。它可以是多维的,可以保存基本类型的数据。虽然可以在运行时创建数组,但是一旦创建数组,就无法更改数组的大小。


Collection 保存单一的元素,而 Map 包含相关联的键值对。使用 Java 泛型,可以指定集合中保存的对象的类型,因此不能将错误类型的对象放入集合中,并且在从集合中获取元素时,不必进行类型转换。各种 Collection 和各种 Map 都可以在你向其中添加更多的元素时,自动调整其尺寸大小。集合不能保存基本类型,但自动装箱机制会负责执行基本类型和集合中保存的包装类型之间的双向转换。


像数组一样, List 也将数字索引与对象相关联,因此,数组和 List 都是有序集合。


如果要执行大量的随机访问,则使用 ArrayList ,如果要经常从表中间插入或删除元素,则应该使用 LinkedList 。


队列和堆栈的行为是通过 LinkedList 提供的。


Map 是一种将对象(而非数字)与对象相关联的设计。 HashMap 专为快速访问而设计,而 TreeMap 保持键始终处于排序状态,所以没有 HashMap 快。 LinkedHashMap 按插入顺序保存其元素,但使用散列提供快速访问的能力。


Set 不接受重复元素。 HashSet 提供最快的查询速度,而 TreeSet 保持元素处于排序状态。 LinkedHashSet 按插入顺序保存其元素,但使用散列提供快速访问的能力。


不要在新代码中使用遗留类 Vector ,Hashtable 和 Stack 。

相关文章
|
2月前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
45 6
|
2月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
43 3
|
2月前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
33 2
|
2月前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
34 3
|
26天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
27 2
|
1月前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
26天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
1月前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
1月前
|
Java 开发者
|
2月前
|
安全 Java 程序员
深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制
本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。
60 5