Java进阶 6. 集合

Java进阶 6. 集合

文章目录

    • [Java进阶 6. 集合](#Java进阶 6. 集合)
    • [6.1. Collection](#6.1. Collection)
      • [6.1.1. iterator](#6.1.1. iterator)
      • [6.1.2. 增强for](#6.1.2. 增强for)
      • [6.1.3. lambda表达式](#6.1.3. lambda表达式)
    • [6.2. List](#6.2. List)
      • [6.2.1. 概述和特点【记忆】](#6.2.1. 概述和特点【记忆】)
      • [6.2.2. 特有方法【应用】](#6.2.2. 特有方法【应用】)
      • [6.2.3. 五种遍历方式【应用】](#6.2.3. 五种遍历方式【应用】)
      • [6.2.4. 细节点注意](#6.2.4. 细节点注意)
      • [6.2.5. 数据结构](#6.2.5. 数据结构)
      • [6.2.6. List集合的实现类==ArrayList==和LinkedList](#6.2.6. List集合的实现类==ArrayList==和LinkedList)
      • [6.2.7. 源码分析](#6.2.7. 源码分析)
      • [6.2.8. 迭代器源码分析](#6.2.8. 迭代器源码分析)
    • [6.3. 泛型](#6.3. 泛型)
      • [6.3.1. 概述](#6.3.1. 概述)
    • [6.4. Set](#6.4. Set)
      • [6.4.1. 概述和特点【应用】](#6.4.1. 概述和特点【应用】)
      • [6.4.2. 使用【应用】](#6.4.2. 使用【应用】)
      • [6.4.3. TreeSet集合](#6.4.3. TreeSet集合)
      • [6.4.4. 自然排序Comparable的使用【应用】](#6.4.4. 自然排序Comparable的使用【应用】)
      • [6.4.5. 比较器排序Comparator的使用【应用】](#6.4.5. 比较器排序Comparator的使用【应用】)
      • [6.4.6. 两种比较方式总结【理解】](#6.4.6. 两种比较方式总结【理解】)
      • [6.4.7. 二叉树【理解】](#6.4.7. 二叉树【理解】)
      • [6.4.8. 二叉查找树【理解】](#6.4.8. 二叉查找树【理解】)
      • [6.4.9. 平衡二叉树【理解】](#6.4.9. 平衡二叉树【理解】)
      • [6.4.10. 红黑树【理解】](#6.4.10. 红黑树【理解】)
      • [6.4.11. ==HashSet==](#6.4.11. ==HashSet==)
      • [6.4.12. LinkedHashSet](#6.4.12. LinkedHashSet)
    • [6.5. Map](#6.5. Map)
      • [6.5.1. 概述和特点【理解】](#6.5.1. 概述和特点【理解】)
      • [6.5.2. 基本功能【应用】](#6.5.2. 基本功能【应用】)
      • [6.5.3. 获取功能【应用】](#6.5.3. 获取功能【应用】)
      • [6.5.4. 遍历](#6.5.4. 遍历)
      • 6.5.5.HashMap集合
      • 6.5.6.TreeMap集合
    • [6.6. 可变参数](#6.6. 可变参数)
    • [6.7. Collections类](#6.7. Collections类)
    • [6.8. 综合练习](#6.8. 综合练习)
    • [6.9. 不可变集合](#6.9. 不可变集合)
    • [6.10. Stream流](#6.10. Stream流)
      • [6.10.1. 体验Stream流【理解】](#6.10.1. 体验Stream流【理解】)
      • [6.10.2. 常见生成方式【应用](#6.10.2. 常见生成方式【应用)
      • [6.10.3. 中间操作方法【应用】](#6.10.3. 中间操作方法【应用】)
      • [6.10.4. 终结操作方法【应用】](#6.10.4. 终结操作方法【应用】)
      • [6.10.5. 收集操作【应用】](#6.10.5. 收集操作【应用】)
      • [6.10.6. 综合练习【应用】](#6.10.6. 综合练习【应用】)
    • [6.11. 方法引用](#6.11. 方法引用)
      • [6.11.1. 体验方法引用【理解】](#6.11.1. 体验方法引用【理解】)
      • [6.11.2. 方法引用符【理解】](#6.11.2. 方法引用符【理解】)
      • [6.11.3. 引用类方法【应用】](#6.11.3. 引用类方法【应用】)
      • [6.11.4. 引用对象的成员方法【应用】](#6.11.4. 引用对象的成员方法【应用】)
      • [6.11.5. 引用类的实例方法【应用】](#6.11.5. 引用类的实例方法【应用】)
      • [6.11.6. 引用构造器【应用】](#6.11.6. 引用构造器【应用】)

数组和集合的区别【理解】

  • 相同点

    都是容器,可以存储多个数据

  • 不同点

    • 数组的长度是不可变 的,集合的长度是可变

    • 数组可以存基本 数据类型和引用数据类型

    • 集合只能存引用 数据类型,如果要存基本数据类型,需要存对应的包装类

集合类体系结构【理解】

6.1. Collection

  • Collection集合概述

    • 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
    • JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现
  • 创建Collection集合的对象

    • 多态的方式
    • 具体的实现类ArrayList
  • Collection集合常用方法

    方法名 说明
    boolean add(E e) 添加元素
    boolean remove(Object o) 从集合中移除指定的元素
    boolean removeIf(Object o) 根据条件进行移除
    void clear() 清空集合中的元素
    boolean contains(Object o) 判断集合中是否存在指定的元素
    boolean isEmpty() 判断集合是否为空
    int size() 集合的长度,也就是集合中元素的个数

6.1.1. iterator

  • 迭代器介绍

    • 迭代器,集合的专用遍历方式
    • Iterator<E> iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到
  • Iterator中的常用方法

    • boolean hasNext(): 判断当前位置是否有元素可以被取出

    • E next(): 获取当前 位置的元素,将迭代器对象移向下一个索引位置

  • Collection集合的遍历

    java 复制代码
    package com.powernode.iterator;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    public class Test {
        public static void main(String[] args) {
            // public interface Collection<E> extends Iterable<E>
    //        Collection<String> col = new Collection(); // java.util.Collection是抽象的; 无法实例化
    
            Collection<String> col = new ArrayList<>();
            col.add("1");
            col.add("2");
            col.add("3");
            col.add("4");
    
            Iterator<String> it = col.iterator();
            while(it.hasNext()){
                String next = it.next();
                System.out.println(next);
            }
        }
    }
  • 迭代器中删除的方法

    ​ void remove(): 删除迭代器对象当前指向的元素

    java 复制代码
    package com.powernode.iterator;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    public class Test {
        public static void main(String[] args) {
            // remove()
            ArrayList<String> list = new ArrayList<>();
            list.add("ab");
            list.add("cd");
            list.add("ef");
            list.add("gh");
            Iterator<String> it2 = list.iterator();
            while(it2.hasNext()){
                String next = it2.next();
                if("cd".equals(next)){
                    it2.remove();
                }
            }
            Iterator<String> it3 = list.iterator();
            while(it3.hasNext()){
                String next = it3.next();
                System.out.println(next);
            }
            System.out.println(list);
        }
    }

6.1.2. 增强for

  • 介绍

    • 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
    • 实现Iterable接口的类才可以使用迭代器和增强for
    • 简化数组和Collection集合的遍历
  • 格式

    ​ for(集合/数组中元素的数据类型 变量名 : 集合/数组名) {

    ​ // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可

    ​ }

  • 代码

    java 复制代码
    public class MyCollectonDemo1 {
        public static void main(String[] args) {
            ArrayList<String> list =  new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            list.add("f");
    
            //1,数据类型一定是集合或者数组中元素的类型
            //2,str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素
            //3,list就是要遍历的集合或者数组
            for(String str : list){
                System.out.println(str);
            }
        }
    }
  • 细节点注意:

1.报错NoSuchElementException

2.迭代器遍历完毕,指针不会复位

3.循环中只能用一次next方法

4.迭代器遍历时,不能用集合的方法 进行增加或者删除

java 复制代码
public class A04_CollectionDemo4 {
    public static void main(String[] args) {
      /*
        迭代器的细节注意点:
            1.报错NoSuchElementException
            2.迭代器遍历完毕,指针不会复位
            3.循环中只能用一次next方法
            4.迭代器遍历时,不能用集合的方法进行增加或者删除
            	暂时当做一个结论先行记忆,在今天我们会讲解源码详细的再来分析。
                如果我实在要删除:那么可以用迭代器提供的remove方法进行删除。
                如果我要添加,暂时没有办法。(只是暂时)
       */

        //1.创建集合并添加元素
        Collection<String> coll = new ArrayList<>();
        coll.add("aaa");
        coll.add("bbb");
        coll.add("ccc");
        coll.add("ddd");

        //2.获取迭代器对象
        //迭代器就好比是一个箭头,默认指向集合的0索引处
        Iterator<String> it = coll.iterator();
        //3.利用循环不断的去获取集合中的每一个元素
        while(it.hasNext()){
            //4.next方法的两件事情:获取元素并移动指针
            String str = it.next();
            System.out.println(str);
        }

        //当上面循环结束之后,迭代器的指针已经指向了最后没有元素的位置
        //System.out.println(it.next());//NoSuchElementException

        //迭代器遍历完毕,指针不会复位
        System.out.println(it.hasNext());

        //如果我们要继续第二次遍历集合,只能再次获取一个新的迭代器对象
        Iterator<String> it2 = coll.iterator();
        while(it2.hasNext()){
            String str = it2.next();
            System.out.println(str);
        }
    }
}

6.1.3. lambda表达式

​ 利用forEach方法,再结合lambda表达式的方式进行遍历

java 复制代码
public class A07_CollectionDemo7 {
    public static void main(String[] args) {
       /* 
        lambda表达式遍历:
                default void forEach(Consumer<? super T> action):
        */

        //1.创建集合并添加元素
        Collection<String> coll = new ArrayList<>();
        coll.add("zhangsan");
        coll.add("lisi");
        coll.add("wangwu");
        //2.利用匿名内部类的形式
        //底层原理:
        //其实也会自己遍历集合,依次得到每一个元素
        //把得到的每一个元素,传递给下面的accept方法
        //s依次表示集合中的每一个数据
       /* coll.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });*/

        //lambda表达式
        coll.forEach(s -> System.out.println(s));
    }
}

6.2. List

6.2.1. 概述和特点【记忆】

  • List集合的概述
    • 有序集合,这里的有序指的是存取顺序
    • 用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
    • 与Set集合不同,列表通常允许重复的元素
  • List集合的特点
    • 存取有序
    • 可以重复
    • 索引

6.2.2. 特有方法【应用】

  • 方法介绍

    方法名 描述
    void add(int index,E element) 在此集合中的指定位置插入指定的元素
    E remove(int index) 删除指定索引处的元素,返回被删除的元素
    E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
    E get(int index) 返回指定索引处的元素
  • 示例代码

    java 复制代码
    package com.powernode.method;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Test {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");
    
            // get()
            String str = list.get(2);
            System.out.println(str);
    
            // remove()
            //在List集合中有两个删除的方法
            //第一个 删除指定的元素,返回值表示当前元素是否删除成功
            //第二个 删除指定索引的元素,返回值表示实际删除的元素
            String s = list.remove(1);
            System.out.println(s);
            System.out.println(list);
    
            // add()
            //原来位置上的元素往后挪一个索引.
            list.add(1, "123");
            System.out.println(list);
    
            // set()
            list.set(0, "qwe");
            System.out.println(list);
    
            /*
                ccc
                bbb
                [aaa, ccc]
                [aaa, 123, ccc]
                [qwe, 123, ccc]
             */
        }
    }

6.2.3. 五种遍历方式【应用】

  1. 迭代器
  2. 列表迭代器
  3. 增强for
  4. Lambda表达式
  5. 普通for循环

代码示例:

java 复制代码
package com.powernode.iterator;

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

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");

        // 1.迭代器
        /*Iterator<String> it = list.iterator();
             while(it.hasNext()){
                String str = it.next();
                System.out.println(str);
        }*/
        
        
        //2.增强for
        //下面的变量s,其实就是一个第三方的变量而已。
        //在循环的过程中,依次表示集合中的每一个元素
        /* for (String s : list) {
               System.out.println(s);
           }*/
        
        //3.Lambda表达式
        //forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
        //并把每一个元素传递给下面的accept方法
        //accept方法的形参s,依次表示集合中的每一个元素
        //list.forEach(s->System.out.println(s) );
        
        
        //4.普通for循环
        //size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素
//        for(int i = 0; i < list.size(); i++){
//            System.out.println(list.get(i));
//        }
        
        // 5.列表迭代器
        //获取一个列表迭代器的对象,里面的指针默认也是指向0索引的
        
        //额外添加了一个方法:在遍历的过程中,可以添加元素
        ListIterator<String> it = list.listIterator();
        while(it.hasNext()){
            String str = it.next();
            if("bbb".equals(str)){
                //qqq
                it.add("qqq");
            }
        }
        System.out.println(list);
    }
}

6.2.4. 细节点注意

List系列集合中的两个删除的方法

java 复制代码
1.直接删除元素
2.通过索引进行删除

代码示例:

java 复制代码
package com.powernode;

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

public class Remove {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();

        list.add(1);
        list.add(2);
        list.add(3);
        
        //2.删除元素
        //请问:此时删除的是1这个元素,还是1索引上的元素?
        //为什么?
        list.remove(1);
        System.out.println(list); // [1, 3], 即直接使用删除的是索引为1的元素
        
        
//        Integer i = Integer.valueOf(1);
//        list.remove(i);
//        System.out.println(list); // [2, 3], 即手动装箱删除的是值为1的元素
        
        
        //因为在调用方法的时候,如果方法出现了重载现象, 优先调用实参跟形参类型一致的那个方法。
    }
}

6.2.5. 数据结构

  • 栈和队列【记忆】

    • 栈结构

      ​ 先进后出

    • 队列结构

      ​ 先进先出

  • 数组和链表【记忆】

    • 数组结构

      ​ 查询快、增删慢

    • 链表结构

      ​ 查询慢、增删快

6.2.6. List集合的实现类ArrayList和LinkedList

  • 子类的特点【记忆】

    • ArrayList集合

      ​ 底层是数组结构实现,查询快、增删慢

    • LinkedList集合

      ​ 底层是链表结构实现,查询慢、增删快

  • LinkedList集合的特有功能【应用】

    • 特有方法

      方法名 说明
      public void addFirst(E e) 在该列表开头插入指定的元素
      public void addLast(E e) 将指定的元素追加 到此列表的末尾
      public E getFirst() 返回 此列表中的第一个元素
      public E getLast() 返回此列表中的最后一个元素
      public E removeFirst() 从此列表中删除返回第一个元素
      public E removeLast() 从此列表中删除并返回最后一个元素
  • 示例代码

    java 复制代码
    package com.powernode.method;
    
    import java.util.LinkedList;
    
    public class Test2 {
        public static void main(String[] args) {
            LinkedList<String> list = new LinkedList<>();
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");
            
            System.out.println(list.getFirst()); // aaa
            System.out.println(list.getLast()); // ccc
    
            list.addFirst("123");
            System.out.println(list); // [123, aaa, bbb, ccc]
    
            list.addLast("456");
            System.out.println(list);// [123, aaa, bbb, ccc, 456]
    
            String strF = list.removeFirst();
            System.out.println(strF); // 123
            System.out.println(list); // [aaa, bbb, ccc, 456]
    
            String strL = list.removeLast();
            System.out.println(strL); // 456
            System.out.println(list); // [aaa, bbb, ccc]
        }
    }

6.2.7. 源码分析

  • ArrayList源码分析:

核心步骤:

  1. 创建ArrayList对象的时候,他在底层先创建了一个长度为0的数组

    数组名字:elementDate,定义变量size。

    size 这个变量有两层含义:

    ①:元素的个数 ,也就是集合的长度

    ②:下一个 元素的存入位置

  2. 添加元素,添加完毕后,size++

扩容时机一:

  1. 存满 时候,会创建一个新的 数组,新数组的长度,是原来的1.5倍 ,也就是长度为15.再把所有的元素,全拷贝到新数组 中。如果继续添加 数据,这个长度为15的数组也满了,那么下次还会继续扩容,还是1.5倍。

扩容时机二:

  1. 一次性添加多个 数据,扩容1.5倍不够,怎么办呀?

    如果一次添加多个元素,1.5倍放不下,那么新创建数组的长度以实际为准

举个例子:

在一开始,如果默认的长度为10的数组已经装满了,在装满的情况下,我一次性要添加100个数据很显然,10扩容1.5倍,变成15,还是不够,

怎么办?

此时新数组的长度,就以实际情况为准,就是110

  • LinkedList源码分析:

底层是双向链表结构

核心步骤如下:

  1. 刚开始创建的时候,底层创建了两个变量 :一个记录头结点 first,一个记录尾结点 last,默认为null
  2. 添加第一个 元素时,底层创建一个结点 对象,first和last都 记录这个结点的地址值
  3. 添加第二个元素时,底层创建一个结点对象,第一个结点会记录第二个结点的地址值,last会记录新结点的地址值

具体分析过程可以参见视频讲解。

6.2.8. 迭代器源码分析

迭代器遍历相关的三个方法:

  • Iterator iterator() :获取一个迭代器对象

  • boolean hasNext() :判断当前指向的位置是否有元素

  • E next() :获取当前指向的元素并移动指针

6.3. 泛型

6.3.1. 概述

  • 泛型的介绍

    ​ 泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制

  • 泛型的好处

    1. 把运行时期的问题提前到了编译期间
    2. 避免了强制类型转换
  • 泛型的定义格式

    • <类型>: 指定一种类型的格式.尖括号里面可以任意书写 ,一般只写一个字母 .例如: <E> <T>
    • <类型1,类型2...>: 指定多种类型的格式,多种类型之间用逗号隔开 .例如: <E,T> <K,V>

6.4. Set

6.4.1. 概述和特点【应用】

  • 可以存储重复元素
  • 没有索引\^底层是红黑树实现,不能使用普通for循环遍历

6.4.2. 使用【应用】

存储字符串并遍历

java 复制代码
package com.powernode.use;

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

public class Test {
    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("ccc");
        set.add("aaa");
        set.add("aaa");
        set.add("bbb");

        // 遍历
//        for (int i = 0; i < set.size(); i++) {
//            //Set集合是没有索引的,所以不能使用通过索引获取元素的方法
//        }
        Iterator<String> it = set.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        for(String str : set){
            System.out.println(str);
        }
    }
}

6.4.3. TreeSet集合

  • 概述和特点【应用】

    • 不可以存储重复元素

    • 没有索引

    • 可以将元素按照规则进行排序

      • TreeSet():根据其元素的自然排序进行排序
      • TreeSet(Comparator comparator) :根据指定的比较器进行排序
  • 基本使用【应用】

存储Integer类型的整数并遍历

java 复制代码
package com.powernode.use;

import java.util.Iterator;
import java.util.TreeSet;

public class Test2 {
    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<Integer>();
        set.add(10);
        set.add(40);
        set.add(30);
        set.add(50);
        set.add(20);
        set.add(30);

        for(Integer i : set){
            System.out.print(i + " "); // 10 20 30 40 50
        }
        System.out.println();
    }
}

TreeSet 特有的方法

方法声明 功能描述
Object first() 返回集合第一个元素
Object last() 返回集合最后一个元素(修正:原文 "最后应该元素" 为笔误)
Object lower(Object o) 返回集合中小于给定元素的最大元素,不存在则返回 NULL
Object floor(Object o) 返回集合中小于或者等于给定元素的最大元素,不存在则返回 NULL
Object higher(Object o) 返回集合中大于给定元素的最小元素,不存在则返回 NULL
Object ceiling(Object o) 返回集合中大于或者等于给定元素的最小元素,不存在则返回 NULL
Object pollFirst() 移除并返回集合的第一个元素
Object pollLast() 移除并返回集合的最后一个元素

6.4.4. 自然排序Comparable的使用【应用】

  • 案例需求

    • 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
    • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
  • 实现步骤

    1. 使用无参构造创建TreeSet集合
      • 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
    2. 自定义的Student类实现Comparable接口
      • 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
    3. 重写接口中的compareTo方法
      • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
  • 代码实现

    学生类

    java 复制代码
    package com.powernode.comparable;
    
    public class Student implements Comparable<Student>{
        private String name;
        private int age;
    
        @Override
        public int compareTo(Student o) {
            //按照对象的年龄进行排序
            //主要判断条件: 按照年龄从小到大排序
            // this: 当前要添加的元素
            // o: 红黑树中已经存在的元素
            int ret =  this.age - o.age; // 负数则存左子树, 正数右子树, 0则舍弃(这里则要进行次要条件的判断)
            //次要判断条件: 年龄相同时,按照姓名的字母顺序排序
            return (ret == 0) ? this.name.compareTo(o.name) : ret;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        public Student(){
    
        }
        public Student(String name, int age){
            this.name = name;
            this.age = age;
        }
    
        String getName(){
            return this.name;
        }
        void setName(String name){
            this.name = name;
        }
        int getAge(){
            return this.age;
        }
        void setAge(int age){
            this.age = age;
        }
    }

    测试类

    java 复制代码
    package com.powernode.comparable;
    
    import java.util.TreeSet;
    
    public class Test {
        public static void main(String[] args) {
            TreeSet<Student> set = new TreeSet<>();
            Student s1 = new Student("zhangsan",28);
            Student s2 = new Student("lisi",27);
            Student s3 = new Student("wangwu",29);
            Student s4 = new Student("zhaoliu",28);
            Student s5 = new Student("qianqi",30);
    
            set.add(s1);
            set.add(s2);
            set.add(s3);
            set.add(s4);
            set.add(s5);
    
            for(Student st : set){
                System.out.println(st);
            }
        }
    }

6.4.5. 比较器排序Comparator的使用【应用】

  • 案例需求

    • 存储老师对象并遍历,创建TreeSet集合使用带参构造方法
    • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
  • 实现步骤

    • 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
    • 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
    • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
  • 代码实现

    老师类

    java 复制代码
    public class Teacher {
        private String name;
        private int age;
    
        public Teacher() {
        }
    
        public Teacher(String name, int age) {
            this.name = name;
            this.age = 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;
        }
    
        @Override
        public String toString() {
            return "Teacher{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

    测试类

    java 复制代码
    package com.powernode.comparator;
    
    import java.util.Comparator;
    import java.util.TreeSet;
    
    public class Test {
        public static void main(String[] args) {
            TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>(){
                // t1: 当前要存入的元素
                // t2: 已经存入红黑树的元素
                @Override
                public int compare(Teacher t1, Teacher t2) {
                    // 主要条件
                    int ret = t1.getAge() - t2.getAge();
                    // 次要条件
                    return ret == 0 ? t1.getName().compareTo(t2.getName()) : ret;
                }
            }); // 传递Comparator的实现类对象
            //创建老师对象
            Teacher t1 = new Teacher("zhangsan",23);
            Teacher t2 = new Teacher("lisi",22);
            Teacher t3 = new Teacher("wangwu",24);
            Teacher t4 = new Teacher("zhaoliu",24);
            //把老师添加到集合
            ts.add(t1);
            ts.add(t2);
            ts.add(t3);
            ts.add(t4);
            //遍历集合
            for (Teacher teacher : ts) {
                System.out.println(teacher);
            }
        }
    }

6.4.6. 两种比较方式总结【理解】

  • 两种比较方式小结
    • 自然排序 : 自定义类实现Comparable接口 , 重写compareTo方法,根据返回值进行排序
    • 比较器排序 : 创建TreeSet对象的时候传递Comparator的实现类对象 , 重写compare方法, 根据返回值进行排序
    • 在使用的时候,默认使用 自然排序,当自然排序不满足 现在的需求时 ,必须使用比较器排序
    • 两种方式同时存在的时候, 要以第二种方式为准, 即以比较器排序为准
  • 两种方式中关于返回值的规则(二叉排序树规则)
    • 如果返回值为负数,表示当前存入的元素是较小值,存左边
    • 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
    • 如果返回值为正数,表示当前存入的元素是较大值,存右边

6.4.7. 二叉树【理解】

  • 二叉树的特点

    • 二叉树中,任意一个节点的度要小于等于2
      • 节点: 在树结构中,每一个元素称之为节点
      • 度: 每一个节点的子节点数量称之为度

6.4.8. 二叉查找树【理解】

  • 二叉查找树的特点

    • 二叉查找树,又称二叉排序树或者二叉搜索树
    • 每一个节点上最多有两个子节点
    • 左子树上所有节点的值都小于根节点的值
    • 右子树上所有节点的值都大于根节点的值
  • 二叉查找树添加节点规则

    • 小的存左边
    • 大的存右边
    • 一样的不存

6.4.9. 平衡二叉树【理解】

  • 平衡二叉树的特点

    • 二叉树左右两个子树的高度差不超过1
    • 任意节点的左右两个子树都是一颗平衡二叉树
  • 平衡二叉树旋转

    • 旋转触发时机

      • 当添加一个节点之后,该树不再是一颗平衡二叉树
    • 左旋

      • 就是将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点
    • 右旋

      • 就是将根节点的左侧往右拉,左子节点变成了新的父节点,并把多余的右子节点出让,给已经降级根节点当左子节点
  • 平衡二叉树旋转的四种情况

    • 左左

      • 左左: 当根节点左子树的左子树有节点插入,导致二叉树不平衡

      • 如何旋转: 直接对整体进行右旋即可

    • 左右

      • 左右: 当根节点左子树的右子树有节点插入,导致二叉树不平衡

      • 如何旋转: 先在左子树对应的节点位置进行左旋,在对整体进行右旋

    • 右右

      • 右右: 当根节点右子树的右子树有节点插入,导致二叉树不平衡

      • 如何旋转: 直接对整体进行左旋即可

    • 右左

      • 右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡

      • 如何旋转: 先在右子树对应的节点位置进行右旋,在对整体进行左旋

6.4.10. 红黑树【理解】

  • 红黑树的特点

    • 平衡二叉B树
    • 每一个节点可以是红或者黑
    • 红黑树不是高度平衡的,它的平衡是通过"自己的红黑规则"进行实现的
  • 红黑树的红黑规则有哪些

    1. 每一个节点或是红色的,或者是黑色的

    2. 根节点必须是黑色

    3. 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的

    4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连 的情况)

    5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

  • 红黑树添加节点的默认颜色

    • 添加节点时,默认为红色,效率高
  • 红黑树添加节点后如何保持红黑规则

    • 根节点位置
      • 直接变为黑色
    • 非根节点位置
      • 父节点为黑色
        • 不需要任何操作,默认红色即可
      • 父节点为红色
        • 叔叔节点为红色
          1. 将"父节点"设为黑色,将"叔叔节点"设为黑色
          2. 将"祖父节点"设为红色
          3. 如果"祖父节点"为根节点,则将根节点再次变成黑色
        • 叔叔节点为黑色
          1. 将"父节点"设为黑色
          2. 将"祖父节点"设为红色
          3. 以"祖父节点"为支点进行旋转

6.4.11. HashSet

概述和特点【应用】

  • 底层数据结构是哈希表
  • 存取无序
  • 可以存储重复元素
  • 没有索引,不能使用普通for循环遍历

存储字符串并遍历

java 复制代码
package com.powernode.hashset;

import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        //创建集合对象
        HashSet<String> set = new HashSet<String>();

        //添加元素
        set.add("hello");
        set.add("world");
        set.add("java");
        //不包含重复元素的集合
        set.add("world");

        for(String str : set){
            System.out.println(str);
        }
        /*
            world
            java
            hello
         */
    }
}

哈希值【理解】

  • 哈希值简介

    ​ 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值

  • 如何获取哈希值

    ​ Object类中的public int hashCode():返回对象的哈希码值

  • 哈希值的特点

    • 同一个对象 多次调用hashCode()方法返回的哈希值是相同的
    • 默认情况下,不同对象 的哈希值是不同的 。而重写 hashCode()方法,可以实现 让不同对象的哈希值相同

HashSet 之所以能确保不出现重复的元素,是因为它在存入无序时做了很多工作

  • 当调用 HashSet 的add()方法存入元素时,首先调用hashCode()方法获得 该元素的哈希值 ,然后根据哈希值计算存储位置\^int index = (数组长度 - 1) \& 哈希值
  • 如果该位置上没有元素 ,则直接 将元素存入
  • 如果该位置上有元素 ,则调用 equals()方法 将要存入的元素和该位置上的元素进行比较,根据返回结果确定是否存人元素。
  • 如果返回的结果为 false ,就将该元素存入 集合, 形成链表或红黑树;
    • JDK8之后, 新元素直接挂在老元素下面
  • 如果返回的结果为 true ,则说明有重复元素,就将要存人的重复元素舍弃

哈希表结构【理解】

  • JDK1.8以前

    ​ 数组 + 链表

  • JDK1.8以后

    • 节点个数少于等于8个

      数组 + 链表

    • 节点个数多于8个

      ​ 数组 + 红黑树

HashSet集合存储学生对象并遍历【应用】

  • 案例需求

    • 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
    • 要求:学生对象的成员变量值相同,我们就认为是同一个对象
  • 代码实现

    学生类

    java 复制代码
    public class Student {
        private String name;
        private int age;
    
        public Student() {
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = 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;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
    
            Student student = (Student) o;
    
            if (age != student.age) return false;
            return name != null ? name.equals(student.name) : student.name == null;
        }
    
        @Override
        public int hashCode() {
            int result = name != null ? name.hashCode() : 0;
            result = 31 * result + age;
            return result;
        }
    }

    测试类

    java 复制代码
    public class HashSetDemo02 {
        public static void main(String[] args) {
            //创建HashSet集合对象
            HashSet<Student> hs = new HashSet<Student>();
    
            //创建学生对象
            Student s1 = new Student("林青霞", 30);
            Student s2 = new Student("张曼玉", 35);
            Student s3 = new Student("王祖贤", 33);
    
            Student s4 = new Student("王祖贤", 33);
    
            //把学生添加到集合
            hs.add(s1);
            hs.add(s2);
            hs.add(s3);
            hs.add(s4);
    
            //遍历集合(增强for)
            for (Student s : hs) {
                System.out.println(s.getName() + "," + s.getAge());
            }
        }
    }
  • 总结

    ​ HashSet集合存储自定义类型元素,要想实现元素的唯一,要求必须重写hashCode方法和equals方法

6.4.12. LinkedHashSet

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

下面通过一个案例学习LinkedHashSet 类的用法:

cpp 复制代码
package com.powernode.linkedhashset;

import java.util.LinkedHashSet;

public class Test {
    public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet<>();
        set.add("张三");
        set.add("李四");
        set.add("王五");
        System.out.println(set);  // [张三, 李四, 王五]
    }
}

从上面可知:元素遍历顺序 和 存入顺序 是一致的

6.5. Map

6.5.1. 概述和特点【理解】

  • Map集合概述

    java 复制代码
    interface Map<K,V>  K:键的类型;V:值的类型
  • Map集合的特点

    • 双列 集合, 一个键 对应一个值
    • 可以重复 , 可以重复
  • Map集合的基本使用

    java 复制代码
    package com.powernode.use;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class Test {
        public static void main(String[] args) {
            //创建集合对象
            Map<String,String> map = new HashMap<String,String>();
    
            //V put(K key, V value) 将指定的值与该映射中的指定键相关联
            map.put("itheima001","林青霞");
            map.put("itheima002","张曼玉");
            map.put("itheima003","王祖贤");
            map.put("itheima003","柳岩");
    
            //输出集合对象
            System.out.println(map);
        }
    }

6.5.2. 基本功能【应用】

  • 方法介绍

    方法名 说明
    V put(K key,V value) 添加元素
    V remove(Object key) 根据键删除键值对元素
    void clear() 移除所有的键值对元素
    boolean containsKey(Object key) 判断集合是否 包含指定的键
    boolean containsValue(Object value) 判断集合是否 包含指定的值
    boolean isEmpty() 判断集合是否为空
    int size() 集合的长度,也就是集合中键值对的个数
  • 示例代码

    java 复制代码
    package com.powernode.use;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class Test2 {
        public static void main(String[] args) {
            //创建集合对象
            Map<String,String> map = new HashMap<String,String>();
    
            //V put(K key,V value):添加元素
            map.put("张无忌","赵敏");
            map.put("郭靖","黄蓉");
            map.put("杨过","小龙女");
    
    //        V remove(Object key):根据键删除键值对元素
    //        System.out.println(map.remove("郭靖"));
    //        System.out.println(map.remove("郭襄"));
    
            //void clear():移除所有的键值对元素
    //        map.clear();
    
            //boolean containsKey(Object key):判断集合是否包含指定的键
    //        System.out.println(map.containsKey("郭靖"));
    //        System.out.println(map.containsKey("郭襄"));
    
            //boolean isEmpty():判断集合是否为空
    //        System.out.println(map.isEmpty());
    
            //int size():集合的长度,也就是集合中键值对的个数
            System.out.println(map.size());
    
            //输出集合对象
            System.out.println(map);
        }
    }

6.5.3. 获取功能【应用】

  • 方法介绍

    方法名 说明
    V get(Object key) 根据取值
    Set keySet() 获取所有键的集合
    Collection values() 获取所有值的集合
    Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合
  • 示例代码

    java 复制代码
    package com.powernode.use;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    public class Test3 {
        public static void main(String[] args) {
            //创建集合对象
            Map<String, String> map = new HashMap<String, String>();
    
            //添加元素
            map.put("张无忌", "赵敏");
            map.put("郭靖", "黄蓉");
            map.put("杨过", "小龙女");
    
            //V get(Object key):根据键获取值
            System.out.println(map.get("张无忌"));
            System.out.println(map.get("张三丰"));
    
            //Set<K> keySet():获取所有键的集合
            Set<String> keySet = map.keySet();
            for(String key : keySet) {
                System.out.println(key);
            }
    
            //Collection<V> values():获取所有值的集合
            Collection<String> values = map.values();
            for(String value : values) {
                System.out.println(value);
            }
        }
    }

6.5.4. 遍历

方式1【应用】

  • 遍历思路

    • 我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
      • 把所有的丈夫给集中起来
      • 遍历丈夫的集合,获取到每一个丈夫
      • 根据丈夫去找对应的妻子
  • 步骤分析

    • 获取所有键的集合。用keySet()方法实现
    • 遍历键的集合,获取到每一个键。用增强for实现
    • 根据键去找值。用get(Object key)方法实现
  • 代码实现

    java 复制代码
    package com.powernode.use;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    public class Test4 {
        public static void main(String[] args) {
            //创建集合对象
            Map<String, String> map = new HashMap<String, String>();
    
            //添加元素
            map.put("张无忌", "赵敏");
            map.put("郭靖", "黄蓉");
            map.put("杨过", "小龙女");
    
            // 遍历方式1
            Set<String> keySet = map.keySet();
            //遍历键的集合,获取到每一个键。用增强for实现
            for(String key : keySet){
                //根据键去找值。用get(Object key)方法实现
                System.out.println(key + ": " + map.get(key));
            }
        }
    }

方式2【应用】

  • 遍历思路

    • 我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
      • 获取所有结婚证的集合
      • 遍历结婚证的集合,得到每一个结婚证
      • 根据结婚证获取丈夫和妻子
  • 步骤分析

    • 获取所有键值对对象的集合
      • Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
    • 遍历键值对对象的集合,得到每一个键值对对象
      • 用增强for实现,得到每一个Map.Entry
    • 根据键值对对象获取键和值
      • 用getKey()得到键
      • 用getValue()得到值
  • 代码实现

    java 复制代码
    package com.powernode.use;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    public class Test4 {
        public static void main(String[] args) {
            //创建集合对象
            Map<String, String> map = new HashMap<String, String>();
    
            //添加元素
            map.put("张无忌", "赵敏");
            map.put("郭靖", "黄蓉");
            map.put("杨过", "小龙女");
    
            // 遍历方式2
            //获取所有键值对对象的集合
            // Set<Map.Entry<K,V>>   entrySet()
            Set<Map.Entry<String, String>> entrySet = map.entrySet();
            for(Map.Entry<String, String> entry : entrySet){
                System.out.println(entry.getKey() + ": " + entry.getValue());
            } // getKey()和getValues是entrySet()特有的
        }
    }

6.5.5.HashMap集合

概述和特点【理解】

  • HashMap底层是哈希表结构的
  • 依赖hashCode()方法和 equals()方法保证键的唯一
  • 如果键要存储的是自定义对象需要重写hashCode()和equals()方法

应用案例【应用】

  • 案例需求

    • 创建一个HashMap集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。
    • 要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象
  • 代码实现

    学生类

    java 复制代码
    package com.powernode.hashmap;
    
    import java.util.Objects;
    
    public class Student {
        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 Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return age == student.age && Objects.equals(name, student.name);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

    测试类

    java 复制代码
    package com.powernode.hashmap;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    public class Test {
        public static void main(String[] args) {
            HashMap<Student, String> map = new HashMap<Student, String>();
    
            //创建学生对象
            Student s1 = new Student("林青霞", 30);
            Student s2 = new Student("张曼玉", 35);
            Student s3 = new Student("王祖贤", 33);
            Student s4 = new Student("王祖贤", 33);
    
            //把学生添加到集合
            map.put(s1, "西安");
            map.put(s2, "武汉");
            map.put(s3, "郑州");
            map.put(s4, "北京");
    
            //遍历集合
            Set<Student> keySet = map.keySet();
            for(Student stu : keySet){
                System.out.println(stu + ": " + map.get(stu));
            }
        }
    }

6.5.6.TreeMap集合

概述和特点【理解】

  • TreeMap底层是红黑树结构
  • 依赖自然排序 或者比较器排序 ,对 进行排序
  • 如果键存储的是自定义对象 , 需要实现Comparable接口 或者在创建TreeMap对象时候给出比较器排序规则

应用案例【应用】

  • 案例需求

    • 创建一个TreeMap集合, 键是学生对象(Student), 值是籍贯(String), 学生属性姓名和年龄, 按照年龄进行排序并遍历
    • 要求按照学生的年龄进行排序, 如果年龄相同则按照姓名进行排序
  • 代码实现

    学生类

    java 复制代码
    package com.powernode.treemap;
    
    public class Student implements Comparable<Student>{
        private String name;
        private int age;
    
        public Student() {
    
        }
        public Student(String name, int age) {
            this.name = name;
            this.age = 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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        @Override
        public int compareTo(Student o) {
            int ret = this.getAge() - o.getAge();
            return ret == 0 ? this.getName().compareTo(o.getName()) : ret;
        }
    }

    测试类

    java 复制代码
    package com.powernode.treemap;
    
    import java.util.Set;
    import java.util.TreeMap;
    
    public class Test {
        public static void main(String[] args) {
            TreeMap<Student, String> map = new TreeMap<Student, String>();
    
            Student s1 = new Student("zhangsan", 15);
            Student s2 = new Student("lisi", 16);
            Student s3 = new Student("wangwu", 19);
            Student s4 = new Student("zhaoliu", 15);
    
            map.put(s1, "安阳");
            map.put(s2, "杭州");
            map.put(s3, "广州");
            map.put(s4, "深圳");
    
            Set<Student> keySet = map.keySet();
            for(Student stu : keySet){
                System.out.println(stu + ": " + map.get(stu));
            }
        }
    }

6.6. 可变参数

JDK1.5 之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致 ,我们可以对其简化.

格式:

复制代码
修饰符 返回值类型 方法名(参数类型... 形参名){  }

底层:

​ 其实就是一个数组

好处:

​ 在传递数据的时候,省的我们自己创建数组并添加元素了,JDK底层帮我们自动创建数组并添加元素了

代码演示:

java 复制代码
  public class ChangeArgs {
    public static void main(String[] args) {
        int sum = getSum(6, 7, 2, 12, 2121);
        System.out.println(sum);
    }
    
    public static int getSum(int... arr) {
   		int sum = 0;
   	     for (int a : arr) {
         sum += a;
        }
   		 return sum;
    }
}

注意:

​ 1.一个方法只能有一个可变参数

​ 2.如果方法中有多个参数,可变参数要放到最后。

应用场景: Collections

​ 在Collections中也提供了添加一些元素方法:

public static <T> boolean addAll(Collection<T> c, T... elements) :往集合中添加一些元素。

代码演示:

java 复制代码
public class CollectionsDemo {
	public static void main(String[] args) {
      ArrayList<Integer> list = new ArrayList<Integer>();
      //原来写法
      //list.add(12);
      //list.add(14);
      //list.add(15);
      //list.add(1000);
      //采用工具类 完成 往集合中添加元素  
      Collections.addAll(list, 5, 222, 1,2);
      System.out.println(list);
}

6.7. Collections类

常用功能

  • java.utils.Collections集合工具类,用来对集合进行操作。

    常用方法如下:

  • public static void shuffle(List<?> list) :打乱 集合顺序

  • public static <T> void sort(List<T> list):将集合中元素按照默认规则排序

  • public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序

代码演示:

java 复制代码
package com.powernode.collections;

import java.util.ArrayList;
import java.util.Collections;

public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();

        list.add(100);
        list.add(300);
        list.add(200);
        list.add(50);
        //排序方法
        Collections.sort(list);
        System.out.println(list);
    }
}

// 结果:
// [50,100, 200, 300]

我们的集合按照默认的自然顺序进行了排列,如果想要指定顺序那该怎么办呢?

Comparator比较器

  • 创建一个学生类,存储到ArrayList集合中完成指定排序操作。

Student 类

java 复制代码
public class Student{
    private String name;
    private int age;
	//构造方法
    //get/set
 	//toString
}

测试类:

java 复制代码
package com.powernode.collections;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Test2 {
    public static void main(String[] args) {
        // 创建四个学生对象 存储到集合中
        ArrayList<Student> list = new ArrayList<Student>();

        list.add(new Student("rose",18));
        list.add(new Student("jack",16));
        list.add(new Student("abc",20));

        Collections.sort(list, new Comparator<Student>(){
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge();
            }
        });


        for (Student student : list) {
            System.out.println(student);
        }
    }
    /*
        Student{name='jack', age=16}
        Student{name='rose', age=18}
        Student{name='abc', age=20}
     */
}

6.8. 综合练习

练习1:随机点名器

需求:班级里有N个学生,实现随机点名器

代码实现:

java 复制代码
public class Test1 {
    public static void main(String[] args) {
        /* 班级里有N个学生,学生属性:姓名,年龄,性别。
        实现随机点名器。*/


        //1.定义集合
        ArrayList<String> list = new ArrayList<>();
        //2.添加数据
        Collections.addAll(list,"范闲","范建","范统","杜子腾","杜琦燕","宋合泛","侯笼藤","朱益群","朱穆朗玛峰","袁明媛");
        //3.随机点名
        /* Random r = new Random();
        int index = r.nextInt(list.size());
        String name = list.get(index);
        System.out.println(name);*/

        //打乱
        Collections.shuffle(list);

        String name = list.get(0);
        System.out.println(name);


    }
}

练习2:带概率的随机

需求:

​ 班级里有N个学生

​ 要求在随机的时候,70%的概率随机到男生,30%的概率随机到女生

代码实现:

java 复制代码
public class Test2 {
    public static void main(String[] args) {
        /* 班级里有N个学生
        要求:
        70%的概率随机到男生
        30%的概率随机到女生

        "范闲","范建","范统","杜子腾","宋合泛","侯笼藤","朱益群","朱穆朗玛峰",
        "杜琦燕","袁明媛","李猜","田蜜蜜",
        */
        //1.创建集合
        ArrayList<Integer> list = new ArrayList<>();
        //2.添加数据
        Collections.addAll(list,1,1,1,1,1,1,1);
        Collections.addAll(list,0,0,0);
        //3.打乱集合中的数据
        Collections.shuffle(list);
        //4.从list集合中随机抽取0或者1
        Random r = new Random();
        int index = r.nextInt(list.size());
        int number = list.get(index);
        System.out.println(number);
        //5.创建两个集合分别存储男生和女生的名字
        ArrayList<String> boyList = new ArrayList<>();
        ArrayList<String> girlList = new ArrayList<>();

        Collections.addAll(boyList,"范闲","范建","范统","杜子腾","宋合泛","侯笼藤","朱益群","朱穆朗玛峰");
        Collections.addAll(girlList,"杜琦燕","袁明媛","李猜","田蜜蜜");

        //6.判断此时是从boyList里面抽取还是从girlList里面抽取
        if(number == 1){
            //boyList
            int boyIndex = r.nextInt(boyList.size());
            String name = boyList.get(boyIndex);
            System.out.println(name);
        }else{
            //girlList
            int girlIndex = r.nextInt(girlList.size());
            String name = girlList.get(girlIndex);
            System.out.println(name);
        }


    }
}

练习3:随机不重复

需求:

​ 班级里有N个学生,被点到的学生不会再被点到。但是如果班级中所有的学生都点完了, 需要重新开启第二轮点名。

代码实现:

java 复制代码
public class Test3 {
    public static void main(String[] args) {
       /* 班级里有5个学生
        要求:
        被点到的学生不会再被点到。
        但是如果班级中所有的学生都点完了,需要重新开启第二轮点名。*/


        //1.定义集合
        ArrayList<String> list1 = new ArrayList<>();
        //2.添加数据
        Collections.addAll(list1, "范闲", "范建", "范统", "杜子腾", "杜琦燕", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰", "袁明媛");
        //创建一个临时的集合,用来存已经被点到学生的名字
        ArrayList<String> list2 = new ArrayList<>();
        //外循环:表示轮数
        for (int i = 1; i <= 10; i++) {
            System.out.println("=========第" + i + "轮点名开始了======================");
            //3.获取集合的长度
            int count = list1.size();
            //4.随机点名
            Random r = new Random();
            //内循环:每一轮中随机循环抽取的过程
            for (int j = 0; j < count; j++) {
                int index = r.nextInt(list1.size());
                String name = list1.remove(index);
                list2.add(name);
                System.out.println(name);
            }
            //此时表示一轮点名结束
            //list1 空了 list2 10个学生的名字
            list1.addAll(list2);
            list2.clear();

        }
    }
}

练习4:集合的嵌套

需求:

​ 定义一个Map集合,键用表示省份名称province,值表示市city,但是市会有多个。

添加完毕后,遍历结果格式如下:

​ 江苏省 = 南京市,扬州市,苏州市,无锡市,常州市

复制代码
湖北省 = 武汉市,孝感市,十堰市,宜昌市,鄂州市

河北省 = 石家庄市,唐山市,邢台市,保定市,张家口市

代码实现:

java 复制代码
package com.powernode.qiantao;

import java.util.*;

public class Test {
    public static void main(String[] args) {
                /* 需求
        定义一个Map集合,键用表示省份名称province,值表示市city,但是市会有多个。
        添加完毕后,遍历结果格式如下:
                江苏省 = 南京市,扬州市,苏州市,无锡市,常州市
                湖北省 = 武汉市,孝感市,十堰市,宜昌市,鄂州市
                河北省 = 石家庄市,唐山市,邢台市,保定市,张家口市*/

        HashMap<String, ArrayList<String>> hm = new HashMap<>();
        //2.创建单列集合存储市
        ArrayList<String> city1 = new ArrayList<>();
        city1.add("南京市");
        city1.add("扬州市");
        city1.add("苏州市");
        city1.add("无锡市");
        city1.add("常州市");

        ArrayList<String> city2 = new ArrayList<>();
        city2.add("武汉市");
        city2.add("孝感市");
        city2.add("十堰市");
        city2.add("宜昌市");
        city2.add("鄂州市");

        ArrayList<String> city3 = new ArrayList<>();
        city3.add("石家庄市");
        city3.add("唐山市");
        city3.add("邢台市");
        city3.add("保定市");
        city3.add("张家口市");

        //3.把省份和多个市添加到map集合
        hm.put("江苏省",city1);
        hm.put("湖北省",city2);
        hm.put("河北省",city3);

        Set<Map.Entry<String, ArrayList<String>>> entries = hm.entrySet();
        for (Map.Entry<String, ArrayList<String>> entry : entries) {
            //entry依次表示每一个键值对对象
            String key = entry.getKey();
            ArrayList<String> value = entry.getValue();
            StringJoiner sj = new StringJoiner(", ","","");
            for (String city : value) {
                sj.add(city);
            }
            System.out.println(key + " = " + sj);

        }
    }
}

练习5: 斗地主发牌

案例介绍

按照斗地主的规则,完成洗牌发牌的动作。

具体规则:

使用54张牌打乱顺序,三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。

案例分析

  • 准备牌:

    牌可以设计为一个ArrayList,每个字符串为一张牌。

    每张牌由花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。

    牌由Collections类的shuffle方法进行随机排序。

  • 发牌

    将每个人以及底牌设计为ArrayList,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。

  • 看牌

    直接打印每个集合。

代码实现

java 复制代码
public class App {
    public static void main(String[] args) {
      /*
        完成控制台版的三步:
          准备牌
          洗牌
          发牌
       */
        //从程序的主入口开启斗地主游戏
        new PokerGame();
    }
}

public class PokerGame {
    //牌盒
    //♥3 ♣3
    static ArrayList<String> list = new ArrayList<>();

    //静态代码块
    //特点:随着类的加载而在加载的,而且只执行一次。
    static {
        //准备牌
        // "♦", "♣", "♥", "♠"
        // "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"
        String[] color = {"♦", "♣", "♥", "♠" };
        String[] number = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};

        for (String c : color) {
            //c依次表示每一种花色
            for (String n : number) {
                //n 依次表示每一个数字
                list.add(c + n);
            }
        }
        list.add("小王");
        list.add("大王");
    }

    public PokerGame(){
        //洗牌
        Collections.shuffle(list);

        //发牌
        ArrayList<String> lord = new ArrayList<>();
        ArrayList<String> player1 = new ArrayList<>();
        ArrayList<String> player2 = new ArrayList<>();
        ArrayList<String> player3 = new ArrayList<>();

        //遍历牌盒得到每一张牌
        for (int i = 0; i < list.size(); i++) {
            //i:索引
            String poker = list.get(i);
            if(i <= 2){
                lord.add(poker);
                continue;
            }

            //给三个玩家轮流发牌
            if(i % 3 == 0){
                player1.add(poker);
            }else if(i % 3 == 1){
                player2.add(poker);
            }else{
                player3.add(poker);
            }
        }
        //看牌
        lookPoker("底牌",lord);
        lookPoker("钢脑壳",player1);
        lookPoker("大帅比",player2);
        lookPoker("蛋筒",player3);

    }

    /*
    * 参数一:玩家的名字
    * 参数二:每位玩家的牌
    * */
    public void lookPoker(String name, ArrayList<String> list){
        System.out.print(name + ": ");
        for (String poker : list) {
            System.out.print(poker + " ");
        }
        System.out.println();
    }
}

排序(第一种排序方式)

java 复制代码
public class App {
    public static void main(String[] args) {
      /*
        完成控制台版的四步:
          准备牌
          洗牌
          发牌
          排序

       */

		//从程序的主入口开启斗地主游戏
        new PokerGame();
    }
}


public class PokerGame {
    //牌盒 Map
    //此时我们只要把牌跟序号产生对应关系就可以了,不需要按照序号进行排序,所以只要HashMap就可以了
    static HashMap<Integer, String> hm = new HashMap<>();
    static ArrayList<Integer> list = new ArrayList<>();

    static {
        String[] color = {"♦", "♣", "♥", "♠"};
        String[] number = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};

        //序号
        int serialNumber = 1;
        //细节
        for (String n : number) {
            //依次表示每一个数字
            for (String c : color) {
                //依次表示每一个花色
                hm.put(serialNumber, c + n);
                list.add(serialNumber);
                serialNumber++;
            }
        }

        hm.put(serialNumber, "小王");
        list.add(serialNumber);
        serialNumber++;
        hm.put(serialNumber, "大王");
        list.add(serialNumber);

    }

    public PokerGame() {
        //洗牌
        Collections.shuffle(list);

        //发牌
        TreeSet<Integer> lord = new TreeSet<>();
        TreeSet<Integer> player1 = new TreeSet<>();
        TreeSet<Integer> player2 = new TreeSet<>();
        TreeSet<Integer> player3 = new TreeSet<>();

        for (int i = 0; i < list.size(); i++) {
            //i :依次表示集合中的每一个索引
            //list.get(i)元素:牌的序号
            int serialNumber = list.get(i);

            if(i <= 2){
                lord.add(serialNumber);
                continue;
            }

            if(i % 3 == 0){
                player1.add(serialNumber);
            }else if(i % 3 == 1){
                player2.add(serialNumber);
            }else{
                player3.add(serialNumber);
            }
        }


        //看牌
        lookPoker("底牌",lord);
        lookPoker("钢脑壳",player1);
        lookPoker("大帅比",player2);
        lookPoker("蛋筒",player3);

    }

    /*
    * 参数一:玩家的名字
    * 参数二:牌的序号
    * */
    public void lookPoker(String name, TreeSet<Integer> ts){
        System.out.print(name + ": ");
        //遍历TreeSet集合得到每一个序号,再拿着序号到Map集合中去找真正的牌
        for (int serialNumber : ts) {
            String poker = hm.get(serialNumber);
            System.out.print(poker + " ");
        }
        System.out.println();
    }
}

排序(第二种排序方式)

java 复制代码
public class App {
    public static void main(String[] args) {
        new PokerGame();
    }
}


public class PokerGame {
    //牌盒
    static ArrayList<String> list = new ArrayList<>();

    //创建一个集合,用来添加牌的价值
    static HashMap<String, Integer> hm = new HashMap<>();

    static {
        //准备牌
        String[] color = {"♦", "♣", "♥", "♠"};
        String[] number = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};

        for (String c : color) {
            for (String n : number) {
                list.add(c + n);
            }
        }
        list.add(" 小王");
        list.add(" 大王");


        //指定牌的价值
        //牌上的数字到Map集合中判断是否存在
        //存在,获取价值
        //不存在,本身的数字就是价值
        hm.put("J", 11);
        hm.put("Q", 12);
        hm.put("K", 13);
        hm.put("A", 14);
        hm.put("2", 15);
        hm.put("小王", 50);
        hm.put("大王", 100);


    }

    public PokerGame() {
        //洗牌
        Collections.shuffle(list);

        //发牌
        ArrayList<String> lord = new ArrayList<>();
        ArrayList<String> player1 = new ArrayList<>();
        ArrayList<String> player2 = new ArrayList<>();
        ArrayList<String> player3 = new ArrayList<>();

        for (int i = 0; i < list.size(); i++) {
            String poker = list.get(i);
            //发底牌
            if (i <= 2) {
                lord.add(poker);
                continue;
            }

            //给三个玩家轮流发牌
            if (i % 3 == 0) {
                player1.add(poker);
            } else if (i % 3 == 1) {
                player2.add(poker);
            } else {
                player3.add(poker);
            }
        }


        //排序
        order(lord);
        order(player1);
        order(player2);
        order(player3);


        //看牌
        lookPoker("底牌",lord);
        lookPoker("钢脑壳",player1);
        lookPoker("大帅比",player2);
        lookPoker("蛋筒",player3);
    }


    /*
     * 参数一:玩家的名字
     * 参数二:每位玩家的牌
     * */
    public void lookPoker(String name, ArrayList<String> list){
        System.out.print(name + ": ");
        for (String poker : list) {
            System.out.print(poker + " ");
        }
        System.out.println();

    }


    //利用牌的价值进行排序
    //参数:集合
    //♥5 ♥3 ♥6 ♥7 ♥9
    public void order(ArrayList<String> list){
        Collections.sort(list, new Comparator<String>() {
            //Array.sort (插入排序 + 二分查找)
            @Override
            public int compare(String o1, String o2) {
                //o1:表示当前要插入到有序序列中的牌
                //o2:表示已经在有序序列中存在的牌

                //负数:o1小 插入到前面
                //正数:o1大 插入到后面
                //0:o1的数字跟o2的数字是一样的,需要按照花色再次排序

                //1.计算o1的花色和价值   大王
                String color1 = o1.substring(0, 1);
                int value1 = getValue(o1);

                //2.计算o2的花色和价值
                String color2 = o2.substring(0, 1);
                int value2 = getValue(o2);

                //3.比较o1和o2的价值    ♥3  ♠3
                int i = value1 - value2;
                return i == 0 ? color1.compareTo(color2) : i;

            }
        });
    }

    //计算牌的价值
    //参数:牌
    //返回值:价值
    public int getValue(String poker){//♥3
        //获取牌上的数字
        String number = poker.substring(1);//把这里截取出来的结果,让这个结果再Map集合中存在 " 大王"
        //拿着数字到map集合中判断是否存在
        if(hm.containsKey(number)){
            //存在,获取价值
            return hm.get(number);
        }else{
            //不存在,类型转换
            return Integer.parseInt(number);
        }
    }
}

6.9. 不可变集合

什么是不可变集合

​ 是一个长度不可变内容无法修改的集合

使用场景

​ 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。

​ 当集合对象被不可信的库调用时,不可变形式是安全的。

简单理解:

​ 不想让别人修改集合中的内容

比如说:

1,斗地主的54张牌,是不能添加,不能删除,不能修改的

2,斗地主的打牌规则:单张,对子,三张,顺子等,也是不能修改的

3,用代码获取的操作系统硬件信息,也是不能被修改的

1.3 不可变集合分类

  • 不可变的list集合
  • 不可变的set集合
  • 不可变的map集合

1.4 不可变的list集合

java 复制代码
package com.powernode.list;

import java.util.List;

public class Test {
    public static void main(String[] args) {
         /*
            创建不可变的List集合
            "张三", "李四", "王五", "赵六"
        */

        //一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        List<String> list = List.of("张三", "李四", "王五", "赵六");

        System.out.println(list.get(0));
        System.out.println(list.get(1));
        System.out.println(list.get(2));
        System.out.println(list.get(3));

        System.out.println("---------------------------");

        for (String s : list) {
            System.out.println(s);
        }

        System.out.println("---------------------------");

        // UnsupportedOperationException
//        list.remove("李四");
//        list.add("aaa");
//        list.set(0,"aaa");
    }
}

1.5 不可变的Set集合

java 复制代码
package com.powernode.set;

import java.util.Set;

public class Test {
    public static void main(String[] args) {
        /*
           创建不可变的Set集合
           "张三", "李四", "王五", "赵六"


           细节:
                当我们要获取一个不可变的Set集合时,里面的参数一定要保证唯一性
        */

        //一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        // IllegalArgumentException
//        Set<String> set = Set.of("张三", "张三", "李四", "王五", "赵六");

        Set<String> set = Set.of("张三", "李四", "王五", "赵六");
        for (String s : set) {
            System.out.println(s);
        }

        System.out.println("-----------------------");
//        set.remove("王五");
    }
}

1.6 不可变的Map集合

1.6.1:键值对个数小于等于10
java 复制代码
package com.powernode.map;

import java.util.Map;
import java.util.Set;

public class Test {
    public static void main(String[] args) {
       /*
        创建Map的不可变集合
            细节1:
                键是不能重复的
            细节2:
                Map里面的of方法,参数是有上限的,最多只能传递20个参数,10个键值对
            细节3:
                如果我们要传递多个键值对对象,数量大于10个,在Map接口中还有一个方法
        */

        //一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        Map<String, String> map = Map.of("张三", "南京", "王五", "上海",
                "赵六", "广州", "孙七", "深圳", "周八", "杭州",
                "吴九", "宁波", "郑十", "苏州", "刘一", "无锡",
                "陈二", "嘉兴");

        Set<String> keys = map.keySet();
        for (String key : keys) {
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }

        System.out.println("--------------------------");
    }
}
1.6.2:键值对个数大于10
java 复制代码
package com.powernode.map;

import java.util.HashMap;
import java.util.Map;

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

        /*
            创建Map的不可变集合,键值对的数量超过10个
        */

        //1.创建一个普通的Map集合
        HashMap<String, String> hm = new HashMap<>();
        hm.put("张三", "南京");
        hm.put("李四", "北京");
        hm.put("王五", "上海");
        hm.put("赵六", "北京");
        hm.put("孙七", "深圳");
        hm.put("周八", "杭州");
        hm.put("吴九", "宁波");
        hm.put("郑十", "苏州");
        hm.put("刘一", "无锡");
        hm.put("陈二", "嘉兴");
        hm.put("aaa", "111");

        //2.利用上面的数据来获取一个不可变的集合
/*
        //获取到所有的键值对对象(Entry对象)
        Set<Map.Entry<String, String>> entries = hm.entrySet();
        //把entries变成一个数组
        Map.Entry[] arr1 = new Map.Entry[0];
        //toArray方法在底层会比较集合的长度跟数组的长度两者的大小
        //如果集合的长度 > 数组的长度 :数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
        //如果集合的长度 <= 数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用
        Map.Entry[] arr2 = entries.toArray(arr1);
        //不可变的map集合
        Map map = Map.ofEntries(arr2);
        map.put("bbb","222");*/


        //Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));

        Map<String, String> map = Map.copyOf(hm);
        map.put("bbb","222");
    }
}

6.10. Stream流

6.10.1. 体验Stream流【理解】

  • 案例需求

    按照下面的要求完成集合的创建和遍历

    • 创建一个集合,存储多个字符串元素
    • 把集合中所有以"张"开头的元素存储到一个新的集合
    • 把"张"开头的集合中的长度为3的元素存储到一个新的集合
    • 遍历上一步得到的集合
  • 原始方式示例代码

    java 复制代码
    public class MyStream1 {
        public static void main(String[] args) {
            //集合的批量添加
            ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));
            //list.add()
    
            //遍历list1把以张开头的元素添加到list2中。
            ArrayList<String> list2 = new ArrayList<>();
            for (String s : list1) {
                if(s.startsWith("张")){
                    list2.add(s);
                }
            }
            //遍历list2集合,把其中长度为3的元素,再添加到list3中。
            ArrayList<String> list3 = new ArrayList<>();
            for (String s : list2) {
                if(s.length() == 3){
                    list3.add(s);
                }
            }
            for (String s : list3) {
                System.out.println(s);
            }      
        }
    }
  • 使用Stream流示例代码

    java 复制代码
    package com.powernode.test;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Test {
        public static void main(String[] args) {
            //集合的批量添加
            ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));
    
            //Stream流
            list1.stream().filter(s->s.startsWith("张")).filter(s->s.length() == 3).forEach(s->System.out.println(s));
        }
    }
  • Stream流的好处

    • 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印
    • Stream流把真正的函数式编程风格引入到Java中
    • 代码简洁

6.10.2. 常见生成方式【应用

  • Stream流的三类方法

    • 获取 Stream流
      • 创建一条流水线,并把数据放到流水线上准备进行操作
    • 中间 方法
      • 流水线上的操作
      • 一次操作完毕之后,还可以继续进行其他操作
    • 终结 方法
      • 一个Stream流只能有一个终结方法
      • 是流水线上的最后一个操作
  • 生成Stream流的方式

    • Collection体系集合

      使用默认方法**stream()**生成流, default Stream stream()

    • Map体系集合

      把Map转成Set集合,间接的生成流

    • 数组

      通过Arrays中的静态方法stream生成流

    • 同种数据类型的多个数据

      通过Stream接口的静态方法**of(T... values)**生成流

  • 代码演示

    java 复制代码
    package com.powernode.stream;
    
    import java.util.*;
    import java.util.stream.Stream;
    
    public class Test {
        public static void main(String[] args) {
            //Collection体系的集合可以使用默认方法stream()生成流
            List<String> list = new ArrayList<String>();
            Stream<String> listStream = list.stream();
    
            Set<String> set = new HashSet<String>();
            Stream<String> setStream = set.stream();
    
            //Map体系的集合间接的生成流
            Map<String,Integer> map = new HashMap<String, Integer>();
            Stream<String> keyStream = map.keySet().stream();
            Stream<Integer> valueStream = map.values().stream();
            Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
    
            //数组可以通过Arrays中的静态方法stream生成流
            String[] strArray = {"hello","world","java"};
            Stream<String> strArrayStream = Arrays.stream(strArray);
    
            //同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流
            Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
            Stream<Integer> intStream = Stream.of(10, 20, 30);
        }
    }

6.10.3. 中间操作方法【应用】

  • 概念

    中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作

  • 常见方法

    方法名 说明
    Stream filter(Predicate predicate) 用于对流中的数据进行过滤
    Stream limit(long maxSize) 返回此流中的元素组成的流,截取 前指定参数个数的数据
    Stream skip(long n) 跳过指定参数个数的数据,返回由该流的剩余元素组成的流
    static Stream concat(Stream a, Stream b) 合并a和b两个流为一个流
    Stream distinct() 返回由该流的不同元素(根据Object.equals(Object) )组成的流
  • filter代码演示

    java 复制代码
    public class Test {
        public static void main(String[] args) {
    //        Stream<T> filter(Predicate predicate):过滤
    //        Predicate接口中的方法	boolean test(T t):对给定的参数进行判断,返回一个布尔值
    
            ArrayList<String> list = new ArrayList<>();
            list.add("张三丰");
            list.add("张无忌");
            list.add("张翠山");
            list.add("王二麻子");
            list.add("张良");
            list.add("谢广坤");
    
            //filter方法获取流中的 每一个数据.
            //而test方法中的s,就依次表示流中的每一个数据.
            //我们只要在test方法中对s进行判断就可以了.
            //如果判断的结果为true,则当前的数据留下
            //如果判断的结果为false,则当前数据就不要.
    //        list.stream().filter(
    //                new Predicate<String>() {
    //                    @Override
    //                    public boolean test(String s) {
    //                        boolean result = s.startsWith("张");
    //                        return result;
    //                    }
    //                }
    //        ).forEach(s-> System.out.println(s));
    
            //因为Predicate接口中只有一个抽象方法test
            //所以我们可以使用lambda表达式来简化
    //        list.stream().filter(
    //                (String s)->{
    //                    boolean result = s.startsWith("张");
    //                        return result;
    //                }
    //        ).forEach(s-> System.out.println(s));
    
            list.stream().filter(s ->s.startsWith("张")).forEach(s-> System.out.println(s));
    
        }
    }
  • limit&skip代码演示

    java 复制代码
    package com.powernode.filter;
    
    import java.util.ArrayList;
    
    public class Test2 {
        public static void main(String[] args) {
            //创建一个集合,存储多个字符串元素
            ArrayList<String> list = new ArrayList<String>();
    
            list.add("林青霞");
            list.add("张曼玉");
            list.add("王祖贤");
            list.add("柳岩");
            list.add("张敏");
            list.add("张无忌");
    
            //需求1:取前3个数据在控制台输出
            list.stream().limit(3).forEach(s-> System.out.println(s));
            System.out.println("--------");
    
            //需求2:跳过3个元素,把剩下的元素在控制台输出
            list.stream().skip(3).forEach(s-> System.out.println(s));
            System.out.println("--------");
    
            //需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
            list.stream().skip(2).limit(2).forEach(s-> System.out.println(s));
        }
    }
  • concat&distinct代码演示

    java 复制代码
    package com.powernode.filter;
    
    import java.util.ArrayList;
    import java.util.stream.Stream;
    
    public class Test3 {
        public static void main(String[] args) {
            //创建一个集合,存储多个字符串元素
            ArrayList<String> list = new ArrayList<String>();
    
            list.add("林青霞");
            list.add("张曼玉");
            list.add("王祖贤");
            list.add("柳岩");
            list.add("张敏");
            list.add("张无忌");
    
            //需求1:取前4个数据组成一个流
            Stream<String> s1 = list.stream().limit(4);
    
            //需求2:跳过2个数据组成一个流
            Stream<String> s2 = list.stream().skip(2);
    
            //需求3:合并需求1和需求2得到的流,并把结果在控制台输出
            // IllegalStateException
    //        Stream.concat(s1,s2).forEach(s-> System.out.println(s));
    
            //需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
            Stream.concat(s1,s2).distinct().forEach(s-> System.out.println(s));
        }
    }

6.10.4. 终结操作方法【应用】

  • 概念

    终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作

  • 常见方法

    方法名 说明
    void forEach(Consumer action) 对此流的每个元素执行操作
    long count() 返回此流中的元素数
  • 代码演示

    java 复制代码
    package com.powernode.foreach;
    
    import java.util.ArrayList;
    import java.util.function.Consumer;
    
    public class Test {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("张三丰");
            list.add("张无忌");
            list.add("张翠山");
            list.add("王二麻子");
            list.add("张良");
            list.add("谢广坤");
    
            method1(list);
    
    //        long count():返回此流中的元素数
    //        int count = list.stream().count(); // java: 不兼容的类型: 从long转换到int可能会有损失
            long count = list.stream().count();
            System.out.println(count);
        }
    
        private static void method1(ArrayList<String> list) {
            //  void forEach(Consumer action):对此流的每个元素执行操作
            //  Consumer接口中的方法void accept(T t):对给定的参数执行此操作
            //在forEach方法的底层,会循环获取到流中的每一个数据.
            //并循环调用accept方法,并把每一个数据传递给accept方法
            //s就依次表示了流中的每一个数据.
            //所以,我们只要在accept方法中,写上处理的业务逻辑就可以了.
            list.stream().forEach(
                    new Consumer<String>() {
                        @Override
                        public void accept(String s) {
                            System.out.println(s);
                        }
                    }
            );
    
            System.out.println("====================");
            //lambda表达式的简化格式
            //是因为Consumer接口中,只有一个accept方法
            list.stream().forEach(
                    (String s)->{
                        System.out.println(s);
                    }
            );
            System.out.println("====================");
            //lambda表达式还是可以进一步简化的.
            list.stream().forEach(s->System.out.println(s));
        }
    }

6.10.5. 收集操作【应用】

  • 概念

    对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中

  • 常用方法

    方法名 说明
    R collect(Collector collector) 把结果收集到集合中
  • 工具类Collectors提供了具体的收集方式

    方法名 说明
    public static Collector toList() 把元素收集到List集合中
    public static Collector toSet() 把元素收集到Set集合中
    public static Collector toMap(Function keyMapper,Function valueMapper) 把元素收集到Map集合中
  • 代码演示

    java 复制代码
    // toList和toSet方法演示 
    public class MyStream7 {
        public static void main(String[] args) {
            ArrayList<Integer> list1 = new ArrayList<>();
            for (int i = 1; i <= 10; i++) {
                list1.add(i);
            }
    
            list1.add(10);
            list1.add(10);
            list1.add(10);
            list1.add(10);
            list1.add(10);
    
            //filter负责过滤数据的.
            //collect负责收集数据.
                    //获取流中剩余的数据,但是他不负责创建容器,也不负责把数据添加到容器中.
            //Collectors.toList() : 在底层会创建一个List集合.并把所有的数据添加到List集合中.
            List<Integer> list = list1.stream().filter(number -> number % 2 == 0)
                    .collect(Collectors.toList());
    
            System.out.println(list);
    
        Set<Integer> set = list1.stream().filter(number -> number % 2 == 0)
                .collect(Collectors.toSet());
        System.out.println(set);
    }
    }
java 复制代码
package com.powernode.collect;

import java.util.ArrayList;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 Stream流的收集方法 toMap方法演示
 创建一个ArrayList集合,并添加以下字符串。字符串中前面是姓名,后面是年龄
 "zhangsan-男-23"
 "lisi-女-24"
 "wangwu-男-25"
 保留男性,并将结果收集到Map集合中,姓名为键,年龄为值
 */


/**
 * toMap : 参数一表示键的生成规则
 *          参数二表示值的生成规则
 *
 *
 * 参数一:
 *      Function泛型一: 表示流中的每一个数据的类型
 *              泛型二: 表示Map集合中键的数据类型
 *
 方法apply形参: 表示流中的每一个数据
 *              方法体: 生成键的代码
 *              返回值: 已经生成的键
 *
 *  参数二:
 *      Function泛型一: 表示流中的每一个数据的类型
 *              泛型二: 表示Map集合中值的数据类型
 *      方法apply形参: 表示流中的每一个数据
 *              方法体: 生成值的代码
 *              返回值: 已经生成的值
 */

public class Test2{
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("zhangsan-男-23");
        list.add("lisi-女-24");
        list.add("wangwu-男-25");

        Map<String, Integer> map = list.stream().filter(
                s -> "男".equals(s.split("-")[1])
        ).collect(Collectors.toMap(new Function<String, String>() {
                                       @Override
                                       public String apply(String s) {
                                           return s.split("-")[0];
                                       }
                                   },
                new Function<String, Integer>() {
                    @Override
                    public Integer apply(String s) {
                        return Integer.parseInt(s.split("-")[2]);
                    }
                }));
        System.out.println(map);
    }
}

6.10.6. 综合练习【应用】

  • 案例需求

    现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作

    • 男演员只要名字为3个字的前三人
    • 女演员只要姓林的,并且不要第一个
    • 把过滤后的男演员姓名和女演员姓名合并到一起
    • 把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据

    演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法

  • 代码实现

    演员类

    java 复制代码
    package com.powernode.test2;
    
    /*
        演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法
     */
    public class Actor {
        private String name;
    
        @Override
        public String toString() {
            return "Actor{" +
                    "name='" + name + '\'' +
                    '}';
        }
    
        public Actor(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

    测试类

    java 复制代码
    package com.powernode.test2;
    
    import java.util.ArrayList;
    import java.util.stream.Stream;
    
    public class Test2 {
        public static void main(String[] args) {
            //创建集合
            ArrayList<String> manList = new ArrayList<String>();
            manList.add("周星驰");
            manList.add("周润发");
            manList.add("成龙");
            manList.add("刘德华");
            manList.add("吴京");
            manList.add("李连杰");
    
            ArrayList<String> womanList = new ArrayList<String>();
            womanList.add("林心如");
            womanList.add("张曼玉");
            womanList.add("林青霞");
            womanList.add("柳岩");
            womanList.add("林志玲");
            womanList.add("王祖贤");
    
            //男演员只要名字为3个字的前三人
            Stream<String> manStream = manList.stream().filter(s -> s.length() == 3).limit(3);
    
            //女演员只要姓林的,并且不要第一个
            Stream<String> womanStream = womanList.stream().skip(1).filter(s -> s.startsWith("林"));
    
            //把过滤后的男演员姓名和女演员姓名合并到一起
            Stream<String> stream = Stream.concat(manStream, womanStream);
    
            // 将流中的数据封装成Actor对象之后打印
            stream.forEach(name -> {
                Actor actor = new Actor(name);
                System.out.println(actor);
            });
        }
    }

6.11. 方法引用

6.11.1. 体验方法引用【理解】

  • 方法引用的出现原因

    在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作

    那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑呢?答案肯定是没有必要

    那我们又是如何使用已经存在的方案的呢?

    这就是我们要讲解的方法引用 ,我们是通过方法引用来使用已经存在的方案

  • 代码演示

    java 复制代码
    public interface Printable {
        void printString(String s);
    }
    
    public class PrintableDemo {
        public static void main(String[] args) {
            //在主方法中调用usePrintable方法
    //        usePrintable((String s) -> {
    //            System.out.println(s);
    //        });
    	    //Lambda简化写法
            usePrintable(s -> System.out.println(s));
    
            //方法引用
            usePrintable(System.out::println);
    
        }
    
        private static void usePrintable(Printable p) {
            p.printString("爱生活爱Java");
        }
    }

6.11.2. 方法引用符【理解】

  • 方法引用符

    :: 该符号为引用运算符 ,而它所在的表达式被称为方法引用

  • 推导与省略

    • 如果使用Lambda,那么根据"可推导就是可省略"的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
    • 如果使用方法引用,也是同样可以根据上下文进行推导
    • 方法引用是Lambda的孪生兄弟

6.11.3. 引用类方法【应用】

​ 引用类方法,其实就是引用类的静态方法

  • 格式

    类名::静态方法

  • 范例

    Integer::parseInt

    Integer类的方法:public static int parseInt(String s) 将此String转换为int类型数据

  • 练习描述

    • 定义一个接口(Converter),里面定义一个抽象方法 int convert(String s);
    • 定义一个测试类(ConverterDemo),在测试类中提供两个方法
      • 一个方法是:useConverter(Converter c)
      • 一个方法是主方法,在主方法中调用useConverter方法
  • 代码演示

    java 复制代码
    public interface Converter {
        int convert(String s);
    }
    
    public class ConverterDemo {
        public static void main(String[] args) {
    
    		//Lambda写法
            useConverter(s -> Integer.parseInt(s));
    
            //引用类方法
            useConverter(Integer::parseInt);
    
        }
    
        private static void useConverter(Converter c) {
            int number = c.convert("666");
            System.out.println(number);
        }
    }
  • 使用说明

    Lambda表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数

6.11.4. 引用对象的成员方法【应用】

引用对象 的成员方法,其实就引用类中的成员方法

  • 格式

    对象::实例方法

  • 范例

    "HelloWorld"::toUpperCase

    String类中的方法:public String toUpperCase() 将此String所有字符转换为大写

  • 练习描述

    • 定义一个类(PrintString),里面定义一个方法

      public void printUpper(String s):把字符串参数变成大写的数据,然后在控制台输出

    • 定义一个接口(Printer),里面定义一个抽象方法

      void printUpperCase(String s)

    • 定义一个测试类(PrinterDemo),在测试类中提供两个方法

      • 一个方法是:usePrinter(Printer p)
      • 一个方法是主方法,在主方法中调用usePrinter方法
  • 代码演示

    java 复制代码
    public class PrintString {
        //把字符串参数变成大写的数据,然后在控制台输出
        public void printUpper(String s) {
            String result = s.toUpperCase();
            System.out.println(result);
        }
    }
    
    public interface Printer {
        void printUpperCase(String s);
    }
    
    public class PrinterDemo {
        public static void main(String[] args) {
    
    		//Lambda简化写法
            usePrinter(s -> System.out.println(s.toUpperCase()));
    
            //引用对象的实例方法
            PrintString ps = new PrintString();
            usePrinter(ps::printUpper);
    
        }
    
        private static void usePrinter(Printer p) {
            p.printUpperCase("HelloWorld");
        }
    }
  • 使用说明

    Lambda表达式被对象的实例方法替代的时候,它的形式参数全部传递给该方法作为参数

6.11.5. 引用类的实例方法【应用】

​ 引用类的实例方法,其实就是引用类中的成员方法

  • 格式

    类名::成员方法

  • 范例

    String::substring

    public String substring(int beginIndex,int endIndex)

    从beginIndex开始到endIndex结束,截取字符串。返回一个子串,子串的长度为endIndex-beginIndex

  • 练习描述

    • 定义一个接口(MyString),里面定义一个抽象方法:

      String mySubString(String s,int x,int y);

    • 定义一个测试类(MyStringDemo),在测试类中提供两个方法

      • 一个方法是:useMyString(MyString my)
      • 一个方法是主方法,在主方法中调用useMyString方法
  • 代码演示

    java 复制代码
    public interface MyString {
        String mySubString(String s,int x,int y);
    }
    
    public class MyStringDemo {
        public static void main(String[] args) {
    		//Lambda简化写法
            useMyString((s,x,y) -> s.substring(x,y));
    
            //引用类的实例方法
            useMyString(String::substring);
        }
    
        private static void useMyString(MyString my) {
            String s = my.mySubString("HelloWorld", 2, 5);
            System.out.println(s);
        }
    }
  • 使用说明

    ​ Lambda表达式被类的实例方法替代的时候

    ​ 第一个参数作为调用者

    ​ 后面的参数全部传递给该方法作为参数

6.11.6. 引用构造器【应用】

​ 引用构造器,其实就是引用构造方法

  • l格式

    类名::new

  • 范例

    Student::new

  • 练习描述

    • 定义一个类(Student),里面有两个成员变量(name,age)

      并提供无参构造方法和带参构造方法,以及成员变量对应的get和set方法

    • 定义一个接口(StudentBuilder),里面定义一个抽象方法

      Student build(String name,int age);

    • 定义一个测试类(StudentDemo),在测试类中提供两个方法

      • 一个方法是:useStudentBuilder(StudentBuilder s)
      • 一个方法是主方法,在主方法中调用useStudentBuilder方法
  • 代码演示

    java 复制代码
    public class Student {
        private String name;
        private int age;
    
        public Student() {
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = 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 interface StudentBuilder {
        Student build(String name,int age);
    }
    
    public class StudentDemo {
        public static void main(String[] args) {
    
    		//Lambda简化写法
            useStudentBuilder((name,age) -> new Student(name,age));
    
            //引用构造器
            useStudentBuilder(Student::new);
    
        }
    
        private static void useStudentBuilder(StudentBuilder sb) {
            Student s = sb.build("林青霞", 30);
            System.out.println(s.getName() + "," + s.getAge());
        }
    }
  • 使用说明

    Lambda表达式被构造器替代的时候,它的形式参数全部传递给构造器作为参数

相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔4 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说9 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json