Prism代码语法高亮

第7章 集合


学习目标

1. 集合概述

集合可以简单理解为一个长度可变、可以存储不同数据类型的动态数组

图1 集合体系核心架构图

虚线框里都是接口类型(单列集合Collection、List、Set和双列集合Map),实线框里是具体的实现类(单例集合ArrayList、LinkedList、Vecor和双列集合Hashtable--Properties、HashMap--LinkedHashMap、TreeMap)

集合中的核心接口

接口 描述
Collection 集合中最基本的接口,一般不直接使用该接口
List Collection的子接口,用于存储一组有序、不唯一的对象,是集合中常用的接口之一
Set Collection的子接口,用于存储一组无序、唯一的对象
Map 用于存储一组键值对k-v,提供键到值的映射,键值唯一

2. Collection接口

Collection是Iterable的子接口,Collection和Iterable后面的表示它们都使用了泛型。

public interface Collection<E> extends Iterable<E>{
    //Query Operations
}

Collection接口的常用方法

方法声明 功能描述
boolean add(Object o) 向集合中添加一个元素
boolean addAll(Collection c) 将指定集合c中的所有元素添加到本集合中
void clear() 删除集合中的所有元素
boolean remove(Object o) 删除集合中指定的元素
boolean removeAll(Collection c) 删除当前集合中包含集合c中的所有元素
boolean isEmpty() 判断集合是否为空
boolean contains(Object o) 判断集合中是否包含某个元素
boolean containsAll(Collection c) 判断集合中是否包含指定集合c中的所有元素
Iterator iterator() 返回集合的的迭代器(Iterator),迭代器用于遍历该集合所有元素
int size() 获取集合元素个数
package cn.zxx.ch07;
import java.util.ArrayList;
import java.util.Collection;
/*
* Collection 单列集合的操作:
*    add() 一次添加一个
*    addAll() 一个添加一个集合
*    remove() 一次删除一个
*    removeAll() 一次删除一个集合
*    isEmpty() 判断是否为空空true 不空 false
*    contains() 是否包含指定元素 是true 不是false
*    containsAll() 是否包含指定集合 是true 不是false
*    size() 集合中元素的个数
**/
public class Ex00_zxx {
    public static void main(String[] args) {
        System.out.println("【例】Collection接口的方法进行增加、删除和获取元素 (张晓轩)");
        Collection<String> col = new ArrayList<String>();
        col.add("张三");
        col.add("李四");
        Collection<String> col2 = new ArrayList<String>();
        col2.add("王五");
        col2.add("赵六");
        col.addAll(col2);
        System.out.println("1. col.size()="+col.size())  ;
        col.remove("张三");
        System.out.println("2. col.size()="+col.size());
        col.removeAll(col2);
        System.out.println("3. col.size()="+col.size());
        System.out. println("4. col.isEmpty()="+col.isEmpty());
        col.remove("李四");
        System.out.println("5. col.isEmpty()="+col.isEmpty());
        System.out.println("6. col.contains('李四')="+col.contains("李四"));
        col.add("李四");
        System.out.println("7. col.contains('李四')="+col.contains("李四"));
        System.out.println("8. col.containsAll(col2)="+col.containsAll(col2)) ;
        col.addAll(col2);
        System.out.println("9. col.containsAll(col2)="+col.containsAll(col2));
    }
}
图2 Collection接口:增加、删除和获取元素方法

3. List接口

3.1 List接口简介

List接口继承自Collection接口,List接口实例中允许存储重复的元素,所有的元素以线性方式进行存储。在程序中可以通过索引访问List接口实例中存储的元素。另外,List接口实例中存储的元素是有序的,即元素的存入顺序和取出顺序一致。

List接口常用方法

方法声明 功能描述
void add(int index,Object element) 将元素element插入List的index索引处
boolean addAll(int index,Collection c) 将集合c所包含的所有元素插入到List集合的index索引处
Object get(int index) 返回集合index索引处的元素
Object remove(int index) 删除index索引处的元素
Object set(int index, Object element) 将index索引处元素替换成element对象,并将替换后的元素返回
int indexOf(Object o) 返回对象o在List中第一次出现的位置索引
int lastIndexOf(Object o) 返回对象o在List中最后一次出现的位置索引
List subList(int fromIndex, int toIndex) 返回从索引fromIndex(包括)到 toIndex(不包括)处所有元素集合组成的子集合
package cn.zxx.ch07;
import java.util.ArrayList;
import java.util.List;
/**
 * List接口 extends Collection接口
 * 特有的方法:
 *   add(int index.0bject obj); //插入元素
 *   get(int index); //查询元素
 *   set(int index, 0bject obj); //修改元素
 **/
public class Ex00_zxx_ {
    public static void main(String[] args) {
        System.out.println("【例】List接口: 增加、删除和获取元素方法 (张晓轩)");
        List list = new ArrayList();
        list.add(0, "张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");
        list.add("张三");
        list.add("赵六");
        System.out.println(list);
        System.out.println(list.size());
        list.add(0, "田七");
        list.add(1, "周星星");
        System.out.println(list);
        System.out.println(list.size());
        System.out.println(list.get(1));
        list.set(1,"周星驰");
        System.out.println(list);
    }
}
图3 List接口:增加、删除和获取元素方法

3.2 ArrayList

ArrayList是List接口的一个实现类,它是程序中最常见的一种集合。ArrayList集合内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组

图4 ArrayList集合的元素插入过程

