JDK8 一文搞定👍

! https://zhuanlan.zhihu.com/p/442182870

Java8 新特性

学习来源于 B站 尚硅谷yyds Java学习源码

2021/11/22

  • 距离,过年还有 57 天,想家~🙃
  • JDK1.8已经发布很久了,在很多企业中都已经在使用
  • 虽然,JDK是向下兼容的不会新特性也一样可以正常开发,但是作为程序员还是要不断更新新的技术. 不要求啥都会,但要求能看懂!😘

Java 8 是oracle公司于2014年3月发布

  • 是自Java 5 以 来最具革命性的版本
  • Java 8为Java语言: 编译器、类库、开发 工具与JVM带来了大量新特性.

Lambda表达式

函数式编程

  • Lambda表达式,最早是Python 的语法,简洁优美,一行代码就是一个方法~

    但,说实话可读性 并不是很好 第一次看到这个时候我都懵😵了. 为了不被同事嘲讽,连夜学习了JDK8

  • Lambda 表达式,也可称为闭包 Java 8 发布的最重要新特性

    闭包: 闭包,就是能够读取其他函数内部变量的函数,例如JS中,只有函数内部的子函数才能读取局部变量

    所以:闭包,可以理解成 “定义在一个函数内部的函数“

  • Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中 👍

基本语法:

/**Lambda语法:*/
	(参数列表) -> { 代码块 }

/**说明:*/
/*Lambda表达式是前提基于:函数式接口来实现的: 只包含一个抽象方法的接口,称为函数式接口 */
	左侧:指定了 Lambda 表达式需要的参数列表
	右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能

Lambda实例:

LambdaTest.Java

  • 以 Runnable接口举例: lambda表达式, 就是 匿名实现类 的,另一种优化声明方式:
@Test
public void test1(){
/** JDK8之前,定义Runnable 接口实现多线程.*/
    //1.  类——>实现Runnable接口,创建实例...填入Thread(Runnable r); .start(); 启动线程
    //2.  匿名内部类方式,获取Runnable 接口实例: 创建一个接口实例
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("匿名内部类实现Runnable 接口实例");
        }
    };
    Thread thread1 = new Thread(runnable);
    thread1.start();

/**JDK8之后,定义Runnable Lambda接口实现多线程.*/
    //Lambda
    //  ->左侧: 指定了 Lambda 表达式需要的参数列表, 这里参数列表是空
    //  ->右侧: 指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能;
    Runnable runnable2 = () -> { System.out.println("Lambda实现Runnable 接口实例");};
	Thread thread2 = new Thread(runnable2);
    thread2.start();
    
    //Lambda 优化:
    //  lambda形参列表的参数类型可以省略(类型推断), 如果lambda形参列表只有一个参数,  其一对()也可以省略
    //  lambda体应该使用一对{}包裹, 如果lambda体只有一条执行语句,可能是return语句, 可以省略这一对{}和return关键字.
    Runnable runnable3 = () -> System.out.println("Lambda优化实现Runnable 接口实例");
    Thread thread3 = new Thread(runnable3);
    thread3.start();
    
    System.out.println();
    System.out.println("创建的 Runnable接口实例,正常使用!");
}

控制台结果集:

匿名内部类实现Runnable 接口实例
Lambda实现Runnable 接口实例

创建的 Runnable接口实例,正常使用!
Lambda优化实现Runnable 接口实例

练习:LambdaTest.Java

@Test
public void test2(){
    /** 练习: Comparator 定制排序
         *      Comparator接口,也是一个 "函数式接口": 只包含一个抽象方法的接口,称为函数式接口
         */
        /** JDK8之前,定义Runnable 接口实现多线程.*/
        System.out.println("自然排序/定制排序: 比较基本/引用数据类型,A>B=1 A<B=-1 A==B=0");
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };
        System.out.println("定制排序1: "+com1.compare(1, 2));


        /**JDK8之后,定义Runnable Lambda接口实现多线程.*/
        //Lambda
        Comparator<Integer> com2 = (Integer o1, Integer o2) -> { return Integer.compare(o1,o2); };
        System.out.println("定制排序2: "+com2.compare(3, 2));

        //Lambda 优化:
        //类型推断: 省略类型
        //如果lambda体只有一条执行语句,可能是return语句, 可以省略这一对{}和return关键字.
        Comparator<Integer> com3 = (o1,o2) ->  Integer.compare(o1,o2);
        System.out.println("定制排序3: "+com3.compare(4, 3));

        /** 方法引用 */
        Comparator<Integer> com4 = Integer :: compare;      //后面介绍
        System.out.println("定制排序4: "+com4.compare(2, 2));
}
自然排序/定制排序: 比较基本/引用数据类型A>B=1 A<B=-1 A==B=0
定制排序1: -1
定制排序2: 1
定制排序3: 1
定制排序4: 0

总结👍:

Lambda表达式:依赖于函数式接口, 是对函数式接口的,另一种:实例化形式~👍 更简洁,难懂🙃

