读书笔记-《ON JAVA 中文版》-摘要12[第十二章 集合]

第十二章 集合

Java有多种方式保存对象(确切地说,是对象的引用)。数组是保存一组对象的最有效的方式,如果想要保存一组基本类型数据,也推荐使用数组。但是数组具有固定的大小尺寸,而且在更一般的情况下,在写程序的时候并不知道将需要多少个对象,或者是否需要更复杂的方式来存储对象,因此数组尺寸固定这一限制就显得太过受限了。

java.util 库提供了一套相当完整的集合类(collection classes)来解决这个问题,其中基本的类型有 ListSetQueueMap。这些类型也被称作容器类(container classes),集合提供了完善的方法来保存对象,可以使用这些工具来解决大量的问题。

—PS:集合就是放对象的篮子

1. 泛型和类型安全的集合

可以把 ArrayList 看作“可以自动扩充自身尺寸的数组”来看待。使用 ArrayList 相当简单:创建一个实例,用 add() 插入对象;然后用 get() 来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号。ArrayList 还有一个 size() 方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误。

—PS:get() 从0开始,如果该值超过 ArrayList 的长度,会报出数组越界错误

在这里插入图片描述

class Apple {
    private static long counter;
    private final long id = counter++;

    public long id() {
        return id;
    }
}

class Orange {
}

public class ApplesAndOrangesWithoutGenerics {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        ArrayList apples = new ArrayList();
        for (int i = 0; i < 3; i++) {
            apples.add(new Apple());
        }
        apples.add(new Orange());
        for (Object apple : apples) {
            ((Apple) apple).id();
        }
                // PS:测试 get(),超过长度会报错
//        Object o = apples.get(4);
//        System.out.println(o);
    }
}

AppleOrange 是截然不同的,它们除了都是 Object 之外没有任何共同点(如果一个类没有显式地声明继承自哪个类,那么它就自动继承自 Object)。因为 ArrayList 保存的是 Object ,所以不仅可以通过 ArrayList 的 add() 方法将 Apple 对象放入这个集合,而且可以放入 Orange 对象,这无论在编译期还是运行时都不会有问题。当使用 ArrayList 的 get() 方法来取出你认为是 Apple 的对象时,得到的只是 Object 引用,必须将其转型为 Apple。然后需要将整个表达式用括号括起来,以便在调用Apple 的 id() 方法之前,强制执行转型。否则,将会产生语法错误。

在运行时,当尝试将 Orange 对象转为 Apple 时,会出现输出中显示的错误。

使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 Apple 对象的 ArrayList ,只需要使用 ArrayList<> 来代替 ArrayList。尖括号括起来的是类型参数(可能会有多个),它指定了这个集合实例可以保存的类型。通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。

class Apple {
    private static long counter;
    private final long id = counter++;

    public long id() {
        return id;
    }
}

class Orange {
}

public class ApplesAndOrangesWithoutGenerics {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        ArrayList<Apple> apples = new ArrayList<Apple>();
        for (int i = 0; i < 3; i++) {
            apples.add(new Apple());
        }
//        apples.add(new Orange());
        for (Object apple : apples) {
            ((Apple) apple).id();
        }

        // PS:测试 get(),超过长度会报错
//        Object o = apples.get(4);
//        System.out.println(o);
    }
}

—PS:使用泛型在声明list时,定义类型,语法:

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

后面的 Apple 从 Java 7 开始,可以省略,即

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

apples.add(new Orange()); 的报错如下:

在这里插入图片描述

提示的解决方法为,1.将 ArrayList 泛型参数设置为 Orange;2.将 Orange 转换为 Apple类型

当指定了某个类型为泛型参数时,并不仅限于只能将确切类型的对象放入集合中。向上转型也可以像作用于其他类型一样作用于泛型:

import java.util.ArrayList;

class GrannySmith extends Apple {
}

class Gala extends Apple {
}

class Fuji extends Apple {
}

class Braeburn extends Apple {
}

public class GenericsAndUpcasting {
    public static void main(String[] args) {
        ArrayList<Apple> apples = new ArrayList<>();
        apples.add(new GrannySmith());
        apples.add(new Gala());
        apples.add(new Fuji());
        apples.add(new Braeburn());
        for (Apple apple : apples) {
            System.out.println(apple);
        }
    }
}