由于ArrayList集合的底层是使用一个数组来存储元素,在指定位置增加或删除元素时,会创建新的数组,效率比较低,因此Arraylist集合不适合做大量的增删操作,而适合元素的查找

package cn.zxx.ch07;
import java.util.ArrayList;
public class Ex01_zxx {
    public static void main(String[] args) {
        System.out.println("【例1】ArrayList集合的元素存取:add/remove/get/set/size (张晓轩)");
        // ArrayList<String> list = new ArrayList<String>(); // 创建ArrayList集合
        ArrayList list = new ArrayList(); // 创建ArrayList集合
        list.add("张三");                     // 向集合中添加元素
        list.add("李四");
        list.add("王五");
        list.add("赵六");
        // 获取集合中元素的个数
        System.out.println("集合的长度:" + list.size());
        // 取出并打印指定位置的元素
        System.out.println("第2个元素是:" + list.get(1));
        //删除索引为3的元素
        list.remove(3);
        System.out.println("删除索引为3的元素:"+list);
        //替换索引为1的元素为李四2
        list.set(1,"李四2");
        System.out.println("替换索引为1的元素为李四2:"+list);
    }
}
图5 ArrayList集合的元素存取:add/remove/get/set/size

ArrayList集合和数组一样,索引的取值范围是从0开始的,最后一个索引是size-1,在访问元素时一定要注意索引不可超出此范围,否则程序会抛出索引越界异常IndexOutOfBoundsException。

3.3 LinkedList

LinkedList集合内部维护了一个双向循环链表,链表中的每一个元素都使用引用的方式记录它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来。当插入一个新元素时,只需要修改元素之间的引用关系即可,删除一个节点也是如此。正因为这样的存储结构,所以LinkedList集合增删效率非常高。索引值从0开始。

图6 LinkedList集合添加、删除元素过程

LinkedList集合特有的方法

方法声明 功能描述
void add(int index, E element) 在集合的index索引处插入element元素
void addFirst(Object o) 将指定元素o插入此集合的开头
void addLast(Object o) 将指定元素o添加到此集合的结尾
Object getFirst() 返回此集合的第一个元素
Object getLast() 返回此集合的最后一个元素
Object removeFirst() 移除并返回集合的第一个元素
Object removeLast() 移除并返回集合的最后一个元素
boolean offer(Object o) 将指定元素o添加到集合的结尾
boolean offerFirst(Object o) 将指定元素o添加到集合的开头
boolean offerLast(Object o) 将指定元素o添加到集合的结尾
Object peekFirst() 获取集合的第一个元素
Object peekLast() 获取集合的最后一个元素
Object pollFirst() 移除并返回集合的第一个元素
Object pollLast() 移除并返回集合的最后一个元素
void push(Object o) 将指定元素o添加到集合的开头
package cn.zxx.ch07;

import java.util.LinkedList;

public class Ex02_zxx {
    public static void main(String[] args) {
        System.out.println("【例2】LinkedList集合元素进行增加、删除和获取操作 (张晓轩)");
        LinkedList<String> link = new LinkedList<String>();   // 创建LinkedList集合
        link.add("张三");
        link.add("李四");
        link.add("王五");
        link.add("赵六");
        System.out.println(link.toString()); // 获取并打印该集合中的元素
        link.add(3, "Student");     // 向link集合中索引3处插入元素Student
        link.addFirst("First");     // 向link集合第一个位置插入元素First
        System.out.println(link);
        System.out.println(link.getFirst()); // 取出link集合中第一个元素
        link.remove(3);               // 移除link集合中指定索引位置为3的元素
        link.removeFirst();          // 移除link集合中第一个元素
        System.out.println(link);
    }
}
图7 LinkedList集合添加、删除元素

4. 集合遍历

4.1 Iterator接口

掌握Iterator接口,能够使用Iterator对集合进行遍历: it.hasNext(),it.Next()

Iterator接口是Java集合框架中的一员,但它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(遍历)Collection中的元素,通常情况下Iterator对象也被称为迭代器。

package cn.zxx.ch07;
import java.util.ArrayList;
import java.util.Iterator;
public class Ex03_zxx {
    public static void main(String[] args) {
        System.out.println("【例3】使用Iterator迭代集合中的元素 (张晓轩)");
        ArrayList list = new ArrayList(); // 创建ArrayList集合
        list.add("张三");                     // 向该集合中添加字符串
        list.add("李四");
        list.add("王五");
        list.add("赵六");
        System.out.println(list);
        Iterator it = list.iterator(); // 获取Iterator对象
        while (it.hasNext()) {           // 判断ArrayList集合中是否存在下一个元素
            Object obj = it.next();     // 取出ArrayList集合中的元素
            System.out.println(obj);
        }
    }
}
图8 使用Iterator迭代集合中的元素
图9 迭代器迭代元素过程

通过迭代器获取ArrayList集合中的元素时,这些元素的类型都是Object类型,如果 想获取到特定类型的元素,则需要进行对数据类型强制转换。

并发修改异常

在使用Iterator迭代器对集合中的元素进行迭代时,如果调用了集合对象的remove()方法删除元素,之后继续使用迭代器遍历元素,会出现异常。