->左侧: 指定了 Lambda 表达式需要的参数列表

  • lambda形参列表的参数类型可以省略 (类型推断)
  • 如果,lambda形参列表只有一个参数, 其一对 ()也可以省略

->右侧: 指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能

  • lambda体应该使用一对{}包裹
  • 如果lambda体只有一条执行语句,可能是return语句, 可以省略这一对{}和return关键字. 省略return时候也要省略 {}

场景:

  • 可以在,将 函数式接口的实例作 自定义方法的入参进行传递,完成一些方法内部方便操作...
  • 直接定义函数式接口,调用内部的方法完成某些操作~

函数式接口:

只包含一个抽象方法的接口,称为函数式接口

  • JDK8.0 可以通过Lambda表达式,来创建该接口的对象~ 所有的 函数式接口 都可以通过Lambda表达式类进行 实例化~

@FunctionalInterface 注解

  • JDK8.0提供一个注解来标识管理:

    该注解, 可以检 查它是否是一个函数式接口,同时 Javadoc 也会包含一条声明,说明这个 接口是一个函数式接口.

  • JDK8 Java.util.function包下定义了Java 8 的丰富的函数式接口

    为了方便不同情况的,lambda表达式的使用场景~

函数式接口实例:

Runnable接口举例:ctrl+单机 进入源码:

image-20211123154757423
  • 一个接口,只有一个abstract抽象方法 @FunctionalInterface 注解 修饰.

自定义函数式接口:

WsmInterface.Java

/** 注解可以省略,没有影响,注解只是对程序编写的一个提醒标识~
 *  提示你: 这是一个函数式接口! */
@FunctionalInterface
public interface WsmInterface {
    public abstract void show();
}

Java.util.function 包:

JDK8.0 之后专门为了,Lambda 不同场景提供的不同的函数式接口

Java 内置四大核心函数式接口:

函数式接口 参数类型 返回类型 用途
Consumer< T >
消费型接口
T void 对类型为T的对象应用操作
void accept(T t)
Supplier< T >
供给型接口
T 返回类型为T的对象
T get()
Function<T, R>
函数型接口
T R 对类型为T的对象应用操作,并返回是R类型的对象.
R apply(T t)
Predicate< T >
断定型接口
T Boolean 确定类型为T的对象是否满足某约束, 返回boolean 值
boolean test(T t)

其他接口:

image-20211123162536588

四大核心函数式接口:

LambdaTest2.Java

import org.junit.Test;
import java.util.function.Consumer;

/** 四大核心函数式接口Function */
public class LambdaTest2 {
/** 正常情况下,函数式接口 实例,当作方法参数传递在方法中完成事情~ */

/** 消费型接口 Consumer<T>     void accept(T t) */
    /** ① 声明一个方法传入Consumer<T> 对象实例:  */
    public void con(Double money, Consumer<Double> con){        //<T> 泛型规范了传入的类型~ Double~
        /** ②方法内,使用Consumer<T>类型参数,调用,它对应的方法,还有自己方法内部的一下操作~  */
        System.out.println("con方法调用~");
        con.accept(money);
        System.out.println("");
    };
/** ③实现 */
    /** J8前 */
    @Test
    public void ConsumerTest(){
        //要传入的参数!
        Double dd = 540.0;
        System.out.println("调用 con(Double,Consumer<Double>) 方法");

        //方式一 创建函数式接口的对象,传入接口的实例: (创建方式,匿名内部类~
        Consumer<Double> con1 = new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("接口实例类,参数传递实现: 身上还有"+aDouble+"块钱!");
            }
        };
        //调用方法
        this.con(dd,con1);

        //方式二: 参数匿名内部类实现
        this.con(dd, new Consumer<Double>() {
            @Override
            public void accept(Double adouble) {
                System.out.println("匿名内部类实现: 身上还有"+adouble+"块钱!");
            }
        });
    }

    /** J8后 */
    @Test
    public void ConsumerTest2(){
        //要传入的参数!
        Double dd = 540.0;

        //JDK8 后Lambda表达式,对 参数匿名内部类 的升级
        /** 调用 con(Double,Consumer<Double>) 方法 */
        this.con(dd, (adouble) -> System.out.println("Lambda表达式实现: 身上还有"+adouble+"块钱!") );
    }
}
# ConsumerTest  运行
调用 con(Double,Consumer<Double>) 方法
con方法调用~
接口实例类,参数传递实现: 身上还有540.0块钱!

con方法调用~
匿名内部类实现: 身上还有540.0块钱!

# ConsumerTest2 运行
con方法调用~
Lambda表达式实现: 身上还有540.0块钱!
  • Java.util.function 包下就是,JDK8为了方便用户操作,二提供的一系列的 函数式接口

  • Consumer< T >

    就是一种 函数式接口,可以 定义一个方法,使用该类型 Consumer<T> 作为参数进行方法实现... 完成一些操作.

