menu 冷 の Codeworld
search self_improvement
目录

Java 8 更新的新特性 (函数式接口 lambda stream option)

冷环渊
冷环渊 2022年04月03日  ·  阅读 375

Java8 新特性

Java 8 (又称为 jdk 8 ) 是java语言开发的一个主要版本,Java8是 oracle 公司 2014 4月发布的,可以看成自java5 以来最具革命性的版本,Java 8 为java 语言,编译器,类库,开发功能与JVM带来了大量的新特性

Java 8 新特性介绍

  • 速度更快
  • 代码更少(增加了 新的语法 :Lambada 表达式)
  • 强大的 Stream API
  • 便于并行 提高程序的运行速度 例: 遍历长度为 100k的数组 快速排序快了 4.7倍
  • 最大化减少空指针异常 Optional
  • Nashom 引擎:允许Jvm上运行JS应用

Lambda表达式(重点)

为什么要使用Lambda 表达式

lambda 表达式是一个匿名函数 ,我们可以把Lambda 表达式理解为是一段可以传递的代码,(将代码像数据一样进行·传递),使用它可以写出更简洁更灵活的代码,作为一种更紧凑的代码风格,使用java的语言表达能力得到了提升.

理论+实践方便理解

Lambda 表达式 coding

简单的用 Lambda做一个hello world的演示

  • 这个语法主要做的就是简化了我们语法
  • 帮我们省去的大多固定要编写的东西
  • 从而达到 语法的简化
/**
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/10
 * @context:<h4>Lambda 表达式 实操案例</h4>
 */
public class LambdaTest {
    /**
     * <h2>示例 1  无参数 传统语法 和 lambda 表达式的区别</h2>
     */
    @Test
    public void Test1() {
        // 传统 的 创建一个 线程 打印
        Runnable r1 = new Runnable() {

            @Override
            public void run() {
                System.out.println("我爱北京天安门");
            }
        };
        r1.run();

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


        // 用 lambda 表达式 的方法
        Runnable r2 = () -> System.out.println("我爱看北京天安门");
        r2.run();
    }

    /**
     * @return :  * @return : null
     * @author : <h2>冷环渊</h2>
     * @date : 2021/12/10
     * @context: <h3>有参数的 Lambda 表达式 传统和 新语法</h3>
     * @params :  null
     */
    @Test
    public void Test2() {
        //传统的语法 对象 实现对应方法
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        };
        int compare1 = com1.compare(12, 21);
        System.out.println(compare1);
        System.out.println("***********************");
        /*
         *  lambda表达式 有参数 语法
         *  函数式接口 使用这个接口你一定要实现且只要是先一个接口的时候就可以使用 Lambda 表达式
         *   @FunctionalInterface
         *   public interface Comparator<T> {
         *   int compare(T o1, T o2);
         *    }
         *  使用之后 就变成来很简洁的语法  他代替我们省区了很多已知的东西
         *  Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
         * */
        Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
        int compare2 = com2.compare(32, 21);
        System.out.println(compare2);
        System.out.println("***********************");

        //我们还可以更加的简化
        //方法引用
        Comparator<Integer> com3 = Integer::compare;
        int compare3 = com3.compare(20, 21);
        System.out.println(compare3);
        System.out.println("***********************");
    }

}

简单的认识了一下 Lambda表达式,我们来看看 不同情况下的lambda表达式的语法变化

/**
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/10
 * @context: <h4>
 * 1. 举例子(o1,o2)-> Integeer.compare(o1,o2)
 * 2.格式:
 * -> Lambda操作符 或者叫箭头操作符
 * ()形参列表 相当于是接口中的抽象方法的参数列表,可以省去类型
 * ->右边 Lambda体 其实就是重写抽象对象的方法体
 * 3.Lambda 表达式的使用(有六种情况可以介绍)
 * 4.本质 Lambda 表达式的本质:作为接口的实例
 * </h4>
 */
public class LambdaTest1 {
    /**
     * @return :  * @return : null
     * @author : <h2>冷环渊</h2>
     * @date : 2021/12/10
     * @context: <h3>语法格式一 :没有参数 没有返回值 </h3>
     * @params :  null
     */
    @Test
    public void Test1() {
        // 传统 的 创建一个 线程 打印
        Runnable r1 = new Runnable() {

            @Override
            public void run() {
                System.out.println("我爱北京天安门");
            }
        };
        r1.run();

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


        // 用 lambda 表达式 的方法
        Runnable r2 = () -> System.out.println("我爱看北京天安门");
        r2.run();
    }

    /**
     * @return :  * @return : null
     * @author : <h2>冷环渊</h2>
     * @date : 2021/12/10
     * @context: <h3> 语法表达式二 Lambda 需要一个参数 但是没有返回值 </h3>
     * @params :  null
     */
    @Test
    public void test2() {
        //传统的区别
        Consumer<String> con = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        con.accept("谎言和誓言的区别");
        System.out.println("**************************");
        //使用lambda
        Consumer<String> con2 = (String s) -> {
            System.out.println(s);
        };
        con2.accept("给你一拳,再给你一拳");
    }

