十七、JDK8 新特性(更新)

十七、JDK8 新特性

17.1 JDK8新特性分类

前面已经学过的JDK8新特性介绍:

本章将学习的 JDK8 新特性:

  • Lambda 表达式
  • ✅函数式接口
  • ✅方法引用
  • Stream
  • Base64 编码表
  • ⬜其他的 JDK 新特性见:菜鸟教程 JDK8新特性

17.2 Lambda表达式介绍

17.2.1 Lambda表达式案例

先来看案例:

package com.itheima.lambda;

/**
 * @author: Carl Zhang
 * @create: 2021-12-31 14:57
 * 体验Lambda表达式和传统表达式区别
 * 实现游泳
 */
public class Lambda01 {
    public static void main(String[] args) {
        /*
        * 结论:1. 传统的匿名内部类方法解决接口参数问题,需要 创建匿名内部类对象,实现接口方法,两步 --关注点 怎么做
        *      2. Lambda表达式只需要一条式子,代码简介,关注点更明确在方法功能和输出 -- 关注点 做什么
        *      3. 这种关注方法能做什么的思想就是函数式编程思想
        * */

        //传统方法实现游泳 - 匿名内部类
        swim(new Swim() {
            @Override
            public void swimming() {
                System.out.println("匿名内部类的游泳....");
            }
        });

        //Lambda表达式实现
        swim(() -> System.out.println("匿名内部类的游泳...."));
    }

    public static void swim (Swim swim) {
        swim.swimming();
    }
}

interface Swim {
    /**
     * 游泳
     * */
    void swimming();
}

注意: lambda 表达式可以理解为对匿名内部类的一种简化 , 但是本质是有区别的

17.2.2 引入函数式编程思想

介绍:

  • 在数学中,函数就是有输入量、输出量的一套计算方案,也就是”拿数据做操作”
  • lambda 是就是函数式编程思想的一种体现

17.2.3 函数式编程思想和面向对象编程思想的对比

  • 面向对象思想 :
  • 强调的是用对象去完成某些功能 — 怎么做
  • 函数式编程思想 :
  • 强调的是结果 , 而不是怎么去做 — *做什么

17.3 函数式接口

17.3.1 函数式接口介绍

  • 概念:
  • 只有 一个抽象方法 需要重写的接口就是函数式接口。
  • 函数式接口是允许有其他的非抽象方法的存在例如静态方法,默认方法,私有方法。
  • 注解: 为了标识接口是一个函数式接口,可以在接口之上加上一个注解: @FunctionalInterface
  • 相关APIJDK 中的 java.util.function 包里的接口都是函数式接口

我们以前学的 Runnable 接口也是函数式接口

十七、JDK8 新特性(更新)

也可以自定义一个函数式接口:

package com.itheima.lambda;

/**
 * @author CarlZhang
 * 自定义一个函数式接口
 */
@SuppressWarnings("ALL")
@FunctionalInterface
public interface FunctionalInterface01 {
    /**
     * 只有一个 要重写的 抽象方法
     */
    void method01();

    /**
     * 继承Object类的方法
     */
    @Override
    String toString();

    /**
     * jdk1.8 接口里可以有静态方法和默认方法
     * */
    static void method02() {
        System.out.println("FunctionalInterface01接口里的静态方法");
    }

    /**
     * 默认方法
     * */
    default void method03() {
        System.out.println("FunctionalInterface01接口里的默认方法");
    }
}

@FunctionalInterface
interface FunctionalInterface02 extends FunctionalInterface01{
    //报错:因为此处有两个要重写的抽象法, 一个父类接口的一个此接口的,
    //结论:函数式接口里只能有一个要重写的抽象方法,父类接口里的也算,而Object类比较特殊,
    //      接口里有重写Object类的抽象方法不影响函数式接口的判定

    //void method02();
}

17.3.2 注意事项和使用细节

  • 接口里只能有 一个要重写的 抽象方法,继承自 **Object** 类的方法 除外
  • 可以用注解 @FunctionalInterface 来表示函数式接口

17.4 Lambda表达式的使用

17.4.1 Lambda表达式语法

