疯狂Java讲义(读书笔记)(第7章——Java集合)

第7章Java集合

7.1 Java集合概述

集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。

7.2 Collection和Iterator接口

Collection接口是List、Set和Queue接口的父接口,该接口里定义的方法既可用于操作Set集合,也可用于操作List和Queue集合。Collection接口里定义了如下操作集合元素的方法。

  • boolean add(Object o):该方法用于向集合里添加一个元素。如果集合对象被添加操作改变了,则返回true。
  • boolean addAll(Collection c):该方法把集合c里的所有元素添加到指定集合里。如果集合对象被添加操作改变了,则返回true。
  • void clear():清除集合里的所有元素,将集合长度变为0。
  • boolean contains(Object o):返回集合里是否包含指定元素。
  • boolean containsAll(Collection c):返回集合里是否包含集合c里的所有元素。
  • boolean isEmpty():返回集合是否为空。当集合长度为0时返回true,否则返回false。
  • Iterator iterator():返回一个Iterator对象,用于遍历集合里的元素。
  • boolean remove(Object o):删除集合中的指定元素o,当集合中包含了一个或多个元素o时,该方法只删除第一个符合条件的元素,该方法将返回true。
  • boolean removeAll(Collection c):从集合中删除集合c里面包含的所有元素(相当于调用该方法的集合减集合c),如果删除了一个或一个以上的元素,则该方法返回true。
  • boolean retainAll(Collection c):从集合中删除集合c里不包含的元素(相当于把调用方法的集合变为该集合和集合c的交集),如果该操作改变了调用该方法的集合,则该方法返回true。
  • int size():该方法返回集合里元素的个数。
  • Object[] toArray():该方法把集合转换成一个数组,所有的集合元素变成对应的数组元素。
7.2.1 使用Lambda表达式遍历集合

下面程序示范了使用Lambda表达式来遍历集合元素。

import java.util.Collection;
import java.util.HashSet;

public class CollectionEach {
    public static void main(String[] args) {
        // 创建一个集合
        Collection books = new HashSet();
        books.add("大家好我是敖武");
        books.add("我在学习Java");
        books.add("现在实在写代码");
        // 调用forEach()方法遍历集合
        books.forEach(obj -> System.out.println("迭代集合元素:"+obj));
    }
}
7.2.2 使用Java 8 增强的Iterator遍历集合元素

Collection系列集合、Map系列集合主要用于盛装其他对象,而Iterator则主要用于遍历(即迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。
Iterator接口里定义了如下4个方法:

  • boolean hasNext():如果被迭代的集合元素还没有被遍历完,则返回true。
  • Obejct next():返回集合里的下一个元素。
  • void remove():删除集合里上一次next方法返回的元素。
  • void forEachRemaining(Consumer action),这是Java 8 为Iterator新增的默认你方法,该方法可使用Lambda表达式来遍历集合元素。
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

public class IteratorTest {
    public static void main(String[] args) {
        // 创建一个集合
        Collection books = new HashSet();
        books.add("大家好我是敖武");
        books.add("我在学习Java");
        books.add("现在实在写代码");
        // 获取books集合对应的迭代器
        Iterator it = books.iterator();
        while (it.hasNext()){
            // it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
            String book = (String) it.next();
            System.out.println(book);
            if (book.equals("我在学习Java")){
                // 从集合中删除上一次next()方法返回的元素
                it.remove();
            }
            // 对book变量赋值,不会改变集合元素本身
            book = "测试字符串";
        }
        System.out.println(books);
    }
}
7.2.3 使用Lambda表达式遍历Iterator

如下程序示范了使用Lambda表达式来遍历集合元素:

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;


public class IteratorEach {
    public static void main(String[] args) {
        // 创建一个集合
        Collection books = new HashSet();
        books.add("大家好我是敖武");
        books.add("我在学习Java");
        books.add("现在实在写代码");
        // 获取books集合对应的迭代器
        Iterator it = books.iterator();
        // 使用Lambda表达式(目标类型是Comsumer)来遍历集合元素
        it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));
    }
}
7.2.4 使用foreach循环遍历集合元素
import java.util.Collection;
import java.util.HashSet;