    /**
     * @return :  * @return : null
     * @author : <h2>冷环渊</h2>
     * @date : 2021/12/10
     * @context: <h3>语法格式三:数据类型可以省略,因为可以用编译器推算的出,称为 :”类型推断“ </h3>
     * @params :  null
     */
    @Test
    public void Test3() {
        //使用lambda 还可以再次简化
        Consumer<String> con2 = (String s) -> {
            System.out.println(s);
        };
        con2.accept("给你一拳,再给你一拳");

        //类型也可以省去
        Consumer<String> con1 = (s) -> {
            System.out.println(s);
        };
        con1.accept("我闪避,闪避 miss");
    }

    /**
     * @return :  * @return : null
     * @author : <h2>冷环渊</h2>
     * @date : 2021/12/10
     * @context: <h3> 类型推断的例子</h3>
     * @params :  null
     */
    @Test
    public void Test4() {
        List<String> list = new ArrayList<>();
        int[] arrpub = {
                1, 2, 3
        };
    }

    /**
     * @return :  * @return : null
     * @author : <h2>冷环渊</h2>
     * @date : 2021/12/10
     * @context: <h3>语法格式四:Lambda 只需要一个参数的时候,参数的小括号也可以省去</h3>
     * @params :  null
     */
    @Test
    public void Test5() {
        //类型也可以省去
        Consumer<String> con1 = (s) -> {
            System.out.println(s);
        };
        con1.accept("我闪避,闪避 miss");
        //参数只有一个小括号也可以省去
        Consumer<String> con2 = s -> {
            System.out.println(s);
        };
        con2.accept("我闪避,闪避 miss");
    }

    /**
     * @return :  * @return : null
     * @author : <h2>冷环渊</h2>
     * @date : 2021/12/10
     * @context: <h3>语法格式五 Lambda 需要两个以上的参数,多条执行语句,且可以有返回值 </h3>
     * @params :  null
     */
    @Test
    public void Test6() {
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                System.out.println(o1);
                System.out.println(o2);
                return o1.compareTo(02);
            }
        };
        System.out.println(com1.compare(12, 21));
        System.out.println(" ***************");
        Comparator<Integer> com2 = (o1, o2) -> {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(02);
        };
        System.out.println(com2.compare(12, 21));
    }


    /**
     * @return :  * @return : null
     * @author : <h2>冷环渊</h2>
     * @date : 2021/12/11
     * @context: <h3>语法格式六:当Lambda体 只有一条语句的时候,return 与大括号若有都可以忽略 </h3>
     * @params :  null
     */
    @Test
    public void Test7() {
        Comparator<Integer> com1 = (o1, o2) -> {
            return o1.compareTo(o2);
        };
        System.out.println(com1.compare(12, 6));
        System.out.println("*********************");
        Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2);
        System.out.println(com2.compare(12, 21));
    }

    @Test
    public void Test8() {
        Consumer<String> con1 = s -> {
            System.out.println(s);
        };
        con1.accept("想你的夜~~~~~~~~~");
        System.out.println("********************");
        Consumer<String> con2 = s -> System.out.println(s);
        con2.accept("我给你一拳");
    }
}

今天我们就先学习到这里,

总结

我们认识了 java8 新加入的语法,

  • 简化了 一些重复且固定的代码,增强了代码的简洁度
  • 缺点,降低了代码的可读性

函数式接口(Functional)

通过上面的 Lambda表达式的学习,我们认识了 新的语法,支持这种语法的接口

  • 只包含一个抽象方法的接口,称为函数式接口
  • 你只可以通过 Lambda表达式,来创建该接口的对象,(Lambda表达式抛出一个抛出一个检查异常(即,运行时异常),这个衣长需要在目标接口的抽象方法上进行声明)
  • 我们可以在接口上使用@FunctionalInterface注解,这样做可以检查这个接口是不是函数式接口,同时javadoc也会包含一条声明说明这个接口是一个函数式接口,
  • java.util.funcion包下定义了Java8丰富的函数式接口

Lambda的表达式本质其实就是函数式接口的实例

什么是函数式接口呢?

Runnable接口就是一个很典型的函数式接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

这里我们自己定一个

@FunctionalInterface
public interface MyinterFace {
    void method1();
}

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

函数式接口参数类型返回类型用途
Consumer 消费型接口Tvoid对类型为t的对象应用操作,包含方法void accept(T,t)
Supperlier供给型接口T返回类型为T的对象,包含方法 T get()
Function<T,R>TR对类型为T的对象应用操作,并返回结果,结果是R类型的对象,包含方法 R apply(T t)
PredicateTboolean确定类型为T的对象是否满足某个约束,并返回boolean值,包含方法 boolean test(T t)

理论+实践

我们以 Consumer 与 Predicate举例子

