常见的七种查找算法

基本查找

说明:顺序查找适合于存储结构为数组或者链表。

基本思想:顺序查找也称为线形查找,属于无序查找算法。从数据结构线的一端开始,顺序扫描,依次将遍历到的结点与要查找的值相比较,若相等则表示查找成功;若遍历结束仍没有找到相同的,表示查找失败。

示例代码:

public class A01_BasicSearchDemo1 {
    public static void main(String[] args) {
        //基本查找/顺序查找
        //核心:
        //从0索引开始挨个往后查找

        //需求:定义一个方法利用基本查找,查询某个元素是否存在
        //数据如下:{131, 127, 147, 81, 103, 23, 7, 79}


        int[] arr = {131, 127, 147, 81, 103, 23, 7, 79};
        int number = 82;
        System.out.println(basicSearch(arr, number));

    }

    //参数:
    //一:数组
    //二:要查找的元素

    //返回值:
    //元素是否存在
    public static boolean basicSearch(int[] arr, int number){
        //利用基本查找来查找number在数组中是否存在
        for (int i = 0; i < arr.length; i++) {
            if(arr[i] == number){
                return true;
            }
        }
        return false;
    }
}

二分查找

折半查找说明:元素必须是有序的,从小到大,或者从大到小都是可以的。

如果是无序的,也可以先进行排序。但是排序之后,会改变原有数据的顺序,查找出来元素位置跟原来的元素可能是不一样的,所以排序之后再查找只能判断当前数据是否在容器当中,返回的索引无实际的意义。

基本思想:也称为是折半查找,属于有序查找算法。用给定值先与中间结点比较。比较完之后有三种情况:

  • 相等

    说明找到了

  • 要查找的数据比中间节点小

    说明要查找的数字在中间节点左边

  • 要查找的数据比中间节点大

    说明要查找的数字在中间节点右边

代码示例:

package com.itheima.search;

public class A02_BinarySearchDemo1 {
    public static void main(String[] args) {
        //二分查找/折半查找
        //核心:
        //每次排除一半的查找范围

        //需求:定义一个方法利用二分查找,查询某个元素在数组中的索引
        //数据如下:{7, 23, 79, 81, 103, 127, 131, 147}

        int[] arr = {7, 23, 79, 81, 103, 127, 131, 147};
        System.out.println(binarySearch(arr, 150));
    }

    public static int binarySearch(int[] arr, int number){
        //1.定义两个变量记录要查找的范围
        int min = 0;
        int max = arr.length - 1;

        //2.利用循环不断的去找要查找的数据
        while(true){
            if(min > max){
                return -1;
            }
            //3.找到min和max的中间位置
            int mid = (min + max) / 2;
            //4.拿着mid指向的元素跟要查找的元素进行比较
            if(arr[mid] > number){
                //4.1 number在mid的左边
                //min不变,max = mid - 1;
                max = mid - 1;
            }else if(arr[mid] < number){
                //4.2 number在mid的右边
                //max不变,min = mid + 1;
                min = mid + 1;
            }else{
                //4.3 number跟mid指向的元素一样
                //找到了
                return mid;
            }

        }
    }
}

插值查找

在介绍插值查找之前,先考虑一个问题:

为什么二分查找算法一定要是折半,而不是折四分之一或者折更多呢?

其实就是因为方便,简单,但是如果我能在二分查找的基础上,让中间的mid点,尽可能靠近想要查找的元素,那不就能提高查找的效率了吗?

二分查找中查找点计算如下:mid=(low+high)/2, 即mid=low+1/2(high-low);*

我们可以将查找的点改进为如下:mid=low+(key-a[low])/(a[high]-a[low])*(high-low),

这样,让mid值的变化更靠近关键字key,这样也就间接地减少了比较次数。

基本思想:基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。当然,差值查找也属于有序查找。

**细节:**对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。

代码跟二分查找类似,只要修改一下mid的计算方式即可。

斐波那契查找

在介绍斐波那契查找算法之前,我们先介绍一下很它紧密相连并且大家都熟知的一个概念——黄金分割。

  黄金比例又称黄金分割,是指事物各部分间一定的数学比例关系,即将整体一分为二,较大部分与较小部分之比等于整体与较大部分之比,其比值约为1:0.618或1.618:1。

  0.618被公认为最具有审美意义的比例数字,这个数值的作用不仅仅体现在诸如绘画、雕塑、音乐、建筑等艺术领域,而且在管理、工程设计等方面也有着不可忽视的作用。因此被称为黄金分割。

  在数学中有一个非常有名的数学规律:斐波那契数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89…….

(从第三个数开始,后边每一个数都是前两个数的和)。

