Java日记(day26)--Java8新特性:Lambda表达式、函数式接口、方法引用、Optional 类

241 阅读19分钟

Java 8 新特性

1、概述

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。

Java8 新增了非常多的特性,有些我们在前面的学习当中已经学过,我们主要讨论以下几个:

  • Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 函数式接口
  • 接口的增强
    • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
    • 静态方法
  • 新的时间和日期 API
    • Date Time API − 加强对日期与时间的处理。
  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
    • 并行流
    • 串行流
  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • 其他新特性
    • 重复注解
    • 类型注解
    • 通用目标类型判断
    • JDK的更新
      • 集合的流式操作
      • 并发
      • Arrays
      • Number和Math类
      • IO/NIO的改进
      • Reflection获取形参名
      • String:join()方法
      • Files
    • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
    • 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
    • JVM中元空间取代PermGen空间

2、Lambda 表达式

2.1、概述

为什么使用 Lambda 表达式

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

语法:

Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 -> , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:无论怎么变,下面两点是最重要的

  • 左侧:指定了 Lambda 表达式需要的参数列表
  • 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能。

演示1:

在介绍如何使用Lambda表达式之前,我们先来看看匿名内部类如何使用的

//匿名内部类,这里就是对象调用方法,没有开启多线程
@Test
public void test1(){
	//实现Runnable接口的匿名类对象 r
    Runnable r = new Runnable() {
        @Override
        public void run () {
            System.out.println("helloworld");
        }
    };
    //调用重写的run方法
    r.run();

    //Lambda表达式写法
    Runnable r1 = () -> System.out.println("helloworld");
    r1.run();
}

演示2:

例如,我们使用匿名内部类比较两个Integer类型数据的大小

public void test1(){
    //实现Comparator接口的匿名类对象 com
    Comparator<Integer> com = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1,o2);
        }
    };
    //调用重写的compare方法
    System.out.println(com.compare(9,2));
}

//采用Lambda表达式进行书写
@Test
public void test2(){
    Comparator<Integer> coms = (o1, o2) -> compare(o1,o2);
    System.out.println(coms.compare(1,5));
}

下面着重细说一下每一种的情况,其实可以看出每个接口里面就一个方法,这种其实就是函数式接口

演示3:

使用匿名内部类作为参数传递

//原来使用匿名内部类作为参数传递
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return Integer.compare(o1.1ength(), o2.1ength());
    }
});

//Lambda表达式作为参数传递
TreeSet<String> ts2 = new TreeSet<>(
    (o1, o2) -> Integer.compare(o1.length(), o2.length())
);

2.2、语法格式

格式1:无参数,无返回值

() -> System.out.println(“Hello Lambda!”);

实例:

@Test
public void test1(){
    Runnable r = new Runnable() {
        @Override
        public void run () {
            System.out.println("helloworld");
        }
    };
    r.run();

    //Lambda表达式
    Runnable r1 = () -> System.out.println("helloworld");
    r1.run();
}

格式2:有一个参数,并且无返回值

(x) -> System.out.println(x)

实例

@Test
public void test2(){
    Consumer<String> consumer = new Consumer<String>{
        @Override
        public void accept(String x) {
            System.out.println(x);
        }
    };
    consumer.accept("hello");
    
    //Lambda表达式
    Consumer<String> consumer = (String x) -> System.out.println(x);
    consumer.accept("hello");
}

格式3:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。

image.png

实例

//Lambda表达式
Consumer<String> consumer = (x) -> System.out.println(x);

格式4:若只有一个参数,小括号可以省略不写

x -> System.out.println(x)

实例

@Test
public void test3(){
    Consumer<String> consumer = x -> System.out.println(x);
    consumer.accept("hello");
}

格式5:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句

(x, y) -> {
    //执行语句
    return [返回值];
};

实例

public void test1(){
    //实现Comparator接口的匿名类对象 com
    Comparator<Integer> com = new Comparator<>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            System.out.println(o1);
            return o1.compareTo(o2);
        }
    };
}

//采用Lambda表达式进行书写
@Test
public void test2(){
    Comparator<Integer> coms = (o1, o2) -> {
        System.out.println(o1);
        return o1.compareTo(o2);
    };
}

格式6:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写

Comparator com = (x, y) -> Integer.compare(x, y);

2.3、变量作用域

lambda 表达式只能引用标记了 final外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

public class Java8Tester {
 
   final static String salutation = "Hello! ";
   
   public static void main(String args[]){
      GreetingService greetService1 = message -> System.out.println(salutation + message);
      greetService1.sayMessage("Runoob");
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
}

执行以上脚本,输出结果为:

Hello! Runoob

我们也可以直接在 lambda 表达式中访问外层的局部变量:

public class Java8Tester {
    public static void main(String args[]) {
        final int num = 1;
        Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
        s.convert(2);  // 输出结果为 3
    }
 
    public interface Converter<T1, T2> {
        void convert(int i);
    }
}

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

int num = 1;  
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
//修改了
num = 5;  
//报错信息:Local variable num defined in an enclosing scope must be final or effectively 
 final

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

String first = "";  
Comparator<String> comparator = (first, second) -> 		
    Integer.compare(first.length(),second.length());  //编译会出错 

2.4、总结

使用 Lambda 表达式需要注意以下两点:

  • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,只有一个简单方法的接口。
  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

语法

左边:lambda形参列表的参数类型可以省略(类型推断),并且如果lambda表达式的形参列表只有一个参数,则可以省略括号

右边:正常情况下是使用一对{}包裹,如果lambda表达式只有一条执行语句(可能是return语句),则return关键字和大括号都可以省略不写

本质:lambda表达式的本质就是作为接口的实例,这一点很重要,上面都体现出了这一点。

Lambda 表达式需要“函数式接口”的支持

3、函数式(Functional)接口

3.1、概述

什么是函数式(Functional)接口

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

如何理解函数式接口

  • Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP) 编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不 得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还 可以支持OOF(面向函数编程)
  • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的 编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在 Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的 对象类型——函数式接口。
  • 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是 Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
  • 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。

3.2、函数式接口举例

JDK 1.8 之前已有的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

JDK 1.8 新增加的函数接口:

  • java.util.function

实例1

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

实例2

public interface Comparable<T> {
    public int compareTo(T o);
}

自定义函数式接口

@FunctionalInterface
public interface MyNumber{
    public double getValue();
}

函数式接口中使用泛型:

@FunctionalInterface
public interface MyFunc<T>{
    public T getValue(T t);
}

作为参数传递 Lambda 表达式

image.png

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

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

image.png

代码实例

这些接口可以搭配 lambda表达式使用,起到简化代码的效果

3.3.1、Consumer<T> 消费型接口

@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("花了400,haha" + money));
}


public void happyTime(double money, Consumer<Double> con) {
    con.accept(money);
}

3.3.2、Supplier<T> 供给型接口

//Supplier<T> 供给型接口 :
@Test
public void test2(){
    //使用
    List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
    //输出
    for (Integer num : numList) {
        System.out.print(num+" ,");
    }
}

//目标方法 需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup){
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < num; i++) {
        Integer n = sup.get();
        list.add(n);
    }
    return list;
}
//控制台输出 :19 ,40 ,55 ,59 ,15 ,32 ,86 ,43 ,66 ,24 ,

3.3.3、Function<T, R> 函数型接口

//Function<T, R> 函数型接口:
@Test
public void test3(){
    //使用
    String newStr = strHandler("\t\t\t 跟阿咚学Java,很简单   ", (str) -> str.trim());
    System.out.println(newStr);

    String subStr = strHandler("跟阿咚学Java,很简单", (str) -> str.substring(1, 3));
    System.out.println(subStr);
}

//目标方法   需求:用于处理字符串
public String strHandler(String str, Function<String, String> fun){
    return fun.apply(str);
}
//控制台输出 :跟阿咚学Java,很简单
阿咚

3.3.4、Predicate<T> 断言型接口

//Predicate<T> 断言型接口:
@Test
public void test4(){
    List<String> list = Arrays.asList("Hello", "阿咚", "do懂");
    List<String> filterString = filtetString(list, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length() > 3;
            }
        });

    //使用
    //List<String> strList = filterStr(list, (s) -> s.length() > 3);
    
    //遍历一下集合输出
    for (String str : strList) {
        System.out.println(str);
    }
}

//目标方法 
//根据给定的规则,过滤集合中的字符串,此规则是由Predicate的方法决定的
public List<String> filterStr(List<String> list, Predicate<String> pre){
    List<String> strList = new ArrayList<>();
    for (String str : list) {
        if(pre.test(str)){
            strList.add(str);
        }
    }
    return strList;
}
//控制台输出 :Hello

3.4、其他接口

java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:

image.png

4、方法引用与构造器引用

参考www.jianshu.com/p/7e5bf7ea6…

4.1、方法引用(Method References)

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!即要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同
  • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向 一个方法,可以认为是Lambda表达式的一个语法糖。
  • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的 方法的参数列表和返回值类型保持一致!

使用方法引用的格式:使用操作符”::“将类(或对象)与方法名分隔开来,主要有以下三种使用情况:

  • 情况一:对象::实例方法名
  • 情况二:类::静态方法名
  • 情况三:类::实例方法名

使用方法引用的要求:实现接口的抽象方法的参数列表和返回值类型,必须和方法引用的方法的参数列表和返回值类型保存一致(只针对情况一和情况二)

代码环境准备

Employee.java

package IO_File;

import lombok.Getter;
import lombok.Setter;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * @Author Lemons
 * @create 2022-03-03-13:32
 */
@Getter
@Setter
@NoArgsConstructor
@Builder
@ToString
@EqualsAndHashCode
public class Employee {
    private int id;
    private String name;
    private int age;
    private double salary;

    public Employee(int id) {
        this.id = id;
        System.out.println("Employee(int id) ");
    }

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public Employee(int id, String name, int age, double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
}

方法引用的使用情况

4.1.1、情况一:对象::实例方法名

实例1

/**
* 情况一:对象::实例方法
* Consumer接口的void accept(T t)
* PrintStream接口的void println(T t)
*/
@Test
public void test1() {
    Consumer<String> consumer1 = str -> System.out.println(str);
    consumer1.accept("北京");
    System.out.println("******************************");
    PrintStream printStream = System.out;
    Consumer<String> consumer2 = printStream::println;
    consumer2.accept("beijing");
}

/**
* Supplier中的T get()
* Employee中的String getName()
*/
@Test
public void test2() {
    Employee employee = new Employee(10001, "Tom", 23, 5600);
    Supplier<String> supplier1 = () -> employee.getName();//返回一个字符串类型
    System.out.println(supplier1.get());
    System.out.println("*******************************************");
    Supplier<String> supplier2 = employee::getName;
    System.out.println(supplier2.get());
}

实例2

自定义一个函数式接口FunctionInterface,里面只有一个抽象方法print方法

// 声明函数式接口的注解
@FunctionalInterface
public interface FunctionInterface {
    public void print(String str);
}

我们想要在实现该接口的实现类重写的print方法中使用PrintStream类的println()方法(也就是System.out.println();),使用lambda表达式的方式:

public static void main(String[] args) {
    FunctionInterface functionInterface = (str) -> {
        System.out.println(str);
    };
    functionInterface.print("哈哈哈");
}

我们将上面的lambda改造成使用方法引用,这里需要注意之前提到过的使用方法引用的前提是否满足:

  1. 在Lambda体也就是->的右边,已经有实现的方法了,也就是说我们能够直接调用println方法实现打印的目的,而这个println方法属于已经实现过的方法属于PrintStream类
  2. 我们所调用的已经实现的方法对应的参数和返回值必须和我们自定义的那个抽象方法一致,也就是我们自己定义的print方法参数是str,返回值是void,而println方法中刚好存在这样的方法

使用方法引用的方式,语法格式,对象::实例方法名:

public static void main(String[] args) {
    // PrintStream对象
    PrintStream printStream = System.out;
    //对象::实例方法名
    FunctionInterface functionInterface = printStream ::println;
    // 这里调用print方法,通过方法引用调用的实际上就是println方法
    functionInterface.print("哈哈哈");
}

4.1.2、情况二:类::静态方法名

和第一种情况的对象::实例方法名区别其实是你lambda体(方法体)中所引用的方法是静态方法还是非静态方法,如果是静态方法就是类::静态方法名,非静态方法则是对象::静态方法名。同样我们可以通过代码来展示一下,先使用Lambda表达式的方法

public static void main(String[] args) {
    // 通过使用Comparator比较两个数的大小
    Comparator<Integer> comparator = (num1, num2) -> Integer.compare(num1,num2);
    System.out.println(comparator.compare(2,3));
}

使用方法引用的方式,语法格式为,类::静态方法名:

public static void main(String[] args) {
    /**
         * 通过使用Comparator比较两个数的大小,由于Integer中的compare方法是静态方法,
         * 且参数和返回值和Comparator的compare方法是一样的,因此可以使用方法引用
         * 由于是静态方法可以直接使用类名调用
         */
    Comparator<Integer> comparator = Integer::compare;
    System.out.println(comparator.compare(2,3));
}

其他实例

/**
     * 情况二:类::静态方法
     * Comparator中的int compare(T t1,T t2)
     * Integer中的int compare(T t1,T t2)
     */
@Test
public void test3() {
    Comparator<Integer> comparator1 = (t1, t2) -> Integer.compare(t1, t2);
    System.out.println(comparator1.compare(12, 21));
    System.out.println("*************************");
    Comparator<Integer> comparator2 = Integer::compareTo;
    System.out.println(comparator2.compare(55, 12));
}
/**
     * Function中的R apply(T t)
     * Math中的Long round(Double d)
     */
@Test
public void test4(){
    Function<Double, Long> function = new Function<Double, Long>() {
        @Override
        public Long apply(Double d) {
            return Math.round(d);
        }
    };
    System.out.println("******************************");
    Function<Double, Long> function1 = (d) -> Math.round(d);
    System.out.println(function1.apply(12.3));
}

4.1.3、情况三:类::实例方法名

这种使用方式相比于前两种使用方式来说可能不太好理解,因为方法中的参数列表个数似乎和方法引用的方法中的参数个数不相匹配,举个例子自定义函数式接口中的方法int myCompare(T t1,T t2),String类中的int s1.compareTo(s2),看上去似乎参数列表不匹配,但是当myCompare的t1可以作为String中compareTo方法的调用者,myCompare的t2可以作为String中compareTo方法的参数时,就能够使用方法引用。我们先看下Lambda表达式的实现:

public static void main(String[] args) {
    /**
     * 比较两个字符串的大小,使用到了String中的compareTo方法
     */
    Comparator<String> comparator = (s1,s2) -> s1.compareTo(s2);
    int result = comparator.compare("abcd", "abcf");
    System.out.println(result);
}

使用方法引用来实现,语法格式,类::实例方法名,这种情况下我们可以这么理解,类(方法的调用者对应的类)::实例方法名(调用的具体方法),先看下使用

public static void main(String[] args) {
    /**
         * 类::实例方法名
         * 类:String代表的是第一个参数对应的类
         * 实例方法名:所调用的实例方法,参数会自动进行匹配
         */
    Comparator<String> comparator = String::compareTo;
    int result = comparator.compare("abcd", "abcf");
    System.out.println(result);
}

这个确实比较不好理解,需要多练习才能理解,下面我们再举个例子来看看,类::实例方法名的使用 先自定义一个函数式接口:

@FunctionalInterface
public interface MyInterface {
    /**
     * 自定义的字符替换方法
     * @param str 要替换的字符串
     * @param oldStr 原字符串
     * @param newStr 新字符串
     * @return
     */
    public String myStrReplace(String str,String oldStr,String newStr);
}

我们知道String类中有个字符串替换方法replace,首先我们使用Lambda表达式来完成myStrReplace方法的实现

public static void main(String[] args) {

    MyInterface myInterface  = (str,oldStr,newStr)->{
        return  str.replace(oldStr,newStr);
    };
    String str = "I am a doctor";
    String oldStr = "doctor";
    String newStr = "policeman";
    String result = myInterface.myStrReplace(str, oldStr, newStr);
    System.out.println(result);
}

方法引用的方式:

public static void main(String[] args) {
    /**
         * ::的左边是第一个参数对应的类
         * ::的右边是调用的方法,参数的话会自动对应
         */
    MyInterface myInterface  = String::replace;
    String str = "I am a doctor";
    String oldStr = "doctor";
    String newStr = "policeman";
    String result = myInterface.myStrReplace(str, oldStr, newStr);
    System.out.println(result);
}

4.2、构造器引用

ClassName::new 类似方法引用,与函数式接口相结合,自动与函数式接口中方法兼容。 可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且抽象方法的返回值即为构造器对应类的对象

例如:
Function<Integer, MyClass> fun = (n)-> new MyClass(n);
等同于:
Function<Integer, MyClass> fun = MyClass::new;

实例1

@Test
public void Test1() {
    Supplier<Student> supplier = () -> new Student();	//使用Lambda表达式
    Supplier<Student> supplier1 = Student::new;			//使用构造器引用
}

上面的构造器引用对应的是空参构造器:public Student() {}

实例2

@Test
public void Test2() {
    Function<String, Student> function1 = (name) -> new Student(name); //使用Lambda表达式
    Function<String, Student> function2 = Student::new;				   //使用构造器引用
    System.out.println(function1.apply("Moti"));
    System.out.println(function2.apply("Moti"));
    BiFunction<String, Integer, Student> function3 = (name,age) -> new Student(name, age);//使用Lambda表达式
    BiFunction<String, Integer, Student> function4 = Student::new;	//使用构造器引用	
    System.out.println(function3.apply("Moti",20));
    System.out.println(function4.apply("Moti",20));	
}

以上方法中function1和function2对应的构造器是

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

而function3和function4对应的构造器是

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

实例3:

自定义一个User类

public class User {
    private String name;
    private Integer age;

    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

}

我们想要获得一个有初始值的User对象,使用Jdk内置的Supplier接口,这个接口在文章开头的推荐文章中有介绍,这里只进行使用不做介绍了。先使用Lambda表达式来完成。

public static void main(String[] args) {
    Supplier<User> supplier = () -> new User();
    User user = supplier.get();
}

Supplier的get方法是无参的,User拥有无参构造,通过方法引用来得到User对象,使用构造器引用实现:

public static void main(String[] args) {
    Supplier<User> supplier = User::new;
    User user = supplier.get();
}

再举个有参数的例子来加强理解,比如我们想要获得指定姓名和年龄的user对象,先自定义创建函数式接口

@FunctionalInterface
public interface UserFunctionalInterface {
    public User getUser(String name,Integer age);
}

使用Lambda表达式完成:

public static void main(String[] args) {
    UserFunctionalInterface userFunctionalInterface = (name, age) -> new User(name, age);
    User user = userFunctionalInterface.getUser("张三", 23);
}

使用构造器引用来实现:

public static void main(String[] args) {
    UserFunctionalInterface userFunctionalInterface = User::new;
    User user = userFunctionalInterface.getUser("张三", 23);
}

4.3、数组引用

大家可以把数组当做一个特殊的类,则写法和构造器引用是一样的

数组引用 格式:type[] :: new ,数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。

比如

例如: 
Function<Integer, Integer[]> fun = (n) -> new Integer(n);
等同于:
Function<Integer, Integer[]> fun =Integer[]::new;

又比如想要获取一个指定大小的数组,我们使用Jdk内置的Function接口来实现,这个接口也不做介绍之前文章介绍过,先用Lambda表达式来完成

public static void main(String[] args) {
    // 想要一个长度为5的String数组
    Function<Integer,String[]> function = (num) -> new String[num];
    String[] apply = function.apply(5);
    System.out.println(Arrays.toString(apply));
}

使用方法引用来实现:

public static void main(String[] args) {
    // 想要一个长度为5的String数组
    Function<Integer, String[]> function = String[]::new;
    String[] apply = function.apply(5);
    System.out.println(Arrays.toString(apply));
}

总结

方法引用、构造器引用、数组引用都是基于Lambda表达式的,方法引用大大简化了代码

5、Optional 类

5.1、概述

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

Optional<T> 类 (java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

Optional类的 Javadoc 描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

5.2、Optional类的方法

以下是一个 java.util.Optional<T> 类的声明:

public final class Optional<T>
extends Object

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

创建Optional类对象的方法:

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

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

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

获取Optional容器的对象

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

5.3、Optional 实例

我们可以通过以下实例来更好的了解 Optional 类的使用:

import java.util.Optional;

public class Java8Tester {
    public static void main(String args[]){

        Java8Tester java8Tester = new Java8Tester();
        Integer value1 = null;
        Integer value2 = new Integer(10);

        // Optional.ofNullable - 允许传递为 null 参数
        Optional<Integer> a = Optional.ofNullable(value1);

        // Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
        Optional<Integer> b = Optional.of(value2);
        System.out.println(java8Tester.sum(a,b));
    }

    public Integer sum(Optional<Integer> a, Optional<Integer> b){

        // Optional.isPresent - 判断值是否存在

        System.out.println("第一个参数值存在: " + a.isPresent());
        System.out.println("第二个参数值存在: " + b.isPresent());

        // Optional.orElse - 如果值存在,返回它,否则返回默认值
        Integer value1 = a.orElse(new Integer(0));

        //Optional.get - 获取值,值需要存在
        Integer value2 = b.get();
        return value1 + value2;
    }
}

执行以上脚本,输出结果为:

第一个参数值存在: false
第二个参数值存在: true
10

5.4、练习

以前情况:

public String getGirlName(Boy boy){
    return boy.getGirl().getName();
}

@Test  //测试,getGirlName方法会出现异常,
public void test2(){
    Boy boy = new Boy(); //造成boy.getGirl()=null
    String girlName = getGirlName(boy);
    System.out.println(girlName);//NullPointerException异常
}

优化1:能保证如下的方法执行中不会出现空指针的异常

//优化以后的getGirlName():
public String getGirlName1(Boy boy){
    if(boy != null){
        Girl girl = boy.getGirl();
        if(girl != null){
            return girl.getName();
        }
    }

    return null;
}

优化2:使用Optional类的getGirlName():

T orElse(T other) :如果有值则将默认值返回,否则返回指定的other对象。

public String getGirlName2(Boy boy){
    
    //不存在就返回(new Boy(new Girl("赵丽颖", 30)));,否则返回boy
    Optional<Boy> boyOptional = Optional.ofNullable(boy);
    Boy boy1 = boyOptional.orElse(new Boy(new Girl("赵丽颖", 30)));

    Girl girl = boy1.getGirl();
	
    //不存在就返回(new Girl("杨幂", 35));,否则返回girl
    Optional<Girl> girlOptional = Optional.ofNullable(girl);
    //girl1一定非空
    Girl girl1 = girlOptional.orElse(new Girl("杨幂", 35));
    return girl1.getName();
}