package cn.zxx.ch07;
import java.util.ArrayList;
import java.util.Iterator;
public class Ex04_zxx {
    public static void main(String[] args) {
        System.out.println("【例4】并发修改异常 (张晓轩)");
        ArrayList list = new ArrayList();    //创建ArrayList集合
        list.add("张三");
        list.add("李四");
        list.add("王五");
        Iterator it = list.iterator();       // 获得Iterator对象
        while (it.hasNext()) {                // 判断该集合是否有下一个元素
            Object obj = it.next();           // 获取该集合中的元素
            System.out.println(obj);
            if ("张三".equals(obj)) {         // 判断该集合中的元素是否为张三
               list.remove(obj);              // 删除该集合中的元素
                //break;
                // it.remove();
            }
        }
        System.out.println(list);
    }
}
图10 并发修改异常
图11 并发修改异常 break
图12 并发修改异常--修正

4.2 foreach循环

foreach循环是一种更加简洁的for循环。foreach循环用于遍历数组或集合中的元素。

for(容器中元素类型 临时变量:容器变量) {
    执行语句
}
package cn.zxx.ch07;
import java.util.ArrayList;
public class Ex05_zxx {
    public static void main(String[] args) {
        System.out.println("【例5】foreach循环用于遍历数组或集合中的元素(张晓轩)");
        ArrayList list = new ArrayList(); // 创建ArrayList集合
        list.add("张三");                    // 向ArrayList集合中添加字符串元素
        list.add("李四");
        list.add("王五");
        for (Object obj : list) {         // 使用foreach循环遍历ArrayList对象
            System.out.println(obj);    // 取出并打印ArrayList集合中的元素
        }
    }
}
图13 foreach循环遍历集合中的元素

foreach循环缺陷

foreach循环虽然书写起来很简洁,但在使用时也存在一定的局限性。当使用foreach循环遍历集合和数组时,只能访问集合中的元素,不能对其中的元素进行修改

package cn.zxx.ch07;
public class Ex06_zxx {
    static String[] strs = { "aaa", "bbb", "ccc" };
    public static void main(String[] args) {
        System.out.println("【例6】foreach循环缺陷:只能访问不能修改元素(张晓轩)");
        // foreach循环遍历数组
        for (String str : strs) {
            str = "ddd";
        }
        System.out.println("foreach循环修改后的数组:" + strs[0] + "," +
                strs[1] + ","+ strs[2]);
        // for循环遍历数组
        for (int i = 0; i < strs.length; i++) {
            strs[i] = "ddd";
        }
        System.out.println("普通for循环修改后的数组:" + strs[0] + "," +
                strs[1] + ","+ strs[2]);
    }
}
图14 foreach循环缺陷:只问不改

5. Set接口

5.1 Set接口简介

Set接口也继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充。

与List接口不同的是,Set接口中元素是无序的,并且都会以某种规则保证存入的元素不出现重复

Set接口常见的实现类有3个,分别是HashSet、LinkedHashSet、TreeSet:

5.2 HashSet

Hash

Hash,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。常用hash算法有MD5、SHA1。

对象的哈希值

对象的哈希值(hashCode())是JDK根据对象的地址或者属性值(整型对象,Integer/Long),算出来的int类型的整数,Object 类中有一个方法可以获取对象的哈希值。

public int hashCode()
package cn.zxx.ch07;

class 学生{
    private String 学号;
    private String 姓名;

    public String get学号() {
        return 学号;
    }

    public void set学号(String 学号) {
        this.学号 = 学号;
    }

    public 学生(String 学号, String 姓名) {
        this.学号 = 学号;
        this.姓名 = 姓名;
    }
}

public class Ex07_zxx_ {
    public static void main(String[] args) {
        System.out.println("【例】对象的哈希值(张晓轩)");
        // JDK根据对象的地址或者属性值,算出来的int类型的整数
        // Object类中的方法 public int hashCode()

        Integer i1 = new Integer(100);
        Integer i2 = 100;
        Long l1=1000l;
        Long l2=1000l;
        String s1 = "abcefghijklmn";
        String s2 = "abcefghijklmn";
        String s3 = new String("abcefghijklmn");
        学生 x1 = new 学生("230001","张三");
        学生 x2 = new 学生("230001","张三");

        System.out.println(i1.hashCode());
        System.out.println(i2.hashCode());
        System.out.println(l1.hashCode());
        System.out.println(l2.hashCode());
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        System.out.println(s3.hashCode());
        System.out.println(x1.hashCode());
        System.out.println(x2.hashCode());
        System.out.println(s1 == s2);
        System.out.println(s1 == s3);
        System.out.println(s2 == s3);
        // Integer,Long对象的hashCode和值相同;String对象值相同hashCode相同;
        // 学生 对象 内容相同 但hashCode不同
    }
}
图15 对象的哈希值

HashSet

package cn.zxx.ch07;
import java.util.HashSet;
import java.util.Iterator;
public class Ex07_zxx {
    public static void main(String[] args) {
        System.out.println("【例7】HashSet举例(张晓轩)");
        HashSet hset = new HashSet();   // 创建HashSet集合
        hset.add("张三");                  // 向该Set集合中添加字符串
        hset.add("李四");
        hset.add("王五");
        hset.add("李四");                  // 向该Set集合中添加重复元素
        Iterator it = hset.iterator();  // 获取Iterator对象
        while (it.hasNext()) {          // 通过while循环,判断集合中是否有元素
            Object obj = it.next();// 如果有元素,就调用迭代器的next()方法获取元素
            System.out.println(obj);
        }
    }
}
图16 HashSet举例

HashSet数据结构

JDK8之前 底层采用数组+链表实现;JDK8以后 底层进行了优化 由数组+链表+红黑树实现

HashSet1.7 版本原理解析

HashSet<String> hm = new HashSet<>();
图17 HashSet数组+链表实现

HashSet1.8版本原理解析