然后我们会发现,随着斐波那契数列的递增,前后两个数的比值会越来越接近0.618,利用这个特性,我们就可以将黄金比例运用到查找技术中。

基本思想:也是二分查找的一种提升算法,通过运用黄金比例的概念在数列中选择查找点进行查找,提高查找效率。同样地,斐波那契查找也属于一种有序查找算法。

斐波那契查找也是在二分查找的基础上进行了优化,优化中间点mid的计算方式即可

代码示例:

public class FeiBoSearchDemo {
    public static int maxSize = 20;

    public static void main(String[] args) {
        int[] arr = {1, 8, 10, 89, 1000, 1234};
        System.out.println(search(arr, 1234));
    }

    public static int[] getFeiBo() {
        int[] arr = new int[maxSize];
        arr[0] = 1;
        arr[1] = 1;
        for (int i = 2; i < maxSize; i++) {
            arr[i] = arr[i - 1] + arr[i - 2];
        }
        return arr;
    }

    public static int search(int[] arr, int key) {
        int low = 0;
        int high = arr.length - 1;
        //表示斐波那契数分割数的下标值
        int index = 0;
        int mid = 0;
        //调用斐波那契数列
        int[] f = getFeiBo();
        //获取斐波那契分割数值的下标
        while (high > (f[index] - 1)) {
            index++;
        }
        //因为f[k]值可能大于a的长度,因此需要使用Arrays工具类,构造一个新法数组,并指向temp[],不足的部分会使用0补齐
        int[] temp = Arrays.copyOf(arr, f[index]);
        //实际需要使用arr数组的最后一个数来填充不足的部分
        for (int i = high + 1; i < temp.length; i++) {
            temp[i] = arr[high];
        }
        //使用while循环处理,找到key值
        while (low <= high) {
            mid = low + f[index - 1] - 1;
            if (key < temp[mid]) {//向数组的前面部分进行查找
                high = mid - 1;
                /*
                  对k--进行理解
                  1.全部元素=前面的元素+后面的元素
                  2.f[k]=k[k-1]+f[k-2]
                  因为前面有k-1个元素没所以可以继续分为f[k-1]=f[k-2]+f[k-3]
                  即在f[k-1]的前面继续查找k--
                  即下次循环,mid=f[k-1-1]-1
                 */
                index--;
            } else if (key > temp[mid]) {//向数组的后面的部分进行查找
                low = mid + 1;
                index -= 2;
            } else {//找到了
                //需要确定返回的是哪个下标
                if (mid <= high) {
                    return mid;
                } else {
                    return high;
                }
            }
        }
        return -1;
    }
}

分块查找

当数据表中的数据元素很多时,可以采用分块查找。

汲取了顺序查找和折半查找各自的优点,既有动态结构,又适于快速查找

分块查找适用于数据较多,但是数据不会发生变化的情况,如果需要一边添加一边查找,建议使用哈希查找

分块查找的过程:

  1. 需要把数据分成N多小块,块与块之间不能有数据重复的交集。
  2. 给每一块创建对象单独存储到数组当中
  3. 查找数据的时候,先在数组查,当前数据属于哪一块
  4. 再到这一块中顺序查找

代码示例:

package com.itheima.search;

public class A03_BlockSearchDemo {
    public static void main(String[] args) {
        /*
            分块查找
            核心思想:
                块内无序,块间有序
            实现步骤:
                1.创建数组blockArr存放每一个块对象的信息
                2.先查找blockArr确定要查找的数据属于哪一块
                3.再单独遍历这一块数据即可
        */
        int[] arr = {16, 5, 9, 12,21, 18,
                     32, 23, 37, 26, 45, 34,
                     50, 48, 61, 52, 73, 66};

        //创建三个块的对象
        Block b1 = new Block(21,0,5);
        Block b2 = new Block(45,6,11);
        Block b3 = new Block(73,12,17);

        //定义数组用来管理三个块的对象(索引表)
        Block[] blockArr = {b1,b2,b3};

        //定义一个变量用来记录要查找的元素
        int number = 37;

        //调用方法,传递索引表,数组,要查找的元素
        int index = getIndex(blockArr,arr,number);

        //打印一下
        System.out.println(index);



    }

    //利用分块查找的原理,查询number的索引
    private static int getIndex(Block[] blockArr, int[] arr, int number) {
        //1.确定number是在那一块当中
        int indexBlock = findIndexBlock(blockArr, number);

        if(indexBlock == -1){
            //表示number不在数组当中
            return -1;
        }

        //2.获取这一块的起始索引和结束索引   --- 30
        // Block b1 = new Block(21,0,5);   ----  0
        // Block b2 = new Block(45,6,11);  ----  1
        // Block b3 = new Block(73,12,17); ----  2
        int startIndex = blockArr[indexBlock].getStartIndex();
        int endIndex = blockArr[indexBlock].getEndIndex();

        //3.遍历
        for (int i = startIndex; i <= endIndex; i++) {
            if(arr[i] == number){
                return i;
            }
        }
        return -1;
    }