标准格式(形参列表) - > { //要实现方法的方法体... }

17.4.2 Lambda表达式使用案例

/**
 * @author: Carl Zhang
 * @create: 2021-12-31 16:32
 * 练习1:
 * 1 编写一个接口(ShowHandler)
 * 2 在该接口中存在一个抽象方法(show),该方法是无参数无返回值
 * 3 在测试类(ShowHandlerDemo)中存在一个方法(useShowHandler)
 *   方法的的参数是ShowHandler类型的,在方法内部调用了ShowHandler的show方法
 */
public class ShowHandlerDemo {
    public static void useShowHandler(ShowHandler showHandler) {
        showHandler.show();
    }

    public static void main(String[] args) {
        //调用useShowHandler方法
        //使用Lambda表达式实现ShowHandler接口作为参数
        useShowHandler(() -> {
            System.out.println("使用Lambda表达式实现ShowHandler接口作为参数");
        });
    }

    /*
    * Lambda表达式格式解析
    * 1. () 表示实现的接口里方法的形参列表
    * 2. -> 语法规定,指向要实现的方法内容
    * 3. {} 要实现的方法的方法体
    *
    * 注意:Lambda表达式实现的接口必须是函数式接口
    * */
}

/**
 * @author CarlZhang
 */
@FunctionalInterface
public interface ShowHandler {
    /**
     * 在该接口中存在一个抽象方法(show),该方法是无参数无返回值
     * */
    void show();
}
/**
 * @author: Carl Zhang
 * @create: 2021-12-31 16:48
 * 需求
 * 1 首先存在一个接口(StringHandler)
 * 2 在该接口中存在一个抽象方法(printMessage),该方法是有参数无返回值
 * 3 在测试类(StringHandlerDemo)中存在一个方法(useStringHandler),
 *   方法的的参数是StringHandler类型的,
 *   在方法内部调用了StringHandler的printMessage方法
 */
public class StringHandlerDemo {
    public static void useStringHandler(StringHandler stringHandler) {
        stringHandler.printMessage("Hello, World");
    }

    public static void main(String[] args) {
        //使用lambda表达式实现StringHandler, 作为参数传递
        //结论:
        // 1. () 里的内容对应接口里方法()的内容,是形式参数,lambda表达式看作一个接口实现类
        // 2. 只有一个参数情况下,可以省略()
        useStringHandler(String s -> {
            System.out.println("调用Lambda表达式的代码块 " + s);
        });

        //匿名内部类的方式实现
        useStringHandler(new StringHandler() {
            @Override
            public void printMessage(String s) {
                System.out.println("匿名内部类的方法 " + s);
            }
        });
    }
}

@FunctionalInterface
public interface StringHandler {
    /**
     * 在该接口中存在一个抽象方法(printMessage),该方法是有参数无返回值
     * @param s 任意字符串
     */
    void printMessage(String s);
}
package com.heima.lambda;

import org.omg.CORBA.PUBLIC_MEMBER;

/**
 * @author Carl Zhang
 * @description
 * @date 2022/1/1 20:32
 * 1 首先存在一个接口(Calculator)
 * 2 在该接口中存在一个抽象方法(calc),该方法是有参数也有返回值
 * 3 在测试类(CalculatorDemo)中存在一个方法(useCalculator)
 * 方法的的参数是Calculator类型的
 * 在方法内部调用了Calculator的calc方法
 */
public class CalculatorDemo {
    public static void useCalculator(Calculator calculator) {
        System.out.println(calculator.calc(11, 12));
    }

    public static void main(String[] args) {
        /*
         * 1. 有参有返回值的方法,直接写(形参列表) -> { return 返回值; },
         *    进一步体现 (输入) - > {输出} 的函数式编程思想
         * 2. 参数类型可以省略,有多个参数不能只省略一个
         * 3. 代码块只有一句,则可以省略大括号和分号,甚至return
         * */
        //useCalculator((int num1, int num2) -> {
        //    return num1 + num2;
        //});

        useCalculator((num1, num2) -> num1 + num2);
    }
}

/**
 * @author CarlZhang
 * 1 首先存在一个接口(Calculator)
 * 2 在该接口中存在一个抽象方法(calc),该方法是有参数也有返回值
 */
@FunctionalInterface
public interface Calculator {
    /**
     * 计算两数之和
     * @param num1 第一个数
     * @param num2 第二个数
     * @return 两数之和
     */
    int calc(int num1, int num2);

}

17.4.3 注意事项和使用细节

使用前提Lambda 表达式实现的接口必须是函数式接口
格式解析

  • Lambda表达式可看做函数式接口的一个实现类对象
  • () 表示实现的接口里方法的 形参列表,没有可以空着。
  • 参数类型可以省略,有多个参数不能只省略一个
  • 只有一个参数可以省略 ()
  • -> 语法规定,指向要实现的方法内容
  • {} 要实现的方法的方法体
  • 代码块里只有一句,则可以省略 {};,甚至 return
  • (形参) -> {返回值} 的格式体现了 (输入) -> {输出} 的函数式编程思想

17.4.3 Lambda表达式和匿名内部类的区别

  • 作用对象不同 :
  • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
  • Lambda表达式 :只能是函数式 接口
  • 使用场景不同 :
  • 如果接口中 有且仅有一个抽象方法,可以使用 Lambda 表达式,也可以使用匿名内部类
  • 如果接口中 多于一个抽象方法,只能使用匿名内部类,而不能使用 Lambda 表达式
  • 实现原理不同 :
  • 匿名内部类:编译之后,产生一个单独的 .class 字节码文件
  • Lambda 表达式:编译之后,没有一个单独的 .class 字节码文件。对应的字节码会在 运行的时候动态生成
/**
 * @author Carl Zhang
 * @description Lambda表达式和匿名内部类的区别
 * @date 2022/1/1 21:36
 */
public class LambdaVsAnonymous {
    public static void main(String[] args) {

        //Lambda表达式调用show方法 -- 编译后没有.class文件
        Test test = () -> System.out.println("Hello, World");
        test.show();

        //匿名内部类调用show方法 -- 有LambdaVsAnonymous$1.class文件
        new Test() {
            @Override
            public void show() {
                System.out.println("Hello, World");
            }
        }.show();
    }
}

@FunctionalInterface
interface Test {
    /**
     * 打印方法
     */
    void show();
}

17.5 方法引用 [ 了解 ]

17.5.1 方法引用概述

  • 当使用 Lambda 实现一个逻辑时,如果这个逻辑已经在某个类中存在相同逻辑的方法 ,可以直接引用此功能而不需要写 Lambda
  • 我们可以理解为 Lambda 用来简化匿名内部类 , 方法引用是用来简化 Lambda

17.5.2 方法引用的分类

  • 方法引用的符号 , 是双冒号 ::
  • 简单操作即可 :有方法可以引用的时候,会有黄色警告。只要按Alt+Enter
    | 种类 | 语法格式 |
    | — | — |
    | 静态方法引用 | 类名 :: 静态方法名 |
    | 构造方法引用 | 类名 :: new |
    | 类的任意对象的方法引用 | 类名 :: 实例方法名 |
    | 特定对象的方法引用 | 对象 :: 实例方法名 |

17.5.3 第一种 : 静态方法引用

  • lambda 表达式要实现的功能 , 和某个类的静态方法业务逻辑是一样 , 可以使用静态方法引用

格式: 类名 :: 静态方法名

实践:

  • 已知函数式接口存在一个方法 , 使用方法引用实现此方法
public interface MathTool{
    int max(int a,int b);
}
  • 要实现的功能 max 和 java.lang.Math中的静态方法逻辑是一样的。
public class Demo01 {
    public static void main(String[] args) {
        //匿名内部类
        MathTool m1 = new MathTool() {
            @Override
            public int max(int a, int b) {
                return a > b ? a : b;
            }
        };

        //Lambda
        MathTool m2 = (a, b) -> a > b ? a : b;

        //方法引用
        MathTool m3 = Math::max;

        System.out.println(m1.max(10,20));
        System.out.println(m2.max(10,20));
        System.out.println(m3.max(10,20));
    }
}

17.5.4 第二种 : 构造方法引用

  • 当Lambda表达式要实现的功能是创建指定的对象 ,恰好与某个构造方法功能一样 , 就可以使用构造方法的引用

格式 : 类名::new

实践

  • 在Stream流中,将流中字符串转换为一个学生类型的对象
  • Stream流中的map方法可以将流中的数据封装成对象
// 学生类
class Student {
    private String name;

    // 接收一个字符串 , 并将字符串创建成学生对象
    public Student(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }

    public void show() {
        System.out.println("我是Student类的show方法....");
    }
}
import java.util.function.Function;
import java.util.stream.Stream;

public class ConstructorMethodDemo {
    public static void main(String[] args) {
        // 匿名内部类
        Stream.of("迪丽热巴", "李沁", "柳岩").map(
                new Function() {
                    @Override
                    public Student apply(String s) {
                        return new Student(s);
                    }
                }
        ).forEach(System.out::println);

        // lambda表达式改进
        Stream.of("迪丽热巴", "李沁", "柳岩").map(Student::new).forEach(
                (Student s) -> System.out.println(s)
        );

        // 方法引用改进
        Stream.of("迪丽热巴", "李沁", "柳岩").map(Student::new).forEach((Student s) -> System.out.println(s));

    }

}

17.5.5 第三种 : 任意对象的同一方法引用

  • 类的任意对象调用一个 无参方法时 可以使用

格式: 类名 :: 方法名

实践:

  • Stream流中遍历拿到一个类的多个对象 ,多个对象调用同一个无参方法
  • 需求 : 把数据 “张三”, “李四”, “王五” 存储到Stream流中 , 使用,map方法封装成学生对象
    使用每一个学生对象调用show方法
import java.util.stream.Stream;

public class ObjectMethodDemo {
    public static void main(String[] args) {
        // lambda表达式改进
        Stream.of("迪丽热巴", "李沁", "柳岩").map(Student::new).forEach(
                (Student s) -> {
                    s.show();
                }
        );

        // 方法引用
        Stream.of("迪丽热巴", "李沁", "柳岩").map(Student::new).forEach(
                Student::show
        );
    }
}

17.5.6 第四种 : 指定对象的方法引用

格式: 对象::方法

实践:

  • 流遍历打印输出的简写
举例 : System.out::println : 输出语句的写法就是特定对象的方法引用

lambda表达式  :  (String s) -> {System.out.println(s)}
                此Lambda表达式是为了接受一个数据 , 把这个数据打印在控制台

System.out是一个对象 , 此对象中有一个方法叫做println方法 , println方法也是接受一个数据并打印在控制台

方法引用存在的目的是为了简化lambda表达式的 , 那么就可以使用System.out对象去引用println方法替代Lambda表达式
         (String s) -> {System.out.println(s)}
方法引用: System.out :: println
  • 需求 : 把 “张三” , “李四” , “王五” 字符串存储在Stream流中 , 并输出打印 , 使用方法引用完成
import java.util.stream.Stream;

public class ObjectMethodDemo {
    public static void main(String[] args) {
        Stream.of( "张三" , "李四" , "王五").forEach(
                (String s) -> {
                    System.out.println(s);
                }
        );

        Stream.of( "张三" , "李四" , "王五").forEach(
                System.out::println
        );
    }
}

17.6 Stream 流

17.6.1 Stream的体验

import java.util.ArrayList;

/**
 * @author Carl Zhang
 * @description 体验Stream流的好处
 * @date 2022/1/2 17:28
 * 需求:按照下面的要求完成集合的创建和遍历
 *
 * 1 创建一个集合,存储多个字符串元素
 * "张无忌" , "张翠山" , "张三丰" , "谢广坤" , "赵四" , "刘能" , "小沈阳" , "张良"
 * 2 把集合中所有以"张"开头的元素存储到一个新的集合
 * 3 把"张"开头的集合中的长度为3的元素存储到一个新的集合
 * 4 遍历上一步得到的集合
 */
public class Stream01 {
    public static void main(String[] args) {
        //集合的方式
        //1.创建集合,添加数据
        ArrayList list = new ArrayList<>();
        list.add("张无忌");
        list.add("张翠山");
        list.add("张三丰");
        list.add("谢广坤");
        list.add("赵四");
        list.add("刘能");
        list.add("小沈阳");
        list.add("张良");

        ArrayList newList = new ArrayList<>();
        ArrayList newList2 = new ArrayList<>();

        //"张"开头的元素添加到新集合
        for (String s : list) {
            if (s.startsWith("张")) {
                //3."张"开头的元素添加到新集合
                newList.add(s);
            }
        }

        //"张"开头的且长度为3的添加到另一个元素
        for (String s : newList) {
            if (s.startsWith("张") && s.length() == 3) {
                newList2.add(s);
            }
        }

        //4.打印
        System.out.println(newList);
        System.out.println(newList2);

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

        //Stream流的方式 获取并打印"张"开头的且长度为3的元素 -- 使对容器里数据的操作进行了简化
        list.stream().filter(s -> s.startsWith("张") && s.length()
                == 3).forEach(s -> System.out.println(s));
    }
}

17.6.2 Stream流介绍

十七、JDK8 新特性(更新)

十七、JDK8 新特性(更新)

17.7 Stream流三类方法

17.7.1 Stream流三类方法介绍

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

17.7.2 Stream流 – 获取方法

  • 单列集合
  • 可以使用 Collection 接口中的默认方法 stream() 生成流
  • default Stream<e> stream()</e>
  • 双列集合
  • 双列集合不能直接获取 , 需要间接的生成流
  • 可以先通过 keySet() 或者 entrySet()获取一个 Set 集合,再获取 Stream
  • 数组
  • Arrays 中的静态方法 stream 生成流
import java.util.*;
import java.util.stream.Stream;

/**
 * @author Carl Zhang
 * @description Stream流的获取方法
 * @date 2022/1/2 17:59
 */
@SuppressWarnings("ALL")
public class StreamGetMethod {
    public static void main(String[] args) {
        //获取单列集合的Stream流
        singleSetStream();

        //获取双列集合的Stream流
        doubleSetStream();

        //获取数组的Stream流
        arrayStream();

        //获取任意元素的stream流 --了解
        int[] array = {1, 2, 3, 4, 5, 6};
        Stream.of(array).forEach(i -> System.out.println(i)); //[I@7ba4f24f
        Stream.of(1, 2, 3, 4, 5, 6).forEach(i -> System.out.println(i));
    }

    private static void arrayStream() {
        System.out.println("获取数组的Stream流");
        int[] arr = {1, 2, 3, 4, 5, 6};
        Arrays.stream(arr).forEach(i -> System.out.println(i));
    }

    private static void doubleSetStream() {
        HashMap hashMap = new HashMap<>();
        hashMap.put("it001", "曹植");
        hashMap.put("it002", "曹丕");
        hashMap.put("it003", "曹熊");
        hashMap.put("it004", "曹冲");
        hashMap.put("it005", "曹昂");

        // 双列集合不能直接获取 , 需要间接的生成流
        // 可以先通过keySet或者entrySet获取一个Set集合,再获取Stream流
        System.out.println("获取双列集合的Stream流");
        Set> entries = hashMap.entrySet();
        entries.stream().forEach(entry -> System.out.println(entry.getKey() +
                "-" + entry.getValue()));
    }

    private static void singleSetStream() {
        ArrayList list = new ArrayList<>();
        list.add("迪丽热巴");
        list.add("古力娜扎");
        list.add("马尔扎哈");
        list.add("欧阳娜娜");

        // 可以使用Collection接口中的默认方法stream()生成流
        // default Stream stream()
        System.out.println("获取单列集合的Stream流");
        list.stream().forEach((String s) -> {
            System.out.println(s);
        });
    }
}

17.7.3 Stream流 – 中间方法

特点:返回了 Stream 流对象,用以继续调用方法进行操作流对象

  • Stream<t> filter(Predicate predicate)</t> :用于对流中的数据进行过滤
  • Predicate 接口中的方法 : boolean test(T t) :对给定的参数进行判断,返回一个布尔值
  • Stream<t> limit(long maxSize)</t> :截取指定参数个数的数据
  • Stream<t> skip(long n)</t> :跳过指定参数个数的数据
  • static <t> Stream<t> concat(Stream a, Stream b)</t></t> :合并a和b两个流为一个流
  • Stream<t> distinct()</t> :去除流中重复的元素。依赖(hashCode和equals方法)
  • Stream<t> sorted ()</t> : 将流中元素按照自然排序的规则排序
  • Stream<t> sorted (Comparator<? super T> comparator)</t> : 将流中元素按照自定义比较器规则排序
import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * @author Carl Zhang
 * @description Stream流的中间方法
 * @date 2022/1/2 19:12
 */
@SuppressWarnings("ALL")
public class StreamCentreMethod {
    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        list.add("张无忌");
        list.add("张翠山");
        list.add("张三丰");
        list.add("谢广坤");
        list.add("赵四");
        list.add("刘能");
        list.add("小沈阳");
        list.add("张良");
        list.add("张良");
        list.add("张良");
        list.add(new String("张良"));

        //1 Stream filter(Predicate predicate):用于对流中的数据进行过滤
        //  Predicate函数式接口的方法 : boolean test(T t):对给定的参数进行判断,返回一个布尔值
        //  T 是泛型,Stream流里的元素类型
        //  返回true就留下,false就过滤掉
        //打印集合中三个字名字的元素
        //list.stream().filter(new Predicate() {
        //    @Override
        //    public boolean test(String s) {
        //        return s.length() == 3;
        //    }
        //}).forEach(s -> System.out.println(s));
        list.stream().filter(s -> s.length() == 3).forEach(s -> System.out.println(s));

        //2 Stream limit(long maxSize):截取指定参数个数的数据
        //获取前两个元素
        Stream stream = list.stream();
        stream.limit(2).forEach(s -> System.out.println(s));

        //3 Stream skip(long n):跳过指定参数个数的数据
        //跳过前两个元素,打印后面的元素
        //异常IllegalStateException:stream has already been operated upon or closed
        //stream.skip(2).forEach(s -> System.out.println(s));
        list.stream().skip(2).forEach(s -> System.out.println(s));

        //4 static  Stream concat(Stream a, Stream b):合并a和b两个流为一个流
        ArrayList list2 = new ArrayList<>();
        list2.add("迪丽热巴");
        list2.add("古力娜扎");
        list2.add("欧阳娜娜");
        list2.add("马尔扎哈");
        Stream.concat(list.stream(), list2.stream()).forEach(s -> System.out.println(s));

        //5 Stream distinct():去除流中重复的元素。依赖(hashCode()和equals())
        list.stream().distinct().forEach(s -> System.out.println(s));
        //这里只剩一个"张良",因为String里的方法根据value的值来返回hashCode
        //new String("张良").hashCode();

        //6 Stream sorted () : 将流中元素按照自然排序的规则排序
        list.stream().sorted().forEach(s -> System.out.println(s));

        //7 Stream sorted (Comparator comparator) : 将流中元素按照自定义比较器规则排序
        list.stream().sorted(new Comparator() {
            @Override
            public int compare(String o1, String o2) {
                return o2.length() - o1.length();
            }
        }).forEach(s -> System.out.println(s));
        System.out.println();
        list.stream().sorted((s1, s2) -> s2.length() - s1.length()).forEach(
                s -> System.out.println(s)
        );
    }
}