/**
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/11
 * @context: <h4>
 * 消费型接口 Consumer<T> Accept(T t)
 * 供给型接口 Supplier<T> T get()
 * 函数式接口 Function<T> R apply(T t)
 * 断定型接口 Predicate<T> boolean test(T t)
 * </h4>
 */
public class LambdaTest2 {


    /**
     * @author 冷环渊 Doomwatcher
     * @context: Consumer<T> 使用
     * @date: 2021/12/11 14:37
     * @param
     * @return: void
     */
    @Test
    public void Test1() {
        happyTime(500, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("天上人间的水今天很贵:" + aDouble);
            }
        });
        System.out.println("****************************");
        happyTime(400, money -> System.out.println("学习太累了,去天上人间买了瓶矿泉水,价格为:" + money));
    }

    /**
     * @author: 冷环渊 Doomwatcher
     * @context:
     * @date: 2021/12/11 14:33
     * @param money
     * @param con
     * @return void
     */
    public void happyTime(double money, Consumer<Double> con) {

        con.accept(money);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context: 断言 Predicate 的使用
     * @date: 2021/12/11 14:39
     * @param
     * @return: void
     */
    @Test
    public void Test2() {
        //传统写法
        List<String> filterList = Arrays.asList("北京", "天津", "南京", "东京", "江南");
        List<String> filterStr = filterString(filterList, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.contains("京");
            }
        });
        System.out.println(filterStr);
        System.out.println("*******************************");
        // Lambda表达式 写法
        List<String> filterList1 = Arrays.asList("北京", "天津", "南京", "东京", "江南");
        List<String> filterStr1 = filterString(filterList1, s -> s.contains("京"));
        System.out.println(filterStr1);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * @date: 2021/12/11 14:49
     * @param list
     * @param pre
     * @return: java.util.List<java.lang.String>
     */
    public List<String> filterString(List<String> list, Predicate<String> pre) {
        ArrayList<String> filterlist = new ArrayList<>();
        for (String s : list) {
            if (pre.test(s)) {
                filterlist.add(s);
            }
        }
        return filterlist;
    }
}

方法引用与构造器引用

方法引用

  • 当前要传递给 Lambda体的操作,已经有了实现的方法了,可以使用方法引用
  • 方法引用可以看做是Lambda表达式的深层次表达,换句话说,方法引用就是Lambda表达式,只不过和之前我们自己编写不同,这里通过方法的名字来指向一个方法,可以认为是Lambda表达式的一种语法糖
  • 要求:实现接口的抽象方法的参数列表和返回值类型。必须与方法引用的方法的参数列表和返回值保持一致
  • 格式:使用操作符“::”将类或者对象与方法名字分开来
  • 如下是三种主要的使用情况
    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名

coding

准备两个类 一个是list集合,一个是对象

Employee get/set 和 tostring 为防止无用篇幅过长,这里我们简写只看一下对象属性即可

package Lambda.MethodReferences;

/**
 *
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/11
 * @context:<h4>提供用于测试的对象</h4>
 */
public class Employee {

    private int id;
    private String name;
    private int age;
    private double salary;
\
}

EmployeeData

package Lambda.MethodReferences;

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

/**
 *
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/11
 * @context:<h4>提供用于测试的数据</h4>
 */
public class EmployeeData {

    public static List<Employee> getEmployees() {
        List<Employee> list = new ArrayList<>();

        list.add(new Employee(1001, "马化腾", 34, 6000.38));
        list.add(new Employee(1002, "马云", 12, 9876.12));
        list.add(new Employee(1003, "刘强东", 33, 3000.82));
        list.add(new Employee(1004, "雷军", 26, 7657.37));
        list.add(new Employee(1005, "李彦宏", 65, 5555.32));
        list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
        list.add(new Employee(1007, "任正非", 26, 4333.32));
        list.add(new Employee(1008, "扎克伯格", 35, 2500.32));

        return list;
    }

}

方法引用 test

/**
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/11
 * @context:<h4>
 * 方法引用的使用
 *
 * 1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
 * 2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以
 *   方法引用,也是函数式接口的实例。
 * 3. 使用格式:  类(或对象) :: 方法名
 * 4. 具体分为如下的三种情况:
 *    情况1     对象 :: 非静态方法
 *    情况2     类 :: 静态方法
 *    情况3     类 :: 非静态方法
 * 5. 方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的
 *    形参列表和返回值类型相同!(针对于情况1和情况2)
 *
 * </h4>
 *
 */
public class MethodRefTest {

    // 情况一:对象 :: 实例方法
    //Consumer中的void accept(T t)
    //PrintStream中的void println(T t)
    @Test
    public void test1() {
        Consumer<String> con1 = str -> System.out.println(str);
        con1.accept("北京");

        System.out.println("*******************");
        PrintStream ps = System.out;
        Consumer<String> con2 = ps::println;
        con2.accept("beijing");
    }