    //定义一个方法,用来确定number在哪一块当中
    public static int findIndexBlock(Block[] blockArr,int number){ //100


        //从0索引开始遍历blockArr,如果number小于max,那么就表示number是在这一块当中的
        for (int i = 0; i < blockArr.length; i++) {
            if(number <= blockArr[i].getMax()){
                return i;
            }
        }
        return -1;
    }



}

class Block{
    private int max;//最大值
    private int startIndex;//起始索引
    private int endIndex;//结束索引


    public Block() {
    }

    public Block(int max, int startIndex, int endIndex) {
        this.max = max;
        this.startIndex = startIndex;
        this.endIndex = endIndex;
    }

    /**
     * 获取
     * @return max
     */
    public int getMax() {
        return max;
    }

    /**
     * 设置
     * @param max
     */
    public void setMax(int max) {
        this.max = max;
    }

    /**
     * 获取
     * @return startIndex
     */
    public int getStartIndex() {
        return startIndex;
    }

    /**
     * 设置
     * @param startIndex
     */
    public void setStartIndex(int startIndex) {
        this.startIndex = startIndex;
    }

    /**
     * 获取
     * @return endIndex
     */
    public int getEndIndex() {
        return endIndex;
    }

    /**
     * 设置
     * @param endIndex
     */
    public void setEndIndex(int endIndex) {
        this.endIndex = endIndex;
    }

    public String toString() {
        return "Block{max = " + max + ", startIndex = " + startIndex + ", endIndex = " + endIndex + "}";
    }
}

哈希查找

哈希查找是分块查找的进阶版,适用于数据一边添加一边查找的情况。

一般是数组 + 链表的结合体或者是数组+链表 + 红黑树的结合体

先计算出当前数据的哈希值,用哈希值跟数组的长度进行计算,计算出应存入的位置,再挂在数组的后面形成链表,如果挂的元素太多而且数组长度过长,我们也会把链表转化为红黑树,进一步提高效率。

树表查找

本知识点涉及到数据结构:树。

基本思想:二叉查找树是先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围。 这个算法的查找效率很高,但是如果使用这种查找方法要首先创建树。

  二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree),具有下列性质的二叉树:

  1)若任意节点左子树上所有的数据,均小于本身;

  2)若任意节点右子树上所有的数据,均大于本身;

  二叉查找树性质:对二叉查找树进行中序遍历,即可得到有序的数列。

  基于二叉查找树进行优化,进而可以得到其他的树表查找算法,如平衡树、红黑树等高效算法。

十大排序算法:

冒泡排序

冒泡排序(Bubble Sort)也是一种简单直观的排序算法。

它重复的遍历过要排序的数列,一次比较相邻的两个元素,如果他们的顺序错误就把他们交换过来。

这个算法的名字由来是因为越大的元素会经由交换慢慢"浮"到最后面。

当然,大家可以按照从大到小的方式进行排列。

算法步骤

  1. 相邻的元素两两比较,大的放右边,小的放左边
  2. 第一轮比较完毕之后,最大值就已经确定,第二轮可以少循环一次,后面以此类推
  3. 如果数组中有n个数据,总共我们只要执行n-1轮的代码就可以

代码示例