输出:

com.collection.GrannySmith@7229724f
com.collection.Gala@4c873330
com.collection.Fuji@119d7047
com.collection.Braeburn@776ec8df

2. 基本概念

Java集合类库采用“持有对象”(holding objects)的思想,并将其分为两个不同的概念,表示为类库的基本接口:

  1. 集合(Collection :一个独立元素的序列,这些元素都服从一条或多条规则。List 必须以插入的 顺序保存元素, Set 不能包含重复元素, Queue 按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)

  2. 映射(Map : 一组成对的“键值对”对象,允许使用键来查找值。 ArrayList 使用数字来查找对象,因此在某种意义上讲,它是将数字和对象关联在一起。 map 允许我们使用一个对象来查找另 一个对象,它也被称作关联数组(associative array),因为它将对象和其它对象关联在一起;或者称作字典(dictionary),因为可以使用一个键对象来查找值对象,就像在字典中使用单词查找定义一样。 Map 是强大的编程工具。

—PS:开发经常用的也就是集合(Collection)中 list,及映射(Map)中的 Map

3. 添加元素组

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

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

import java.util.*;

public class AddingGroups {
    public static void main(String[] args) {

        String s = new String();
        Collection<Integer> collection = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        Integer[] moreInts = {6, 7, 8, 9, 10};
        // PS:为了直观,将内容输出一下,sout 语句都是另外加的
        System.out.println("collection原内容---" + collection);

        // PS:addAll() 只能接受另一个 Collection 作为参数
        collection.addAll(Arrays.asList(moreInts));
        System.out.println("collection.addAll操作后的内容---" + collection);

        // PS:使用 Collections 工具类的 addAll()
        Collections.addAll(collection, 11, 12, 13, 14, 15);
        System.out.println("Collections.addAll---并列元素---操作后的内容---" + collection);

        // PS:使用 Collections 工具类的 addAll(),但是后面入参变成了一个数组
        Collections.addAll(collection, moreInts);
        System.out.println("Collections.addAll---数组---操作后的内容---" + collection);

        
        List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);
        System.out.println("list修改前---" + list);
//        list.add( 99);
//        System.out.println("list修改后---" + list);

        // Arrays.asList() 的输出作为一个 List ,但是这里的底层实现是数组,没法调整大 小。如果尝试在这个 List 上调用 add() 或 remove() ,
        // 由于这两个方法会尝试修改数组大小,所以 会在运行时得到“Unsupported Operation(不支持的操作)”错误
//        list.add(45);
//        list.remove(0);
        // PS:可以重新创建个 List ,这样就可以操作了
        List<Integer> arrayList = new ArrayList<>(list);
        arrayList.add(66);
        System.out.println("arrayList增加元素---" + arrayList);
        arrayList.remove(0);
        System.out.println("arrayList移除元素---" + arrayList);
        
        // PS:还可以这样写,称为显式类型参数说明,不过没啥用
        List<Integer> list2 = Arrays.<Integer>asList(16, 17, 18, 19, 20);
    }
}

4. 集合的打印

import java.util.*;

public class PrintingCollections {
    static Collection fill(Collection<String> collection) {
        collection.add("rat");
        collection.add("cat");
        collection.add("dog"); // PS:两个 dog
        collection.add("dog");
        return collection;
    }

    static Map fill(Map<String, String> map) {
        map.put("rat", "Fuzzy");
        map.put("cat", "Rags");
        map.put("dog", "Bosco"); // PS:两个 key 都为 dog
        map.put("dog", "Spot");
        return map;
    }

    public static void main(String[] args) {
        System.out.println("new ArrayList<>()---" + fill(new ArrayList<>()));
        System.out.println("new LinkedList<>()---" + fill(new LinkedList<>()));
        System.out.println("new HashSet<>()---" + fill(new HashSet<>()));
        System.out.println("new TreeSet<>()---" + fill(new TreeSet<>()));
        System.out.println("new LinkedHashSet<>()---" + fill(new LinkedHashSet<>()));
        System.out.println("new HashMap<>()---" + fill(new HashMap<>()));
        System.out.println("new TreeMap<>()---" + fill(new TreeMap<>()));
        System.out.println("new LinkedHashMap<>()---" + fill(new LinkedHashMap<>()));
    }
}

