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表达式被构造器替代的时候,它的形式参数全部传递给构造器作为参数

相关推荐
阿里-于怀2 小时前
AgentScope AutoContextMemory:告别 Agent 上下文焦虑
android·java·数据库·agentscope
OpenTiny社区2 小时前
TinyPro v1.4.0 正式发布:支持 Spring Boot、移动端适配、新增卡片列表和高级表单页面
java·前端·spring boot·后端·开源·opentiny
lonelyhiker2 小时前
IDEA未编译完就启动项目的问题
java·ide·intellij-idea
-凌凌漆-2 小时前
【JS】JavaScript Promise
开发语言·javascript·ecmascript
羊村积极分子懒羊羊2 小时前
python课程三月二十九号粗略总结
开发语言·python
月入2k没钱吃饭没得朋友还被迫加班2 小时前
meterSphere二开遇到的问题
java
aloha_7892 小时前
langchain4j如何使用mcp
java·人工智能·python·langchain
時肆4852 小时前
Linux命令创意组合大赛:管道里的魔法
开发语言·性能优化
Traced back2 小时前
C#/.NET 常用控件、属性、方法和语句大全(或许全)
开发语言·c#·.net