public class A01_BubbleDemo {
    public static void main(String[] args) {
        /*
            冒泡排序:
            核心思想:
            1,相邻的元素两两比较,大的放右边,小的放左边。
            2,第一轮比较完毕之后,最大值就已经确定,第二轮可以少循环一次,后面以此类推。
            3,如果数组中有n个数据,总共我们只要执行n-1轮的代码就可以。
        */


        //1.定义数组
        int[] arr = {2, 4, 5, 3, 1};

        //2.利用冒泡排序将数组中的数据变成 1 2 3 4 5

        //外循环:表示我要执行多少轮。 如果有n个数据,那么执行n - 1 轮
        for (int i = 0; i < arr.length - 1; i++) {
            //内循环:每一轮中我如何比较数据并找到当前的最大值
            //-1:为了防止索引越界
            //-i:提高效率,每一轮执行的次数应该比上一轮少一次。
            for (int j = 0; j < arr.length - 1 - i; j++) {
                //i 依次表示数组中的每一个索引:0 1 2 3 4
                if(arr[j] > arr[j + 1]){
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }

        printArr(arr);

    }

    private static void printArr(int[] arr) {
        //3.遍历数组
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

选择排序

算法步骤

  1. 从0索引开始,跟后面的元素一一比较
  2. 小的放前面,大的放后面
  3. 第一次循环结束后,最小的数据已经确定
  4. 第二次循环从1索引开始以此类推
  5. 第三轮循环从2索引开始以此类推
  6. 第四轮循环从3索引开始以此类推。
public class A02_SelectionDemo {
    public static void main(String[] args) {

        /*
            选择排序:
                1,从0索引开始,跟后面的元素一一比较。
                2,小的放前面,大的放后面。
                3,第一次循环结束后,最小的数据已经确定。
                4,第二次循环从1索引开始以此类推。

         */


        //1.定义数组
        int[] arr = {2, 4, 5, 3, 1};


        //2.利用选择排序让数组变成 1 2 3 4 5
       /* //第一轮:
        //从0索引开始,跟后面的元素一一比较。
        for (int i = 0 + 1; i < arr.length; i++) {
            //拿着0索引跟后面的数据进行比较
            if(arr[0] > arr[i]){
                int temp = arr[0];
                arr[0] = arr[i];
                arr[i] = temp;
            }
        }*/

        //最终代码:
        //外循环:几轮
        //i:表示这一轮中,我拿着哪个索引上的数据跟后面的数据进行比较并交换
        for (int i = 0; i < arr.length -1; i++) {
            //内循环:每一轮我要干什么事情?
            //拿着i跟i后面的数据进行比较交换
            for (int j = i + 1; j < arr.length; j++) {
                if(arr[i] > arr[j]){
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }


        printArr(arr);

    }
    private static void printArr(int[] arr) {
        //3.遍历数组
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

}

插入排序

插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过创建有序序列和无序序列,然后再遍历无序序列得到里面每一个数字,把每一个数字插入到有序序列中正确的位置。

插入排序在插入的时候,有优化算法,在遍历有序序列找正确位置时,可以采取二分查找

算法步骤

将0索引的元素到N索引的元素看做是有序的,把N+1索引的元素到最后一个当成是无序的。

遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同数据,插在后面。

N的范围:0~最大索引

package com.itheima.mysort;


public class A03_InsertDemo {
    public static void main(String[] args) {
        /*
            插入排序:
                将0索引的元素到N索引的元素看做是有序的,把N+1索引的元素到最后一个当成是无序的。
                遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同数据,插在后面。
                N的范围:0~最大索引

        */
        int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};

        //1.找到无序的哪一组数组是从哪个索引开始的。  2
        int startIndex = -1;
        for (int i = 0; i < arr.length; i++) {
            if(arr[i] > arr[i + 1]){
                startIndex = i + 1;
                break;
            }
        }

        //2.遍历从startIndex开始到最后一个元素,依次得到无序的哪一组数据中的每一个元素
        for (int i = startIndex; i < arr.length; i++) {
            //问题:如何把遍历到的数据,插入到前面有序的这一组当中

            //记录当前要插入数据的索引
            int j = i;

            while(j > 0 && arr[j] < arr[j - 1]){
                //交换位置
                int temp = arr[j];
                arr[j] = arr[j - 1];
                arr[j - 1] = temp;
                j--;
            }

        }
        printArr(arr);
    }

    private static void printArr(int[] arr) {
        //3.遍历数组
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

}

快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。

快速排序又是一种分而治之思想在排序算法上的典型应用。

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!

它是处理大数据最快的排序算法之一了。

算法步骤

  1. 从数列中挑出一个元素,一般都是左边第一个数字,称为 "基准数";
  2. 创建两个指针,一个从前往后走,一个从后往前走。
  3. 先执行后面的指针,找出第一个比基准数小的数字
  4. 再执行前面的指针,找出第一个比基准数大的数字
  5. 交换两个指针指向的数字
  6. 直到两个指针相遇
  7. 将基准数跟指针指向位置的数字交换位置,称之为:基准数归位。
  8. 第一轮结束之后,基准数左边的数字都是比基准数小的,基准数右边的数字都是比基准数大的。
  9. 把基准数左边看做一个序列,把基准数右边看做一个序列,按照刚刚的规则递归排序
package com.itheima.mysort;

import java.util.Arrays;

public class A05_QuickSortDemo {
    public static void main(String[] args) {
        System.out.println(Integer.MAX_VALUE);
        System.out.println(Integer.MIN_VALUE);
      /*
        快速排序:
            第一轮:以0索引的数字为基准数,确定基准数在数组中正确的位置。
            比基准数小的全部在左边,比基准数大的全部在右边。
            后面以此类推。
      */

        int[] arr = {1,1, 6, 2, 7, 9, 3, 4, 5, 1,10, 8};


        //int[] arr = new int[1000000];

       /* Random r = new Random();
        for (int i = 0; i < arr.length; i++) {
            arr[i] = r.nextInt();
        }*/


        long start = System.currentTimeMillis();
        quickSort(arr, 0, arr.length - 1);
        long end = System.currentTimeMillis();

        System.out.println(end - start);//149

        System.out.println(Arrays.toString(arr));
        //课堂练习:
        //我们可以利用相同的办法去测试一下,选择排序,冒泡排序以及插入排序运行的效率
        //得到一个结论:快速排序真的非常快。

       /* for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }*/

    }


    /*
     *   参数一:我们要排序的数组
     *   参数二:要排序数组的起始索引
     *   参数三:要排序数组的结束索引
     * */
    public static void quickSort(int[] arr, int i, int j) {
        //定义两个变量记录要查找的范围
        int start = i;
        int end = j;

        if(start > end){
            //递归的出口
            return;
        }



        //记录基准数
        int baseNumber = arr[i];
        //利用循环找到要交换的数字
        while(start != end){
            //利用end,从后往前开始找,找比基准数小的数字
            //int[] arr = {1, 6, 2, 7, 9, 3, 4, 5, 10, 8};
            while(true){
                if(end <= start || arr[end] < baseNumber){
                    break;
                }
                end--;
            }
            System.out.println(end);
            //利用start,从前往后找,找比基准数大的数字
            while(true){
                if(end <= start || arr[start] > baseNumber){
                    break;
                }
                start++;
            }



            //把end和start指向的元素进行交换
            int temp = arr[start];
            arr[start] = arr[end];
            arr[end] = temp;
        }

        //当start和end指向了同一个元素的时候,那么上面的循环就会结束
        //表示已经找到了基准数在数组中应存入的位置
        //基准数归位
        //就是拿着这个范围中的第一个数字,跟start指向的元素进行交换
        int temp = arr[i];
        arr[i] = arr[start];
        arr[start] = temp;

        //确定6左边的范围,重复刚刚所做的事情
        quickSort(arr,i,start - 1);
        //确定6右边的范围,重复刚刚所做的事情
        quickSort(arr,start + 1,j);

    }
}

Collection集合

数组和集合的区别

  • 相同点

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

  • 不同点

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

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

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

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()集合的长度,也就是集合中元素的个数

Collection集合的遍历

迭代器遍历

  • 迭代器介绍

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

    boolean hasNext(): 判断当前位置是否有元素可以被取出 E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置

  • Collection集合的遍历

    public class IteratorDemo1 {
        public static void main(String[] args) {
            //创建集合对象
            Collection<String> c = new ArrayList<>();
    
            //添加元素
            c.add("hello");
            c.add("world");
            c.add("java");
            c.add("javaee");
    
            //Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
            Iterator<String> it = c.iterator();
    
            //用while循环改进元素的判断和获取
            while (it.hasNext()) {
                String s = it.next();
                System.out.println(s);
            }
        }
    }
    
  • 迭代器中删除的方法

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

    public class IteratorDemo2 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("b");
            list.add("c");
            list.add("d");
    
            Iterator<String> it = list.iterator();
            while(it.hasNext()){
                String s = it.next();
                if("b".equals(s)){
                    //指向谁,那么此时就删除谁.
                    it.remove();
                }
            }
            System.out.println(list);
        }
    }
    

增强for

  • 介绍

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

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

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

    }

  • 代码

    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.迭代器遍历时,不能用集合的方法进行增加或者删除

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);
        }
    }
}

