JDK1.8新特性:lambda表达式,方法引用和构造器引用,Stream API,Optional类(二十四)

214 阅读26分钟

JDK1.8新特性

jdk经典版本

1 JDK1.0

2 JDK1.2 集合等框架

3 JDK1.5

(1)可变参数

(2)枚举

(3)注解

(4)包装类自动装箱与拆箱

(5)foreach

(6)泛型

...

5 JDK1.8

​ 从Java9开始,JDK的版本更新周期变为半年一次,Java之前的都是长期支持版本,Java9之后不是所有版本都是长期支持版本,有的是临时版本或试用版本,Java11,Java17是长期支持版本。

Java8新特性:

(1)接口中增加了 静态方法和默认方法

(2)日期时间API的第三代 java.time及其子包

(3)Lambda表达式 --> 函数式编程思想引入到Java8中 方法引用和构造器引用

(4)StreamAPI --> 在内存中对数据进行处理

(5)Optional工具类 --> NullPointException异常

1 lambda表达式

强调做什么,而不是以什么形式做。

foreach为了简化迭代,是迭代的“语法糖”;

lambda可以简化匿名内部类的声明。

​ lambda表达式是引入了函数式编程思想,所谓函数式编程思想就是把函数堪称第一位的,可以把函数当成“参数”或者“返回值”,不想面向对象,过分强调“对象”的形式。

lambda表达式的演示:

public class exam {
    public static void main(String[] args){
        System.out.println("------------线程--------------");
        //    匿名内部类实现一个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello innerclass");
            }
        }).start();
//        lambda方式
        new Thread(()-> System.out.println("hello lambda")).start();

    }
}

public class exam {
    public static void main(String[] args){
//        忽略大小写进行排序
        String[] str1 = {"Haha","new","birth","flOWER"};
        String[] str2 = {"Haha","new","birth","flOWER"};
//        匿名内部类
        Arrays.sort(str1, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2);
            }
        });
        for (String s : str1) {
            System.out.print(s+" ");
        }
        System.out.println();
//lambda
        Arrays.sort(str2,((o1, o2) -> o1.compareToIgnoreCase(o2)));
        for (String s : str2) {
            System.out.print(s+" ");
        }
    }
}

(1)是不是所有的Java的语法都可以使用lambda表达式进行简化?

不是。

Java仍然是面向对象的编程语言,以面向对象为主。

(2)哪些情况下,可以使用lambda表达式?

lambda表达式其实就是实现SAM接口的语法糖。

(3)什么是SAM接口?

SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包括其他非抽象方法。

分析下列接口是否属于SAM接口。

Ⅰjava.lang.Runnable接口:是的,有单一抽象方法void run

Ⅱ java.lang.Cloneable接口:不是,他没有抽象方法

Ⅲ java.io.Serializable接口:不是,他没有抽象方法。

Ⅳ java.lang.Comparable接口:是,有单一抽象方法int compareTo(T t)方法。至于它的Equals是推荐重写的方法,因为比如说,有两个学生都是100分,我们通过compare认为这两个学生是“相等的”,排序时通过这个条件来排序,但是在逻辑上我们不认为是同一个学生(对象),所以期望实现一个equals抽象方法来分析“是不是同一个对象”。

Ⅴ java.util.Comparator接口:是,有单一抽象方法int compare(T t)方法,至于它的Equals是推荐重写的方法,因为比如说,有两个学生都是100分,我们通过compare认为这两个学生是“相等的”,排序时通过这个条件来排序,但是在逻辑上我们不认为是同一个学生(对象),所以期望实现一个equals抽象方法来分析“是不是同一个对象”。

Ⅵ java.lang.Iterable接口:是,有单一抽象方法Iterator iterator()

Ⅶ java.util.Iterator接口:不是

Ⅷ java.util.Collection,List,Set,Map<k,v>接口:不是

Ⅸ java.io.FileFilter接口:是,有单一抽象方法boolean accept(File pathname)

Ⅹ java.io.Externalizable接口:不是

Java8版本,在java.util.function 包中给我们引入了大量的函数式接口(Functional Interface)

(4)新的注解@FunctionalInterface?

​ Java8引入了函数式接口的概念之后,建议我们SAM接口,即函数式接口,在声明时加@FunctionalInterface注解标记。

​ Lambda表达式针对标记了@FunctionalInterface注解的接口进行使用。如果没有加@FunctionalInterface标记的接口(就算它符合SAM特征,也尽量不要用)

上面的接口中,符合SAM接口特征的,哪些增加了@FunctionalInterface注解标记。

(1)java.lang.Runnable接口: void run()

(5)java.util.Comparator接口:int compare(T t1, T t2)

(9)java.io.FileFilter接口:boolean accept(File pathname)

(5)函数式接口(SAM)

​ 在java.util.function包中新增的函数式接口(有很多),我们可以分为四大类。

①消费型接口

经典代表:Cunsumer 抽象方法:void accept(T t)。

​ 抽象方法的特点是:有参无返回值。(消费行为,有去无回)。

变形:

接口名抽象方法参数列表和返回值
BiConsumer<T,U>void accept(T t, U u)接收两个对象用于完成功能
DoubleConsumervoid accept(double value)接收一个double值
IntConsumervoid accept(int value)接收一个int值
LongConsumervoid accept(long value)接收一个long值
ObjDoubleConsumervoid accept(T t, double value)接收一个对象和一个double值
ObjIntConsumervoid accept(T t, int value)接收一个对象和一个int值
ObjLongConsumervoid accept(T t, long value)接收一个对象和一个long值
②供给型接口

经典代表:Supplier 抽象方法:T get().

​ 抽象方法的特点是,无参有返回值。(空手套白狼)

变形:

接口名抽象方法参数列表和返回值
BooleanSupplierboolean getAsBoolean()返回一个boolean值
DoubleSupplierdouble getAsDouble()返回一个double值
IntSupplierint getAsInt()返回一个int值
LongSupplierlong getAsLong()返回一个long值
③判断型接口

经典代表:Predicate 抽象方法:Boolean test(T t).

​ 抽象方法的特点是:有参有返回值。但是返回值是Boolean。用来判断参数是否符合某一条件。

接口名抽象方法参数列表和返回值
BiPredicate<T,U>boolean test(T t, U u)接收两个对象
DoublePredicateboolean test(double value)接收一个double值
IntPredicateboolean test(int value)接收一个int值
LongPredicateboolean test(long value)接收一个long值
④功能型接口(函数型接口)

经典代表:Function<T,R> 抽象方法:R apply(T t)

​ 抽象方法的特点是:有参有返回值。(有来有往,参数和返回值的类型可能不同)。

接口名抽象方法参数列表和返回值
UnaryOperatorT apply(T t)接收一个T类型对象,返回一个T类型对象结果
DoubleFunctionR apply(double value)接收一个double值,返回一个R类型对象
IntFunctionR apply(int value)接收一个int值,返回一个R类型对象
LongFunctionR apply(long value)接收一个long值,返回一个R类型对象
ToDoubleFunctiondouble applyAsDouble(T value)接收一个T类型对象,返回一个double
ToIntFunctionint applyAsInt(T value)接收一个T类型对象,返回一个int
ToLongFunctionlong applyAsLong(T value)接收一个T类型对象,返回一个long
DoubleToIntFunctionint applyAsInt(double value)接收一个double值,返回一个int结果
DoubleToLongFunctionlong applyAsLong(double value)接收一个double值,返回一个long结果
IntToDoubleFunctiondouble applyAsDouble(int value)接收一个int值,返回一个double结果
IntToLongFunctionlong applyAsLong(int value)接收一个int值,返回一个long结果
LongToDoubleFunctiondouble applyAsDouble(long value)接收一个long值,返回一个double结果
LongToIntFunctionint applyAsInt(long value)接收一个long值,返回一个int结果
DoubleUnaryOperatordouble applyAsDouble(double operand)接收一个double值,返回一个double
IntUnaryOperatorint applyAsInt(int operand)接收一个int值,返回一个int结果
LongUnaryOperatorlong applyAsLong(long operand)接收一个long值,返回一个long结果
BiFunction<T,U,R>R apply(T t, U u)接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperatorT apply(T t, T u)接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction<T,U>double applyAsDouble(T t, U u)接收一个T类型和一个U类型对象,返回一个double
ToIntBiFunction<T,U>int applyAsInt(T t, U u)接收一个T类型和一个U类型对象,返回一个int
ToLongBiFunction<T,U>long applyAsLong(T t, U u)接收一个T类型和一个U类型对象,返回一个long
DoubleBinaryOperatordouble applyAsDouble(double left, double right)接收两个double值,返回一个double结果
IntBinaryOperatorint applyAsInt(int left, int right)接收两个int值,返回一个int结果
LongBinaryOperatorlong applyAsLong(long left, long right)接收两个long值,返回一个long结果

Bi:二元 binary

Unary:一元

(6)Lambda表达式语法

ⅠLambda表达式是用来给“函数式接口”的变量或者参数赋值用的。

Ⅱ 语法格式

(形参列表)->{
	lambda体
}

说明:

  • ->:新的运算符,是lambda表达式,也称为Lambda操作符,又称为箭头符号,中间不要有空格
  • (形参列表):这个形参列表是函数的参数列表,它不是程序员随便定义的,它是当前lambda表达式对应的函数式接口的抽象方法的(形参列表)。
  • {lambda体}:这个lambda体其实就是实现当前Lambda表达式对应的函数式接口的抽象方法

总的来说:

​ lambda表达式就是实现 函数式接口 的抽象方法的代码。

​ 只不过我们此时只关系(形参列表)和{方法体/lambda体}。

为什么?

​ (形参列表)决定了我们要给他传入的xx数据;

​ {lambda体}决定了它能够完成什么功能。

(7)lambda可以简化为:

①当{lambda体},只有一个语句时,我们可以省略{}以及语句后面的;(分号)。

②如果{lambda体}的唯一的语句时return语句,我们在省略{}和;的同时,要把return也省略掉。

③当lambda表达式的(形参列表)的类型是一致的或者是可以推断的,那么形参列表的类型可以省略。

④当lambda表达式的(形参列表)是空参,()不能省略。