    //Supplier中的T get()
    //Employee中的String getName()
    @Test
    public void test2() {
        Employee emp = new Employee(1001, "Tom", 23, 5600);

        Supplier<String> sup1 = () -> emp.getName();
        System.out.println(sup1.get());

        System.out.println("*******************");
        Supplier<String> sup2 = emp::getName;
        System.out.println(sup2.get());

    }

    // 情况二:类 :: 静态方法
    //Comparator中的int compare(T t1,T t2)
    //Integer中的int compare(T t1,T t2)
    @Test
    public void test3() {
        Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
        System.out.println(com1.compare(12, 21));

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

        Comparator<Integer> com2 = Integer::compare;
        System.out.println(com2.compare(12, 3));

    }

    //Function中的R apply(T t)
    //Math中的Long round(Double d)
    @Test
    public void test4() {
        Function<Double, Long> func = new Function<Double, Long>() {
            @Override
            public Long apply(Double d) {
                return Math.round(d);
            }
        };

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

        Function<Double, Long> func1 = d -> Math.round(d);
        System.out.println(func1.apply(12.3));

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

        Function<Double, Long> func2 = Math::round;
        System.out.println(func2.apply(12.6));
    }

    // 情况三:类 :: 实例方法  (有难度)
    // Comparator中的int comapre(T t1,T t2)
    // String中的int t1.compareTo(t2)
    @Test
    public void test5() {
        Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
        System.out.println(com1.compare("abc", "abd"));

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

        Comparator<String> com2 = String::compareTo;
        System.out.println(com2.compare("abd", "abm"));
    }

    //BiPredicate中的boolean test(T t1, T t2);
    //String中的boolean t1.equals(t2)
    @Test
    public void test6() {
        BiPredicate<String, String> pre1 = (s1, s2) -> s1.equals(s2);
        System.out.println(pre1.test("abc", "abc"));

        System.out.println("*******************");
        BiPredicate<String, String> pre2 = String::equals;
        System.out.println(pre2.test("abc", "abd"));
    }

    // Function中的R apply(T t)
    // Employee中的String getName();
    @Test
    public void test7() {
        Employee employee = new Employee(1001, "Jerry", 23, 6000);


        Function<Employee, String> func1 = e -> e.getName();
        System.out.println(func1.apply(employee));

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


        Function<Employee, String> func2 = Employee::getName;
        System.out.println(func2.apply(employee));
    }

}

构造方法引用

通过 简化 的方式,来调用不同的构造器

一、构造器引用

  • 和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
  • 抽象方法的返回值类型即为构造器所属的类的类型

二、数组引用

  • 大家可以把数组看做是一个特殊的类,则写法与构造器引用一致。
/**
 *
 * @author : <h2>冷环渊</h2>
 * @date : 2021/12/11
 * @context:<h4>
 *
 * 一、构造器引用
 *      和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
 *      抽象方法的返回值类型即为构造器所属的类的类型
 *
 * 二、数组引用
 *     大家可以把数组看做是一个特殊的类,则写法与构造器引用一致。
 * </h4>
 */
public class ConstructorRefTest {
    //构造器引用
    //Supplier中的T get()
    //Employee的空参构造器:Employee()
    @Test
    public void test1() {

        Supplier<Employee> sup = new Supplier<Employee>() {
            @Override
            public Employee get() {
                return new Employee();
            }
        };
        System.out.println("*******************");

        Supplier<Employee> sup1 = () -> new Employee();
        System.out.println(sup1.get());

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

        Supplier<Employee> sup2 = Employee::new;
        System.out.println(sup2.get());
    }

    //Function中的R apply(T t)
    @Test
    public void test2() {
        Function<Integer, Employee> func1 = id -> new Employee(id);
        Employee employee = func1.apply(1001);
        System.out.println(employee);

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

        Function<Integer, Employee> func2 = Employee::new;
        Employee employee1 = func2.apply(1002);
        System.out.println(employee1);

    }

    //BiFunction中的R apply(T t,U u)
    @Test
    public void test3() {
        BiFunction<Integer, String, Employee> func1 = (id, name) -> new Employee(id, name);
        System.out.println(func1.apply(1001, "Tom"));

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

        BiFunction<Integer, String, Employee> func2 = Employee::new;
        System.out.println(func2.apply(1002, "Tom"));

    }

    //数组引用
    //Function中的R apply(T t)
    @Test
    public void test4() {
        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));

    }
}

总结

  • 全新的语法带来了很多的便捷,理解起来可能相对麻烦
  • 这里在改变语法为Lambda的时候,可以自己找找可以省去,那一些部分
  • 思考,为什么构造器引用可以根据数量和类型去找到对应的构造器

强大Stream API(重点)

Java 8 是一个非常成功的版本,Java8 新增的Stream,配合同版本出现的 Lambda ,给我们操作集合提供了极大的便利。