lambda表达式

利用forEach方法,再结合lambda表达式的方式进行遍历
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));
    }
}

List集合

List集合的概述和特点【记忆】

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

List集合的特有方法【应用】

  • 方法介绍

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

    public class MyListDemo {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");
            //method1(list);
            //method2(list);
            //method3(list);
            //method4(list);
        }
    
        private static void method4(List<String> list) {
            //        E get(int index)		返回指定索引处的元素
            String s = list.get(0);
            System.out.println(s);
        }
    
        private static void method3(List<String> list) {
            //        E set(int index,E element)	修改指定索引处的元素,返回被修改的元素
            //被替换的那个元素,在集合中就不存在了.
            String result = list.set(0, "qqq");
            System.out.println(result);
            System.out.println(list);
        }
    
        private static void method2(List<String> list) {
            //        E remove(int index)		删除指定索引处的元素,返回被删除的元素
            //在List集合中有两个删除的方法
            //第一个 删除指定的元素,返回值表示当前元素是否删除成功
            //第二个 删除指定索引的元素,返回值表示实际删除的元素
            String s = list.remove(0);
            System.out.println(s);
            System.out.println(list);
        }
    
        private static void method1(List<String> list) {
            //        void add(int index,E element)	在此集合中的指定位置插入指定的元素
            //原来位置上的元素往后挪一个索引.
            list.add(0,"qqq");
            System.out.println(list);
        }
    }
    