⑤当lambda表达式的(形参列表)只有一个形参,它的数据类型已经省略的情况下,()可以省略。

public class TestLambdaGrammer2 {
    public static void main(String[] args) {
        System.out.println("------------第一个例子---------------");
        //下面用匿名内部类的方式实现Runnable接口的方式启动一个线程,这个线程负责打印一句话:hello lambda
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello lambda");
            }
        }).start();

        System.out.println("---------------------------");
/*        new Thread(Runnable target).start();
        new Thread()构造器的形参是Runnable接口,它是函数式接口,它有唯一的抽象方法void run(),所以可以使用Lambda表达式给他赋值
        抽象方法:public void run()
            形参列表:()
            实现run()的方法体: { System.out.println("hello lambda"); }
            对应的Lambda表达式:() -> { System.out.println("hello lambda"); }
        */
        new Thread(() -> { System.out.println("hello lambda"); }).start();

        //(1)当{Lambda体},只有一个语句时,我们可以省略Lambda体的{}以及语句后面的;
        new Thread(() -> System.out.println("hello lambda")).start();

        System.out.println("------------第二个例子---------------");
        String[] arr = {"Lala","hello","HI","Joke"};
        System.out.println("------------匿名内部类的方式---------------");

        Arrays.sort(arr, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2);
            }
        });
         /*
        我们调用的是Arrays类中public static <T> void sort(T[] a, Comparator<? super T> c)方法进行排序
        这个方法的第二个形参类型,Comparator<? super T> ,它是一个函数式接口,他的抽象方法: int compare(T o1, T o2)
            就可以使用Lambda表达式给形参 Comparator<? super T> c赋值。
            抽象方法:int compare(T o1, T o2)
                形参列表:(T o1, T o2)
                方法体:{ return o1.compareToIgnoreCase(o2);}
            Lambda表达式:(T o1, T o2) -> { return o1.compareToIgnoreCase(o2);}
            这个T,在当前例子中,是代表String类型
            Lambda表达式:(String o1, String o2) -> { return o1.compareToIgnoreCase(o2);}
         */
        Arrays.sort(arr, (String o1, String o2) -> { return o1.compareToIgnoreCase(o2);});

        //(1)当{Lambda体},只有一个语句时,我们可以省略Lambda体的{}以及语句后面的;
        //(2)如果{Lambda体}的唯一的语句是一个return语句,我们在省略{}和;的同时,要把return也省略了。
        Arrays.sort(arr, (String o1, String o2) ->  o1.compareToIgnoreCase(o2));

        //(3)当Lambda表达式的(形参列表)的类型是已知的或者是可以推断的,那么形参列表的类型可以省略
        Arrays.sort(arr, (o1, o2) ->  o1.compareToIgnoreCase(o2));

        System.out.println("------------第三个例子---------------");
        //Iterable接口,实现它就可以支持foreach循环
        //我们看java.lang.Iterable<E>接口中,Java8增加了一个默认方法
        //default void forEach(Consumer<? super T> action)
        //Iterable接口增加的方法,它的子接口也拥有,它的子接口有Collection<E>,List<E>,Set<E>等
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");

        /*
        调用list集合的 default void forEach(Consumer<? super T> action),这个方法的作用是遍历集合的元素
        这个方法的形参是 Consumer<T>接口,是消费型接口的代表, 它的抽象方法是  void accept(T t)
            它是函数式接口,就可以用Lambda表达式赋值
            抽象方法:void accept(T t)
                形参列表:(T t)
                方法体:{打印集合中的元素}
                Lambda表达式:(T t) -> {打印集合中的元素}
                Lambda表达式:(T t) -> {System.out.println(元素);}
                Lambda表达式:(T t) -> {System.out.println(t);}
                这里的T的类型就是list集合的元素类型<String>
         */
        list.forEach((String t) -> {System.out.println(t);});

        //(1)当{Lambda体},只有一个语句时,我们可以省略{}以及语句后面的;
        list.forEach((String t) -> System.out.println(t));

        //(3)当Lambda表达式的(形参列表)的类型是已知的或者是可以推断的,那么形参列表的类型可以省略
        list.forEach((t) -> System.out.println(t));

        //(5)当Lambda表达式的(形参列表)只有一个形参,它的数据类型已经省略的情况下,()可以省略
        list.forEach(t -> System.out.println(t));
    }
}

(8)练习

①自定义函数式接口
package com.atguigu.lambda;

/*
1、自定义函数式接口
要求:只有一个抽象方法,使用@FunctionalInterface注解标记

举例:

 */
public class TestDefineFunctionalInterface {
    public static void main(String[] args) {
//        Call call = 接口的实现类对象;
        //因为Call是函数式接口,可以使用Lambda表达式赋值
        //Call接口的抽象方法 void shout()
        Call call = () -> System.out.println("妈妈喊你回家吃饭");
        //可以调用call对象的shout方法
        call.shout();
    }


}

//自己定义的接口
//如果接口中只有一个抽象方法,可以标记为函数式接口,用@FunctionalInterface注解标记
@FunctionalInterface
interface Call{
    void shout();//唯一的抽象方法
}
②消费型接口
/*
练习2:消费型接口
1、经典代表:Consumer<T>
抽象方法:void accept(T t)

例子:Iterable<T>中forEach方法
        default void forEach(Consumer<? super T> action)

2、
JDK1.8中的Map接口,增加了一些方法,其中有一个:
    default void forEach(BiConsumer<? super K,? super V> action)
 */