public class ForeachTest {
    public static void main(String[] args) {
        // 创建一个集合
        Collection books = new HashSet();
        books.add("大家好我是敖武");
        books.add("我在学习Java");
        books.add("现在实在写代码");

        for (Object obj : books){
            // 此处的book变量也不是集合元素本身
            String book = (String) obj;
            System.out.println(book);
            if (book.equals("我在学习Java")){
                // 下面代码会引发ConcurrentModificationException异常
                books.remove(book);
            }
        }
        System.out.println(books);

    }
}
7.2.4 使用Java 8 新增的Predicate操作集合
7.2.5 使用Java 8 新增的Predicate集合
7.2.6 使用Java 8 新增的Stream操作集合

独立使用Stream的步骤如下:

  • 使用Stream或XxxStream的builder()类方法创建该Stream对应的Builder。
  • 重复调用Builder的add()方法向该流中添加多个元素。
  • 调用Builder的build()方法获取对应的Stream。
  • 调用Stream的聚集方法。
import java.util.stream.IntStream;

public class IntStreamTest {
    public static void main(String[] args) {
        IntStream is = IntStream.builder()
                .add(20)
                .add(13)
                .add(-2)
                .add(18)
                .build();
        // 下面调用聚集方法的代码每次只能执行一次
        System.out.println("is所有元素的最大值:" + is.max().getAsInt());
        System.out.println("is所有元素的最小值" + is.min().getAsInt());
        System.out.println("is元素的总和" + is.sum());
        System.out.println("is所有元素的总数" + is.count());
        System.out.println("is所有元素的平均值" + is.average());
        System.out.println("is所有元素的平方是否都大于20:" + is.allMatch(ele -> ele*ele > 20));
        System.out.println("is是否包含任何元素的平方大于20:" + is.anyMatch(ele -> ele*ele > 20));
        // 将is映射成一个新的Stream,新Stream的每个元素是原Stream元素的2倍+1
        IntStream newIs = is.map(ele -> ele * 2 + 1);
        // 使用方法引用的方式来遍历集合元素
        newIs.forEach(System.out::println);
    }
}

7.3 set集合

7.3.1 HashSet类

HashSet具有以下特点:

  • 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
  • HashSet不是同步的,如果多个线程同时访问同一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合时,则必须通过代码来保证其同步。
  • 集合元素值可以是null。

重写hashCode()方法的基本规则:

  • 在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值。
  • 当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法返回相等的值。
  • 对象中作equals()方法比较标准的实例变量,都应该用于计算hashCode值。

下面给出重写hashCode()方法的一般步骤:
1.把对象内每个有意义的实例变量(即每个参与equals()方法比较标准的实例变量)计算出一个int类型的hashCode值。
2.用第一步计算出来的多个hashCode值组合计算出一个hashCode值返回

7.3.2 LinkedHashSet类
import java.util.LinkedHashSet;

public class LinkedHashSetTest {
    public static void main(String[] args) {
        LinkedHashSet books = new LinkedHashSet();
        books.add("疯狂Java讲义");
        books.add("轻量级Java EE企业应用实战");
        System.out.println(books);
        // 删除 疯狂Java讲义
        books.remove("疯狂Java讲义");
        // 重复添加 疯狂Java讲义
        books.add("疯狂Java讲义");
        System.out.println(books);
    }
}
7.3.3 TreeSet类

与HashSet集合相比,TreeSet还提供了如下几个额外的方法。

  • Comparator comparator():如果TreeSet采用了定制排序,则该方法返回定制排序所使用的Comparator;如果TreeSet采用了自然排序,则返回null。
  • Object first():返回集合中的第一个元素。
  • Object last():返回集合中的最后一个元素。
  • Object lower(Obejct e):返回集合中位于指定元素之前的元素(即小于指定元素的最大元素,参考元素不需要时TreeSet集合里的元素)。
  • Object heiger(Object e):返回结合中位于指定元素之后的元素(即大于指定元素的最小元素,参考元素不需要是TreeSet集合里的元素)。
  • SortSet subSet(Object fromElement, Object toElement):返回此Set的子集合,范围从fromElement(包含)到toElement(不包含)。
  • SortedSet headSet(Object toElement):返回此Set的子集,由小于toElement的元素组成。
  • SortedSet tailSet(Object fromElement):返回此Set的子集,由大于或等于fromElement的元素组成。
    1.自然排序
    TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排序,这种方式就是自然排序。
    Comparable接口的常用类:
  • BigDecimal、BigInteger以及所有的数值型对应的包装类:按它们对应的数值大小进行比较。
  • Character:按字符的UNICODE值进行比较。
  • Boolean:true对应的包装类实例大于false对应的包装类实例。
  • String:按字符串中字符的UNICODE值进行比较。
  • Date、Time:后面的时间、日期比前面的时间、日期大。
    2.定制排序