List集合的五种遍历方式【应用】

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

代码示例:

//创建集合并添加元素
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++) {
            //i:依次表示集合中的每一个索引
            String s = list.get(i);
            System.out.println(s);
        }*/

// 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);

细节点注意:

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

1.直接删除元素
2.通过索引进行删除

代码示例:

//List系列集合中的两个删除的方法
//1.直接删除元素
//2.通过索引进行删除

//1.创建集合并添加元素
List<Integer> list = new ArrayList<>();

list.add(1);
list.add(2);
list.add(3);


//2.删除元素
//请问:此时删除的是1这个元素,还是1索引上的元素?
//为什么?
//因为在调用方法的时候,如果方法出现了重载现象
//优先调用,实参跟形参类型一致的那个方法。

//list.remove(1);


//手动装箱,手动把基本数据类型的1,变成Integer类型
Integer i = Integer.valueOf(1);

list.remove(i);

System.out.println(list);

数据结构

栈和队列

  • 栈结构

    先进后出

  • 队列结构

    先进先出

数组和链表

  • 数组结构

    查询快、增删慢

  • 队列结构

    查询慢、增删快

List集合的实现类

List集合子类的特点

  • 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()从此列表中删除并返回最后一个元素
  • 示例代码

    public class MyLinkedListDemo4 {
        public static void main(String[] args) {
            LinkedList<String> list = new LinkedList<>();
            list.add("aaa");
            list.add("bbb");
            list.add("ccc");
    //        public void addFirst(E e)	在该列表开头插入指定的元素
            //method1(list);
    
    //        public void addLast(E e)	将指定的元素追加到此列表的末尾
            //method2(list);
    
    //        public E getFirst()		返回此列表中的第一个元素
    //        public E getLast()		返回此列表中的最后一个元素
            //method3(list);
    
    //        public E removeFirst()		从此列表中删除并返回第一个元素
    //        public E removeLast()		从此列表中删除并返回最后一个元素
            //method4(list);
          
        }
    
        private static void method4(LinkedList<String> list) {
            String first = list.removeFirst();
            System.out.println(first);
    
            String last = list.removeLast();
            System.out.println(last);
    
            System.out.println(list);
        }
    
        private static void method3(LinkedList<String> list) {
            String first = list.getFirst();
            String last = list.getLast();
            System.out.println(first);
            System.out.println(last);
        }
    
        private static void method2(LinkedList<String> list) {
            list.addLast("www");
            System.out.println(list);
        }
    
        private static void method1(LinkedList<String> list) {
            list.addFirst("qqq");
            System.out.println(list);
        }
    }
    

源码分析

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会记录新结点的地址值

迭代器源码分析:

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

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

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

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

泛型

泛型概述

  • 泛型的介绍

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

  • 泛型的好处

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

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

Set集合

Set集合概述和特点

  • 不可以存储重复元素
  • 没有索引,不能使用普通for循环遍历

Set集合的使用

存储字符串并遍历

public class MySet1 {
    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()){
            String s = it.next();
            System.out.println(s);
        }
        System.out.println("-----------------------------------");
        for (String s : set) {
            System.out.println(s);
        }
    }
}

TreeSet集合

TreeSet集合概述和特点

  • 不可以存储重复元素
  • 没有索引
  • 可以将元素按照规则进行排序
    • TreeSet():根据其元素的自然排序进行排序
    • TreeSet(Comparator comparator) :根据指定的比较器进行排序

TreeSet集合基本使用

存储Integer类型的整数并遍历

public class TreeSetDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Integer> ts = new TreeSet<Integer>();

        //添加元素
        ts.add(10);
        ts.add(40);
        ts.add(30);
        ts.add(50);
        ts.add(20);

        ts.add(30);

        //遍历集合
        for(Integer i : ts) {
            System.out.println(i);
        }
    }
}

自然排序Comparable的使用

  • 案例需求

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

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

    学生类

    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 result = this.age - o.age;
            //次要判断条件: 年龄相同时,按照姓名的字母顺序排序
            result = result == 0 ? this.name.compareTo(o.getName()) : result;
            return result;
        }
    }
    

    测试类

    public class MyTreeSet2 {
        public static void main(String[] args) {
            //创建集合对象
            TreeSet<Student> ts = 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);
    		//把学生添加到集合
            ts.add(s1);
            ts.add(s2);
            ts.add(s3);
            ts.add(s4);
            ts.add(s5);
    		//遍历集合
            for (Student student : ts) {
                System.out.println(student);
            }
        }
    }
    