public class TestConsumer {
    @Test
    public void test03(){
        HashMap<Integer,String> map = new HashMap<>();
        //添加元素,用put
        map.put(1, "java");
        map.put(2, "c");
        map.put(3, "python");

        /*
        回忆之前遍历map:
            Set<Map.Entry<K,V>> entrySet()
            Set<K> keySet()
            Collection<V> values();
         今天遍历集合用新的forEach方法
            void forEach(BiConsumer<? super K,? super V> action)
          forEach方法的形参是BiConsumer,是函数式接口,是Consumer接口的变形,可以使用Lambda表达式赋值
            BiConsumer<T,U>接口的抽象方法, void accept(T t, U u)

            我们forEach方法的形参类型BiConsumer<? super K,? super V>对应BiConsumer<T,U>
            T就是K
            U就是V
            抽象方法的类型不能随便改,抽象方法的形参名字,可以自己修改。
            原来抽象方法的形参名分别是t和u,我可以修改为key,value
         */
        map.forEach((key, value) -> System.out.println("key:" + key + ",value=" +value));
    }

    @Test
    public void test02(){
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("world");

        /*
        list.forEach(Consumer<? super T> action)遍历集合
          forEach方法的形参Consumer类型,是函数式接口,可以使用Lambda表达式
          Consumer接口的抽象方法void accept(T t)
          Lambda表达式: (T t) -> {遍历集合的元素要做的事情}
         */
        list.forEach(t -> System.out.println(t));
    }


    @Test
    public void test01(){
        ArrayList list = new ArrayList();
        list.add("hello");
        list.add(1);

        /*
        list.forEach(Consumer<? super T> action)遍历集合
          forEach方法的形参Consumer类型,是函数式接口,可以使用Lambda表达式
          Consumer接口的抽象方法void accept(T t)
          Lambda表达式: (T t) -> {遍历集合的元素要做的事情}
         */
        list.forEach(t -> System.out.println(t));
    }
}

③功能型接口
/*
练习4:功能型接口
经典代表:Function<T,R>      R:return 返回值类型
抽象方法:R apply(T t)

例子:在JDK1.8时Map接口增加了很多方法,
    public default void replaceAll(BiFunction<? super K,? super V,? extends V> function)

变形:BiFunction<T,U,R>
抽象方法:R apply(T t, U u)
 */
public class TestFunction {
    @Test
    public void test02(){
        HashMap<Integer,String> map = new HashMap<>();
        //添加元素,用put
        map.put(1, "java");
        map.put(2, "c");
        map.put(3, "python");

        //需求:把里面的value,取首字母存储,即原来是java,变成j
        /*
        调用map的void replaceAll(BiFunction<? super K,? super V,? extends V> function)方法
        replaceAll方法的形参是BiFunction<? super K,? super V,? extends V>是函数式接口,可以使用Lambda表达式
        BiFunction<T,U,R>接口的抽象方法R apply(T t, U u)
        看这个BiFunction<? super K,? super V,? extends V>来判断T,U,R分别代表:
            T:key的类型
            U:value的类型,原来value的类型
            R:value的类型,替换之后value的类型
        Lambda表达式:(T t, U u) -> {...}
        Lambda表达式:(K key, V value) -> {把value转换为大写}

         */
        map.replaceAll((key,value) -> value.charAt(0)+"");

        //遍历结果
        map.forEach((key,value) -> System.out.println(key+","+value));
    }

    @Test
    public void test01(){
        HashMap<Integer,String> map = new HashMap<>();
        //添加元素,用put
        map.put(1, "java");
        map.put(2, "c");
        map.put(3, "python");

        //需求:把里面的value,转换为大写的形式
        /*
        调用map的void replaceAll(BiFunction<? super K,? super V,? extends V> function)方法
        replaceAll方法的形参是BiFunction<? super K,? super V,? extends V>是函数式接口,可以使用Lambda表达式
        BiFunction<T,U,R>接口的抽象方法R apply(T t, U u)
        看这个BiFunction<? super K,? super V,? extends V>来判断T,U,R分别代表:
            T:key的类型
            U:value的类型,原来value的类型
            R:value的类型,替换之后value的类型
        Lambda表达式:(T t, U u) -> {...}
        Lambda表达式:(K key, V value) -> {把value转换为大写}

         */
        map.replaceAll((key,value) -> value.toUpperCase());

        //遍历结果
        map.forEach((key,value) -> System.out.println(key+","+value));
    }
}

④供给型接口
/*
练习4:供给型接口
经典代表:Supplier<T>
抽象方法:T get()

例如:
在JDK1.8中增加了StreamAPI,java.util.stream.Stream<T>是一个数据流。
    static <T> Stream<T> generate(Supplier<T> s),它是静态方法,通过“类型名.方法"
 */