输出:

new ArrayList<>()---[rat, cat, dog, dog]
new LinkedList<>()---[rat, cat, dog, dog]
new HashSet<>()---[rat, cat, dog]
new TreeSet<>()---[cat, dog, rat]
new LinkedHashSet<>()---[rat, cat, dog]
new HashMap<>()---{rat=Fuzzy, cat=Rags, dog=Spot}
new TreeMap<>()---{cat=Rags, dog=Spot, rat=Fuzzy}
new LinkedHashMap<>()---{rat=Fuzzy, cat=Rags, dog=Spot}

这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。

1)Collection 类型在每个槽中只能保存一个元素。此类集合包括:

  • List ,它以特定的顺序保存一组元素;

  • Set ,其中元素不允许重复;

  • Queue ,只能在集合一端插入对象,并从另一端移除对象。

2) Map 在每个槽中存放了两个元素,即键和与之关联的值。

默认的打印行为,使用集合提供的 toString() 方法即可生成可读性很好的结果。 Collection 打印出的内容用方括号括住,每个元素由逗号分隔。 Map 则由大括号括住,每个键和值用等号连接(键在左侧,值在右侧)。

—PS:集合自身的 toString() 可以得到很好的打印结果

ArrayListLinkedList 都是 List 的类型,从输出中可以看出,它们都按插入顺序保存元素。两者之间的区别不仅在于执行某些类型的操作时的性能,而且 LinkedList 包含的操作多于 ArrayList

HashSetTreeSetLinkedHashSetSet 的类型。从输出中可以看到, Set 仅保存每个相同项中的一个,并且不同的 Set 实现存储元素的方式也不同。 如果存储顺序很重要,则可以使用 TreeSet ,(它将按比较结果的升序保存对象)或 LinkedHashSet ,它按照被添加的先后顺序保存对象。

—PS:List 类型顺序保留所有的元素,Set 不保存重复元素

键和值保存在 HashMap 中的顺序不是插入顺序,因为 HashMap 实现使用了非常快速的算法来控制顺序。 TreeMap 通过比较结果的升序来保存键, LinkedHashMap 在保持 HashMap 查找速度的同时按键的插入顺序保存键。

—PS:Map 存放元素当 key 相同时,前一个 value 会被覆盖

5. 列表List

有两种类型的 List

  • 基本的 ArrayList ,擅长随机访问元素,但在 List 中间插入和删除元素时速度较慢。

  • LinkedList ,它通过代价较低的在 List 中间进行的插入和删除操作,提供了优化的顺序访问。 LinkedList 对于随机访问来说相对较慢,但它具有比 ArrayList 更大的特征集。

import java.util.*;