17.7.4 Stream流 – 终结方法

特点:空返回值,不能继续调用方法进行操作流对象

  • void forEach(Consumer action) :对此流的每个元素执行操作
  • Consumer 接口中的方法 void accept(T t) :对给定的参数执行此操作
  • long count() :返回此流中的元素数
import java.util.ArrayList;
import java.util.function.Consumer;

/**
 * @author Carl Zhang
 * @description Stream流的终结方法
 * @date 2022/1/2 19:48
 */
public class StreamEndMethod {
    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        list.add("张无忌");
        list.add("张翠山");
        list.add("张三丰");
        list.add("谢广坤");

        //1 void forEach(Consumer action):对此流的每个元素执行操作
        //  Consumer接口中的方法 void accept(T t):对给定的参数执行此操作
        // 把list集合中的元素放在stream流中
        // forEach方法会循环遍历流中的数据
        // 并循环调用accept方法 , 把数据传给s
        // 所以s就代表的是流中的每一个数据
        // 我们只要在accept方法中对数据做业务逻辑处理即可
        list.stream().forEach(s -> System.out.println(s));

        //2. long count():返回此流中的元素数
        //结果:4
        System.out.println(list.stream().count());
    }
}

17.8 Stream流的收集方法

17.8.1 使用收集方法的原因