7.4 List集合

7.4.1 Java 8 改进的List接口和Listlterator接口

List作为Collection接口的子接口,当然可以使用Collection接口里的全部方法。而且由于List是有序集合,因此List集合里增加了一些根据索引来操作集合元素的方法。
下面程序示范了List集合的常规用法:

import java.util.ArrayList;
import java.util.List;

public class ListTest {
    public static void main(String[] args) {
        List books = new ArrayList<>();
        // 向books集合中添加三个元素
        books.add(new String("轻量级Java EE企业应用实战"));
        books.add(new String("疯狂Java讲义"));
        books.add(new String("疯狂Android讲义"));
        System.out.println(books);
        // 将新字符串对象插入在第二个位置
        books.add(1, new String("疯狂Ajax讲义"));
        for (int i=0; i<books.size(); ++i){
            System.out.println(books.get(i));
        }
        // 删除第三个元素
        books.remove(2);
        System.out.println(books);
        // 将books集合的第二个元素(包括)
        // 到第三个元素(不包括)截取成子集合
        System.out.println(books.subList(1, 2));
    }
}
7.4.2 ArrayList和Vector是实现类

ArrayList和Vector作为List类的两个典型实现,完全支持前面介绍的List接口的全部功能。
如果开始就知道ArrayList或Vector集合需要保存多少个元素,则可以在创建它们时就指定initialCapacity大小。如果创建空的ArrayList或Vector集合时不指定initialCapcity参数,则Object[]数组长度默认为10。
实际上,Vector具有很多缺点,通常尽量少用Vector实现类。
需要指出的是,由于Stack继承了Vector,因此它也是一个非常古老的Java集合类,它同样是线程安全性能较差的,因此应该尽量少用Stack类。如果程序需要使用“栈”这种数据结构,则可以考虑使用后面将要介绍的ArrayDeque。

7.4.3 固定长度的List

Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素。

7.5 Queue集合

Queue用于模拟队列这种数据结构,队列通常是指“先进先出”(FIFO)的容器。通常,队列不允许随机访问队列中的元素。

7.5.1 PriorityQueue实现类

PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序。因此当调用peek()方法或者poll()方法取出队列中的元素时,并不是去除最先进入队列的元素,而是取出队列中最小的元素。从这个意义上看,PriorityQueue已经违反了队列的最基本规则:先进先出(FIFO)。

7.5.2 Deque接口于ArrayDeque实现类

Deque接口是Queue接口的子接口,它代表一个双端队列,Deque接口里定义了一份双端队列的方法,这些方法从两端来操作队列的元素。

import java.util.ArrayDeque;

public class ArrayDequeQueue {
    public static void main(String[] args) {
        ArrayDeque queue = new ArrayDeque();
        // 依次将三个元素加入队列
        queue.offer("疯狂Java讲义");
        queue.offer("轻量级Java EE企业应用实战");
        queue.offer("疯狂Android讲义");
        // 输出[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
        System.out.println(queue);
        // 访问队列头部的元素,但并不将其poll出队列”栈“,输出:疯狂Java讲义
        System.out.println(queue.peek());
        // 依然输出[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
        System.out.println(queue);
        // poll出第一个元素,输出:疯狂Java讲义
        System.out.println(queue.poll());
        // 输出:[轻量级Java EE企业应用实战, 疯狂Android讲义]
        System.out.println(queue);
    }
}
import java.util.ArrayDeque;

public class ArrayDequeStack {
    public static void main(String[] args) {
        ArrayDeque stack = new ArrayDeque();
        // 依次将三个元素加入队列
        stack.push("疯狂Java讲义");
        stack.push("轻量级Java EE企业应用实战");
        stack.push("疯狂Android讲义");
        // 输出[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
        System.out.println(stack);
        // 访问队列头部的元素,但并不将其poll出队列”栈“,输出:疯狂Java讲义
        System.out.println(stack.peek());
        // 依然输出[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
        System.out.println(stack);
        // poll出第一个元素,输出:疯狂Java讲义
        System.out.println(stack.pop());
        // 输出:[轻量级Java EE企业应用实战, 疯狂Android讲义]
        System.out.println(stack);
    }
}

通过上面两个程序可以看出,ArrayDeque不仅可以作为栈使用,也可以作为队列使用。

7.5.3 LinkedList实现类

下面程序简单示范了LinkedList集合的用法:

import java.util.LinkedList;

public class LinkedListTest {
    public static void main(String[] args) {
        LinkedList books = new LinkedList();
        // 将字符串元素加入队列的尾部
        books.offer("疯狂Java讲义");
        // 将一个字符串元素加入栈的顶部
        books.push("轻量级Java EE企业应用实战");
        // 将字符串元素添加到队列的头部(相当于栈的顶部)
        books.offerFirst("疯狂Android讲义");
        // 以List方式(按索引访问的方式)来遍历集合元素
        for (int i=0; i<books.size(); ++i){
            System.out.println("遍历中:" + books.get(i));
        }
        // 访问但不删除栈顶元素
        System.out.println(books.peekFirst());
        // 访问并不删除队列的最后一个元素
        System.out.println(books.peekLast());
        // 将栈顶的元素弹出“栈”
        System.out.println(books.pop());
        // 下面输出将看到队列中第一个元素被删除
        System.out.println(books);
        // 访问并删除队列的最后一个元素
        System.out.println(books.pollLast());
        // 下面输出:[轻量级Java EE企业应用实战]
        System.out.println(books);
    }
}
7.5.4 各种线性表的性能分析

Java提供的List就是一个线性表接口,而ArrayList、LinkedList又是线性表的两种典型实现:基于数组的线性表和基于链的线性表。Queue代表了队列,Deque代表了双端队列(既可作为队列使用,也可作为栈使用)。

7.6 Java 8 增强的Map集合

7.6.2 java 8改进的HashMap和Hashtable实现类

Hashtable和HashMap存在两点典型区别:

  • Hashtable是一个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMap比Hashtable的性能高一些;但如果有多个线程访问同一个Map对象,使用Hashtable实现类会更好。
  • Hashtable不允许使用null作为key和value,如果试图把null值放进Hashtable中,会引发NullPointerException异常;但HashMap可以使用null作为key或value。
7.6.6 各Map实现类的性能分析

对于Map的常用实现类语言而言,虽然HashMap和Hashtable的实现机制几乎一样,但由于Hashtable是一个古老的、线程安全的集合,因此HashMap通常比Hashtable更快。
TreeMap通常比HashMap、Hashtable更慢(尤其在插入、删除key-value对时更慢),因为TreeMap底层采用红黑树来管理key-value对(红黑树的每个节点就是一个key-value对)。
使用TreeMap有一个好处:TreeMap中的key-value对总是处于有序状态,无须进行专门进行排序操作。当TreeMap被填充之后,就可以调用keySet(),取得key组成的Set,然后使用toArray()方法生成key的数组,接下来使用Arrays的binarySearch()方法在已排序的数组中快速地查询对象。
对于一般地应用场景,程序应该多考虑使用HashMap,因为HashMap正式为快速查询设计地(HashMap底层其实也是采用数组来存储key-value对)。但如果程序需要一个总是排好序的Map时,则可以考虑使用TreeMap。

7.7 HashSet和HashMap的性能选项

因为HashSet和HashMap、Hashtable都使用hash算法来决定其决定元素(HashMap则只考虑key的存储),因此HashSet、HashMap和hash表包含如下属性:

  • 容量(capacity):hash表中桶的数量。
  • 初始化容量(initial capacity):创建hash表时桶的数量。

HashMap和HashSet都允许在构造器中指定初始化容量。

  • 尺寸(size):当前hash表中记录的数量。
  • 负载因子(load factor):负载因子等于“size/capacity”。负载因子为0,表示空的hash表,0.5表示半满的hash表,依此类推。轻负载的hash表具有冲突少、适宜插入与查询的特点(但是使用Iterator迭代元素时比较慢)。

7.8 操作集合的工具类:Collections

7.9 烦琐的接口:Enumeration

如果现在编写Java程序,应该尽量采用Iterator迭代器,而不是用Enumerator迭代器。

本章练习

1.创建一个Set集合,并用Set集合保存用户通过控制台输入的20个字符串。
2.创建一个List集合,并随意添加10个元素。然后获取索引为5处的元素;再获取其中某2个元素的索引;再删除索引为3处的元素。
3.给定[“a”, “b”, “a”, “b”, “c”, “a”, “b”, “c”, “b”]字符串数组,然后使用Map的key来保存数组中字符串元素,value保存该字符串元素的出现次数,最后统计处各字符串元素的出现次数。
4.将本章未完成的梭哈游戏补充完整,不断地添加梭哈游戏规则,并开发一个控制台的梭哈游戏。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值