public class ListFeatures {
    public static void main(String[] args) {
        // PS:在原文中没有找到相关的类,在这直接创建一个符合的 list
        ArrayList<Integer> pets = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6));
        System.out.println("1: " + pets);

        Integer h = 14;
        // PS:add() 添加元素,列表会自动调整大小
        pets.add(h);
        System.out.println("2: " + pets);

        // PS:contains() 确定对象是否在列表中
        System.out.println("3: " + pets.contains(h));

        // PS:remove() 将对象从列表中移除,
        System.out.println("移除前:" + pets);
        pets.remove(h);
        System.out.println("移除后:" + pets);

        // PS:get() 从列表中获取对应下标的对象
        Integer p = pets.get(2);
        // PS:indexOf() 从列表中获取对象的对应下标,如果没查到,返回 -1
        int pi = pets.indexOf(p);
        System.out.println("4: " + p + "的下标是 " + pi);

        // PS:add() 还可以指定下标位置添加元素
        pets.add(1, 15);
        System.out.println("9: " + pets);

        // PS:subList() 截取列表,从起始下标开始,至结束下标,但是不包含结束下标的元素,即前包后不包
        List<Integer> sub = pets.subList(0, 5);
        System.out.println("subList: " + sub);

        // PS:containsAll() 判断是否包含列表
        System.out.println("10: " + pets.containsAll(sub));

        // PS:Collections.sort() 对列表元素进行排序
        Collections.sort(sub);
        System.out.println("sorted subList: " + sub);
        System.out.println("11: " + pets.containsAll(sub));

        Random rand = new Random(47);
        // PS:Collections.shuffle() 对列表元素进行随机排序
        Collections.shuffle(sub, rand);
        System.out.println("shuffle subList: " + sub);
        System.out.println("12: " + pets.containsAll(sub));
        // PS:Collections.sort() 和 Collections.shuffle() 方法,不会影 响 containsAll() 的结果。

        // PS:subList() 所产生的列表的幕后支持就是原始列表。
        // 因此,对所返回 列表的更改都将会反映在原始列表中,反之亦然。
        System.out.println("对截取列表操作后,原列表为:" + pets);

        ArrayList<Integer> copy = new ArrayList<>(pets);
        sub = Arrays.asList(pets.get(1), pets.get(3));
        System.out.println("sub: " + sub);

        // PS:retainAll() 方法实际上是一个“集合交集”操作,在本例中,
        // 它保留了同时在 copy 和 sub 中的所有 元素。
        // 请再次注意,所产生的结果行为依赖于 equals() 方法。
        copy.retainAll(sub);
        System.out.println("13: " + copy);

        copy = new ArrayList<>(pets); // 重新赋值

        // PS:removeAll() 方法也是基于 equals() 方法运行的。
        // 顾名思义,它会从 List 中删除在参数 List 中 的所有元素。
        copy.removeAll(sub);
        System.out.println("15: " + copy);

        // PS:set() 方法的命名显得很不合时宜,因为它与 Set 类存在潜在的冲突。
        // 在这里使用“replace”可能更适合,
        // 因为它的功能是用第二个参数替换索引处的元素(第一个参数)。
        copy.set(0, 20);
        System.out.println("16: " + copy);

        // PS:对于 List ,有一个重载的 addAll() 方法
        // 可以将新列表插入到原始列表的中间位 置,
        // 而不是仅能用 Collection 的 addAll() 方法将其追加到列表的末尾。
        copy.addAll(1, sub);
        System.out.println("17: " + copy);

        // PS:isEmpty() 判断列表是否为空
        System.out.println("18:" + pets.isEmpty());
        System.out.println("19:" + pets);
        // PS:clear() 清空列表
        pets.clear();
        System.out.println("20:" + pets.isEmpty());

        // PS:重新来一个列表
        pets = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
        System.out.println("21:" + pets);

        // PS:toArray() 将列表转为数组
        Object[] o = pets.toArray();
        System.out.println("22:" + o[0]);

        // PS:toArray() 还有一个重载方法,传入一个形参,可以直接返回对应类型的数组,
        // 这里的 new Integer[0] 就是起一个模板的作用,指定了返回数组的类型,
        // 0是为了节省空间
        Integer[] pa = pets.toArray(new Integer[0]);
        System.out.println("pa 的元素如下:");
        for (Integer integer : pa) {
            System.out.println(integer);
        }
    }
}

输出:

1: [1, 2, 3, 4, 5, 6]
2: [1, 2, 3, 4, 5, 6, 14]
3: true
移除前:[1, 2, 3, 4, 5, 6, 14]
移除后:[1, 2, 3, 4, 5, 6]
4: 3的下标是 2
9: [1, 15, 2, 3, 4, 5, 6]
subList: [1, 15, 2, 3, 4]
10: true
sorted subList: [1, 2, 3, 4, 15]
11: true
shuffle subList: [3, 1, 15, 2, 4]
12: true
对截取列表操作后,原列表为:[3, 1, 15, 2, 4, 5, 6]
sub: [1, 2]
13: [1, 2]
15: [3, 15, 4, 5, 6]
16: [20, 15, 4, 5, 6]
17: [20, 1, 2, 15, 4, 5, 6]
18:false
19:[3, 1, 15, 2, 4, 5, 6]
20:true
21:[1, 2, 3, 4]
22:1
pa 的元素如下:
1
2
3
4

6. 迭代器Iterators

6.1 Iterators