问题:使用 Stream 流的方式操作完毕之后,我想把流中的数据起来,该怎么办呢?
解决:引出收集方法

package com.heima.stream;

import java.util.ArrayList;

/**
 * @author Carl Zhang
 * @description 使用收集方法的原因
 * @date 2022/1/2 20:04
 * 需求:过滤元素并遍历集合
 * 定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
 * 将集合中的奇数删除,只保留偶数。
 * 遍历集合得到2,4,6,8,10
 */
public class CollectionMethod01 {
    public static void main(String[] args) {
        //JDK9 新特性 直接传入一个不可变集合的元素,来创建新集合
        //ArrayList list = new ArrayList<>(List.of(1,2,3,4,5,6,7,8,9,10));
        ArrayList list = new ArrayList<>();
        for (int i = 1; i  i % 2 == 0).forEach(i -> System.out.println(i));

        //结论:list集合里的数未改变
        //如果需要保留流里面过滤后的元素 -> 使用收集方法
        System.out.println(list);
    }
}

17.8.2 收集方法介绍

Stream 流的收集方法

  • R collect(Collector collector) : 此方法只负责收集流中的数据 , 创建集合添加数据动作需要依赖于参数

收集方法也可以看作一种终结方法,调用完 collect() 不返回 Stream 对象,不可再对流进行操作