public class TestSupplier {
    @Test
    public void test01(){
        /*使用java.util.stream.Stream<T>接口的静态方法
        static <T> Stream<T> generate(Supplier<T> s),创建一个数据流
           generate方法的形参是 Supplier类型,它是函数式接口,可以使用Lambda表达式
        Supplier接口的抽象方法 T get()
        Lambda表达式: () -> {...}
        Lambda体的功能是产生数据,这里我调用Math.random()方法,随机产生一个数字
         */
        Stream<Double> stream = Stream.generate(() -> Math.random());

        //查看Stream流中的元素
        stream.forEach(t-> System.out.println(t));
    }
}

2 方法引用和构造器引用

(1)方法引用与构造器引用的作用是干什么的?

​ 作用:简化Lambda表达式

​ 就像原来的匿名内部类不一定都可以使用Lambda表达式进行简化一样,Lambda表达式也不是所有情况都可以使用法引用与构造器引用进行简化的。

(2)什么情况下才可以使用方法引用进行简化呢?

​ ①Lambda体只有一个语句,并且该语句是调用现有的类或对象的一个方法来完成

​ ②Lambda体中所调用的方法,使用的实参,都是来自于 Lambda表达式的形参列表,而没有其他额外的参数

(3)什么情况下才可以使用构造器引用进行简化呢?

​ ①Lambda体只有一个语句,并且该语句是通过new一个对象来完成的

​ ②Lambda体中所调用的构造器,使用的实参,都是来自于 Lambda表达式的形参列表,而没有其他额外的参数

(4)形式

​ ①类名或对象名 :: 方法名 方法引用

​ ②类名/数组类型::new 构造器引用

例子:

public class TestFunctionReference {
    @Test
    public void test07(){
        //调用这个createArray方法,得到一个对象数组
        //createArray方法的第一个形参是Function,它是函数式接口,可以使用lambda表达式
        //Function<T,R>接口的抽象方法: R apply(T t)
        //Function<Integer,String[]> fun;
//        createArray( (Integer t) -> {return new String[t];},10);
        String[] array = createArray(String[]::new, 10);
        System.out.println(array.length);//16
    }

    /*
    下面的方法,可以创建一个对象数组,并且该对象数组的长度是与指定的length最接近的2的幂次方
     */
    public <R> R[] createArray(Function<Integer,R[]> fun, int length){
        int n = length - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        length = n < 0 ? 1 : n + 1;//上面这段代码的作用就是把length处理成2的幂次方
        /*
        假设length一开始是30
        int n= length-1=29;             29的二进制  0000 0000  0000 0000 0000 0000 0001 1101
        n |= n >>> 1;  n = n | (n>>>1)   n 29的二进制  0000 0000  0000 0000 0000 0000 0001 1101
                                        n>>>1的二进制  0000 0000  0000 0000 0000 0000 0000 1110
                                        |             0000 0000  0000 0000 0000 0000 0001 1111
        n |= n >>> 2;  n = n | (n>>>2)   n            0000 0000  0000 0000 0000 0000 0001 1111
                                        n>>>2         0000 0000  0000 0000 0000 0000 0000 0111
                                        |             0000 0000  0000 0000 0000 0000 0001 1111
         ....
         发现>>>然后又|,结果就是  一个特殊的二进制,右边全是1,左边全是0

         length = n < 0 ? 1 : n + 1;    如果n不是小于0,length=n+1,就得到了2的幂次方
         */
        return fun.apply(length);
    }

    @Test
    public void test06(){
//        Supplier<String> supplier = () -> new String();
        Supplier<String> supplier = String::new;
    }

    @Test
    public void test05(){
        String[] arr = {"hello","HI","chai","lala"};
//        Arrays.sort(arr, (s1,s2) -> s1.compareToIgnoreCase(s2));
        Arrays.sort(arr, String::compareToIgnoreCase);
    }

    @Test
    public void test04(){
        Stream.generate(Math::random).forEach(System.out::println);
    }

    @Test
    public void test03(){
        Stream<Double> stream = Stream.generate(() -> Math.random());
        //遍历流中的数据
        stream.forEach(t-> System.out.println(t));
    }
    @Test
    public void test02(){
        new Thread(() -> System.out.println("hello lambda")); //无法使用方法引用进行简化
        new Thread(System.out::println);//打印一个空行
    }
    @Test
    public void test01(){
        //集合遍历
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("atguigu");

        //集合遍历的forEach
        list.forEach(t -> System.out.println(t));

        //进行简化
        //这里Lambda体是调用  System.out对象的println方法来完成
        list.forEach(System.out::println);
    }
}

3 StreamAPI-->内存中的输入输出

(1)概述

​ Lambda表达式能够在原来的API中使用的不多,因为原来的API中满足函数式接口的情况不多。

​ 新的Stream API中有大量的可以使用Lambda表达式的地方,因为它很多方法的参数类型都是函数式接口类型。

​ Stream API ( java.util.stream) 的作用是希望可以像sql一样,可以对“容器”中的数据进行处理,处理包括:筛选,统计,排序等等。

StreamAPI如何工作的?

(1)先根据某个“容器”,得到一个“数据流”

(2)调用StreamAPI中的方法,对数据进行“处理”。可以是处理0步,也可以处理1步,还可以处理n步 比喻成加工