Stream API

  • 强大Stream API 的 为什么用强大呢? java8两大招牌 一个是函数式编程 Lambda 表达式,一个是Stream流
  • Stream API(java.util.stream)把真正的函数式编程风格引入到java中,这是目前为止对java类库最好的补充。因为Stream API可以极大的提供Java程序员的生产力,让程序员写出更高效率和干净,简洁的代码
  • Stream是Java8处理结合的关键抽象概念,他可以指定你希望对集合进行的操作,可以执行非常复杂的查找,过滤和映射数据等操作,使用StreamAPI对集合数据进行操作,就类似于使用SQL执行的数据库查询。可以使用Stream API来并行执行操作,简言之,Stream API提供一种高效且易于使用的处理数据的方式

为什么使用Stream API

  • 实际开发中 ,项目中多数据源来自于Mysql,Oracle 等,但现在使用数据源可以更多了,有MongoDB,redis等,而这些NoSql的数据就需要Java层面去处理

  • Stream和 Collection集合的区别

    • Collection 是一种静态内存数据结构的一个容器
    • Stream是有关计算的

    前面是主要面向内存,存储在内存中后者是主要是面向CPU的,通过CPU实现计算

Stream的操作三个步骤

  • 1-创建Stream

    一个数据源(如集合,数组)获取一个流

  • 2-中间操作

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

  • 3-终止操作(终端操作)

    一旦执行终止操作,就执行中间操作链,并且产生结果,之后不在被使用

流程图

image-20211212011700769

创建各种场景stream流

/**
 * @projectName: Java8
 * @package: Stream
 * @className: StreamTest
 * @author: 冷环渊 doomwatcher
 * @description:
 * <h3>
 *  1.Stream 关注的是对数据的运算,与CPU打交道
 *  集合关注的事数据的存储,与内存打交道
 *
 * 2.
 * - Stream 自己不会存储元素
 * - Stream 不会改变源对象 相反,他们会返回一个持有结果的新Stream
 * - Stream 操作是延时执行的,这意味的他们会等到需要结果的时候才执行
 *
 * 3. Stream 执行操作
 *    Stream 的实例化
 *    一系列的中间操作(过滤 映射 。。。。)
 *    终止操作
 *
 * 4. 说明
 * 4.1 一个中间操作链,对数据源进行处理
 * 4.2 一旦执行终止操作,就执行中间操作链,并产生结果,之后,不会再被使用
 *
 * </h3>
 * @date: 2021/12/12 1:37
 * @version: 1.0
 */
public class StreamTest {