17.8.3 三种收集方式

工具类 Collectors 提供了具体的收集方式

  • public static <t> Collector toList()</t> :把元素收集到List集合中
  • public static <t> Collector toSet()</t> :把元素收集到Set集合中
package com.heima.stream;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * @author Carl Zhang
 * @description Stream流的收集方法
 * @date 2022/1/2 20:22
 * 需求 :
 * 定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
 * 将集合中的奇数删除,只保留偶数。
 * 遍历集合得到2,4,6,8,10
 */
public class CollectionMethod02 {
    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        for (int i = 1; i  Collector toList():把元素收集到List集合中
        List list1 = list.stream().filter(i -> i % 2 == 0).collect(
                Collectors.toList());
        System.out.println(list1); //[2, 4, 6, 8, 10]

        //将过滤好的元素收集到Set集合里
        //public static  Collector toSet():把元素收集到Set集合中
        Set set = list.stream().filter(i -> i % 2 == 0).collect(
                Collectors.toSet()
        );
        System.out.println(set); //[2, 4, 6, 8, 10]
    }
}

public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中

import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Carl Zhang
 * @description 将过滤好的元素收集到Map集合里
 * @date 2022/1/2 20:33
 * public static  Collector toMap(Function keyMapper,Function valueMapper):
 * 把元素收集到Map集合中
 *
 * 1 创建一个ArrayList集合,并添加以下字符串。字符串中前面是姓名,后面是年龄
 * "zhangsan,23"
 * "lisi,24"
 * "wangwu,25"
 * 2 保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值
 */