迭代器(也是一种设计模式)的概念实现了这种抽象。迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为轻量级对象(lightweight object):创建它的代价小。

经常可以看到一些对迭代器有些奇怪的约束。例如,Java 的Iterator 只能单向移动。这个 Iterator 只能用来:

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

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

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

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class SimpleIteration {
    public static void main(String[] args) {
        // PS:随便创建一个 list
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        System.out.println("处理前的list:" + list);

        // PS:iterator() 获取迭代器
        Iterator<Integer> it = list.iterator();

        // PS:hasNext() 检查序列中是否还有元素
        while (it.hasNext()) {
            // PS:next() 获得序列中的下一个元素
            Integer next = it.next();
            System.out.println("序列中的下一个元素:" + next);
            // PS:随便加个判断,删除元素3
            if (next.equals(3)) {
                it.remove();
            }
        }
        System.out.println("处理后的list:" + list);
    }
}

输出:

处理前的list:[1, 2, 3, 4, 5]
序列中的下一个元素:1
序列中的下一个元素:2
序列中的下一个元素:3
序列中的下一个元素:4
序列中的下一个元素:5
处理后的list:[1, 2, 4, 5]

迭代器统一了对集合的访问方式

—PS:集合都有迭代器

6.2 ListIterator

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;

public class ListIteration {
    public static void main(String[] args) {
        // PS:随便创建一个 list
        List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d", "e"));

        // PS:listIterator() 获取迭代器
        ListIterator<String> it = list.listIterator();

        // PS:hasNext() 检查序列中是否还有元素
        while (it.hasNext()) {
            // PS:next() 获得序列中的下一个元素
            // 迭代器是有指针的,next() 会将当前元素返回,并将指针+1
            String next = it.next();
            // PS:nextIndex() 获取当前指针的下标,这个用法和 next() 一样
            int ni = it.nextIndex();
            // PS:previousIndex() 获取前一个指针的下标
            int pi = it.previousIndex();
            System.out.println("当前元素:" + next + ",当前下标为:" + ni + ",前一个下标为:" + pi);
        }

        // PS:hasPrevious() 逆向检查序列中是否还有元素
        while (it.hasPrevious()) {
            // PS:previous() 逆向获得序列中的下一个元素
            System.out.println("逆向遍历,当前元素:" + it.previous());
        }

        System.out.println("原list:" + list);

        // PS:listIterator(3) 从下标3开始,获取迭代器
        it = list.listIterator(3);
        while (it.hasNext()) {
            String next = it.next();
            System.out.println("新生成的迭代器中元素为:" + next);
            if (next.equals("d")) {
                // PS:set() 替换当前元素
                it.set("f");
            }
        }
        // PS:add() 在当前元素后面增加一个元素
        it.add("g");

        System.out.println("操作后的list:" + list);
    }
}

输出:

当前元素:a,当前下标为:1,前一个下标为:0
当前元素:b,当前下标为:2,前一个下标为:1
当前元素:c,当前下标为:3,前一个下标为:2
当前元素:d,当前下标为:4,前一个下标为:3
当前元素:e,当前下标为:5,前一个下标为:4
逆向遍历,当前元素:e
逆向遍历,当前元素:d
逆向遍历,当前元素:c
逆向遍历,当前元素:b
逆向遍历,当前元素:a
原list:[a, b, c, d, e]
新生成的迭代器中元素为:d
新生成的迭代器中元素为:e
操作后的list:[a, b, c, f, e, g]

7. 链表LinkedList

LinkedList 也像 ArrayList 一样实现了基本的 List 接口,但它在 List 中间执行插入和删除操作时比 ArrayList 更高效,在随机访问操作效率方面却要逊色一些。

LinkedList 还添加了一些方法,使其可以被用作栈、队列或双端队列(deque) 。在这些方法中,有些彼此之间可能只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用(特别是在 Queue 中)。例如:

  • getFirst() 和 element() 是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果 List 为空,则抛出 NoSuchElementException 异常。 peek() 方法与这两个方法只是稍有差异,它在列表为空时返回 null

  • removeFirst() 和 remove() 也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出 NoSuchElementException 异常。 poll() 稍有差异,它在列表为空时返回 null

  • addFirst() 在列表的开头插入一个元素。

  • offer() 与 add() 和 addLast() 相同。 它们都在列表的尾部(末尾)添加一个元素。

  • removeLast() 删除并返回列表的最后一个元素。