    //Stream 创建方式一:通过集合
    @Test
    public void test1() {
        List<Employee> employees = EmployeeData.getEmployees();
        /*
         * default Stream<E> stream() 返回一个程序流
         * 这里我们用的是 Collection接口中就实现的方法
         *  default Stream<E> stream() {
         *    return StreamSupport.stream(spliterator(), false);
         *}
         * */
        Stream<Employee> stream = employees.stream();

        // default Stream<E> parallelStream() 返回一个并行流
        Stream<Employee> parallelStream = employees.parallelStream();
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context: 创建 Stream 的方式二 数组
     * Java 8 中 Arrays 的静态方法 stream() 可以获取数组流
     *    IntStream stream = Arrays.stream(arr); static <T> Stream<T> stream(T[] array)返回一个流
     * 重载形式:
     * 能够处理 对应基本类型的数组
     *  ① public Static intStream stream(int[] array)
     *  ② public Static LongStraem stream(long[] array)
     *  ③ public Static DoubleStraam stream(double[] array)
     * @date: 2021/12/12 12:38
     * @param
     * @return:
     */
    @Test
    public void Test2() {
        int[] arr = {1, 2, 3, 4, 5, 6};
        IntStream stream = Arrays.stream(arr);
        Employee e1 = new Employee(1001, "tom");
        Employee e2 = new Employee(1002, "Treey");
        Employee[] employees = {e1, e2};
        Stream<Employee> employeeStream = Arrays.stream(employees);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     *  创建 Stream三 : Stream本身的 of() 我们需要创建数据的时候
     * @date: 2021/12/12 12:45
     * @param
     * @return: void
     */
    @Test
    public void Test3() {
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * 创建方式四 创建无限流
     * 我们需要批量的产生数据
     * @date: 2021/12/12 12:47
     * @param
     * @return: void
     */
    @Test
    public void Test4() {
        /* 迭代
         * public static<T> Stream<T> iterate(final T sead,final UnaryOperator)
         * 遍历前十个偶数
         * 这里 参数 UNaryOperator继承自 function 也就是说我们最终调用的方法 是重写的apply
         * @FunctionalInterface
         * public interface UnaryOperator<T> extends Function<T, T> {
         * static <T> UnaryOperator<T> identity() {
         *   return t -> t;
         * }
         * }
         * */
        Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
        /*生成
         *  public static<T> Stream<T> generate(Supplier<T> s)
         *
         * */
        Stream.generate(Math::random).forEach(System.out::println);
    }
}

Stream的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止 操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全 部处理,称为“惰性求值”。

筛选与切片

  • filter(Predicate p) 接收 Lambda , 从流中排除某些元素
  • distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
  • limit(long maxSize) 截断流,使其元素不超过给定数量
  • skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一 个空流。与 limit(n) 互补
    //filter(Predicate p) 接收 Lambda , 从流中排除某些元素
    @Test
    public void test1() {
        List<Employee> employeeList = EmployeeData.getEmployees();
        Stream<Employee> stream = employeeList.stream();
        //小练习,查询员工表中薪资大于7000的员工
        System.out.println("filter-------------------");
        stream.filter(e -> e.getSalary() > 7000).forEach(System.out::println);

        /*limit(long maxSize) 截断流,使其元素不超过给定数量
         * 这里我们用  stream 会抛出异常,所以我们直接调用构造方法,每次都会生成新的流
         * */
        System.out.println("limit-------------------");
        employeeList.stream().limit(3).forEach(System.out::println);

        /*    skip(long n)
         *   跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
         * */
        System.out.println("skip--------------------");
        employeeList.stream().skip(3).forEach(System.out::println);

        //distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去 除重复元素
        System.out.println("distinct----------------");
        employeeList.add(new Employee(1010, "刘强东", 40, 8000));
        employeeList.add(new Employee(1010, "刘强东", 40, 8000));
        employeeList.add(new Employee(1010, "刘强东", 40, 8000));
        employeeList.stream().distinct().forEach(System.out::println);
    }

映射

  • map(Function f) 接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素。
  • mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream。
  • mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream。
  • mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream。
  • flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流
    /**
     * @author 冷环渊 Doomwatcher
     * @context: 映射
     * @date: 2021/12/12 14:15
     * @param
     * @return: void
     */
    @Test
    public void test2() {
        /*        map(Function f)
         *    接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
         *  映射结果:
         *  aa   --> AA
         *  bb   --> BB
         *  cc   --> CC
         *  dd   --> DD
         * */
        List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
        list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
        System.out.println();
        List<Employee> employeeList = EmployeeData.getEmployees();
        employeeList.stream().map(Employee::getName).filter(name -> name.length() > 3).forEach(System.out::println);
        System.out.println();
        
        /*flatMap(Function f)
         * 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流、*/
        //这里是没有用 flatmap的版本 ,我们需要foreach之后再foreach才可以拆分到所有的元素
        Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest2::fromStringToStream);
        streamStream.forEach(s -> s.forEach(System.out::print));
        System.out.println();
        //使用了 flatmap
        Stream<Character> stream = list.stream().flatMap(StreamAPITest2::fromStringToStream);
        stream.forEach(System.out::print);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * 将字符串中的多个字符构成的集合,转换成对应的Stream的实例
     * @date: 2021/12/12 14:44
     * @param str
     * @return: java.util.stream.Stream<java.lang.Character>
     */
    public static Stream<Character> fromStringToStream(String str) {
        ArrayList<Character> list = new ArrayList<>();
        for (Character c : str.toCharArray()) {
            list.add(c);
        }
        return list.stream();
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * =演示 list里放list
     * 【1,2,3,【4,5,6】】
     * 打散开了再放入list
     * 【1,2,3,4,5,6】
     * @date: 2021/12/12 14:33
     * @param
     * @return: void
     */
    @Test
    public void test3() {
        ArrayList list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        ArrayList list1 = new ArrayList();
        list1.add(4);
        list1.add(5);
        list1.add(6);
        list.add(list1);
        System.out.println(list);
    }

排序

  • sorted() 产生一个新流,其中按自然顺序排序
  • sorted(Comparator com) 产生一个新流,其中按比较器顺序排序

Comparator 概念

Comparator 是一个函数式接口。它经常用于没有天然排序的集合进行排序,如 Collections.sortArrays.sort或者对于某些有序数据结构的排序规则进行声明,如 TreeSetTreeMap 。该接口主要用来进行集合排序。

抽象方法 compare

作为Comparator 唯一的抽象方法,int compare(T o1,T o2) 比较两个参数的大小, 返回负整数,零,正整数 ,分别代表 o1<o2o1=o2o1>o2,通常分别返回 -101

//输入两个同类型的对象,返回比较结果为int的数字
//`o1<o2`、`o1=o2`、`o1>o2`,通常分别返回 `-1`、`0` 或 `1`
(x1,x2)-> int
    /**
     * @author 冷环渊 Doomwatcher
     * @context: 排序
     * @date: 2021/12/12 16:42
     * @param
     * @return: void
     */
    @Test
    public void tset4() {
        //sorted() 产生一个新流,其中按自然顺序排序
        List<Integer> list = Arrays.asList(12, 43, 65, 0, -1, 7);
        list.stream().sorted().forEach(System.out::println);

        /* 这里会出现问题
         * 为什么?
         * 原因:Employee没有实现Comparble接口、
         * List<Employee> employees = EmployeeData.getEmployees();
         * employees.stream().sorted().forEach(System.out::println);
         * */

        //sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
        List<Employee> employees = EmployeeData.getEmployees();
        employees.stream().sorted((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge())).forEach(System.out::println);

        System.out.println();
        //小练习,如果年龄一样就按收入排序
        employees.add(new Employee(1011, "冷环渊", 34, 8000));
        employees.stream().sorted((e1, e2) -> {
            int agevalue = Integer.compare(e1.getAge(), e2.getAge());
            if (agevalue != 0) {
                return agevalue;
            } else {
                return -Double.compare(e1.getSalary(), e2.getSalary());
            }
        }).forEach(System.out::println);

    }

Stream 的终止操作

  • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例 如:List、Integer,甚至是 void 。
  • 流进行了终止操作后,不能再次使用。

匹配与查找

  • allMatch(Predicate p) 检查是否匹配所有元素
  • anyMatch(Predicate p) 检查是否至少匹配一个元素
  • noneMatch(Predicate p) 检查是否没有匹配所有元素
  • findFirst() 返回第一个元素
  • findAny() 返回当前流中的任意元素
  • count() 返回流中元素总数
  • max(Comparator c) 返回流中最大值
  • min(Comparator c) 返回流中最小值
  • forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。相反,Stream API 使用内部迭 代——它帮你把迭代做了)
    /**
     * @author 冷环渊 Doomwatcher
     * @context: 匹配与查找
     * @date: 2021/12/12 17:11
     * @param
     * @return: void
     */
    @Test
    public void test1() {
        //- allMatch(Predicate p) 检查是否匹配所有元素
        List<Employee> employees = EmployeeData.getEmployees();
        boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
        System.out.println("allMatch(Predicate p) 检查是否匹配所有元素: " + allMatch);
        //-  anyMatch(Predicate p) 检查是否至少匹配一个元素
        boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
        System.out.println(" anyMatch(Predicate p) 检查是否至少匹配一个元素: " + anyMatch);
        //- noneMatch(Predicate p) 检查是否没有匹配所有元素
        boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
        System.out.println("noneMatch(Predicate p) 检查是否没有匹配所有元素: " + noneMatch);
        //-  findFirst() 返回第一个元素
        Optional<Employee> first = employees.stream().findFirst();
        System.out.println("findFirst() 返回第一个元素: " + first);
        //- findAny() 返回当前流中的任意元素
        Optional<Employee> any = employees.parallelStream().findAny();
        System.out.println("findFirst() 返回第一个元素: " + any);
        //- count() 返回流中元素总数
        Long salary = employees.stream().filter(e -> e.getSalary() > 5000).count();
        System.out.println("count() 返回流中元素总数: " + salary);
    }


    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * @date: 2021/12/12 23:30
     * @param
     * @return: void
     */
    @Test
    public void test2() {
        //- max(Comparator c) 返回流中最大值
        List<Employee> employees = EmployeeData.getEmployees();
        Stream<Double> stream = employees.stream().map(e -> e.getSalary());
        Optional<Double> max = stream.max(Double::compare);
        System.out.println("返回流中最大值:" + max);
        //- min(Comparator c) 返回流中最小值
        Optional<Employee> min = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        System.out.println("返回流中最小值: " + min);
        //- forEach(Consumer c) 内部迭代(使用 Collection 接口需要用户去做迭代, 称为外部迭代。相反,Stream API 使用内部迭 代——它帮你把迭代做了)
        employees.stream().forEach(System.out::println);
        //使用集合的方式
        employees.forEach(System.out::println);
    }

规约

map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

  • reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一 个值。返回 T
  • reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一 个值。返回 Optional
/**
     * @author 冷环渊 Doomwatcher
     * @context: 规约
     * @date: 2021/12/12 23:56
     * @param
     * @return: void
     */
    @Test
    public void tset3() {

        //- reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一 个值。返回 T
        // 小练习 计算1-10的自然数总和
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Integer resultReduce = list.stream().reduce(0, Integer::sum);
        System.out.println("resultReduce: " + resultReduce);
        //- reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一 个值。返回 Optional
        //    小练习 计算公司所有员工的工资总和
        List<Employee> employees = EmployeeData.getEmployees();
        Optional<Double> slaryStream = employees.stream().map(Employee::getSalary).reduce(Double::sum);
        System.out.println(slaryStream);
    }

收集

Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、 Map)。 另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,常用案例如下

常用语法

toList List 把流中元素收集到List
List List emps= list.stream().collect(Collectors.toList());

toSet Set 把流中元素收集到Set
Set emps= list.stream().collect(Collectors.toSet());

toCollection Collection 把流中元素收集到创建的集合
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 Double 计算流中元素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 根据比较器选择最大值
Optional<Emp>max=list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));