public class CollectionMethod03 {
    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        list.add("zhangsan,23");
        list.add("lisi,24");
        list.add("wangwu,25");

        //筛选出24岁以上的元素
        Stream stream = list.stream().filter(s -> {
            String[] split = s.split(",");
            //获取年龄
            int age = Integer.parseInt(split[1]);
            //筛选出大于等于24岁的
            return age >= 24;
        });

        //将过滤好的元素收集到Map集合
        //public static  Collector toMap(Function keyMapper,Function valueMapper):
        Map collect = stream.collect(Collectors.toMap(
                // 获取键:Function keyMapper -- 传入一个函数式接口的实现类
                // s 表示流里的元素
                // 获取第一个元素,做为键
                s -> s.split(",")[0],
                // 获取值Function valueMapper)
                s -> s.split(",")[1]
        ));

        //遍历
        Set> entries = collect.entrySet();
        for (Map.Entry entry : entries) {
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }
    }
}

17.9 Base64(编码表) [了解]

17.9.1 Base64 介绍

编码表 :定义了字符和数字的映射关系

Base64 编码是一种常用的字符编码,在很多地方都会用到,他核心作用是 保证传输数据的正确性,有些系统只能使用 ASCII 字符(本文字符) , Base64 就是用来将 **ASCII** 字符 的数据转换成 **ASCII** 字符的一种方法,而且 base64 特别适合在后面学习到的 http , mime 协议下快速传输数据。

  • MIME : 是描述消息内容类型的因特网标准。消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。
  • base64 不是安全领域下的加密解密算法。能起到安全作用的效果很差,而且很容易破解。
  • 只是对数据的编解码 , 不是加密解密算法
  • Base64 编码表由以下64个字符组成部分 :
  • 26个英文字母包含大小写 :52个
  • 0~9 : 10个
    • / : 2

十七、JDK8 新特性(更新)
  • 如何将普通的数据转换为 Base64 字符数据。
编码的规则:把3个字节变成4个字节。
原始数据 : 3 x 8bit = 24 bit
目标数据 : 24 / 4bit = 6 bit

例如 :
转换前 11111111, 11111111, 11111111 (二进制)
转换后 00111111, 00111111, 00111111, 00111111 (二进制)

一个字节代表的范围 :
    最小的字节是 0000 0000  -> 对应Base64是 0 编号 -> A
    最大的字节是 0011 1111  -> 对应Base64是 63编号 -> /
    所以一个字节可以代表的是Base64所有的字符

解码的规则 :
    编码的规则反过来  (把0去掉,所有二进制加在一起除以三 , 就可以恢复三个字节)
  • 编码 : 如何将普通的数据编码成为Base64数据
  • 把三个字节变成四个字节
  • 解码 : 如何将 Base64 数据解码成为普通数据
  • 把四个字节在恢复三个字节
  • 注意 : 这个普通数据可以是 : 文本,音频,视频,可以是任何的数据。

17.9.2 Base64 API

JDK8 版本中, Base64 编码已经成为 Java 类库的标准。 内置了 Base64 编码的编码器和解码器。

  • java.util.Base64 工具类中存在两个静态内部类:
    | 序号 | 内嵌类 & 描述 |
    | — | — |
    | 1 | static class Base64.Decoder该类实现一个解码器,使用 Base64来解码字节数据。 |
    | 2 | static class Base64.Encoder该类实现一个编码器,使用 Base64来编码字节数据 |

  • 编码器: **Encoder**
    目的:就是将普通的字节数据变成 Base64 的数据

byte[] encode(byte[] src)
    使用Base64将指定字节数组中的所有字节 编码为新分配的字节数组。

String encodeToString(byte[] src)
    使用Base64将指定的字节数组,编码为字符串。