import java.util.Arrays;
import java.util.LinkedList;

public class LinkedListFeatures {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>(Arrays.asList("a", "b", "c", "d", "e"));
        System.out.println("原列表:" + list);

        // PS:创建个空列表,测试用
        LinkedList<Object> list2 = new LinkedList<>();

        // PS:getFirst() 返回列表的第一个元素,若列表为空,则抛出 NoSuchElementException 异常
        System.out.println("getFirst():" + list.getFirst());
//        System.out.println(list2.getFirst());

        // PS:element() 返回列表的第一个元素,若列表为空,则抛出 NoSuchElementException 异常
        System.out.println("element():" + list.element());
//        System.out.println(list2.element());

        // PS:peek() 返回列表的第一个元素,若列表为空,则返回 null
        System.out.println("peek():" + list.peek());
        System.out.println("空列表peek():" + list2.peek());
        // PS:getFirst()/element()/peek() 均不会改变列表
        System.out.println("getFirst()/element()/peek() 后的列表:" + list);

        // PS:removeFirst() 删除并返回列表的第一个元素,若列表为空,则抛出 NoSuchElementException 异常
        System.out.println("removeFirst():" + list.removeFirst());
//        System.out.println(list2.removeFirst());
        System.out.println("removeFirst() 后的列表:" + list);

        // PS:remove() 删除并返回列表的第一个元素,若列表为空,则抛出 NoSuchElementException 异常
        System.out.println("remove():" + list.remove());
//        System.out.println(list2.remove());
        System.out.println("remove() 后的列表:" + list);

        // PS:poll() 删除并返回列表的第一个元素,若列表为空,则返回 null
        System.out.println("poll():" + list.poll());
        System.out.println("空列表poll():" + list2.poll());
        System.out.println("poll() 后的列表:" + list);

        // PS:addFirst() 在列表的开头插入一个元素
        list.addFirst("f");
        System.out.println("addFirst() 后的列表:" + list);

        // PS:offer() 在列表的结尾插入一个元素
        list.offer("g");
        System.out.println("offer() 后的列表:" + list);

        // PS:add() 在列表的结尾插入一个元素
        list.add("h");
        System.out.println("add() 后的列表:" + list);

        // PS:addLast() 在列表的结尾插入一个元素
        list.addLast("i");
        System.out.println("addLast() 后的列表:" + list);

        // PS:removeLast() 删除并返回列表的最后一个元素
        System.out.println("removeLast():" + list.removeLast());
        System.out.println("removeLast() 后的列表:" + list);
    }
}

输出:

原列表:[a, b, c, d, e]
getFirst():a
element():a
peek():a
空列表peek():null
getFirst()/element()/peek() 后的列表:[a, b, c, d, e]
removeFirst():a
removeFirst() 后的列表:[b, c, d, e]
remove():b
remove() 后的列表:[c, d, e]
poll():c
空列表poll():null
poll() 后的列表:[d, e]
addFirst() 后的列表:[f, d, e]
offer() 后的列表:[f, d, e, g]
add() 后的列表:[f, d, e, g, h]
addLast() 后的列表:[f, d, e, g, h, i]
removeLast():i
removeLast() 后的列表:[f, d, e, g, h]

报错.jpg

8. 堆栈Stack

堆栈是“后进先出”(LIFO)集合。它有时被称为叠加栈(pushdown stack),因为最后“压入”(push)栈的元素,第一个被“弹出”(pop)栈。

—PS:堆栈类似弹夹,最后 push 的子弹,先 pop 出去

import java.util.ArrayDeque;
import java.util.Deque;

public class StackTest {
    public static void main(String[] args) {
        Deque<String> stack = new ArrayDeque<>();
        String s = "My dog has fleas";
        for (String s1 : s.split(" ")) {
            stack.push(s1);
        }

        // PS:这里使用 stack.size() > 0 进行判断效果一样
        while (!stack.isEmpty()) {
            System.out.print(stack.pop() + " ");
        }
    }
}