存储流程:计算哈希值——计算应存入的索引——判断是否为空,为空直接存,不为空则根据是链表还是红黑树,用equals方法比较属性值然后判断存入位置

图18 HashSet存储元素的流程

向HashSet存储自定义类对象

package cn.zxx.ch07;
import java.util.HashSet;
class Student08 {
    String id;
    String name;
    public Student08(String id, String name) {         // 创建构造方法
        this.id=id;
        this.name = name;
    }
    public String toString() {                          // 重写toString()方法
        return id+":"+name;
    }
}
public class Ex08_zxx {
    public static void main(String[] args) {
       System.out.println("【例8】向HashSet存储自定义类对象(张晓轩)");
        HashSet hs = new HashSet();                      // 创建HashSet集合
        Student08 stu1 = new Student08("1", "张三");    // 创建Student对象
        Student08 stu2 = new Student08("2", "李四");
        Student08 stu3 = new Student08("2", "李四");
        hs.add(stu1);
        hs.add(stu2);
        hs.add(stu3);
        System.out.println(hs);
    }
}
图19 向HashSet存储自定义类对象

向HashSet存储自定义类对象--修正

package cn.zxx.ch07;

import java.util.*;
class Student09 {
    private String 学号;
    private String 姓名;
    public Student09(String 学号, String 姓名) {
        this.学号 = 学号;
        this.姓名 = 姓名;
    }
    // 重写toString()方法
    public String toString() {
        return 学号 + ":" + 姓名;
    }
    // 重写hashCode方法
    public int hashCode() {
       return 学号.hashCode();                     // 返回id属性的哈希值
    }
    // 重写equals方法
    public boolean equals(Object obj) {
        if (this == obj) {                        // 判断是否是同一个对象
            return true;                          // 如果是,直接返回true
        }
        if (!(obj instanceof Student09)) {       // 判断对象是为Student类型
            return false;
        }
        Student09 stu = (Student09) obj;            // 将对象强转为Student类型
        boolean b = this.学号.equals(stu.学号);   // 判断id值是否相同
        return    b;                                   // 返回判断结果
    }
}
public class Ex09_zxx {
    public static void main(String[] args) {
       System.out.println("【例9】向HashSet存储自定义类对象--修正(张晓轩)");
        HashSet hs = new HashSet();                  // 创建HashSet对象
        Student09 stu1 = new Student09("1", "张三");   // 创建Student对象
        Student09 stu2 = new Student09("2", "李四");
        Student09 stu3 = new Student09("2", "李四");
        Student09 stu4 = new Student09("2", "李斯");
        Student09 stu5 = new Student09("3", "李四");
        hs.add(stu1);                                   // 向集合存入对象
        hs.add(stu2);
        hs.add(stu3);
        hs.add(stu4);
        hs.add(stu5);
        System.out.println(hs);                      // 打印集合中的元素
    }
}
图20 向HashSet存储自定义类对象 -- 修正

5.3 LinkedHashSet

HashSet集合存储的元素是无序的,如果想让元素的存取顺序一致,可以使用Java提供的LinkedHashSet类,LinkedHashSet类是HashSet的子类,与LinkedList一样,它也使用双向链表来维护内部元素的关系。