(3)最终得到结果

StreamAPI的特点:

(1)处理期间,每一次都会得到一个新的数据流,不会影响原来的数据。

(2)StreamAPI的中间处理操作是一个延迟操作,即如果没有最后一步拿结果的代码,我所有中间的操作都不执行

(3)Stream本身是不负责存储数据,最终要存储结果啥的还得依靠“容器”

(2)创建Stream

①方式一:通过集合创建Stream

JDK1.8之后,集合类型增加了一些方法

例如:Collection系列的集合增加了:default Stream stream()

②方式二:通过数组创建Stream

​ JDK1.8之后,在数组工具类Arrays中增加了一些方法,可以利用某个数组,创建Stream

③方式三:Stream接口里面有一些静态方法可以创建Stream流对象

static Stream generate(Supplier s) :创建无限流

static Stream iterate(T seed, UnaryOperator f):创建无限流

static Stream of(T... values) :创建有限流

static Stream of(T t) :创建有限流

public class TestCreateStream {
    @Test
    public void test06(){
        Stream<String> stream = Stream.of("hello", "world", "java");
    }
    @Test
    public void test05(){
        /*
        static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
        iterate方法的第二个形参是UnaryOperator<T>,它是Function接口的子接口,是Function接口的变形。
        interface UnaryOperator<T> extends Function<T,T>
        Function<T,R> 抽象方法  R apply(T t)
        UnaryOperator<T> 抽象方法 T apply(T t)
         */
        Stream<Integer> stream = Stream.iterate(1, t -> t + 2);
        stream.forEach(System.out::println);
    }
    @Test
    public void test04(){
        Stream<Double> generate = Stream.generate(Math::random);
    }
    @Test
    public void test03(){
        String[] arr = {"hello","world","java"};
        Stream<String> stream = Arrays.stream(arr);
    }
    @Test
    public void test02(){
        HashMap<Integer,String> map = new HashMap<>();
        map.put(1,"java");
        map.put(2,"c++");
        map.put(3,"python");

        Stream<Map.Entry<Integer, String>> stream = map.entrySet().stream();
    }
    @Test
    public void test01(){
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");

        Stream<String> stream = list.stream();
        //只有拿结果时,上面创建才有意义
        //比如:遍历结果,也是表示结果
        stream.forEach(System.out::println);
        /*
        这里先演示创建stream,还未演示加工处理,所以看起来和直接遍历集合没有区别
         */
    }
}

(3)中间操作

Stream的中间加工处理操作:可以0~n步 (1)Stream filter(Predicate<? super T> predicate):过滤

(2)Stream distinct() :去重

(3)Stream limit(long maxSize) :限制maxSize个元素

(4)Stream skip(long n) :跳过n个

(5)Stream peek(Consumer<? super T> action)

(6)Stream sorted():默认使用元素的Comparable接口的compareTo方法进行排序 Stream sorted(Comparator<? super T> comparator) :使用Comparator接口的compare方法进行排序

(7) Stream map(Function<? super T,? extends R> mapper) :映射操作,对流中的每一个元素执行xx操作,得到的结果构成新的流

(8) Stream flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

​ 观察:上面所有方法的返回值类型都是Stream类型, 因为方法的返回值是Stream类型的结果,因此可以再对结果进行处理操作。即可以再调用Stream的其他方法。

public class TestStreamMiddleHandler {
    @Test
    public void test11() {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("Lily");
        list.add("HanMeiMei");
        list.add("LiLei");

        list.stream().map(t-> Stream.of(t.split("|"))).forEach(System.out::println);
    }
    @Test
    public void test10() {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("Lily");
        list.add("HanMeiMei");
        list.add("LiLei");

        /*
        <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
        flatMap方法的形参类型是:Function接口,是函数式接口,它的抽象方法:R apply(T t)

        对比一下:
        <R> Stream<R> map(Function<? super T,? extends R> mapper)

         Function<T,R>接口  ==> map方法的形参Function,Function<? super T,? extends R>
                                            T ->  ? super T
                                            R ->  ? extends R
                            ==>flatMap方法的形参Function, Function<? super T,? extends Stream<? extends R>>
                                            T ->  ? super T
                                            R -> ? extends Stream<? extends R>
                                            意思是对流中的元素t处理,处理完的结果是一个Stream

                                            例如:现在对流中的元素 “hello"加工处理完,要得到一个Stream的流对象
         */
        //需求:把每一个字符串打散,变成 字符数组,例如:hello-> h,e,l,l,o
        //t.split("|")得到的是String[]  "hello" ==> {"h","e","l","l","o"}
        list.stream().flatMap(t-> Stream.of(t.split("|"))).forEach(System.out::println);
    }
    @Test
    public void test09() {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("Lily");
        list.add("HanMeiMei");
        list.add("LiLei");

        /*
        <R> Stream<R> map(Function<? super T,? extends R> mapper)
        map方法的形参是 Function接口类型,它是函数式接口,它的抽象方法:R apply(T t)
         */
        //需求:把它们都转为大写
        list.stream().map(String::toUpperCase).forEach(System.out::println);
    }