OutputStream wrap(OutputStream os)
    使用Base64将参数中字节流中的数据,编码为base64格式的字节流
  1. 解码器: **Decoder**
    目的 :将 Base64 的数据变成普通正常的数据
byte[] decode(byte[] src)
    使用Base64从输入字节数组中解码所有字节,将结果写入新分配的字节数组。

byte[] decode(String src)
    使用Base64将Base64编码的字符串,解码为新分配的字节数组。

InputStream wrap(InputStream is)
    用于解码Base64编码字节流 , 返回一个输入流

17.9.3 Basic 编码器

  • 使用的字符A-Za-z0-9+/ 编解码
A-Za-z0-9+/

是标准的 BASE64 编码,用于处理常规的需求 (编码少量数据)

static Base64.Decoder getDecoder()
    返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。

static Base64.Encoder getEncoder()
    返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。
  • 【代码实例】
package com.heima.base64;

import java.util.Base64;

/**
 * @author Carl Zhang
 * @description
 * 小明要给小红发送一句话,这句话使用Base64编码
 * 小红解码查看消息
 * @date 2022/1/9 17:15
 */
public class Base6401 {
    public static void main(String[] args) {
        //编码:
        //获取base64的编码器
        Base64.Encoder encoder = Base64.getEncoder();

        //将字符串转成字节数组
        byte[] bytes = "hello, world".getBytes();

        //调用方法把字符串编码成base64编码的字符串
        String base64String = encoder.encodeToString(bytes);

        System.out.println("小明:" + base64String); //aGVsbG8sIHdvcmxk

        //解码
        //获取base64的解码器
        Base64.Decoder decoder = Base64.getDecoder();

        //调用方法对base64编码的字符串进行解码,获取普通编码的字节数组
        byte[] decode = decoder.decode(base64String);

        //把字节数组转换成字符串打印
        System.out.println("小红:" + new String(decode)); //hello, world
    }
}

17.9.4 URL 编码器

专门对URL进行编解码

  • 使用的字符 A-Za-z0-9 -_ 编解码
  • 一般用于对网址进行编解码
 A-Za-z0-9-_
  • Base64 的静态方法:
static Base64.Decoder getUrlDecoder()
    返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。

static Base64.Encoder getUrlEncoder()
    返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。

【代码实例】

import java.util.Base64;

/**
 * @author Carl Zhang
 * @description URL编码
 * 小明给小灰发送了一个网址,使用Base64编码了。
 * 小灰解码看网址
 * @date 2022/1/9 17:29
 */
public class Base6402 {
    public static void main(String[] args) {
        //编码
        //获取Url编码器
        Base64.Encoder urlEncoder = Base64.getUrlEncoder();

        //将字符串转成字节数组
        byte[] simpleUrl = "https://820.workarea7.live/index.php".getBytes();

        //调用方法对字节数组编码成base64编码的字符串
        String base64Url = urlEncoder.encodeToString(simpleUrl);
        System.out.println("url编码 = " + base64Url); //aHR0cHM6Ly84MjAud29ya2FyZWE3LmxpdmUvaW5kZXgucGhw

        //解码
        //获取Url解码器
        Base64.Decoder urlDecoder = Base64.getUrlDecoder();

        //将Base64的字符串解码成普通编码的字节数组
        byte[] decode = urlDecoder.decode(base64Url);

        //将字节数组转换成字符串并打印
        System.out.println("url解码 = " + new String(decode)); //https://820.workarea7.live/index.php
    }
}

17.9.5 MIME 编码器

  • 使用的字符 A-Za-z0-9+/ 编解码
  • 并且对 MIME 格式友好 :每一行输出不超过76个字符 ,而且每行以”\r\n”符结束
static Base64.Encoder getMimeEncoder()
    返回一个Base64.Encoder编码使用MIME型base64编码方案。

static Base64.Decoder getMimeDecoder()
    返回一个Base64.Decoder解码使用MIME型BASE64解码方案。
  • 如果需要将字节流包装成为具有编码解码的能力,分别使用 EncoderDecoderwrap 方法,如下:
Encoder:
  OutputStream wrap(OutputStream os) 使用Base64编码方案包装用于编码字节数据的输出流。

Decoder:
  InputStream wrap(InputStream is) 返回一个输入流,用于解码Base64编码字节流。

【代码实例】

  • 需求:设计两个方法分别实现对文件的编码和解码
import java.io.*;
import java.util.Base64;

/**
 * @author Carl Zhang
 * @description MIME编码解码
 * @date 2022/1/9 17:48
 */
public class Base6403 {
    public static void main(String[] args) throws IOException {
        //调用方法对文件进行编码
        encodeFile("img\\测试图片.png", "img\\编码后的测试图片.txt", Base64.getMimeEncoder());

        //调用方法对编码后的文件进行解码
        decodeFile("img\\编码后的测试图片.txt", "img\\解码后的测试图片.png", Base64.getMimeDecoder());
    }

    /**
     * 实现对文件进行Base64编码得到新文件
     *
     * @param src     代表的是源文件的路径
     * @param dest    代表的是目标文件的路径
     * @param encoder 编码器对象
     * @throws IOException
     */
    public static void encodeFile(String src, String dest, Base64.Encoder encoder) throws IOException {
        // 把普通的字节输出流封装成base64字节输出流
        OutputStream fos = encoder.wrap(new FileOutputStream(dest));

        // 创建一个普通的字节输入流
        FileInputStream fis = new FileInputStream(src);

        byte[] bytes = new byte[1024];
        int len;
        // 读取原始的文件数据
        while ((len = fis.read(bytes)) != -1) {
            // 写出去的是Base64数据
            fos.write(bytes, 0, len);
        }
        // 释放资源
        fis.close();
        fos.close();
    }