minBy Optional 根据比较器选择最小值
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> 根据某属性值对流分组,属性为K,
结果为V
Map<Emp.Status, List<Emp>> map= list.stream() .collect(Collectors.groupingBy(Employee::getStatus));

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

    /**
     * @author 冷环渊 Doomwatcher
     * @context: 收集
     * @date: 2021/12/13 0:23
     * @param
     * @return:
     */
    @Test
    public void test4() {
        /* collet(Collector c) 将流转换成其他的形式,接收一个 Collector接口实现,用于给Stream中元素做汇总方法
         * 练习一 查找工资大于6000的员工 返回一个 list 或者set
         * */
        List<Employee> employees = EmployeeData.getEmployees();
        //大于六千的list
        List<Employee> employeeList = employees.stream().filter(employee -> employee.getSalary() > 6000).collect(Collectors.toList());
        employeeList.forEach(System.out::println);
        System.out.println();
        //小于六千的set
        Set<Employee> employeeSet = employees.stream().filter(employee -> employee.getSalary() < 6000).collect(Collectors.toSet());
        employeeSet.forEach(System.out::println);

    }
}

Optional类

​ 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。 以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类, Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代 码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

  • Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表 这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不 存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
  • Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在 则isPresent()方法会返回true,调用get()方法会返回该对象。