比较器排序Comparator的使用

  • 案例需求

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

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

    老师类

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

    测试类

    public class MyTreeSet4 {
        public static void main(String[] args) {
          	//创建集合对象
            TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
                @Override
                public int compare(Teacher o1, Teacher o2) {
                    //o1表示现在要存入的那个元素
                    //o2表示已经存入到集合中的元素
                  
                    //主要条件
                    int result = o1.getAge() - o2.getAge();
                    //次要条件
                    result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
                    return result;
                }
            });
    		//创建老师对象
            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);
            }
        }
    }
    

两种比较方式总结

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

数据结构

二叉树

  • 二叉树的特点

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

二叉查找树

  • 二叉查找树的特点

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

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

红黑树

  • 红黑树的特点

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

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

    2. 根节点必须是黑色

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

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

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

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

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

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

HashSet集合

HashSet集合概述和特点

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

HashSet集合的基本应用

存储字符串并遍历

public class HashSetDemo {
    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 s : set) {
            System.out.println(s);
        }
    }
}

哈希值

  • 哈希值简介

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

  • 如何获取哈希值

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

  • 哈希值的特点

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

哈希表结构

  • JDK1.8以前

    数组 + 链表

  • JDK1.8以后

    • 节点个数少于等于8个

      数组 + 链表
      
    • 节点个数多于8个

      数组 + 红黑树
      
  • 总结

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

Map集合

Map集合概述和特点

  • Map集合概述

    interface Map<K,V>  K:键的类型;V:值的类型
    
  • Map集合的特点

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

    public class MapDemo01 {
        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);
        }
    }
    

Map集合的基本功能

  • 方法介绍

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

    public class MapDemo02 {
        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);
        }
    }
    

Map集合的获取功能

  • 方法介绍

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

    public class MapDemo03 {
        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);
            }
        }
    }
    

Map集合的遍历(方式1)

  • 遍历思路

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

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

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

Map集合的遍历(方式2)【应用】

  • 遍历思路

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

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

    public class MapDemo02 {
        public static void main(String[] args) {
            //创建集合对象
            Map<String, String> map = new HashMap<String, String>();
    
            //添加元素
            map.put("张无忌", "赵敏");
            map.put("郭靖", "黄蓉");
            map.put("杨过", "小龙女");
    
            //获取所有键值对对象的集合
            Set<Map.Entry<String, String>> entrySet = map.entrySet();
            //遍历键值对对象的集合,得到每一个键值对对象
            for (Map.Entry<String, String> me : entrySet) {
                //根据键值对对象获取键和值
                String key = me.getKey();
                String value = me.getValue();
                System.out.println(key + "," + value);
            }
        }
    }
    

HashMap集合

HashMap集合概述和特点

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

HashMap集合应用案例

  • 案例需求

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

    学生类

    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;
        }
    }
    

    测试类

    public class HashMapDemo {
        public static void main(String[] args) {
            //创建HashMap集合对象
            HashMap<Student, String> hm = new HashMap<Student, String>();
    
            //创建学生对象
            Student s1 = new Student("林青霞", 30);
            Student s2 = new Student("张曼玉", 35);
            Student s3 = new Student("王祖贤", 33);
            Student s4 = new Student("王祖贤", 33);
    
            //把学生添加到集合
            hm.put(s1, "西安");
            hm.put(s2, "武汉");
            hm.put(s3, "郑州");
            hm.put(s4, "北京");
    
            //遍历集合
            Set<Student> keySet = hm.keySet();
            for (Student key : keySet) {
                String value = hm.get(key);
                System.out.println(key.getName() + "," + key.getAge() + "," + value);
            }
        }
    }
    

TreeMap集合

TreeMap集合概述和特点

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

TreeMap集合应用案例

  • 案例需求

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

    学生类

    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 result = o.getAge() - this.getAge();
            //次要条件,按照姓名排序。
            result = result == 0 ? o.getName().compareTo(this.getName()) : result;
            return result;
        }
    }
    

    测试类

    public class Test1 {
        public static void main(String[] args) {
          	// 创建TreeMap集合对象
            TreeMap<Student,String> tm = new TreeMap<>();
          
    		// 创建学生对象
            Student s1 = new Student("xiaohei",23);
            Student s2 = new Student("dapang",22);
            Student s3 = new Student("xiaomei",22);
          
    		// 将学生对象添加到TreeMap集合中
            tm.put(s1,"江苏");
            tm.put(s2,"北京");
            tm.put(s3,"天津");
          
    		// 遍历TreeMap集合,打印每个学生的信息
            tm.forEach(
                    (Student key, String value)->{
                        System.out.println(key + "---" + value);
                    }
            );
        }
    }
    

可变参数

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

格式:

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

底层:

其实就是一个数组

好处:

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

代码演示:

  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)  `:往集合中添加一些元素。

代码演示:

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);
}

Collections类

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> ):将集合中元素按照指定规则排序。

代码演示:

public class CollectionsDemo {
    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 类

public class Student{
    private String name;
    private int age;
	//构造方法
    //get/set
 	//toString
}

测试类:

public class Demo {
    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>() {
  		  @Override
    		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}

不可变集合

什么是不可变集合

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

使用场景

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

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

简单理解:

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

比如说:

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

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

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

不可变集合分类

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

不可变的list集合

public class ImmutableDemo1 {
    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("---------------------------");


        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }
        System.out.println("---------------------------");

        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }
        System.out.println("---------------------------");

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

不可变的Set集合

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


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

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

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

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

        Iterator<String> it = set.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }

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

不可变的Map集合

键值对个数小于等于10

public class ImmutableDemo3 {
    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("--------------------------");

        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "=" + value);
        }
        System.out.println("--------------------------");
    }
}

键值对个数大于10

public class ImmutableDemo4 {
    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");
    }
}

Stream流

体验Stream流

  • 案例需求

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

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

    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流示例代码

    public class StreamDemo {
        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中
    • 代码简洁

Stream流的常见生成方式

  • Stream流的三类方法

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

    • Collection体系集合

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

    • Map体系集合

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

    • 数组

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

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

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

  • 代码演示

    public class StreamDemo {
        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);
        }
    }
    

Stream流中间操作方法

  • 概念

    中间操作的意思是,执行完此方法之后,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代码演示

    public class MyStream3 {
        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代码演示

    public class StreamDemo02 {
        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代码演示

    public class StreamDemo03 {
        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得到的流,并把结果在控制台输出
    //        Stream.concat(s1,s2).forEach(s-> System.out.println(s));
    
            //需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
            Stream.concat(s1,s2).distinct().forEach(s-> System.out.println(s));
        }
    }
    

Stream流终结操作方法

  • 概念

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

  • 常见方法

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

    public class MyStream5 {
        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():返回此流中的元素数
            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));
        }
    }
    

Stream流的收集操作

  • 概念

    对数据使用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集合中
  • 代码演示

    // 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);
    }
    }
    /**
    Stream流的收集方法 toMap方法演示
    创建一个ArrayList集合,并添加以下字符串。字符串中前面是姓名,后面是年龄
    "zhangsan,23"
    "lisi,24"
    "wangwu,25"
    保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值
    */
    public class MyStream8 {
    	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 -> {
                        String[] split = s.split(",");
                        int age = Integer.parseInt(split[1]);
                        return age >= 24;
                    }
    
             //   collect方法只能获取到流中剩余的每一个数据.
             //在底层不能创建容器,也不能把数据添加到容器当中
    
             //Collectors.toMap 创建一个map集合并将数据添加到集合当中
    
              // s 依次表示流中的每一个数据
    
              //第一个lambda表达式就是如何获取到Map中的键
              //第二个lambda表达式就是如何获取Map中的值
            ).collect(Collectors.toMap(
                    s -> s.split(",")[0],
                    s -> Integer.parseInt(s.split(",")[1]) ));
    
            System.out.println(map);
    	}
    }
    

方法引用

体验方法引用

  • 方法引用的出现原因

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

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

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

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

  • 代码演示

    public interface Printable {
        void printString(String s);
    }
    
    public class PrintableDemo {
        pupblic 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");
        }
    }
    
    

方法引用符

  • 方法引用符

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

  • 推导与省略

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

引用类方法

引用类方法,其实就是引用类的静态方法
  • 格式

    类名::静态方法

  • 范例

    Integer::parseInt

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

  • 练习描述

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

    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表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数

引用对象的实例方法

引用对象的实例方法,其实就引用类中的成员方法
  • 格式

    对象::成员方法

  • 范例

    "HelloWorld"::toUpperCase

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

  • 练习描述

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

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

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

      void printUpperCase(String s)

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

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

    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表达式被对象的实例方法替代的时候,它的形式参数全部传递给该方法作为参数

引用类的实例方法

引用类的实例方法,其实就是引用类中的成员方法
  • 格式

    类名::成员方法

  • 范例

    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方法
  • 代码演示

    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表达式被类的实例方法替代的时候
    第一个参数作为调用者
    后面的参数全部传递给该方法作为参数
    

引用构造器

引用构造器,其实就是引用构造方法
  • l格式

    类名::new

  • 范例

    Student::new

  • 练习描述

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

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

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

      Student build(String name,int age);

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

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

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