    /**
     * 实现对Base64编码文件,解码得到源文件
     *
     * @param src     代表的是源文件的路径
     * @param dest    代表的是目标文件的路径
     * @param decoder 编码器对象
     * @throws IOException
     */
    public static void decodeFile(String src, String dest, Base64.Decoder decoder) throws IOException {
        // 把普通的字节输入流 , 封装成base64字节输入流
        InputStream fis = decoder.wrap(new FileInputStream(src));

        //将普通的字节输入流,包装成为一个能够解码Base64数据的输入流
        OutputStream fos = new FileOutputStream(dest);

        byte[] bytes = new byte[1024];
        int len;
        //按照Base64的解码方案进行读取
        while ((len = fis.read(bytes)) != -1) {
            //将正常的数据写到文件中
            fos.write(bytes, 0, len);
        }

        // 释放资源
        fis.close();
        fos.close();
    }
}

Original: https://www.cnblogs.com/Carl-Zhang/p/15763065.html
Author: Carl-Zhang
Title: 十七、JDK8 新特性(更新)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/572490/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • MyBatis 结果映射总结

    前言 结果映射指的是将数据表中的字段与实体类中的属性关联起来,这样 MyBatis 就可以根据查询到的数据来填充实体对象的属性,帮助我们完成赋值操作。其实 MyBatis 的官方文…

    Java 2023年6月9日
    076
  • 2021总结、2022展望

    2021总结 2021总结 参与开源 Skywalking Incubator-Shenyu 学习内容 2022展望 深入学习Golang及相关技术栈 参与公司Oteam 组织后端…

    Java 2023年6月5日
    063
  • 立个flag

    每天刷1道以上算法题! posted @2022-09-13 20:51 pzistart 阅读(8 ) 评论() 编辑 Original: https://www.cnblogs…

    Java 2023年6月15日
    094
  • hibernate and spring links

    http://blog.itpub.net/post/329/1756 http://blog.itpub.net/category/11/76 http://domaindriv…

    Java 2023年5月29日
    082
  • springboot整合三 共享session,集成springsession

    Mave依赖 参数配置 2.1 redis 配置: 2.1 若使用yml文件,则如下配置 设置Redis支持的Spring Session 3.1 方案一 基于springboot…

    Java 2023年5月30日
    073
  • 自用代码css获取任意网址的/favicon.ico的方法教程

    尝试过使用网友说的API接口获取 找到的都是失效了 暂时就使用这种办法获取 如果有好的方法望评论告知 谢谢 html;gutter:true; alt="" w…

    Java 2023年6月5日
    083
  • harbor安装

    Harbor 简介 Docker容器应用的开发和运行离不开可靠的镜像管理,虽然Docker官方也提供了公共的镜像仓库,但是从安全和效率等方面考虑,部署我们私有环境内的Registr…

    Java 2023年6月15日
    096
  • Java后端代码规范与优化建议

    1、尽量指定类、方法的final修饰符 带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String,整个类都是f…

    Java 2023年5月29日
    066
  • gulp: Did you forget to signal async completion? 解决方案

    学习gulp的前端自动化构建,按照示例代码,跑了一个简单的task,控制台打出如下提示: The following tasks did not complete: testGul…

    Java 2023年6月15日
    062
  • 0010自助旅游管理系统-java毕设

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年5月29日
    063
  • HashMap底层原理及jdk1.8源码解读

    一、前言 写在前面:小编码字收集资料花了一天的时间整理出来,对你有帮助一键三连走一波哈,谢谢啦!! HashMap在我们日常开发中可谓经常遇到,HashMap 源码和底层原理在现在…

    Java 2023年6月15日
    079
  • 订单缓存查询实践

    最近在做订单缓存查询相关需求,记录下该过程中缓存查询考虑的几个问题以及处理方案。 实际场景中使用缓存都是先去缓存中查询,如果缓存没有命中,在去查询数据库并将结果缓存。如果查询一个在…

    Java 2023年6月7日
    071
  • [学习笔记] Java数据类型

    Java有两种数据类型:基本数据类型和引用数据类型 基本数据类型 CPU可直接对基本数据类型进行运算,Java提供8种基本数据类型:字符型、布尔型、四种整型、两种浮点型; 计算机内…

    Java 2023年6月5日
    085
  • Mybatis系列全解(三):Mybatis简单CRUD使用介绍

    Mybatis系列全解(三):Mybatis简单CRUD使用介绍 Mybatis系列全解(三):Mybatis简单CRUD使用介绍 – + 前言 Mybaits系列全解…

    Java 2023年6月7日
    074
  • java反编译工具——Jd-gui

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年5月29日
    060
  • JAVA中“LIST泛型MAP根据某一个KEY去重

    JAVA中”LIST泛型MAP根据某一个KEY去重 Java中”List泛型Map根据某一个key去重,保留一个数据。利用jdk8stream()流实现去重…

    Java 2023年5月29日
    060
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球