方法/构造器/数组 引用:

一种更加 高级 的Lambda表达式 的表现形式 本质上就是一种Lambda表达式的 “语法糖”🍬

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用

  • 要求:

    实现接口的抽象方法的 参数列表返回值 类型,必须与方法引用的方法的参数列表和返回值类型保持一致!👍

  • 语法格式:

    使用操作符 :: 将类(或对象) 与 方法名分隔开来

  • 三种场景:

    对象 :: 实例方法名

    类 :: 静态方法名

    类 :: 实例方法名

总结:👍

实现接口的抽象方法的 参数列表返回值 类型, **须与方法引用的方法的参数列表和返回值类型保持一致!👍

也可以理解,方法引用就是更加,简化了Lambda的操作,如果有一个类已经实现了 “函数式接口的方法”`假设有一个函数式接口A	函数式接口A		内部方法 af();	lambda表达式创建 A 的实例	A a = () -> { 方法内部的操作... }	如果 函数式接口 内部的方法,已经有一个B类bf()对其进行了实现,则可以在方法内部直接通过: B对象.bf(); 一行完成方法的调用	A a = () -> {     	B b = new B();    	b.bf();    }方法引用 简化升级: 	B b = new B();		创建B类的 对象;	A a = b::bf;		放法引用的 对象::实例方法名 引用;	 省略方法的参数列表...		

注意:

  • 实现接口的抽象方法的 参数列表返回值 类型,必须与方法引用的方法的参数列表和返回值类型保持一致!👍

    这样: 对象/类 :: 方法名 后面不需要跟着 (参数列表) ,因为函数式接口的方法, 和 实现类方法的 “参数列表一致可以省略...”

对象 :: 实例方法名

com.wsm.met 包下:

A 接口

/** 自定义函数式: */@FunctionalInterfacepublic interface A {    //上面实例是一个 无参, 这里定义一个有参的方法();    public void af(int i);}

B类 实现

/** 自定义类,实现函数式接口 */public class B {    //方法参数列表,与函数式接口相同~    public void bf(int i){        System.out.println("对象::实例方法引用 参数列表i="+i);    }}

MethodTest类 方法引用测试类

import org.junit.Test;/** 方法引用测试类 */public class MethodTest {/** JDK1.8之前创建A接口实例: */    @Test    public void Test(){        A a = new A() {            @Override            public void af(int i) {                System.out.println("JDK8之前实现接口~ 参数列表i="+i);            }        };        a.af(1);    }/** Lambda表达式创建A接口实例: */    @Test    public void Test2(){        //Lambda表示式实现A接口:        A a =  i ->  System.out.println("Lambda表达式实现接口 参数列表i="+i);        int i = 2;        a.af(i);    }/** 对象 :: 非静态方法 */    /** 升级Lambda表达式: 方法引用 */    @Test    public void Test3(){    //① 创建B  类对象;        B b = new B();    //② 方法引用:        A a = b::bf;    //③ 调用方法~        a.af(3);        /** 因为 af(i) 和 bf(i) 方法实现`af实现的操作bf已经完成了` 返回值 参数列表相同~  */        /** 则满足方法引用,直接使用! */    }}

cmd

TestJDK8之前实现接口~ 参数列表i=1Test2Lambda表达式实现接口 参数列表i=2Test3对象::实例方法引用 参数列表i=3

类 :: 静态方法名

对象 :: 实例方法名 Demo测试扩展~

B类 扩展

//类的静态方法, bsf(int i);public static void bsf(int i){    System.out.println("类::static静态方法引用 参数列表i="+i);}

MethodTest类 扩展

/** 类 :: 静态方法 */@Testpublic void Test4(){    System.out.println("Test4");    //① 方法引用:    A a = B::bsf;           //直接通过 类::匹配的静态方法()~    //② 调用方法~    a.af(4);}

cmd

Test4类::static静态方法引用 参数列表i=4

总结👍

  • 对象 :: 非静态方法类 :: 静态方法
  • 实现都类似, 一个通过 对象.实例方法~ 一个 通过类.静态方法 而,类.实例方法 有点不同~

类 :: 实例方法名

MethodTest.Java

/** 类 :: 实例方法  (有难度) */    /** 以Comparator 方法举例: int comapre(T t1,T t2) 方法 */    /** String中的int t1.compareTo(t2) */    @Test    public void Test5(){        System.out.println("Test5");        /** lambda表达式实现 */        Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);        System.out.println("lambda比较两个字符串大小~"+com1.compare("abc","abd"));        Comparator<String> com2 = String :: compareTo;        System.out.println("方法引用: 类 :: 实例方法 比较两个字符串大小~"+com2.compare("abc","abd"));        /**         * 如果:         *  A 函数式接口的的实现来源于~         *    af(T1,T2);   T1类.方法(T2); 实现,则属于 类::实例方法;  的方法引用~         * */    }

cmd

Test5lambda比较两个字符串大小~-1方法引用: 类 :: 实例方法 比较两个字符串大小~-1

类::实例方法 的方法引用, 需要通过, 函数式接口的方法(T1,T2) 参数列表: T1类型.实例方法(T2参数); 完成“函数式接口的实现!”

构造器引用

B.Java 扩展

public String name = "default";public B(){    System.out.println("B() 无参构造");}public B(String name) {    System.out.println("B(name,age) 有参构造");    this.name = name;}//省略get/set

ConstructorRefTest.Java

/** 构造器引用 */    //JDK8提供的函数式接口 Supplier中的T get()  返回一个 T 类型对象    //B 类的空参构造器: B()    public void Test(){        /** JDK8之前 */        Supplier<B> supB = new Supplier<B>() {            @Override            public B get() {              return new B();            };        };        System.out.println("JDK8之前");        System.out.println("无参默认创建的对象: supB.get().getName();"+supB.get().getName());        /** Lambda构造器引用 */        Supplier<B>  supB1 = () -> new B();        System.out.println("Lambda创建的对象: supB.get().getName();"+supB1.get().getName());        Supplier<B>  supB2 = B :: new;        System.out.println("构造器引用创建的对象: supB.get().getName();"+supB2.get().getName());    }/** 构造器引用2.0 */    //Function中的R apply(T t);   Function<T, R>函数式接口,对类型为T的对象应用操作,并返回是R类型的对象    @Test    public void Test2(){        System.out.println("Lambda创建的对象:");        Function<String ,B> func1 = name -> new B(name);        System.out.println("Lambda创建的对象: supB.get().getName()= "+func1.apply("wsm1").getName());        System.out.println("*******\n");        System.out.println("构造器引用创建的对象:");        Function<String ,B> func2 = B :: new;           //参数自动匹配~        System.out.println("Lambda创建的对象: supB.get().getName()= "+func2.apply("wsm2").getName());    }

CMD

TestJDK8之前B() 无参构造无参默认创建的对象: supB.get().getName();defaultB() 无参构造Lambda创建的对象: supB.get().getName();defaultB() 无参构造构造器引用创建的对象: supB.get().getName();defaultTest2Lambda创建的对象:B(name,age) 有参构造Lambda创建的对象: supB.get().getName()= wsm1*******构造器引用创建的对象:B(name,age) 有参构造Lambda创建的对象: supB.get().getName()= wsm2

总结:👍

  • 构造器引用,就是 与函数式接口相结合,自动与函数式接口中方法兼容

  • 可以把构造器引用赋值给定义的方法

注意:

  • 要求构造器参数列表要与接口中抽象 方法的参数列表一致! 且方法的返回值即为构造器对应类的对象

格式: ClassName::new

数组引用:

与构造器引用类似 不详细介绍了...

@Testpublic void Test3(){    Function<Integer,String[]> func1 = length -> new String[length];    String[] arr1 = func1.apply(5);    System.out.println(Arrays.toString(arr1));    System.out.println("*******************");    Function<Integer,String[]> func2 = String[] :: new;    String[] arr2 = func2.apply(10);    System.out.println(Arrays.toString(arr2));}

Stream API

java.util.stream包下

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则 是 Stream API

  • Stream API 把真正的函数式编程风格引入到Java中 这是目前为止对Java类库最好的补充

  • Stream API可以极大提供Java程 序员的生产力,让程序员写出高效率、干净、简洁的代码.

  • Stream 是 Java8 中处理集合的关键抽象概念

    它可以指定你希望对集合进 行的操作,可以执行非常复杂的查找、过滤和映射数据等操作

    Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询,Stream API 提供了一种 高效且易于使用的处理数据的方式

为什么要使用Stream API:

  • 实际开发中,项目中多数数据源都来自于Mysql,Oracle等 关系性数据库
  • 而对于 Redsi MongDB 非关系性数据库 并不能提供,复杂性查询操作:过滤 分组 计算... 而这些NoSQL的数据就需要 Java层面去处理 很麻烦🙃

Stream 和 Collection 集合的区别:

  • Collection 是一种静态的内存数据结构 基于内存的存储数据的空间
  • 而 Stream 是有关计算的CPU计算~

Stream 的操作三个步骤

创建 Stream

  • 一个数据源如:集合、数组,获取一个流

    ①Stream 自己不会存储元素

    ②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream

    ③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行

中间操作

  • 一个中间操作链,对数据源的数据进行处理

终止操作(终端操作)

  • 一旦执行终止操作,就执行中间操作链,并产生结果。之后的Stream对象,不会再被使用

创建 Stream

Emp.Java 自定义操作实体类:

/** 自定义一个实体类: */public class Emp {    private int id;    private String name;    private int age;    private double salary;    //无参构造    public Emp() {    }    //有参构造    public Emp(int id, String name, int age, double salary) {        this.id = id;        this.name = name;        this.age = age;        this.salary = salary;    }    //自定义操作数据集, `模拟从 Redis 数据库获取到的数据集合!`    public static List<Emp> getEmployees(){        List<Emp> list = new ArrayList<>();        list.add(new Emp(1001, "马化腾", 34, 6000.38));        list.add(new Emp(1001, "马化腾", 34, 6000.38));        list.add(new Emp(1002, "马云", 12, 9876.12));        list.add(new Emp(1002, "马云", 12, 9876.12));        list.add(new Emp(1003, "刘强东", 33, 3000.82));        list.add(new Emp(1004, "雷军", 26, 7657.37));        list.add(new Emp(1005, "李彦宏", 65, 5555.32));        list.add(new Emp(1006, "比尔盖茨", 42, 9500.43));        list.add(new Emp(1007, "任正非", 26, 4333.32));        list.add(new Emp(1008, "扎克伯格", 35, 2500.32));        return list;    }    //重新toString();    @Override    public String toString() {        return "Emp{" +                "id=" + id +                ", name='" + name + '\'' +                ", age=" + age +                ", salary=" + salary +                '}';    }    //省略get/set...}    

StreamTest.Java 测试类:创建Stream

import java.util.Arrays;import java.util.List;import java.util.stream.IntStream;import java.util.stream.Stream;/** 强大的Stream Api */public class StreamTest {/** 创建 Stream */    @Test    public void test1(){        /** 方式一: 通过集合: 集合.stream/parallelStream(); 返回一个stream顺序流/并行流~ */        List<Emp> emps = Emp.getEmployees();        //default Stream<E> stream() : 返回一个顺序流        Stream<Emp> stream = emps.stream();        //default Stream<E> parallelStream() : 返回一个并行流        Stream<Emp> parallelStream = emps.parallelStream();        /** Java8 Collection接口添加了新的方法 stream()、parallelStream()、forEach()和removeIf()... */        /** 方式二: 通过数组: Arrays.stream(数组); 返回对应的Stream流对象! */        int[] arr = new int[]{1,2,3,4,5,6};        //调用Arrays类的static <T> Stream<T> stream(T[] array): 返回一个流        IntStream intstream = Arrays.stream(arr);              /** 基本数据类型返回,对应的 xxxstream  Stream流对象~ */        Emp e1 = new Emp(1001,"Tom",34, 6000.38);        Emp e2 = new Emp(1002,"Jerry",34, 6000.38);        Emp[] arr1 = new Emp[]{e1,e2};        Stream<Emp> empStream = Arrays.stream(arr1);                /** 自定类型数组,返回 Stream<自定义类型> 的Sream类型对象! */        /** 方式三: 通过Stream的of() */        Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5, 6);      /** 用的少了解即可~ */    }    /** 方式四: 创建无限流: 通过Stream类的静态方法 iterate()迭代 generate()生成 */    public void test2(){        /** 两种方式: 迭代流 生成流 *///      迭代//      public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)        //参数 T , UnaryOperator函数式接口(内部方法: 传入参数T类型,返回T类型结果,正好用于对T的每次迭代的改变~)        //Stream.iterate(0, t -> t + 2).forEach(System.out::println);        //  注释原因: 迭代流会无限的迭代下去 +2 +2 +2...        //  .limit(10) 之取前十个数据        //  .forEach() 结束操作! 结束时候做的事情~        //遍历前10个偶数        Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);//      生成//      public static<T> Stream<T> generate(Supplier<T> s)        //参数 Supplier函数式接口对象, 内部方法, 返回一个T类型对象... 可以根据后一个规则无限的来生成一些数据~        Stream.generate(Math::random).limit(10).forEach(System.out::println);       /** 生成10个小于 1 随机数! */    }}

总结:

创建Stream 一共有四种方式:

  • 集合.stream/parallelStream(); 返回一个stream顺序流/并行流~

    顺序流:使用主线程,单线程,顺序执行~

    并行流: .parallel()可以将其修改成并行流,内部以多线程并行执行任务的方式执行~

  • 通过数组: Arrays.stream(数组); 返回对应的Stream流对象!

    基本数据类型返回,对应的 xxxstream Stream流对象~

    自定类型数组,返回 Stream<自定义类型> 的Stream类型对象!

  • 通过Stream的of() 使用的少,类似于数组创建Stream流~

  • 创建无限流: 通过Stream类的静态方法 iterate()迭代 generate()生成

中间操作

创建完Stream流对象之后,就可以通过 流对象S.xx().xx().xx() 各种的中间操作,完成对 流种数据的计算: 筛选 切片 映射 排序...等操作

  • 中间操作, 是多个方法, 每个方法可以对流中的数据进行筛选计算~
  • 多个方法可以像链条一样 拼接操作~

中间操作,方法描述

方 法( ); 描 述:
筛选与切片
filter(Predicate p) 接收 Lambda , 从流中排除某些元素,传入一共 函数式接口 (方法参数,传入一个 T 返回Boolean结果)
distinct() 筛选,对流中元素进行 hashCode() 和 equals() 去除重复元素.
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一 个空流。与 limit(n) 互补
映 射
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream
mapToLong(ToLongFunctionf) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream
排 序
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序

示例: Demo

StreamTest2.Java

import org.junit.Test;import java.util.ArrayList;import java.util.Arrays;import java.util.List;import java.util.stream.Stream;/** Stream中间操作 *      创建完Stream流对象之后,就可以通过 流对象S.xx().xx().xx() 各种的中间操作,完成对 流种数据的计算: 筛选 切片 映射 排序...等操作 *      中间操作, 是多个方法, 每个方法可以对流中的数据进行筛选计算~ *      多个方法可以像链条一样 拼接操作~ * */public class StreamTest2 {    /** 筛选与切片 */    @Test    public void test1(){        System.out.println("筛选与切片");        //获取 emp 集合~        List<Emp> employees = Emp.getEmployees();        //JDK8 Collection接口新增,foreach(); 方法: 遍历结果集操作~        employees.forEach(System.out::println);        //集合创建Stream 流~        Stream<Emp> stream = employees.stream();        System.out.println("\nfilter: 从流中排除某些元素");        System.out.println("练习: 查询员工表中薪资大于7000的员工信息");        stream.filter(e->e.getSalary()>7000).forEach(System.out::println);  //filter(Predicate<T>); lambda表达式,每次传入一个流中对象,返回一共 Boolean结果,过滤掉false数据!        /** 注意一共Stream 使用之后就不可以在次使用,流已经关闭了... 但创建流的,集合依然存在~ *///        stream.filter(e->e.getSalary()>7000).forEach(System.out::println);  //异常:stream has already been operated upon or closed 需要重新创建一共新的流~        System.out.println("\ndistinct:筛选,对流中元素进行 hashCode() 和 equals() 去除重复元素");        employees.stream().distinct().forEach(System.out::println);        System.out.println("\nlimit(n)——截断流,使其元素不超过给定数量(获取前几个元素~)");        employees.stream().limit(3).forEach(System.out::println);        System.out.println("\nskip(n) —— 跳过元素(跳过前几个元素不要~)");        employees.stream().skip(3).forEach(System.out::println);    }    /** **映 射** */    @Test    public void test2(){        System.out.println("映射");        System.out.println("\nmap(Function f): ——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素");        List<String> strlist = Arrays.asList("aa", "bb", "cc", "dd");        strlist.stream().map(str -> str.toUpperCase()).forEach(System.out::println);   //遍历每一个元素,对元素进行二次操作~        System.out.println("\n过年了,给所有员工工资+1000");        //获取 emp 集合~        List<Emp> employees = Emp.getEmployees();        System.out.println("遍历一遍集合:");        employees.stream().forEach(System.out::println);        System.out.println("\n加薪: map( 内部函数式接口方法,要求传入什么参数类型,则返回什么类型~ ); 顺便去重 distinct()");        employees.stream().map(emp -> {emp.setSalary(emp.getSalary()+1000.0); return emp; }).distinct().forEach(System.out::println);        System.out.println("\nflatMap(f); 是map(f)的高级版~");        System.out.println("1.Map(f) 实现遍历每一个strlist 的字符");        Stream<Stream<Character>> streamStream = strlist.stream().map(StreamTest2::fromStringToStream);        streamStream.forEach(s ->{            s.forEach(System.out::println);        });        System.out.println("2.flatMap(f) 实现遍历每一个strlist 的字符");        Stream<Character> characterStream = strlist.stream().flatMap(StreamTest2::fromStringToStream);        characterStream.forEach(System.out::println);        System.out.println("flatMap 会将,内部的每一个元素进行操作,如果是Stream元素也会重新拆开执行~");        System.out.println("\n下面的映射,就不详细介绍了: mapToInt(T) mapToLong(T) mapToDouble(T) 传入泛型T 返回对应的类似数据~");    }    /** 将字符串中的多个字符构成的集合转换为对应的Stream的实例 */    public static Stream<Character> fromStringToStream(String str){        ArrayList<Character> list = new ArrayList<>();        for(Character c : str.toCharArray()){            list.add(c);        }        return list.stream();    }    /** flatMap 和 Map :就类似于,数组里面套数组 集合里面套集合 遍历数组和集合的所有元素~*/    @Test    public void flat(){        ArrayList list1 = new ArrayList();        list1.add(1);        list1.add(2);        list1.add(3);        ArrayList list2 = new ArrayList();        list2.add(4);        list2.add(5);        list2.add(6);        /** map 就相当于是 add(集合) *///        list1.add(list2);        /** flatMap 就相当于是 addAll(集合) 将集合拆分,对每个单独的元素进行操作~*///        list1.addAll(list2);        System.out.println(list1);    }    /** 排序 */    @Test    public void test3(){        System.out.println("sorted()——自然排序");        System.out.println("注意,如果是自定义类型需要,实现Comparable接口,int 默认实现了Comparable");        List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);        list.stream().sorted().forEach(System.out::println);        System.out.println("sorted(Comparator com)——定制排序");        list.stream().sorted((e1,e2)->-e1.compareTo(e2)).forEach(System.out::println);        /**         * 排序 A 比较 B         *  返回 0  A相等B         *  返回 -1 A小于B         *  返回 1  A大于B         * */    }}

终止操作(终端操作)

每个Stream流都是有三个步骤:创建 中间操作 终止操作

  • 一共Stream一旦调用了终止操作 就表示改Strema 对象,关闭了, 就不可以在进行操作~
  • 同样,一共没有调用 终止操作 的Stream 是不会结束的, 一直占用系统资源~
  • 终端操作会从流的流水线生成结果,其结果可以是任何不是流的值,例 如:List、Integer 流进行了终止操作后,不能再次使用
方法 描述
匹配与查找
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代)
Stream API 使用内部迭 代——它帮你把迭代做了)
归 约
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一 个值,返回 T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一 个值,返回 Optional< T >
收 集collect⭐
collect(Collector c) 将流转换为其他形式。接收一个 Collector 接口的实现,用于给Stream中元素做汇总 的方法

collect(Collector c)

Collector 接口中方法的实现决定了如何对流执行收集的操作:

  • 如收集到 List、Set、 Map

  • Collectors 实用类提供了很多静态方法

    可以方便地创建常见收集器实例, 具体方法与实例如下表:

方法 返回类型 作用 示例:
toList List< T > 把流中元素收集到List List emps= list.stream().collect(Collectors.toList());
toSet Set< T > 把流中元素收集到Set Set emps= list.stream().collect(Collectors.toSet());
toCollection Collection< T > 把流中元素收集到创建的集合 Collection emps =list.stream().collect(Collectors.toCollection(ArrayList::new));
counting **Long ** 计算流中元素的个数 long count = list.stream().collect(Collectors.counting());
summingInt Integer 对流中元素的整数属性求和 int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt averagingInt 计算流中元素Integer属性的平均值 double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值
如:平 均值
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
方法				返回类型               作用                                   示例:

joining 			String 				连接流中每个字符串
String str= list.stream().map(Employee::getName).collect(Collectors.joining());

maxBy 				Optional<T> 		根据比较器选择最大值
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));

minBy 				Optional<T> 		根据比较器选择最小值
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));

reducing 			归约产生的类型			从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));

collectingAndThen 	转换函数返回的类型 	   包裹另一个收集器,对其结果转换函数
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));

groupingBy 			Map<K, List<T>> 	  根据某属性值对流分组,属性为K,结果为V
Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));

partitioningBy 		Map<Boolean, List<T>> 根据true或false进行分区
Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));

示例:Demo

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/** 终止操作 */
public class StreamTest3 {

    @Test
    public void test(){
        System.out.println("匹配与查找\n");
        List<Emp> employees = Emp.getEmployees();
//        allMatch(Predicate p)——检查是否匹配所有元素。
//          练习:是否所有的员工的年龄都大于18
        boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
        System.out.println(allMatch);

//        anyMatch(Predicate p)——检查是否至少匹配一个元素。
//         练习:是否存在员工的工资大于 10000
        boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
        System.out.println(anyMatch);

//        noneMatch(Predicate p)——检查是否没有匹配的元素。
//          练习:是否存在员工姓“雷”
        boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
        System.out.println(noneMatch);
//        findFirst——返回第一个元素
        Optional<Emp> employee = employees.stream().findFirst();
        System.out.println(employee);
//        findAny——返回当前流中的任意元素
        Optional<Emp> employee1 = employees.parallelStream().findAny();
        System.out.println(employee1);
    }

    @Test
    public void test2(){
        List<Emp> employees = Emp.getEmployees();
        // count——返回流中元素的总个数
        long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
        System.out.println(count);
//        max(Comparator c)——返回流中最大值
//        练习:返回最高的工资:
        Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
        Optional<Double> maxSalary = salaryStream.max(Double::compare);
        System.out.println(maxSalary);
//        min(Comparator c)——返回流中最小值
//        练习:返回最低工资的员工
        Optional<Emp> employee = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        System.out.println(employee);
        System.out.println();
//        forEach(Consumer c)——内部迭代
        employees.stream().forEach(System.out::println);

        //使用集合的遍历操作
        employees.forEach(System.out::println);
    }

    //2-归约
    @Test
    public void test3(){
//        reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
//        练习1:计算1-10的自然数的和
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        Integer sum = list.stream().reduce(0, Integer::sum);
        System.out.println(sum);


//        reduce(BinaryOperator) —— 函数式接口对象——>可以将流中元素反复结合起来,得到一个值,返回 Optional<T>
//        练习2:计算公司所有员工工资的总和
        List<Emp> employees = Emp.getEmployees();
        Stream<Double> salaryStream = employees.stream().map(Emp::getSalary);
        Optional<Double> sumMoney = salaryStream.reduce((d1,d2) -> d1 + d2);
        System.out.println(sumMoney.get());

    }

    //3-收集
    @Test
    public void test4(){
//        collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
//        练习1:查找工资大于6000的员工,结果返回为一个List或Set
        List<Emp> employees = Emp.getEmployees();
        List<Emp> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
        employeeList.forEach(System.out::println);

        System.out.println();
        Set<Emp> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
        employeeSet.forEach(System.out::println);
    }

}

Optional 类

Java应用中最常见的bug就是空值异常

  • 在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException从而避免源码被各种**null**检查污染,以便开发者写出更加整洁的代码

  • Java 8也将Optional加入了官方库

Optional 类(java.util.Optional) 是一个容器类

  • 就是对一共对象,的一共包装~ 保证调用对象之后不会产生 空指针

    它可以保存类型T的值,代表这个值存在,或者仅仅保存null,表示这个值不存在

常用方法:

创建Optional类对象的方法

  • Optional.of(T t) : 创建一个 Optional 实例,t必须非空
  • Optional.empty() : 创建一个空的 Optional 实例
  • Optional.ofNullable(T t):t可以为null

判断Optional容器中是否包含对象

  • boolean isPresent() : 判断是否包含对象
  • void ifPresent(Consumer consumer) :如果有值,就执行Consumer 接口的实现代码,并且该值会作为参数传给它

获取Optional容器的对象

  • T get(): 如果调用对象包含值,返回该值,否则抛异常
  • T orElse(T other) :如果有值则将其返回,否则返回指定的other对象
  • T orElseGet(Supplier other) :如果有值则将其返回,否则返回由 Supplier接口实现提供的对象
  • T orElseThrow(Supplier exceptionSupplier) :如果有值则将其返 回,否则抛出由Supplier接口实现提供的异常

接口默认方法

这个最为简单,可以简单的理解现在的接口中方法可以定默认实现

这样做到了像以前一样的抽象方法实现接口的默认实现,也方便了我们不在需要像以前一样做抽象的模板模式

interface A{	defalut void method1(){		method2(); //默认实现	}	void method2();}

java8接口中除了default method,还提供定义(并实现)静态方法

interface B{	static String method(){		return "xxx";	}}

注解的影响:

本人的注解学习~

新日期API

本人常用类学习

其它新增:

Base64 加解密:

Java 8将Base64 加入到JDK库中 样不需要使用第三方库就可以进行Base64编码

import java.nio.charset.StandardCharsets;import java.util.Base64;/** Base64 加解密 */public class Base {    public static void main(String[] args) {        final String text = "Java慈祥,yyds!";        final String encoded = Base64.getEncoder().encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );        System.out.println("加密:"+encoded);        final String decoded = new String(Base64.getDecoder().decode( encoded ),StandardCharsets.UTF_8 );        System.out.println("解密:"+decoded);    }}

MD5 加解密:

尽然提到加密就顺便题一下MD5 加密:

  • MD5的全称是Message-Digest Algorithm信息-摘要算法
  • MD5其实不算是加密算法,而是一种信息的摘要,它的特性是不可逆的 除了暴力破解 一般逆序算法是得不到结果的一个个实验暴力循环~

举个例子:

  • 1+99=100

    **MD5接到的字符是1和99 然后通过自己的算法最后生成100 **但知道结果是100却很难推测出是通过1+99得来的

  • 再比如 一本书的每一页取一个字,最后通过计算得出一个MD5码

    但却很难通过这个MD5码去推测出这本书的内容...

MD5加密的特点主要有以下几点:

  • 针对不同长度待加密的数据、字符串等等,其都可以返回一个固定长度的MD5加密字符串
  • 其加密过程几乎不可逆,除非维护一个庞大的Key-Value数据库来进行碰撞破解,否则几乎无法解开
  • 对于一个固定的字符串。数字等等,MD5加密后的字符串是固定的,也就是说不管MD5加密多少次,都是同样的结果

java.security.MessageDigest

import java.math.BigInteger;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;/** Md5加密 */public class Md5 {    public static void main(String[] args) throws NoSuchAlgorithmException {        // 生成一个MD5加密计算摘要        MessageDigest md = MessageDigest.getInstance("MD5");        md.update("Java.慈祥,yyds".getBytes());        //digest()最后确定返回md5 hash值,返回值为8位字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符        //BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值        //一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方)        System.out.println(new BigInteger(1, md.digest()).toString(16));    }}

MD5 简单的字符串加密之后可以在线解密,复杂的话解不出来的 在线解密

UUID 🐶

UUID 是Java1.5 就新增的~

  • UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的

UUID由以下几部分的组合:

  1. 当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同
  2. 时钟序列
  3. 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得
import java.util.UUID;String uuid = UUID.randomUUID().toString();

posted on 2021-12-07 14:07  Java慈祥s  阅读(329)  评论(0编辑  收藏  举报

导航