    @Test
    public void test08() {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("Lily");
        list.add("HanMeiMei");
        list.add("LiLei");

        //加工处理, 首先要取字符串长度超过4的字符串,按照字母顺序排序,不区分大小写
        list.stream()
                .filter(s->s.length()>4)
                .sorted(String::compareToIgnoreCase)
                .forEach(System.out::println);
    }
    @Test
    public void test07() {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");

        /*
        Stream<T> peek(Consumer<? super T> action)
        形参是Consumer接口,是消费性的函数式接口,可以使用Lambda表达式
         */
        long count = list.stream().peek(System.out::println).count();
        System.out.println("count = " + count);
    }

    @Test
    public void test06() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 1, 3, 4, 5);
        list.stream().skip(2).forEach(System.out::println);
    }
    @Test
    public void test05() {
        Stream.generate(Math::random).limit(10).forEach(System.out::println);
    }
    @Test
    public void test04() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 1, 3, 4, 5);
        list.stream().limit(2).forEach(System.out::println);
    }
    @Test
    public void test03(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 1, 3, 4, 5);
/*        //创建Stream
        Stream<Integer> stream = list.stream();
        //加工处理
        stream = stream.distinct();
        //看结果
        stream.forEach(System.out::println);*/
        list.stream().distinct().forEach(System.out::println);
    }
    @Test
    public void test02(){
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");

        //创建Stream,加工处理,取结果合起来写
        list.stream()                   //创建
            .filter(t-> t.length()>4)   //加工
            .forEach(System.out::println);//结果
    }

    @Test
    public void test01(){
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");

        //创建Stream
        Stream<String> stream = list.stream();
        //加工处理
        /*
        Stream<T> filter(Predicate<? super T> predicate)
        (1)filter的形参是Predicate类型,是函数式接口,它的抽象方法  boolean test(T t)
        (2)filter方法的返回值类型是Stream类型,需要接收
            返回值类型是Stream,意味着可以再次调用Stream的方法
         */
        //需求:只要流中长度>4字符串
        stream = stream.filter(t-> t.length()>4);
        //看结果
        stream.forEach(System.out::println);
    }
}

(4)终结操作

​ 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。

​ 流进行了终止操作后,不能再次使用。

​ 意思:StreamAPI中的方法,只要返回值类型不是Stream类型都属于Stream的终结操作。

(1)void forEach(Consumer<? super T> action):遍历流中的数据

(2)long count() :统计流中的元素的个数

(3)boolean allMatch(Predicate<? super T> predicate):流中的所有元素是否都匹配xx条件

​ boolean anyMatch(Predicate<? super T> predicate):流中的元素是否有一些匹配xx条件

​ boolean noneMatch(Predicate<? super T> predicate)::流中的所有元素是否都不匹配xx条件

(4)Optional findFirst() :取出流的第一个

Optional findAny() :取出流的任意一个,如果当前流是一个固定的流,那么默认也返回第一个

这两个方法的返回值结果用Optional容器包装起来的

(5)Optional max(Comparator<? super T> comparator)

​ Optional min(Comparator<? super T> comparator)

(6)<R,A> R collect(Collector<? super T,A,R> collector)

​ collect方法的形参Collector是一个接口,又不是函数式接口,无法直接使用Lambda表达式,

看collect方法的API文档中给了我们一下样例:

​ API Note:

​ 以下将将字符串累加到ArrayList中:

​ List asList = stringStream.collect(Collectors.toList()); 以下将按城市分类Person对象:

​ Map<String, List> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity)); 以下将按国家和城市对Person对象进行分类,将两个Collector组合在一起:

​ Map<String, Map<String, List>> peopleByStateAndCity = personStream.collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)));

发现Collector接口需要配合Collectors工具类使用。

(7)Optional reduce(BinaryOperator accumulator)

​ T reduce(T identity, BinaryOperator accumulator)

public class TestStreamEndding {
    @Test
    public void test09(){
        //Stream.of(1, 7, 3, 9, 5, 2, 6)把流中的元素累加起来,求它们的和
        /*
        Optional<T> reduce(BinaryOperator<T> accumulator)
        reduce方法的形参是BinaryOperator类型,它是一个函数式接口,
        BinaryOperator<T>它继承了BiFunction<T,T,T>

        BiFunction<T,U,R>:R apply(T t, U u)
        BinaryOperator<T>:T apply(T t1, T t2)
         */
        Optional<Integer> result = Stream.of(1, 7, 3, 9, 5, 2, 6).reduce((t1, t2) -> t1 + t2);
        System.out.println("result = " + result);
    }
    @Test
    public void test08(){
        List<Integer> collect = Stream.of(1, 7, 3, 9, 5, 2, 6).sorted().collect(Collectors.toList());
        Collections.reverse(collect);
        System.out.println(collect.stream().skip(2).findFirst());
    }
    @Test
    public void test07(){
        //取出流中排第三的数字,从大到小
        //1,2,3,5,6,7,9
        Optional<Integer> middle = Stream.of(1, 7, 3, 9, 5, 2, 6 ).sorted().skip(4).findFirst();
        System.out.println(middle);
    }