package cn.zxx.ch07;
import java.util.Iterator;
import java.util.LinkedHashSet;
public class Ex10_zxx {
    public static void main(String[] args) {
        System.out.println("【例10】LinkedHashSet举例--有序add(张晓轩)");
        LinkedHashSet set = new LinkedHashSet();
        set.add("张三");                   // 向该Set集合中添加字符串
        set.add("李四");
        set.add("王五");
        Iterator it = set.iterator();  // 获取Iterator对象
        while (it.hasNext()){           // 通过while循环,判断集合中是否有元素
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}
图21 LinkedHashSet举例--有序add

5.4 TreeSet

TreeSet集合介绍

TreeSet是Set接口的另一个实现类,它内部采用平衡二叉树来存储元素,这样的结构可以保证TreeSet集合中没有重复的元素,并且可以对元素进行排序

所谓二叉树就是说每个节点最多有两个子节点的有序树,每个节点及其子节点组成的树称为子树,通常左侧的子节点称为“左子树”,右侧的节点称为“右子树”,其中左子树上的元素小于它的根结点,而右子树上的元素大于它的根结点

二叉树中元素的存储结构

图22 二叉树中元素的存储结构
图23 二叉树中元素的存储结构2

TreeSet集合的特有方法

方法声明 功能描述
Object first() 返回TreeSet集合的首个元素
Object last() 返回TreeSet集合的最后一个元素
Object lower(Object o) 返回TreeSet集合中小于给定元素的最大元素,如果没有返回null
Object floor(Object o) 返回TreeSet集合中小于或等于给定元素的最大元素,如果没有返回null
Object higher(Object o) 返回TreeSet集合中大于给定元素的最小元素,如果没有返回null
Object ceiling(Object o) 返回TreeSet集合中大于或等于给定元素的最小元素,如果没有返回null
Object pollFirst() 移除并返回集合的第一个元素
Object pollLast() 移除并返回集合的最后一个元素
package cn.zxx.ch07;
import java.util.TreeSet;
public class Ex11_zxx {
    public static void main(String[] args) {
        System.out.println("【例11】TreeSet举例--有序、比较(张晓轩)");
        // 创建TreeSet集合
        TreeSet ts = new TreeSet();
        // 1、向TreeSet集合中添加元素
        ts.add(3);
        ts.add(29);
        ts.add(101);
        ts.add(21);
        System.out.println("创建的TreeSet集合为:"+ts);
        // 2、获取首尾元素
        System.out.println("TreeSet集合首元素为:"+ts.first());
        System.out.println("TreeSet集合尾部元素为:"+ts.last());
        // 3、比较并获取元素
        System.out.println("集合中小于或等于9的最大的一个元素为:"
                +ts.floor(9));
        System.out.println("集合中大于10的最小的一个元素为:"+ts.higher(10));
        System.out.println("集合中大于100的最小的一个元素为:"
                +ts.higher(100));
        // 4、删除元素
        Object first = ts.pollFirst();
        System.out.println("删除的第一个元素是:"+first);
        System.out.println("删除第一个元素后TreeSet集合变为:"+ts);
    }
}
图24 TreeSet举例--有序、比较

TreeSet的排序规则

Java提供了两种TreeSet的排序规则,分别为自然排序和自定义排序。

TreeSet的自然排序

自然排序要求向TreeSet集合中存储的元素所在类必须实现Comparable接口,并重写compareTo()方法,然后TreeSet集合就会对该类型元素使用compareTo()方法进行比较。

compareTo()方法将当前对象与指定的对象进行顺序比较,返回值为一个整数,其中返回负整数、零或正整数分别表示当前对象小于、等于或大于指定对象,默认根据比较结果顺序排列。

package cn.zxx.ch07;
import java.util.TreeSet;
class Student12 implements Comparable{
    private String name;
    private int age;
    public Student12(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 重写toString()方法
    public String toString() {
        return name + ":" + age;
    }
    //重写Comparable接口的compareTo()方法
    public int compareTo(Object obj) {
        Student12 stu = (Student12)obj;
        //定义比较方式,先比较age,再比较name
        if(this.age - stu.age > 0){
            return 1;
        }
        if(this.age - stu.age == 0){
            return this.name.compareTo(stu.name);
        }
        return -1;
    }
}
public class Ex12_zxx {
    public static void main(String[] args) {
        System.out.println("【例12】TreeSet举例--自然排序(张晓轩)");
        TreeSet ts = new TreeSet();
        ts.add(new Student12("Lucy",18));
        ts.add(new Student12("Tom",20));
        ts.add(new Student12("Bob",20));
        // name,age相同值1人,如Tom 20
        ts.add(new Student12("Tom",20));
        System.out.println(ts);
    }
}
图25 TreeSet举例---自然排序

TreeSet自定义排序

如果不想实现Comparable接口或者不想按照实现了Comparable接口的类中compareTo()方法的规则进行排序,可以通过自定义比较器的方式对TreeSet集合中的元素自定义排序规则。实现Comparator接口的类都是一个自定义比较器,可以在自定义比较器中的compare()方法中自定义排序规则。

package cn.zxx.ch07;
import java.util.Comparator;
        import java.util.TreeSet;
class Student13 {                             //创建Student类
    private String 学号;
    private String 姓名;
    public Student13(String 学号, String 姓名) {
        this.学号 = 学号;
        this.姓名 = 姓名;
    }
    public String get学号() {
        return 学号;
    }

    public String get姓名() {
        return 姓名;
    }

    // 重写toString()方法
    public String toString() {
        return 学号 + ":" + 姓名;
    }
}
public  class Ex13_zxx {
    public static void main(String[] args) {
        System.out.println("【例13】TreeSet举例--自定义排序(张晓轩)");
        TreeSet ts = new TreeSet(new Comparator() {
            // 通过匿名内部类的方式实现了Comparator接口,在内部类中重写了Comparator接口的compare()方法。
            @Override
            public int compare(Object o1, Object o2) {  //重写
                Student13 stu1= (Student13)o1;
                Student13 stu2= (Student13)o2;
                // 先比较姓名
                if(!stu1.get姓名().equals(stu2.get姓名())){
                    return  stu1.get姓名().compareTo(stu2.get姓名());
                }
                else{
                    // 姓名相同在比较学号
                    return  stu1.get学号().compareTo(stu2.get学号());
                }
            }
        });
        ts.add(new Student13("2", "Lisa"));//向ts集合中添加元素
        ts.add(new Student13("1","Jack"));
        ts.add(new Student13("3", "Lisa"));
        ts.add(new Student13("4", "Mary"));
        System.out.println(ts);
    }
}
图26 TreeSet举例---自定义排序

6. Map接口

6.1 Map接口简介

Map接口介绍

Map接口是一种双列集合,它的每个元素都包含一个键对象Key和值对象Value,键和值对象之间存在一种对应关系,称为映射。Map中键对象Key不允许重复,访问Map集合中的元素时,只要指定了Key,就能找到对应的Value。

Map接口常用方法

方法声明 功能描述
void put(Object key, Object value) 将指定的值和键存入到集合中,并进行映射关联
Object get(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回null
void clear() 移除所有的键值对元素
V remove(Object key) 根据键删除对应的值,返回被删除的值
int size() 返回集合中的键值对的个数
boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true。
boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true
Set keySet() 返回此映射中包含的键的Set集合
Collection values() 返回此映射中包含的值的Collection集合
Set>entrySet() 返回此映射中包含的映射关系的Set集合
package cn.zxx.ch07;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Ex17_zxx {
    public static void main(String[] args) {
        System.out.println("【例17】Map操作集合的常用方法(张晓轩)");
        Map map = new HashMap();      // 创建HashMap集合
        map.put("1", "张三");          // 存储键和值
        map.put("3", "李四");
        map.put("2", "王五");
        map.put("4", "赵六");
        System.out.println("集合大小为:"+map.size());
        System.out.println("判断是否包含传入的键(2):"+map.containsKey("2"));
        System.out.println("判断是否包含传入的值(王五):"+map.containsValue("王五"));
        System.out.println("移除键为1的值是:"+map.remove("1"));
        Collection values = map.values();
        Iterator it = values.iterator();
        while (it.hasNext()) {
            Object value = it.next();
            System.out.println(value);
        }
    }
}
图31 Map操作集合的常用方法

6.2 HashMap

HashMap集合是Map接口的一个实现类,HashMap集合中的大部分方法都是Map接口方法的实现。在开发中,通常把HashMap集合对象的引用赋值给Map接口变量,那么接口变量就可以调用类实现的接口方法。

HashMap集合用于存储键值映射关系,但HashMap集合没有重复的键并且键值无序

package cn.zxx.ch07;
import java.util.HashMap;
import java.util.Map;
public class Ex14_zxx {
    public static void main(String[] args) {
        System.out.println("【例14】HashMap举例 (张晓轩)");
        Map map = new HashMap(); // 创建HashMap对象
        map.put("1", "张三");          // 存储键和值
        map.put("2", "李四");
        map.put("3", "王五");
        //map.put("3", "赵六");
        System.out.println("1:" + map.get("1"));  // 根据键获取值
        System.out.println("2:" + map.get("2"));
        System.out.println("3:" + map.get("3"));
    }
}
图27 HashMap举例
图28 HashMap举例--键相同值覆盖

HashMap遍历--遍历键获取值

package cn.zxx.ch07;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Ex15_zxx {
    public static void main(String[] args) {
        System.out.println("【例15】HashMap遍历--遍历键获取值 (张晓轩)");
        Map map = new HashMap();      // 创建HashMap集合
        map.put("1", "张三");               // 存储键和值
        map.put("2", "李四");
        map.put("3", "王五");
        Set keySet = map.keySet();         // 获取键的集合
        Iterator it = keySet.iterator();  // 获取Iterator对象
        while (it.hasNext()) {
            Object key = it.next();
            Object value = map.get(key);  // 获取每个键所对应的值
            System.out.println(key + ":" + value);
        }
    }
}
图29 HashMap遍历--遍历键获取值

HashMap遍历--先获取集合中所有的映射关系,然后从映射关系中取出键和值

package cn.zxx.ch07;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Ex16_zxx {
    public static void main(String[] args) {
        System.out.println("【例16】HashMap遍历--先获取集合中所有的映射关系,然后从映射关系中取出键和值。 (张晓轩)");
        Map map = new HashMap();         // 创建HashMap集合
        map.put("1", "张三");               // 存储键和值
        map.put("2", "李四");
        map.put("3", "王五");
        Set entrySet = map.entrySet();
        Iterator it = entrySet.iterator();              // 获取Iterator对象
        while (it.hasNext()) {
            // 获取集合中键值对映射关系
            Map.Entry entry = (Map.Entry) (it.next());
            Object key = entry.getKey();                // 获取Entry中的键
            Object value = entry.getValue();           // 获取Entry中的值
            System.out.println(key + ":" + value);
        }
    }
}
图30 HashMap遍历----先获取集合中所有的映射关系,然后从映射关系中取出键和值

6.3 LinkedHashMap

HashMap集合迭代出来元素的顺序和存入的顺序是不一致的。如果想让这Map集合中的元素迭代顺序与存入顺序一致,可以使用LinkedHashMap集合。

LinkedHashMap是HashMap的子类,与LinkedList一样,LinkedHashMap集合也使用双向链表维护内部元素的关系,使Map集合元素迭代顺序与存入顺序一致

package cn.zxx.ch07;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class Ex18_zxx {
    public static void main(String[] args) {
        System.out.println("【例18】LinkedHashMap举例--迭代顺序和存入顺序一致 (张晓轩)");
        Map map = new LinkedHashMap();             // 创建LinkedHashMap集合
        map.put("3", "李四");                           // 存储键和值
        map.put("2", "王五");
        map.put("4", "赵六");
        Set keySet = map.keySet();
        Iterator it = keySet.iterator();
        while (it.hasNext()) {
            Object key = it.next();
            Object value = map.get(key); // 获取每个键所对应的值
            System.out.println(key + ":" + value);
        }
    }
}
图32LinkedHashMap举例--迭代顺序和存入顺序一致

6.4 TreeMap

HashMap集合存储的元素的键值是无序的和不可重复的,为了对集合中的元素的键值进行排序,Map接口还有了另一个可以对集合中元素键和值进行排序的实现类TreeMap。键值有序

package cn.zxx.ch07;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class Ex19_zxx {
    public static void main(String[] args) {
        System.out.println("【例19】TreeMap举例 (张晓轩)");
        Map map = new TreeMap();      // 创建TreeMap集合
        map.put(3, "李四");                  // 存储键和值
        map.put(2, "王五");
        map.put(4, "赵六");
        map.put(3, "张三");  //键相同值覆盖
        Set keySet = map.keySet();
        Iterator it = keySet.iterator();
        while (it.hasNext()) {
            Object key = it.next();
            Object value = map.get(key); // 获取每个键所对应的值
            System.out.println(key+":"+value);
        }
    }
}
图33 TreeMap举例
package cn.zxx.ch07;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;
class Student20 {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Student20(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student20 [name=" + name + ", age=" + age + "]";
    }
}

public class Ex20_zxx {
    public static void main(String[] args) {
        System.out.println("【例20】TreeMap举例--键是自定义类 (张晓轩)");
        TreeMap tm = new TreeMap(new Comparator<Student20>() {
            // 通过匿名内部类的方式实现了Comparator接口,在内部类中重写了Comparator接口的compare()方法。
            @Override
            public int compare(Student20 s1, Student20 s2) {
                // 先比较姓名
                if(!s1.getName().equals(s2.getName())){
                    return  s1.getName().compareTo(s2.getName());
                }
                else{
                    // 姓名相同在比较年龄
                    return  s1.getAge()-(s2.getAge());
                }
                //int num = s1.getName().compareTo(s2.getName());//按照姓名比较--有?
                //return num == 0 ? num:s1.getAge() - s2.getAge();
            }
        });
        tm.put(new Student20("张三", 23), "北京");
        tm.put(new Student20("李四", 13), "上海");
        tm.put(new Student20("赵六", 43), "深圳");
        tm.put(new Student20("张三", 33), "广州");
        Set keySet = tm.keySet();
        Iterator it = keySet.iterator();
        while (it.hasNext()) {
            Object key = it.next();
            Object value = tm.get(key); // 获取每个键所对应的值
            System.out.println(key+":"+value);
        }
    }
}
图34 TreeMap举例--键是自定义类

6.5 Properties

Properties主要用于存储字符串类型的键和值,在实际开发中,经常使用Properties集合存储应用的配置项。

package cn.zxx.ch07;
import java.util.Enumeration;
import java.util.Properties;
public class Ex21_zxx {
    public static void main(String[] args) {
        System.out.println("【例21】Properties举例 (张晓轩)");
        Properties p=new Properties();         // 创建Properties对象
        p.setProperty("Backgroup-color", "red");
        p.setProperty("Font-size", "14px");
        p.setProperty("Language", "Chinese");
        Enumeration names = p.propertyNames();//获取Enumeration对象所有键枚举
        while(names.hasMoreElements()){        //循环遍历所有的键
            String key=(String) names.nextElement();
            String value=p.getProperty(key);   // 获取对应键的值
            System.out.println(key+" = "+value);
        }
    }
}
图35 Properties举例

7. 常用工具类

7.1 Collections工具类

使用Collections工具类使用静态方法对List集合进行添加和排序操作,对Set、List和 Map进行查找、替换操作

1. 添加、排序操作

方法声明 功能描述
static boolean addAll(Collection<? super T> c, T... elements) 将所有指定元素添加到指定集合c中
static void reverse(List list) 反转指定List集合中元素的顺序
static void shuffle(List list) 随机打乱List集合中元素的顺序
static void sort(List list) 根据元素的自然顺序(从小到大)对List集合中的元素进行排序
static void swap(List list,int i,int j) 将指定List集合中索引为i的元素和索引为j的元素进行交换
package cn.zxx.ch07;
import java.util.ArrayList;
import java.util.Collections;
public class Ex22_zxx {
    public static void main(String[] args) {
        System.out.println("【例22】Collections工具类添加、排序 (张晓轩)");
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "C","Z","B","K");   // 添加元素
        System.out.println("排序前: " + list);
        Collections.reverse(list);                   // 反转集合
        System.out.println("反转后: " + list);
        Collections.sort(list);                      // 按自然顺序排列
        System.out.println("按自然顺序排序后: " + list);
        Collections.shuffle(list);                   // 随机打乱集合元素, 每次运行都不一样
        System.out.println("按随机顺序排序后:  " + list);
        Collections.swap(list, 0, list.size()-1);    // 将集合首尾元素交换
        System.out.println("集合首尾元素交换后: " + list);
    }
}
图36 Collections工具类添加、排序---随机排序每次都不一样

2. 查找、替换操作

方法声明 功能描述
static int binarySearch(List list,Object key) 使用二分法搜索指定对象在List集合中的索引,要求查找的List集合中的元素必须是有序的
static Object max(Collection col) 根据元素的自然顺序,返回给定集合中最大的元素
static Object min(Collection col) 根据元素的自然顺序,返回给定集合中最小的元素
static boolean replaceAll(List list,Object oldVal,Object newVal) 用一个新值newVal替换List集合中所有的旧值oldVal
package cn.zxx.ch07;
import java.util.ArrayList;
import java.util.Collections;
public class Ex23_zxx {
    public static void main(String[] args) {
        System.out.println("【例23】Collections工具类查找、替换 (张晓轩)");
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, -3,2,9,5,8);// 向集合中添加所有指定元素
        System.out.println("集合中的元素: " + list);
        System.out.println("集合中的最大元素: " + Collections.max(list));
        System.out.println("集合中的最小元素: " + Collections.min(list));
        Collections.replaceAll(list, 8, 0); // 将集合中的8用0替换掉
        System.out.println("替换后的集合: " + list);
        Collections.sort(list);               //使用二分查找前,必须保证元素有序
        System.out.println("集合排序后为: "+list);
        int index = Collections.binarySearch(list, 9);
        System.out.println("集合通过二分查找方法查找元素9所在索引为:"+index);
    }
}
图37 Collections工具类查找、替换

7.2 Arrays工具类

静态方法 描述
1 Arrays.sort(arr); 对数组进行排序
2 Arrays.binarySearch(arr, 位置); 在有序数组中查找指定元素的位置
3 newArr = Arrays.copyOf(arr, 长度); 复制数组的一部分到一个新的数组中
4 Arrays.fill(arr, 指定值); 用指定值填充数组
5 boolean b = Arrays.equals(arr1, arr2); 比较两个数组值是否相等
6 Arrays.toString(arr) 将数组转换成字符串形式输出
7 List list = Arrays.asList(arr); 将数组转换成列表
8 int hashCode = Arrays.hashCode(arr); 返回数组的哈希码值
9 IntStream stream = Arrays.stream(arr); 将数组转换成流
10 Arrays.parallelSort(arr); 对数组进行并行排序

1. 使用sort()方法排序

package cn.zxx.ch07;
import java.util.Arrays;
public class Ex24_zxx {
    public static void main(String[] args) {
        System.out.println("【例24】Arrays工具类sort()方法排序 (张晓轩)");
        int[] arr = { 9, 8, 3, 5, 2 };   // 初始化一个数组
        System.out.print("排序前:");
        printArray(arr);                    // 打印原数组
        Arrays.sort(arr);                   // 调用Arrays的sort()方法排序
        System.out.print("排序后:");
        printArray(arr);                    // 打印排序后数组
    }
    // 定义打印数组元素方法
    public static void printArray(int[] arr) {
        System.out.print("[");
        for (int x = 0; x < arr.length; x++) {
            if (x != arr.length - 1) {
                System.out.print(arr[x] + ", ");
            } else {
                System.out.println(arr[x] + "]");
            }
        }
    }
}
图38 Arrays工具类sort()方法排序

2. 使用binarySearch()方法查找元素

package cn.zxx.ch07;
import java.util.Arrays;
public class Ex25_zxx {
    public static void main(String[] args) {
        System.out.println("【例25】Arrays工具类binarySearch()方法查找元素 (张晓轩)");
        int[] arr = { 9, 8, 3, 5, 2 };
        Arrays.sort(arr);                            // 对数组进行排序
        int index = Arrays.binarySearch(arr, 3); // 查找指定元素3
        System.out.println("元素3的索引是:" + index);
    }
}
图39 Arrays工具类binarySearch()方法查找元素

3.使用copyOfRange()方法复制元素

package cn.zxx.ch07;
import java.util.Arrays;
public class Ex26_zxx {
    public static void main(String[] args) {
        System.out.println("【例26】Arrays工具类copyOfRange()方法复制元素 (张晓轩)");
        int[] arr = { 9, 8, 3, 5, 2 };
        // 复制一个指定范围的数组
        int[] copied = Arrays.copyOfRange(arr, 1, 7);
        for (int i = 0; i < copied.length; i++) {
            System.out.print(copied[i] + " ");
        }
    }
}
图40 Arrays工具类copyOfRange()方法复制元素

4. 使用fill()方法填充元素

package cn.zxx.ch07;
import java.util.Arrays;
public class Ex27_zxx {
    public static void main(String[] args) {
        System.out.println("【例27】Arrays工具类fill()方法填充元素 (张晓轩)");
        int[] arr = { 1, 2, 3, 4 };
        Arrays.fill(arr, 8);       // 用8替换数组中的每个元素
        for (int i = 0; i < arr.length; i++) {
            System.out.println(i + ": " + arr[i]);
        }
    }
}
图41 Arrays工具类fill()方法填充元素

8. Lambda表达式

Lambda表达式是JDK 8之后新增的一个新特性,Lambda可以取代大部分的匿名内部类,写出更优雅的Java代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。

JDK也提供了大量的内置函数式接口供我们使用,使得Lambda表达式的运用更加方便、高效。

Lambda表达式由参数列表、箭头符号 -> 和方法体组成。方法体既可以是一个表达式,也可以是一个语句块。

Lambda表达式常用的语法格式

语法格式 描述
()-> System.out.println("Hello Lambda!"); 无参数,无返回值
(x) -> System.out.println(x) 有一个参数,并且无返回值
x -> System.out.println(x) 若只有一个参数,小括号可以省略不写
Comparator com = (x, y) -> {System.out.println("函数式接口");return Integer.compare(x, y); }; 有两个以上的参数,有返回值,并且 Lambda 方法体中有多条语句
Comparator com = (x, y) -> Integer.compare(x, y); 若Lambda 体中只有一条语句,return 和大括号都可以省略不写
(Integer x, Integer y) -> Integer.compare(x, y); Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
package cn.zxx.ch07;
import java.util.Arrays;
import java.util.Comparator;
public class Ex28_zxx {
    public static void main(String[] args) {
        System.out.println("【例28】Lambda表达式举例 (张晓轩)");
        String[] arr = {"program", "creek", "is", "a", "java", "site"};
        System.out.println("原始值:"+ Arrays.toString(arr));

        Arrays.sort(arr, (m, n) -> Integer.compare(m.length(), n.length()));
        System.out.println("Lambda语句体中只有一条语句,参数类型可推断:"+ Arrays.toString(arr));

        Arrays.sort(arr, (String m, String n) -> {
            if (m.length() > n.length())
                return -1;
            else
                return 0;
        });
        System.out.println("Lambda语句体中有多条语句:"+Arrays.toString(arr));

        //需求:按照长度对字符串进行排序
        //长度从小到大
        Arrays.sort(arr,new Comparator<String>(){
            public int compare(String o1, String o2){
                return o1.length() - o2.length() ;
            }
        }) ;
        System.out.println(Arrays.toString(arr)) ;

        Arrays.sort(arr,(o1, o2)-> o2.length() - o1.length());
        //长度从大到小
        System. out. println(Arrays.toString(arr)) ;

    }
}
图42 Lambda表达式举例
图43 本章系统开发截图

返回