输出:

fleas has dog My

即使它是作为一个堆栈在使用,我们仍然必须将其声明为 Deque

—PS:虽然是队列,干的是堆栈的活。如果认为 Deque 名字不爽,可以自定义一个 Stack 类(底层还是 Deque ),但是使用这个 Stack 类时,需要显式导入,即

import 全路径类名。

9. 集合Set

Set 不保存重复的元素。查找通常是 Set 最重要的操作,因此通常会选择 HashSet 实现,该实现针对快速查找进行了优化。

Set 具有与 Collection 相同的接口,因此没有任何额外的功能,不像前面两种不同类型的 List 那样。实际上, Set 就是一个 Collection ,只是行为不同。

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class SetOfInteger {
    public static void main(String[] args) {
        Random random = new Random(47);
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < 1000; i++) {
            set.add(random.nextInt(30));
        }
        System.out.println(set);
    }
}

输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]

在 0 到 29 之间的 10000 个随机整数被添加到 Set 中,因此可以想象每个值都重复了很多次。但是从结果中可以看到,每一个数只有一个实例出现在结果中。

—PS:从输出结果看好像是排序了,不过是凑巧了,元素换成字符试试

要对结果进行排序,一种方法是使用 TreeSet

import java.util.Set;
import java.util.TreeSet;

public class SortedSetOfString {
    public static void main(String[] args) {
        Set<String> colors = new TreeSet<>();
        for (int i = 0; i < 100; i++) {
            colors.add("Yellow");
            colors.add("Blue");
            colors.add("Red");
            colors.add("Red");
            colors.add("Orange");
            colors.add("Yellow");
            colors.add("Blue");
            colors.add("Purple");
        }
        System.out.println(colors);
    }
}

输出:

[Blue, Orange, Purple, Red, Yellow]

10. 映射Map

将对象映射到其他对象的能力是解决编程问题的有效方法。

import java.util.HashMap;
import java.util.Random;

public class Statistics {
    public static void main(String[] args) {
        // PS:new Random(47) 保证代码运行时,随机生成的结果是一样的
        Random rand = new Random(47);
        HashMap<Integer, Integer> m = new HashMap<>();
        for (int i = 0; i < 10000; i++) {
            // PS:nextInt(20) 随机生成一个0-19的整数
            int r = rand.nextInt(20);

            // PS:get(r) 获取下标r的值
            Integer freq = m.get(r);

            // PS:利用三目运算,将下标r的值加1,其实就是统计随机生成的r的次数
            m.put(r, freq == null ? 1 : freq + 1);
        }
        System.out.println(m);
    }
}

输出:

{0=481, 1=502, 2=489, 3=508, 4=481, 5=503, 6=519, 7=471, 8=468, 9=549, 10=513, 11=531, 12=521, 13=506, 14=477, 15=497, 16=533, 17=509, 18=478, 19=464}
import java.util.HashMap;

public class PetMap {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        map.put("a", 1);
        map.put("b", 2);
        map.put("c", 3);

        System.out.println("map:" + map);

        // PS: get() 获取 key 对应的 value 值
        Integer a = map.get("a");
        System.out.println("a的value:" + a);

        // PS: containsKey() 和 containsValue() 方法去测试一个 Map ,
        // 以查看它是否包含某个键或某个值
        System.out.println("map是否包含 key b:" + map.containsKey("b"));
        System.out.println("map是否包含 value 2:" + map.containsValue(2));

        // PS: keySet() 获取 map 的所有 key
        System.out.println("map所有的 key:" + map.keySet());

        // PS: values() 获取 map 的所有 value
        System.out.println("map所有的 value:" + map.values());
    }
}

输出:

map:{a=1, b=2, c=3}
a的value:1
map是否包含 key b:true
map是否包含 value 2:true
map所有的 key:[a, b, c]
map所有的 value:[1, 2, 3]

Map 与数组和其他的 Collection 一样,可以轻松地扩展到多个维度,只需要创建一个值为 MapMap(这些 Map 的值可以是其他集合,甚至是其他 Map)。

—PS:比如 HashMap<String, Map> map1 = new HashMap<>();

11. 队列Queue

11.1 队列Queue