常用方法

Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

创建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接口实现提供的异常。

Optional Coding

/**
 * @projectName: Java8
 * @package: OptionalPic
 * @className: OptionalTest
 * @author: 冷环渊 doomwatcher
 * @description: TODO
 * @date: 2021/12/13 0:35
 * @version: 1.0
 */
public class OptionalTest {
    /**
     * @author 冷环渊 Doomwatcher
     * @context:
     * Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
     * Optional.empty() : 创建一个空的 Optional 实例
     * Optional.ofNullable(T t):t可以为null
     * @date: 2021/12/13 0:35
     * @param
     * @return: void
     */
    @Test
    public void test() {
        //创建 Optional
        Girl girl = new Girl();
        Optional<Girl> girl1 = Optional.of(girl);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context: Optional.ofNullable(T t):t可以为null
     * @date: 2021/12/13 0:38
     * @param
     * @return: void
     */
    @Test
    public void test2() {
        //创建 Optional
        Girl girl = new Girl();
        girl = null;
        Optional<Girl> girl1 = Optional.ofNullable(girl);
        //如果当前这个 Optional内部封装的t是非空的,则返回内部的t
        //如果内部的t是空的,则返回prElse 方法的参数t1
        Girl girl2 = girl1.orElse(new Girl("赵丽颖"));
        System.out.println(girl2);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context: 获取女孩名字
     * @date: 2021/12/13 0:41
     * @param boy
     * @return: java.lang.String
     */
    public String getGirlName(Boy boy) {
        return boy.getGirl().getName();
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context: 用 Optional优化获取女孩名字
     * @date: 2021/12/13 0:41
     * @param boy
     * @return: java.lang.String
     */
    public String newGetGirlName(Boy boy) {
        Optional<Boy> boyOptional = Optional.ofNullable(boy);
        //此时的 boy1 一定非空
        Boy boy1 = boyOptional.orElse(new Boy(new Girl("乃林")));
        Girl girl = boy1.getGirl();
        Optional<Girl> girl1 = Optional.ofNullable(girl);
        //此时的 girl2 一定非空
        Girl girl2 = girl1.orElse(new Girl("嘉然"));
        return girl2.getName();
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context: 没有Optional的时候优化获取女孩名字
     * @date: 2021/12/13 0:41
     * @param boy
     * @return: java.lang.String
     */
    public String oldGetGirlName(Boy boy) {
        if (boy != null) {
            Girl girl = boy.getGirl();
            if (girl != null) {
                return girl.getName();
            }
        }
        return null;
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:这里调用 应该是空指针因为女孩对象是null
     * @date: 2021/12/13 0:42
     * @param
     * @return: void
     */
    @Test
    public void test3() {
        Boy boy = new Boy();
        String girlName = getGirlName(boy);
        System.out.println(girlName);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:这里调用 这里我们用老的方法进行了处理
     * @date: 2021/12/13 0:42
     * @param
     * @return: void
     */
    @Test
    public void test4() {
        Boy boy = new Boy();
        boy = null;
        String girlName = oldGetGirlName(boy);
        System.out.println(girlName);
    }

    /**
     * @author 冷环渊 Doomwatcher
     * @context:这里调用 这里我们用Optional的方法进行了处理
     * @date: 2021/12/13 0:42
     * @param
     * @return: void
     */
    @Test
    public void test5() {
        // boy 为null 返回乃琳
        // boy的参数 girl为null 返回嘉然
        //如果正常 就返回 正常参数
        Boy boy = new Boy(new Girl("林志玲"));
        //boy = null;
        String girlName = newGetGirlName(boy);
        System.out.println(girlName);
    }

}

总结

到这里我们的 java8新特性就学习完了,这里我们来回顾一下java8 都带给我们了什么帮助

  • Lambda 表达式 新的语法 (函数式接口,方法引用)一切都是为了让我们代码更加的简洁
  • Stream API 我们会发现他和我们的sql很相似,是代码级别的数据处理,同时支持简洁的Lambda
  • Optional 这列类我们一般想不到回去用它,但是很多方法都帮我们用了,我们也可以用它来处理空指针,省去了多个if的嵌套,
分类: