JavaSE Java8新特性

Java8引入了Lambda表达式和StreamAPI,使得代码更加简洁和高效。Lambda表达式是一种匿名函数,可以作为函数式接口的实例,简化了匿名内部类的使用。函数式接口是指只有一个抽象方法的接口,通常配合Lambda表达式使用。StreamAPI提供了一种处理集合数据的新方式,支持链式操作和延迟执行,可以高效地进行数据过滤、转换和收集。Stream操作包括中间操作(如filter、limit、skip)和终结操作(如forEach、count)。这些新特性极大地提高了Java的函数式编程能力。
摘要由CSDN通过智能技术生成

第17章 Java8其他新特性

学习目标

  • 能够理解函数式编程相对于面向对象的优点
  • 理解函数式接口概念
  • 了解@FunctionalInterface注解
  • 能够掌握Lambda表达式的标准格式
  • 能够掌握Lambda表达式的省略格式与规则
  • 能够使用Supplier函数式接口
  • 能够使用Consumer函数式接口
  • 能够使用Function函数式接口
  • 能够使用Predicate函数式接口
  • 能够自定义函数式接口并使用Lambda表达式使用它
  • 能够使用方法引用和构造器引用
  • 能够理解流与集合相比的优点
  • 能够理解流的延迟执行特点
  • 能够通过集合、映射或数组获取流
  • 能够掌握常用的流操作
  • 能够使用Optional类包装对象并获取其中包装的对象

Java8的新特征有很多,之前我们在学习接口时,学习了接口的静态方法和默认方法,在学习常用类时,学习了新版的日期时间API。

今天我们来学习Java8最具革命性的两个新特性:Lambda表达式Stream流

17.1 Lambda表达式

Lambda表达式是JDK1.8之后的一种语法。Lambda表达式本质上是⼀个匿名⽅法。Lambda允许把函数作为⼀个⽅法的参数(函数作为参数传递进⽅法中)或者把代码看成数据。使⽤Lambda 表达式可以使代码变的更加简洁紧凑,使Java的语言表达能力得到了提升

17.1.1 Lambda入门示例

需求:开启一个线程,完成一个任务,任务是在控制台输出一句话"革命尚未成功,同志还需努力"

实现方式一:自定义Runnable接口实现类
package com.atguigu.lambda;

public class TestLambda {

    public static void main(String[] args) {

        // 方式一:自定义Runnable接口实现类来定义任务内容,并使用Thread类来启动该线程
        MyRunnable mr = new MyRunnable();
        new Thread(mr).start();

        
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("革命尚未成功,同志还需努力11111111");
    }
}
实现方式二:匿名内部类方式
package com.atguigu.lambda;

public class TestLambda {

    public static void main(String[] args) {

        // 方式一:自定义Runnable接口实现类来定义任务内容,并使用Thread类来启动该线程
        MyRunnable mr = new MyRunnable();
        new Thread(mr).start();

        // 方式二,创建一个Runnable匿名内部类来定义任务内容,并使用Thread类来启动该线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("革命尚未成功,同志还需努力22222222");
            }
        }).start();
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("革命尚未成功,同志还需努力11111111");
    }
}
实现方式三:通过Lambda方式
package com.atguigu.lambda;

public class TestLambda {

    public static void main(String[] args) {

        // 方式三:通过Lambda方式简化Runnable实现类,并使用Thread类来启动该线程
        new Thread(() -> System.out.println("革命尚未成功,同志还需努力33333")).start();
    }
}
代码分析:
面向对象思想

对于Runnable的匿名内部类用法,可以分析出几点内容:

  • Thread类需要Runnable接口作为参数,其中的抽象run方法是用来指定线程任务内容的核心;

  • 为了指定run的方法体,不得不需要Runnable接口的实现类;

  • 为了省去定义一个RunnableImpl实现类的麻烦,不得不使用匿名内部类;

  • 必须覆盖重写抽象run方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;

  • 而实际上,似乎只有方法体才是关键所在

  • 在当前需求中,我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将run方法体内的代码传递给Thread类知晓。

    传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。

    编程思想转换——当前需求中,我们更在意做什么,怎么做,而不是"谁来做"。

17.1.2 函数式编程思想

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbaVV9oD-1680573818944)(尚硅谷-JavaSE-第17章 Java8新特性-陈叶.assets/03-Overview.png)]

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”。编程中的函数,也有类似的概念,你调用我的时候,给我实参为形参赋值,然后通过运行方法体,给你返回一个结果。对于调用者来做,关注这个方法具备什么样的功能。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做

  • 面向对象的思想:

    • 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.
  • 函数式编程思想:

    • 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程

Java8引入了Lambda表达式之后,Java也开始支持函数式编程。

Lambda表达式不是Java最早使用的,很多语言就支持Lambda表达式,例如:C++,C#,Python,Scala等。如果有Python或者Javascript的语言基础,对理解Lambda表达式有很大帮助,可以这么说lambda表达式其实就是实现SAM接口的语法糖,使得Java也算是支持函数式编程的语言。Lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的(啰嗦的)匿名内部类。

备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实
底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部
类的“语法糖”,但是二者在原理上是不同的。

刚才案例中的方式三代码和方式一、二的执行效果是完全一样的,可以在1.8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。

不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担,就是这么简单!

17.1.3 Lambda表达式语法

17.1.3.1 Lambda表达式语法格式及说明
(形参列表) -> {Lambda}

语法格式说明:

- (形参列表)它就是你要赋值的函数式接口的抽象方法的(形参列表),照抄
- {Lambda}就是实现这个抽象方法的方法体
- ->称为Lambda操作符(减号和大于号中间不能有空格,而且必须是英文状态下半角输入方式) 

以一个Runnable为例,Lambda简化过程:

Lambda简化过程
第一步,去掉匿名内部类的声明
第二步,去掉方法名和返回值
第三步,参数与作用域{}之间加上->
第四步,去掉参数数据类型(自动推断类型)
17.1.3.2 使用Lambda表达式语法示例
需求:实现一个字符串数组的排序,要求字符串排序过程中忽略字母的大小写

	例:字符串数组为{"xx","YY","BB","aa"}   
		排序后结果为:{"aa","bb","xx","YY"}
实现方式一:匿名内部类
package com.atguigu.lambda;

import java.util.Arrays;
import java.util.Comparator;

/**
 * @author Sarah
 * @description 练习Lambda表达式的标准语法
 *  需求:实现一个字符串数组的排序,要求字符串排序过程中忽略字母的大小写
 * 		例:字符串数组为{"xx","YY","BB","aa"}
 * 			 排序后结果为:{"aa","bb","xx","YY"}
 *      分析:字符串默认的排序方式是按照字母的ascii码表值排序,
 *          所以,需要自定义排序(忽略大小写)
 *          Arrays.sort(arr,Comparator接口)
 *
 *   Lambda表达式标准语法
 *   (形参列表) -> {Lambda体}
 */
public class LambdaDemo02 {

    public static void main(String[] args) {
        // 1.定义一个字符串数组
        String[] arr = {"xx","YY","BB","aa"};

        // 2.对字符串按照要求进行排序(忽略大小写)
        // 2.1 使用匿名内部类方式实现
        Arrays.sort(arr, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2);
            }
        });

        // 3.将排序后的字符串数组输出
        System.out.println(Arrays.toString(arr));
    }
}

实现方式二:Lambda表达式
package com.atguigu.lambda;

import java.util.Arrays;
import java.util.Comparator;

/**
 * @author Sarah
 * @description 练习Lambda表达式的标准语法
 *  需求:实现一个字符串数组的排序,要求字符串排序过程中忽略字母的大小写
 * 		例:字符串数组为{"xx","YY","BB","aa"}
 * 			 排序后结果为:{"aa","bb","xx","YY"}
 *      分析:字符串默认的排序方式是按照字母的ascii码表值排序,
 *          所以,需要自定义排序(忽略大小写)
 *          Arrays.sort(arr,Comparator接口)
 *
 *   Lambda表达式标准语法
 *   (形参列表) -> {Lambda体}
 */
public class LambdaDemo02 {

    public static void main(String[] args) {
        // 1.定义一个字符串数组
        String[] arr = {"xx","YY","BB","aa"};

        // 2.对字符串按照要求进行排序(忽略大小写)
        // 2.1 使用匿名内部类方式实现
        /*Arrays.sort(arr, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2);
            }
        });*/

        // 2.2 使用Lambda方式实现
        // 2.2.1 Lambda表达式标准语法形式
        // Arrays.sort(arr, (String o1, String o2)->{return o1.compareToIgnoreCase(o2);});
        // 2.2.2 Lambda表达式中参数的数据类型可以省略(可自行推断)
        // Arrays.sort(arr, ( o1,  o2)->  {return o1.compareToIgnoreCase(o2);} );

        // 2.2.3 Lambda体中如果只有一行代码,则可以省略{;}
        // 如果省略{}时有return关键字,则return也必须一并省略
        Arrays.sort(arr, (o1,  o2)->  o1.compareToIgnoreCase(o2));

        // 3.将排序后的字符串数组输出
        System.out.println(Arrays.toString(arr));
    }
}

Lambda表达式的简化说明

某些情况下Lambda表达式可以精简:

- 当{Lambda体}中只有一句语句时,可以省略{}和{;}
- 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么{}、return、;三者可以省略。它们三要么一起省略,要么都不省略。
- 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
- 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
- 当Lambda表达式(形参列表)是空参时,()不能省略
17.1.3.4 Lambda的应用场景
是不是所有的接口实例都能使用Lambda表达式进行替代呢?
实例:一个接口里面有多个抽象方法
代码演示
package com.atguigu;

public class Demo3 {

    public static void main(String[] args) {

    ClassBB bb = new ClassBB();
    /*bb.show(new InterAA() {
        @Override
        public void method1() {
            System.out.println("aaa");
        }

        @Override
        public void method2() {
            System.out.println("bbb");
        }
    });*/

        bb.show(() -> { System.out.println("aaa"); });


    }
}
class ClassBB{
    public void show(InterAA aa){
        aa.method1();
    }
}

interface InterAA{
    void method1();
    // void method2();

}

答案:不是,并不是所有的接口实例都可以使用Lambda表达式进行替代,

寻找Runnable和Comparator的规律

17.1.4 函数式接口

17.1.4.1 函数接口的概念

lambda表达式其实就是实现SAM接口的语法糖,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法。

其实只要满足“SAM”特征的接口都可以称为函数式接口都可以使用Lambda表达式,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

17.1.4.2 盘点之前学过的接口

之前学过的接口已经很多了:Cloneable、Comparable、Comparator、Runnable、Iterable、Iterator、Collection、List、Set、Map、Serializable等。

上述接口中,满足SAM接口特点的有:

  • java.lang.Runnable
    • public void run()
  • java.lang.Iterable
    • public Iterator iterate()
  • java.lang.Comparable
    • public int compareTo(T t)
  • java.util.Comparator
    • public int compare(T t1, T t2)

上述SAM接口中,标记了@FunctionalInterface注解有:Runnable,Comparator

结论:

Lambda表达式就是当做一个【函数式接口】的实例 用来给的变量或形参赋值用的。

总结
Lambda表达式
        本质:  可以当做函数式接口的实例
        一个实例的基本用法
            用法
                   当做 参数实例
                   当做 返回值实例
Lambda表达式的简化说明

某些情况下Lambda表达式可以精简:

- 当{Lambda体}中只有一句语句时,可以省略{}和{;}
- 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么{}、return、;三者可以省略。它们三要么一起省略,要么都不省略。
- 当Lambda表达式(形参列表)的类型已知,获取根据泛型规则可以自动推断,那么(形参列表)的数据类型可以省略。
- 当Lambda表达式(形参列表)的形参个数只有一个,并且类型已知或可以自动推断,则形参的数据类型和()可以一起省略,但是形参名不能省略。
- 当Lambda表达式(形参列表)是空参时,()不能省略
17.1.4.3 java.util.function包四大类函数式接口

Java8在java.util.function新增了很多函数式接口:主要分为四大类,供给型、消费型、判断型、功能型。基本可以满足我们的开发需求。当然你也可以定义自己的函数式接口。

体验函数式接口的四大类型
1.消费型接口 Comsumer  特点:有参数,无返回值
           void    accept(T t)   接收一个对象用于完成功能
2.供给型接口 Supplier  特点:无参数,有返回值
           T       get()        返回一个对象
3.判断型接口 Predicate  特点:有参数,返回值类型为boolean
           boolean test(T t)    接收一个对象,返回值为boolean类型
4. 功能型接口 Function/UnaryOperator 特点:有参数,有返回值
		   R       apply(T t)   接收一个T类型对象,返回一个R类型对象结果

其中UnaryOperator接口的参数类型和返回值类型需一致
  • 消费型接口(Consumer)

消费型接口的抽象方法特点:有形参,但是返回值类型是void

接口名抽象方法描述
Consumervoid 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值
消费型接口使用示例:
JDK1.8中java.lang.Iterable接口中增加了一个默认方法
因为Collection接口继承了Iterable接口,这就意味着所有Collection系列的接口都包含该方法。

public default void forEach(Consumer<? super T> consumer) 该方法功能是遍历Collection集合,并将传递给consumer参数的操作代码应用在每一个元素上

forEach方法的功能:遍历集合拿到每一个元素,并对元素做操作
	具体什么操作取决于参数列表中Comsumer接口中的accpet()的定义
package com.atguigu.lambda;

import java.util.ArrayList;

/**
 * @author Sarah
 * @description 体验函数式接口的四大类型
 *      1.消费型接口 Comsumer  特点:有参数,无返回值
 *          void accept(T t)  接收一个对象用于完成功能
 */
public class FunntionInterfaceDemo {

    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("atguigu");
        list.add("sarah");

        // forEach方法的功能:遍历集合拿到每一个元素,并对元素做操作
        // 具体什么操作取决于参数列表中Comsumer接口中的accpet()的定义
        list.forEach(s -> System.out.println(s));
    }
}

  • 供给型接口(Supplier)

Supplier接口是一个供给型的接口,本质就是一个容器,可以用来存储数据(或者是产生数据的规则),然后可以供其他方法使用的这么一个接口

供给型接口的抽象方法特点:无参,但是有返回值

接口名抽象方法描述
SupplierT get()返回一个对象
BooleanSupplierboolean getAsBoolean()返回一个boolean值
DoubleSupplierdouble getAsDouble()返回一个double值
IntSupplierint getAsInt()返回一个int值
LongSupplierlong getAsLong()返回一个long值
供给型接口使用示例:

测试类:

package com.atguigu.lambda;

import com.atguigu.bean.Student;

import java.util.ArrayList;
import java.util.Random;
import java.util.function.Supplier;

/**
 * @author Sarah
 * @description 体验函数式接口的四大类型
 *      1.消费型接口 Comsumer  特点:有参数,无返回值
 *          void  accept(T t)  接收一个对象用于完成功能
 *      2.供给型接口 Supplier  特点:无参数,有返回值
 *          T     get()        返回一个对象
 */
public class FunntionInterfaceDemo01 {

    public static void main(String[] args) {

        /*Supplier<Integer> supplier1 = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return new Random().nextInt(100);
            }
        };
        System.out.println(supplier1.get());*/
        Supplier<Integer> supplier1 = () -> new Random().nextInt();
        System.out.println(supplier1.get());

        Supplier<String> supplier2 = () ->"尚硅谷";
        System.out.println(supplier2.get());

        Supplier<Student> supplier3 = () ->new Student("张三",23);
        System.out.println(supplier3.get());
    }

}

Student类

package com.atguigu.bean;

/**
 * @author Sarah
 * @description 学生类
 */
public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

  • 3、判断型接口

判断型接口的抽象方法特点:有参,返回值类型是boolean结果。

接口名抽象方法描述
Predicateboolean test(T t)接收一个对象
BiPredicate<T,U>boolean test(T t, U u)接收两个对象
DoublePredicateboolean test(double value)接收一个double值
IntPredicateboolean test(int value)接收一个int值
LongPredicateboolean test(long value)接收一个long值
判断型接口使用示例:
JDK1.8时,Collecton<E>接口增加了一下方法,其中一个如下:

public default boolean removeIf(Predicate<? super E> predicate)用于删除集合中满足predicate指定的条件判断的。
	具体哪些元素符合条件取决于参数列表中Predcate接口中的test()的定义	
package com.atguigu.lambda;

import com.atguigu.bean.Student;

import java.util.ArrayList;
import java.util.Random;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * @author Sarah
 * @data 2022/6/9 11:14
 * @description 体验函数式接口的四大类型
 *      1.消费型接口 Comsumer  特点:有参数,无返回值
 *          void    accept(T t)   接收一个对象用于完成功能
 *      2.供给型接口 Supplier  特点:无参数,有返回值
 *          T       get()        返回一个对象
 *      3.判断型接口 Predcate  特点:有参数,返回值类型为boolean
 *          boolean test(T t)    接收一个对象
 */
public class FunntionInterfaceDemo01 {

    public static void main(String[] args) {

        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("atgui");
        list.add("sarah");

        // 集合原数据
        System.out.println("集合原数据:----------");
        list.forEach(s -> System.out.println(s));

        // list.removeIf(new Predicate<String>() {
        //     @Override
        //     public boolean test(String s) {
        //         return s.contains("o");
        //     }
        // });
        
        // removeIf方法的功能:删除符合条件的元素
        // 具体哪些元素符合条件取决于参数列表中Predcate接口中的test()的定义
        list.removeIf(s -> s.contains("o"));

        System.out.println("删除包含o字母的元素之后:----------");
        list.forEach(s -> System.out.println(s));

    }
}
  • 功能型接口(Function)

用来根据一个类型的数据(T)得到另一个类型的数据®。

这类接口的抽象方法特点:既有参数又有返回值

接口名抽象方法描述
Function<T,R>R apply(T t)接收一个T类型对象,返回一个R类型对象结果
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结果
功能型接口使用示例:
package com.atguigu.lambda;

import java.util.function.Function;
import java.util.function.UnaryOperator;

/**
 * @author Sarah
 * @description 体验函数式接口的四大类型
 *      1.消费型接口 Comsumer  特点:有参数,无返回值
 *          void    accept(T t)   接收一个对象用于完成功能
 *      2.供给型接口 Supplier  特点:无参数,有返回值
 *          T       get()        返回一个对象
 *      3.判断型接口 Predcate  特点:有参数,返回值类型为boolean
 *          boolean test(T t)    接收一个对象
 *      4. 功能型接口 Function/UnaryOperator 特点:有参数,有返回值
 *          R       apply(T t)   接收一个T类型对象,返回一个R类型对象结果
 *          其中UnaryOperator接口的参数类型和返回值类型需一致
 */
public class FunntionInterfaceDemo {

    public static void main(String[] args) {

        Function<String,Integer> fun1 = s -> Integer.parseInt(s);
        System.out.println(fun1.apply("22"));

        Function<String,String> fun2 = s -> s.substring(0,1).toUpperCase();
        System.out.println(fun2.apply("sarah"));

        UnaryOperator<String> operator = s -> "尚硅谷";// 此接口要求参数类型和返回值类型保持一致
        System.out.println(operator.apply("sarah"));

    }

17.1.5 方法引用与构造器引用

17.1.5.1 方法引用
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式, 也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。

jdk1.8提供了另外⼀种调⽤⽅式::,当你需要使⽤⽅法引⽤时,⽬标引⽤放在分隔符::前,⽅法的名称放在后⾯ ,即 ClassName :: methodName 。例如,Apple::getWeight 就是引⽤了Apple类中定义的⽅法getWeight。请记住,不需要括号,因为你没有实际调⽤这个⽅法。

	Apple::getWeight⽅法引⽤就是"(Apple a) ->a.getWeight()"此Lambda表达式的快捷写法。
应用场景
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

当某个方法与函数式接口的抽象方法的方法签名一致时,可以使用方法引用简化Lambda表达式
	方法标签一致,即函数式接口的参数列表和返回值类型,与方法引用的参数列表和返回值类型保持一致!
语法格式:
对象名::实例方法
类名::静态方法
类名::实例方法
格式说明:
- ::称为方法引用操作符(两个:中间不能有空格,而且必须英文状态下半角输入)

- Lambda表达式的形参列表,全部在Lambda体中使用上了,

  - 类名.静态方法:Lambda表达式的形参列表与某类静态方法的形参列表完全一致
  - 对象名.实例方法:Lambda表达式的形参列表与某类实例方法的形参列表完全一致
  - 类名.实例方法:Lambda表达式的形参列表的第1个形参就是方法引用的对象,剩余的形参列表与所调用实例方法的形参列表一致。
  
- 在整个Lambda体中只有一条语句没有额外的数据。
举例
1> Lambda体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的
例如:System.out对象,调用println()方法来完成Lambda体
	 Math类,调用random()静态方法来完成Lambda体

2> 并且Lambda表达式的形参正好是给该方法的实参
例如:t->System.out.println(t)
	 () -> Math.random() 都是无参
代码演示
package com.atguigu.reference;

import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;

public class MethodReference {

    @Test
    public void test4(){
        String[] arr = {"Hello","java","chai"};
		Arrays.sort(arr, (s1,s2) -> s1.compareToIgnoreCase(s2));

        //用方法引用简化
        /*
         * Lambda表达式的形参,第一个(例如:s1),正好是调用方法的对象,剩下的形参(例如:s2)正好是给这个方法的实参
         */
        Arrays.sort(arr, String::compareToIgnoreCase);
    }

    @Test
    public void test3(){
        Random random = new Random();
        Supplier<Integer> s1 = () -> random.nextInt();
        Supplier<Integer> s2 = random :: nextInt;//用方法引用简化
        System.out.println(s1.get());
        System.out.println(s2.get());
        //上面两个写法是等价的

        Supplier<Integer> s3 = () -> random.nextInt(100);
        Supplier<Integer> s4 = random :: nextInt;//用方法引用简化  缺100
        System.out.println(s3.get());
        System.out.println(s4.get());
        //上面两个写法是不等价的
    }

    @Test
    public void test2(){
        Supplier<Double> s1 = () -> Math.random();
        Supplier<Double> s2 = Math :: random;//用方法引用简化
        System.out.println(s1.get());
        System.out.println(s2.get());
        //上面两个写法是等价的
    }

    @Test
    public void test1(){
        List<Integer> list = Arrays.asList(1,3,4,8,9);
        //list.forEach(t -> System.out.println(t));

        //用方法引用再简化
        list.forEach(System.out::println);
    }
}

代码演示二:
package com.atguigu;

public class Demo1 {

    /*
      - 对象名.实例方法:Lambda表达式的形参列表与某类实例方法的形参列表完全一致
      - 类名.静态方法:Lambda表达式的形参列表与某类静态方法的形参列表完全一致
      - 类名.实例方法:Lambda表达式的形参列表的第1个形参就是调用方法的对象,
      Lambda表达式的形参列表与所调用实例方法的形参列表一致。

     */
    public static void main(String[] args) {

        // - 对象名.实例方法:Lambda表达式的形参列表与某类实例方法的形参列表完全一致
        ClassB cb = new ClassB();
        cb.mt1(new InterAA() {
            @Override
            public void method1() {
                System.out.println("--InterAA-----method1");
            }
        });

        cb.mt1(() ->{ System.out.println("--InterAA-----method1"); });
        cb.mt1(new ClassA()::show1);

        // - 类名.静态方法:Lambda表达式的形参列表与某类静态方法的形参列表完全一致
        System.out.println("=======================================");
        cb.mt2(new InterBB() {
            @Override
            public String method2() {
                return "------InterBB----method2";
            }
        });

        cb.mt2(() ->"------InterBB----method2");
        cb.mt2(ClassA::show2);

        // 类名.实例方法:Lambda表达式的形参列表的第1个形参就是调用方法的对象,
        //       Lambda表达式的形参列表与所调用实例方法的形参列表一致。
        System.out.println("=======================================");
        cb.mt3(new InterCC() {
            @Override
            public void method3(ClassA aa, int num) {
                aa.show3(num);
            }
        });

        cb.mt3((aa,num) ->{aa.show3(num);});
        cb.mt3(ClassA::show3);
    }
}

class ClassB{
    public void mt1(InterAA aa){
        aa.method1();
    }

    public void mt2(InterBB bb){
        System.out.println(bb.method2());
    }
    public void mt3(InterCC cc){
        cc.method3(new ClassA(),88);
    }

}

interface InterAA{
    void method1();
}

interface InterBB{
    String method2();
}

interface InterCC{
    void method3(ClassA aa,int num);
}

class ClassA{
    public void show1(){
        System.out.println("---ClassA----show1----");
    }

    public static String show2(){
        return "---ClassA----show2----";
    }
    public void show3(int num){
        System.out.println("---ClassA----show3----" + num);
    }
}
17.1.5.2 构造器引用
当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表,就可以使用构造器引用:

- 类名::new
package com.atguigu;

import java.util.function.Function;

public class Demo1 {

    /*
    当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表,就可以使用构造器引用:

    - 类名::new
     */
    public static void main(String[] args) {

        // Function<String,Person> fun = s -> new Person(s);
        Function<String,Person> fun2 = Person::new;
        Person p = fun2.apply("张三");
        System.out.println(p);

        // Function<Integer,Student> fun3 = i -> new Student(i);
        Function<Integer,Student> fun4 = Student::new;
        System.out.println(fun4.apply(3));
    }
}

class Person{
    String name;

    public Person(String name) {
        this.name = name;
    }

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

class Student{
    int age;

    public Student(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Studetn{" +
                "age=" + age +
                '}';
    }
}

17.1.5.3 数组构造引用
当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用:

- 数组类型名::new
代码演示
package com.atguigu.reference;

import java.util.Arrays;
import java.util.function.Function;

public class ArrayCreateReference {
    /*
    当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用:

    - 数组类型名::new
     */
    public static void main(String[] args) {
        
        // Function<Integer, Integer[]> function = (n) -> new Integer[n];
        Function<Integer,Integer[]> function = Integer[]::new;

        Integer[] arr = function.apply(5);
        System.out.println(Arrays.toString(arr));
    }
}

17.2 StreamAPI

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

17.2.1 Stream特点

Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式

Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

集合讲的是数据,负责存储数据,Stream流讲的是计算,负责处理数据!”

注意:

1> Stream 自己不会存储元素。

2> Stream 不会改变源对象。每次处理都会返回一个持有结果的新Stream。

3> Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
引入案例
案例需求
按照下面的要求完成集合的创建和遍历

- 创建一个集合,存储多个字符串元素
- 把集合中所有以"张"开头的元素存储到一个新的集合
- 把"张"开头的集合中的长度为3的元素存储到一个新的集合
- 遍历上一步得到的集合
代码演示:
package com.atguigu;

import java.util.ArrayList;

public class Demo1 {

    /*
    案例需求
     按照下面的要求完成集合的创建和遍历

    - 创建一个集合,存储多个字符串元素
    - 把集合中所有以"张"开头的元素存储到一个新的集合
    - 把"张"开头的集合中的长度为3的元素存储到一个新的集合
    - 遍历上一步得到的集合
     */
    public static void main(String[] args) {

        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        // - 把集合中所有以"张"开头的元素存储到一个新的集合
        ArrayList<String> list2 = new ArrayList<>();
        for (String s : list) {
            if(s.startsWith("张")){
                list2.add(s);
            }
        }
        System.out.println(list2);

        // - 把"张"开头的集合中的长度为3的元素存储到一个新的集合
        ArrayList<String> list3 = new ArrayList<>();
        for (String s : list2) {
            if(s.length() == 3){
                list3.add(s);
            }
        }
        // - 遍历上一步得到的集合
        for (String s : list3) {
            System.out.println(s);
        }
        System.out.println("-----------------");

        // 使用Stream流完成
        list.stream().filter(s -> s.startsWith("张"))
                .filter(s -> s.length() == 3)
                .forEach(s -> System.out.println(s));
        System.out.println("------------------");
        
        // 方法引用
        list.stream().filter(s -> s.startsWith("张"))
                .filter(s -> s.length() == 3)
                .forEach(System.out::println);

    }
}

17.2.2 Stream流的思想特点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NkagKEjF-1680573818946)(尚硅谷-JavaSE-第17章 Java8新特性-陈叶.assets/1663604798505.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LdbgyjXh-1680573818947)(尚硅谷-JavaSE-第17章 Java8新特性-陈叶.assets/1663604863367.png)]

17.2.3 Stream 的操作三个步骤

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

17.2.4 生成Stream流的方式

- Collection体系集合
  使用默认方法stream()生成流, default Stream<E> stream()

- Map体系集合
  把Map转成Set集合,**间接**的生成流

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

- 同种数据类型的多个数据
  通过Stream接口的静态方法of(T... values)生成流
  如:1,2,3,4,5,...
     " aa","bb","cc"," dd"...
代码演示:
package com.atguigu;

import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Demo1 {

    /*
    - Collection体系集合
      使用默认方法stream()生成流, default Stream<E> stream()

    - Map体系集合
      把Map转成Set集合,**间接**的生成流

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

    - 同种数据类型的多个数据
      通过Stream接口的静态方法of(T... values)生成流
      如:1,2,3,4,5,...
         " aa","bb","cc"," dd"...
     */
    public static void main(String[] args) {
        // - Collection体系集合
        // 使用默认方法stream()生成流, default Stream<E> stream()
        Stream<Object> stream1 = new ArrayList<>().stream();
        Stream<Object> stream2 = new HashSet<>().stream();

        // - Map体系集合
        // 把Map转成Set集合,**间接**的生成流
        Stream<Integer> stream3 = new HashMap<Integer, String>().keySet().stream();
        Stream<Map.Entry<Integer, String>> stream4 = new HashMap<Integer, String>().entrySet().stream();

        // - 数组
        // 通过Arrays中的静态方法stream生成流
        Stream<String> stream5 = Arrays.stream(new String[5]);

        //  - 同种数据类型的多个数据
        // 通过Stream接口的静态方法of(T... values)生成流
        Stream<Integer> stream6 = Stream.of(1, 2, 3, 4);
        Stream<String> stream7 = Stream.of("aa", "bb");
    }

}

17.2.5 中间操作API

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

执行完此方法之后,Stream流依然可以继续执行其他操作
常见方法
方法名说明
Stream filter(Predicate predicate)用于对流中的数据进行过滤
Stream limit(long maxSize)返回此流中的元素组成的流,截取前指定参数个数的数据
Stream skip(long n)跳过指定参数个数的数据,返回由该流的剩余元素组成的流
static Stream concat(Stream a, Stream b)合并a和b两个流为一个流
Stream distinct()返回由该流的不同元素(根据Object.equals(Object) )组成的流
ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");
        
需求:
	1> 把list集合中以张开头的,长度为3的元素在控制台输出
	
	2> 取前3个数据在控制台输出
	3> 跳过4个元素,把剩下的元素在控制台输出
	4> 跳过2个元素,把剩下的元素中前2个在控制台输出
	
	5> 取前4个数据组成一个流
	6> 跳过2个数据组成一个流
	7> 合并需求5和需求6得到的流,并把结果在控制台输出
	8> 合并需求5和需求6得到的流,并把结果在控制台输出,要求字符串元素不能重复
代码演示
package com.atguigu;


import org.junit.Test;

import java.util.ArrayList;
import java.util.stream.Stream;

public class Demo1 {

    // 把list集合中以张开头的,长度为3的元素在控制台输出
    @Test
    public void test1(){
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        // list.stream().filter(new Predicate<String>() {
        //     @Override
        //     public boolean test(String s) {
        //         return s.startsWith("张");
        //     }
        // });

        // list.stream().filter(s ->s.startsWith("张"));

        list.stream()
                .filter(s->s.startsWith("张"))
                .filter(s -> s.length() == 3)
                .forEach(s-> System.out.println(s));
    }

    /*
     limit&skip
     2> 取前3个数据在控制台输出
	3> 跳过4个元素,把剩下的元素在控制台输出
	4> 跳过2个元素,把剩下的元素中前2个在控制台输出
     */
    @Test
    public void test2(){
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        // 2> 取前3个数据在控制台输出
        list.stream().limit(3).forEach(System.out::println);
        System.out.println("---------------");

        // 3> 跳过4个元素,把剩下的元素在控制台输出
        list.stream().skip(4).forEach(System.out::println);
        System.out.println("---------------");

        // 4> 跳过2个元素,把剩下的元素中前2个在控制台输出
        list.stream().skip(2).limit(2).forEach(System.out::println);

    }

    /*
    5> 取前4个数据组成一个流
	6> 跳过2个数据组成一个流
	7> 合并需求5和需求6得到的流,并把结果在控制台输出
	8> 合并需求5和需求6得到的流,并把结果在控制台输出,要求字符串元素不能重复
     */
    @Test
    public void test3(){
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        // 5> 取前4个数据组成一个流
        Stream<String> stream1 = list.stream().limit(4);

        // 6> 跳过2个数据组成一个流
        Stream<String> stream2 = list.stream().skip(2);

        // 7> 合并需求5和需求6得到的流,并把结果在控制台输出
        // Stream.concat(stream1,stream2).forEach(System.out::println);

        // 8> 合并需求5和需求6得到的流,并把结果在控制台输出,要求字符串元素不能重复
        Stream.concat(stream1,stream2).distinct().forEach(System.out::println);
        
    }
}

17.2.6 终结操作API

终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
常见方法
方法名说明
void forEach(Consumer action)对此流的每个元素执行操作
long count()返回此流中的元素数
需求:
	1> 把集合中的元素在控制台输出
	2> 统计集合中有几个以张开头的元素,并把统计结果在控制台输出
代码演示
package com.atguigu;


import org.junit.Test;

import java.util.ArrayList;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class Demo1 {

    // 终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
    @Test
    public void test1(){
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

       // list.stream().forEach(new Consumer<String>() {
       //     @Override
       //     public void accept(String s) {
       //         System.out.println(s);
       //     }
       // });
       //  list.stream().forEach(s -> System.out.println(s));
        list.stream().forEach(System.out::println);

        long count = list.stream().filter(s -> s.startsWith("张")).count();
        System.out.println("count = " + count);
    }


}

oncat(stream1,stream2).distinct().forEach(System.out::println);

}

}


### 17.2.6  终结操作API

```tex
终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
常见方法
方法名说明
void forEach(Consumer action)对此流的每个元素执行操作
long count()返回此流中的元素数
需求:
	1> 把集合中的元素在控制台输出
	2> 统计集合中有几个以张开头的元素,并把统计结果在控制台输出
代码演示
package com.atguigu;


import org.junit.Test;

import java.util.ArrayList;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class Demo1 {

    // 终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
    @Test
    public void test1(){
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

       // list.stream().forEach(new Consumer<String>() {
       //     @Override
       //     public void accept(String s) {
       //         System.out.println(s);
       //     }
       // });
       //  list.stream().forEach(s -> System.out.println(s));
        list.stream().forEach(System.out::println);

        long count = list.stream().filter(s -> s.startsWith("张")).count();
        System.out.println("count = " + count);
    }


}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值