队列是一个典型的“先进先出”(FIFO)集合。 即从集合的一端放入事物,再从另一端去获取它们,事物放入集合的顺序和被取出的顺序是相同的。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中尤为重要,因为它们可以安全地将对象从一个任务传输到另一个任务。

—PS:类似排队,通常用于并发编程

LinkedList 实现了 Queue 接口,并且提供了一些方法以支持队列行为,因此 LinkedList 可以用作 Queue 的一种实现。

import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;

public class QueueDemo {
    public static void printQ(Queue queue) {
        while (queue.peek() != null) {
            System.out.print(queue.remove() + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Random rand = new Random(47);
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < 10; i++) {
            queue.offer(rand.nextInt(i + 10));
        }
        printQ(queue);

        Queue<Character> qc = new LinkedList<>();
        char[] chars = "Brontosaurus".toCharArray();
        for (char aChar : chars) {
            qc.offer(aChar);
        }
        printQ(qc);
    }
}

输出:

8 1 1 1 5 14 3 1 0 1 
B r o n t o s a u r u s 

11.2 优先级队列PriorityQueue

优先级队列声明下一个弹出的元素是最需要的元素(具有最高的优先级)。

当在 PriorityQueue 上调用 offer() 方法来插入一个对象时,该对象会在队列中被排序。默认的排序使用队列中对象的自然顺序(natural order),但是可以通过提供自己的 Comparator 来修改这个顺 序。 PriorityQueue 确保在调用 peek() , poll() 或 remove() 方法时,获得的元素将是队列中优先级最高的元素。

import java.util.*;

public class PriorityQueueDemo {
    public static void printQ(Queue queue) {
        while (queue.peek() != null) {
            System.out.print(queue.remove() + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
        Random rand = new Random(47);
        for (int i = 0; i < 10; i++) {
            priorityQueue.offer(rand.nextInt(i + 10));
        }
        printQ(priorityQueue);

        List<Integer> ints = Arrays.asList(25, 22, 20, 18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25);
        priorityQueue = new PriorityQueue<>(ints);
        printQ(priorityQueue);

        // PS:new PriorityQueue 有很多重载方法,
        // 这个是返回一个长度为 ints.size(),并带有比较器 Comparator 的优先级队列
        priorityQueue = new PriorityQueue<>(ints.size(), Collections.reverseOrder());
        priorityQueue.addAll(ints);
        printQ(priorityQueue);
    }
}

输出:

0 1 1 1 1 1 3 5 8 14 
1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25 
25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1 

12. 集合与迭代器

Collection 是所有序列集合共有的根接口。它可能会被认为是一种“附属接口”(incidental interface), 即因为要表示其他若干个接口的共性而出现的接口。此外,java.util.AbstractCollection 类提供了 Collection 的默认实现,使得你可以创建 AbstractCollection 的子类型,而其中没有不必要的代码重复。

标准 C++ 类库中的的集合并没有共同的基类——集合之间的所有共性都是通过迭代器实现的。在 Java 中,遵循 C++ 的方式看起来似乎很明智,即用迭代器而不是 Collection 来表示集合之间的共性。但是,这两种方法绑定在了一起,因为实现 Collection 就意味着需要提供 iterator() 方法:

—PS:迭代器表示集合之间的共性

13. for-in和迭代器

到目前为止,for-in 语法主要用于数组,但它也适用于任何 Collection 对象

—PS:for-in 语法底层使用 Iterable 接口来遍历序列。Collection 继承了 Iterable 接口

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;

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 + "' ");
        }
    }
}

输出:

'Take' 'the' 'long' 'way' 'home' 

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

14. 本章小结

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集合的简图(不包含抽象类或遗留组件)会很有帮助。

在这里插入图片描述

可以看到,实际上只有四个基本的集合组件: MapListSetQueue ,它们各有两到三个实现版本(Queuejava.util.concurrent 实现未包含在此图中)。

  • 最常使用的集合用黑色粗线线框表示。
  • 虚线框表示接口,实线框表示普通的(具体的)类。
  • 带有空心箭头的虚线表示特定的类实现了一个接口。
  • 实心箭头表示某个类可以生成箭头指向的类的对象。

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

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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值