    @Test
    public void test06(){
        /*
        Optional<T> max(Comparator<? super T> comparator)
        max方法的形参类型是Comparator类型,是函数式接口,可以使用Lambda表达式
         */
        Optional<Integer> max = Stream.of(1, 7, 3, 9, 5).max(Integer::compareTo);
        System.out.println("max = " + max);
    }


    @Test
    public void test05(){
        Optional<Integer> first = Stream.of(1, 7, 3, 9, 5).filter(t -> t % 2 == 0).findFirst();
        System.out.println(first);

        Optional<Integer> result = Stream.of(1, 2, 3, 4, 5).filter(t -> t % 2 == 0).findFirst();
        System.out.println("result = " + result);
    }
    @Test
    public void test04(){
        System.out.println(Stream.of(1, 7, 3, 9, 5).anyMatch(t -> t % 2 == 0));//流中是否有偶数  false
        System.out.println(Stream.of(1, 7, 3, 9, 5).noneMatch(t -> t % 2 == 0));//流中是否没有偶数  true
        System.out.println(Stream.of(1, 7, 3, 9, 5).allMatch(t -> t % 2 == 0));//流中是否全是偶数   false
    }

    @Test
    public void test03(){
        System.out.println(Stream.of(1, 2, 3, 4, 5).anyMatch(t -> t % 2 == 0));//流中是否有偶数  true
        System.out.println(Stream.of(1, 2, 3, 4, 5).noneMatch(t -> t % 2 == 0));//流中是否没有偶数  false
        System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(t -> t % 2 == 0));//流中是否全是偶数   false
    }
    @Test
    public void test02(){
        long count = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).filter(t -> t % 2 == 0).count();
        System.out.println("count = " + count);//统计流中偶数的个数

        System.out.println("----------------");
        System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).filter(t -> t % 2 == 0).count());
    }
    @Test
    public void test01(){
        Stream.of("hello","java","world").forEach(System.out::println);
    }
}

4 Optional -->解决NullPointerException

​ 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。

​ Java8为了避免空指针异常,做了很多的工作。

(1)ArrayList以前“new”ArrayList时,内部默认创建长度为10的数组。

​ 因为很多方法的返回值类型就是ArrayList,有些程序员为了节省内存,当方法的结果没有元素时,就返回null处理,会导致调用这些方法的地方,另一些程序忘了判断结果是否为null,直接遍历集合导致空指针异常。Java8再次对ArrayList进行处理,“new”ArrayList时,内部默认创建一个长度为0的空数组,既节约的内存,又避免得到一个null结果。

(2)Optional类

​ 也是用来解决很多方法的返回值类型可能返回null的问题。

​ 例如:StreamAPI中的findFirst方法,它原来返回值应该是一个T ,即一个对象,但是有可能这个流中现在没有对象 Stream.of(1, 7, 3, 9, 5).filter(t -> t % 2 == 0).findFirst() 里面因为没有偶数,结果是null如果调用者并没有考虑到null的情况,直接用对象,很容易造成空指针异常。为了避免返回null,findFirst方法把返回值类型设置为Optional类型。至少会得到一个Optional对象,肯定不是null.

​ Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。即Optional这个容器要么保存一个对象,要么什么都没有的空容器。

①如何用Optional包装对象

​ of(t):仅适用于包装非空对象 ​

​ ofNullable(t):包装一个可能为空也可能非空的对象 ​

​ empty():一定是空的元素

②如何从Optional中取出数据使用

​ T get() -->配合of方法用的

T orElse(T other) -->配合 ofNullable(t)方法使用

​ 如果Optional容器中是非空,返回实际的元素,否则返回other替换元素

T orElseGet(Supplier<? extends T> other) -->配合 ofNullable(t)方法使用

​ 如果Optional容器中是非空,返回实际的元素,否则返回Supplier供给型接口提供的对象

③其他方法

​ boolean isPresent() :判断Optional容器是否存在元素

​ void ifPresent(Consumer<? super T> consumer) :如果Optional容器有元素,就执行xx操作,否则就不执行

public class TestOptional {
    @Test
    public void test10(){
//        String str = "hello";
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
        opt.ifPresent(System.out::println);
    }
    @Test
    public void test09(){
//        String str = "hello";
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
        System.out.println(opt.orElseGet(()->new String()));
    }
    @Test
    public void test08(){
        String str = "hello";
        Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
        System.out.println(opt.orElse("world"));
    }
    @Test
    public void test07(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
        System.out.println(opt.orElse("world"));
    }

    @Test
    public void test06(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
        System.out.println(opt.get());//NoSuchElementException: No value present
    }
    @Test
    public void test05(){
        String str = "hello";
        Optional<String> opt = Optional.of(str);//一定非空
        System.out.println(opt.get());//取出里面的元素
    }

    @Test
    public void test04(){
        Optional<Object> opt = Optional.empty();//明确包装的是空的
    }
    @Test
    public void test03(){
        String str = "hello";
        Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
        System.out.println(opt);
    }
    @Test
    public void test02(){
        String str = null;
        Optional<String> opt = Optional.ofNullable(str);//可能为空,可能非空
        System.out.println(opt);
    }
    @Test
    public void test01(){
        String str = "hello";
        Optional<String> opt = Optional.of(str);//一定非空
        System.out.println(opt);
    }
}