Hey, Nice to meet You. 

必有过人之节.人情有所不能忍者,匹夫见辱,拔剑而起,挺身而斗,此不足为勇也,天下有大勇者,猝然临之而不惊,无故加之而不怒.此其所挟持者甚大,而其志甚远也.          ☆☆☆所谓豪杰之士,

夯实Java基础(二十二)----Java8新特性之Lambda表达式

1、本篇前言

Java 8于 2014 年发布到现在已经有5年时间了,经过时间的验证,毫无疑问,Java 8是继Java 5(发布于2004年)之后的又一个非常重要的版本。因为Java 8里面出现了非常多新的特征,这些特征主要包含语言、编译器、库、工具和JVM等方面,具体如下:

  • Lambda表达式 👉 传送门👈
  • Stream API 👉 传送门👈
  • 方法引用/构造器引用 👉 传送门👈
  • 新的日期处理类 👉 传送门👈
  • 函数式接口 👉 传送门👈
  • 接口中允许定义默认方法
  • Optional类 👉 传送门👈
  • 重复注解、类型注解、通用类型推断
  • 新的编译工具:jjs、jdeps
  • JVM中的PermGen被Metaspace取代
  • 新的Nashron引擎,允许在JVM上允许JS代码
  • ……

以上最值得我们学习的应该就是Lambda表达式、Stream API和新的日期处理类。并不是说其他的就不用去学了,还是有必要去了解一下的,而这三个(Lambda、Stream、新的日期)对我们来说很重要所以必须学习,因为在实际的工作中非常常用。

2、Lambda表达式简介

Lambda表达式本质上是一个匿名函数(方法),它没有方法名,没有权限修饰符,没有返回值声明。它看起来就是一个箭头->从左边指向右边。我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样进行传递),它的核心思想是将面向对象中的传递数据变成传递行为。

Lambda表达式的出现就是为了简化匿名内部类,让匿名内部类在方法中作为参数的使用更加方便。所以使用Lambda表达式可以让我们的代码更少,看上去更简洁,代码更加灵活。而Lambda表达式作为一种更紧凑的代码风格,使得Java的语言表达能力得到了提升。但也有它的缺点所在,如果Lambda表达式用的不好的话,调试运行和后期维护非常的麻烦,尤其是调试的时候,debug模式都不会进去。

Lambda表达式对接口的要求:Lambda只能接受函数式接口(@FunctionalInterface),而函数式接口规定接口中只能有一个需要被实现的方法,意思就是只能有一个抽象方法,但不是规定接口中只能有一个方法,也可以有其它的方法,jdk8接口中可以定义普通方法,但是必须用default关键字修饰。

总结下来Lambda表达式的特点就是:

  • 匿名:没有一个确定的名称
  • 函数:lambda不属于一个特定的类,但是却有参数列表、函数主体、返回类型、异常列表
  • 传递:可以作为参数传递给方法、或者存储在变量中
  • 简洁:不需要写很多模板代码
  • 接口:必须为函数式接口(@FunctionalInterface)

3、Lambda表达式语法

Lambda表达式在Java语言中引入了一个新的语法元素和操作符。这个操作符为->,该操作符被称为Lambda操作符或箭头操作符,它将Lambda分为两个部分:

  • 左侧:指定了Lambda表达式所需要的所有参数。
  • 右侧:指定了Lambda体,即Lambda表达式所要执行的功能。

Java8中的Lambda表达式的基本语法结构如下,当然,这里只是简单的Lambda 表达式的应用。后面还有使用多个简单Lambda 表达式组成的复合 Lambda 表达式。例如: 函数复合、谓词复合、比较器复合等各种形式的组合起来的Lambda表达式。对于初学者而言刚刚看到这种写法肯定是非常懵逼的,所以不要心急,慢慢来:

描述 格式
无参数,无返回值 void () -> System.out.print(“Lambda…”) ;
有一个参数,但无返回值 void (String s) -> System.out.print(“Lambda…”) ;
有参数,但是参数数据类型省略,由编译器推断,称为"类型推断" (s) –> System.out.print(“Lambda…”) ;
若只有一个参数,方法的括号可以省略,如果多个参数则必须写上 s–> System.out.print(“Lambda…”) ;
有参数,且有返回值,如果显式返回语句时就必须使用花括号“{}” (s,t) –> s+t ;或   (s,t) –> {return s+t;};
如果有两个或两个以上的参数,并且有多条语句则需要加上“{}”,一条执行语句可以省略。 (s,t) –> {
   System.out.print(s) ;
   System.out.print(t) ;
   return s+t;
};

到目前为止,我们对Lambda表达式有了基本的认识,而前面讲了那么多的理论,就是为了接下来快乐的写代码时光,用几个简单的例子来让我们好好理解一下Lambda表达式的使用,看看Lambda是怎么来消灭冗余的匿名内部类的:

/**
 * lambda表达式举例
 * @author tanghaorong
 */
public class LambdaTest {
    public static void main(String[] args) {
        //1、创建线程举例
        //普通写法(匿名内部类)
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Ordinary Writing");
            }
        }); thread.start();

        //Lambda写法。无参无返回void
        Thread thread1 = new Thread(() -> System.out.println("Lambda Writing")); 
        thread1.start();


        //2、排序举例
        //普通写法,默认升序
        List<Integer> list = Arrays.asList(26, 65, 13, 79, 6, 123);
        list.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        }); System.out.println(list.toString());

        //Lambda表达式写法。有参有返回,“类型推断”
        list.sort((o1, o2) -> Integer.compare(o1, o2)); System.out.println(list.toString());


        //3、遍历list集合
        //普通写法
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });

        //Lambda表达式写法。遍历List集合,forEach方法中的参数是Consumer<? super T> action
        //其中Consumer是Java8新的新出现的函数式接口,下面会讲到函数式接口
        list.forEach(alist -> System.out.println(alist));
    }
}

注意:要使用Lambda表达式的前提是函数式接口,所以接下来学习一下函数式接口。

4、函数式接口介绍

函数式接口(Functional Interface)也是Java8中的新特征。函数式接口的定义:只允许有一个抽象方法的接口,那么它就是一个函数式接口,这样接口就可以被隐式转换为Lambda表达式。但是为了避免后来人给这个接口添加函数后,导致该接口有多个函数,就不再是函数式接口了,所以官方给我们提供了一个注解@FunctionalInterface,该注解会检查它是否是一个函数式接口,所以如果我们需要自定义一个函数式接口的话,可以在接口类的上方声明@FunctionalInterface。注意:函数式接口虽然规定只能有一个抽象方法,但是同时可以有多个非抽象方法(Java8中接口可以定义普通方法)。简单的举个定义函数式接口的例子,代码如下:

/**
 * 自定义函数式接口注解
 * @author tanghaorong
 */
@FunctionalInterface
public interface MyInterface {
    /**
     * 抽象方法
     */
    void method();
    //void method1();再定义一个会提示:找到多个抽象方法

    /**
     * 默认方法,必须用default修饰
     */
    default void defaultMethod() {
        System.out.println("默认方法...");
    }

    /**
     * 静态方法方法
     */
    static void staticMethod() {
        System.out.println("静态方法...");
    }
}

上面的例子可以很容易的转换成如下Lambda表达式:

MyInterface myInterface = () -> System.out.println("MyInterface...");

我需要注意的一点是,接口中的默认方法和静态方法并不会破坏函数式接口的定义,既不会影响到Lambda表达式。同时也正因为Lambda表达式的引入,所以函数式接口也变得流行起来。其实早在Java8之前就有很多接口是函数式接口,只是在Java8才正式提出之一特性,例如:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.util.Comparator
  • java.io.FileFilter
  • ……

除了以上这些,在Java 8中还增加了一个新的包:java.util.function。它们里面包含了常用的函数式接口,该包下定义的函数式接口非常多,主要有四大类,下面介绍。

5、四大类型接口

image

为了便于记忆,这里对四大函数式接口的代表接口进行一个总结:

接口类型 函数式接口 参数类型 返回类型 方法
消费型接口 Consumer<T> T void void accept(T t)
供给型接口 Supplier<T> T T get()
判断型接口 Predicate<T> T boolean boolean test(T t)
功能(函数)型接口 Function<T, R> T R R apply(T t)

而下面则是所有关于java.util.function包下的相关接口,同时也会简单的举例。

①、消费型接口

消费型接口的抽象方法特点:有参数传入,无结果返回。

接口名 参数类型 返回类型 抽象方法 描述
Consumer T void void accept(T t) 接收一个对象用于完成功能,不返回结果
BiConsumer<T,U> T,U void void accept(T t, U u) 接收两个对象用于完成功能,不返回结果
DoubleConsumer double void void accept(double value) 接收一个double值,不返回结果
IntConsumer int void void accept(int value) 接收一个int值,不返回结果
LongConsumer long void void accept(long value) 接收一个long值,不返回结果
ObjDoubleConsumer T,double void void accept(T t, double value) 接收一个对象和一个double值,不返回结果
ObjIntConsumer T,int void void accept(T t, int value) 接收一个对象和一个int值,不返回结果
ObjLongConsumer T,long void void accept(T t, long value) 接收一个对象和一个long值,不返回结果

消费型接口的简单举例:

/**
 * 消费型接口举例
 * @author tanghaorong
 */
public class LambdaDemo {
    public static void main(String[] args) {
        //1、传入一个参数:Consumer<T> : void accept(T t);
        Consumer<Integer> consumer = (a) -> System.out.println("消费型接口:" + a);
        consumer.accept(1000);

        //2、传入集合:Consumer<T> : void accept(T t);
        List<String> list = Arrays.asList("hello", "world", "java", "html", "css");
        list.forEach((s) -> {
            System.out.println(s);
        });

        //3、传入两个参数:BiConsumer<T, U> : void accept(T t, U u);
        HashMap<Integer, String> map = new HashMap<>(10);
        map.put(1, "张三");
        map.put(2, "李四");
        map.put(3, "王五");
        map.put(4, "赵六");
        map.forEach((a, b) -> {
            System.out.println(a + ":" + b);
        });
    }
}

②、供给型接口

这类接口的抽象方法特点:无参数传入,但是有返回值

接口名 参数类型 返回类型 抽象方法 描述
Supplier T T get() 供给一个T类型对象的结果
BooleanSupplier boolean boolean getAsBoolean() 供给一个boolean类型的结果
DoubleSupplier double double getAsDouble() 供给一个double类型的结果
IntSupplier int int getAsInt() 供给一个int类型的结果
LongSupplier long long getAsLong() 供给一个long类型的结果

供给型接口的简单举例:

/**
 * 供给型接口举例
 * @author tanghaorong
 */
public class LambdaDemo {
    public static void main(String[] args) {
        //Supplier<T> : T get();
        Supplier<Double> supplier = () -> (Math.random() * 100);
        Double d = supplier.get();
        System.out.println("供给型接口:" + d);

        Supplier<String> supplier1 = () -> "hello supplier";
        String s = supplier1.get();
        System.out.println("供给型接口:" + s);
    }
}

③、判断型接口

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

接口名 参数类型 返回类型 抽象方法 描述
Predicate T boolean boolean test(T t) 接收一个对象,断定并返回boolean类型结果
BiPredicate<T,U> T,U boolean boolean test(T t, U u) 接收两个对象,断定并返回boolean类型结果
DoublePredicate double boolean boolean test(double value) 接收一个double类型参数值,断定并返回boolean类型结果
IntPredicate int boolean boolean test(int value) 接收一个int类型参数值,断定并返回boolean类型结果
LongPredicate long boolean boolean test(long value) 接收一个long类型参数值,断定并返回boolean类型结果

判断型接口的简单举例:

/**
 * 判断型接口举例
 * @author tanghaorong
 */
public class LambdaDemo {
    public static void main(String[] args) {
        //1、Predicate<T> : boolean test(T t);
        Predicate<String > predicate = (c) -> "predicate".equals(c);
        boolean test = predicate.test("predicate");
        System.out.println("断定型接口:" + test);

        //2、BiPredicate<T,U> : boolean test(T t);
        BiPredicate<String,String> biPredicate = (a, b) -> a.equals(b);
        boolean test1 = biPredicate.test("hello", "biPredicate");
        System.out.println("断定型接口:" + test1);
    }
}

④、功能型接口

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

接口名 参数类型 返回类型 抽象方法 描述
Function<T,R> T R R apply(T t) 接收一个T类型对象,返回一个R类型对象结果
UnaryOperator T T T apply(T t) 接收一个T类型对象,返回一个T类型对象结果
DoubleFunction double R R apply(double value) 接收一个double值,返回一个R类型对象
IntFunction int R R apply(int value) 接收一个int值,返回一个R类型对象
LongFunction long R R apply(long value) 接收一个long值,返回一个R类型对象
ToDoubleFunction T double double applyAsDouble(T value) 接收一个T类型对象,返回一个double
ToIntFunction T int int applyAsInt(T value) 接收一个T类型对象,返回一个int
ToLongFunction T long long applyAsLong(T value) 接收一个T类型对象,返回一个long
DoubleToIntFunction double int int applyAsInt(double value) 接收一个double值,返回一个int结果
DoubleToLongFunction double long long applyAsLong(double value) 接收一个double值,返回一个long结果
IntToDoubleFunction int double double applyAsDouble(int value) 接收一个int值,返回一个double结果
IntToLongFunction int long long applyAsLong(int value) 接收一个int值,返回一个long结果
LongToDoubleFunction long double double applyAsDouble(long value) 接收一个long值,返回一个double结果
LongToIntFunction long int int applyAsInt(long value) 接收一个long值,返回一个int结果
DoubleUnaryOperator double double double applyAsDouble(double operand) 接收一个double值,返回一个double
IntUnaryOperator int int int applyAsInt(int operand) 接收一个int值,返回一个int结果
LongUnaryOperator long long long applyAsLong(long operand) 接收一个long值,返回一个long结果
BiFunction<T,U,R> T,U R R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperator T,T T T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction<T,U> T,U double double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
ToIntBiFunction<T,U> T,U int int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
ToLongBiFunction<T,U> T,U long long applyAsLong(T t, U u) 接收一个T类型和一个U类型对象,返回一个long
DoubleBinaryOperator double,double double double applyAsDouble(double left, double right) 接收两个double值,返回一个double结果
IntBinaryOperator int,int int int applyAsInt(int left, int right) 接收两个int值,返回一个int结果
LongBinaryOperator long,long long long applyAsLong(long left, long right) 接收两个long值,返回一个long结果

功能型接口的简单举例:

/**
 * 功能型接口举例
 * @author tanghaorong
 */
public class LambdaDemo {
    public static void main(String[] args) {
        //1、Function<T, R> : R apply(T t);
        Function<String, String> function = (b) -> b;
        Object apply = function.apply("功能(函数)型接口");
        System.out.println(apply);

        //2、BiFunction<T, U, R>:R apply(T t, U u);
        HashMap<Integer, Employee> map = new HashMap<>(10);
        Employee p1 = new Employee(1, "张三", 8000.0);
        Employee p2 = new Employee(2, "李四", 9000.0);
        Employee p3 = new Employee(3, "王五", 10000.0);
        Employee p4 = new Employee(4, "赵六", 11000.0);
        map.put(p1.getId(), p1);
        map.put(p2.getId(), p2);
        map.put(p3.getId(), p3);
        map.put(p4.getId(), p4);

        map.forEach((k, v) -> System.out.println(k + ":" + v));
        System.out.println("+++++++++++++++++++++++");
        //将map中薪资低于10000的员工的薪资设置为10000
        map.replaceAll((k, v) -> {
            if (v.getSalary() < 10000) {
                v.setSalary(10000.0);
            }
            return v;
        });
        map.forEach((k, v) -> System.out.println(k + ":" + v));

        //3、UnaryOperator T apply(T t),注:xxxOperator结尾的接口都继承了Function接口
        UnaryOperator<Integer> uoi = x -> x + 1;
        System.out.println(uoi.apply(10));
        UnaryOperator<String> uos = x -> x + "bb";
        System.out.println(uos.apply("aa"));
    }
}

/**
 * 功能型接口举例时用到的实体类
 */
class Employee {
    private Integer id;
    private String name;
    private Double salary;
    //getter、setter和toString省略

    public Employee(Integer id, String name, Double salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }
}

6、方法引用介绍

通过上面Lambda表达式的学习,如果你认为Lambda表达式已经让代码够简洁了,那么这里还有一个更加简洁的方法——方法引用。简单来说,方法引用就是进一步的简化Lambda表达式声明的一种语法糖。也正是因为方法引用实在太简洁了,所以学习方法引用前必须要对Lambda表达式非常的熟悉,否则学习方法引用会非常的吃力😱,你可能都不知道这是在干啥😂。

方法引用使用操作符::将对象或类的名字和方法名分隔开来。方法引用有好几种形式,它们的语法如下:

描述 格式 方法引用示例(后面不要写括号) 等价的Lambda表达式
静态方法引用 ClassName::staticMethodName Integer::parseInt (a) -> Integer.parseInt(a)
实例方法引用 instanceName::methodName System.out::println 或 String::trim (str) -> System.out.println(str) 或 (s) -> s.trim()
类上的实例方法引用 ClassName::methodName String::equals 或 Person::getName (x, y) -> x.equals(y) 或 (p) -> p.getName()
父类上的实例方法引用 super::methodName super::toString () -> super.toString()
构造方法引用 ClassName::new Integer::new (n) -> new String(n)
数组构造方法引用 TypeName[]::new Integer[]::new (n) -> new Integer[n]

下面是关于这六种格式的举例:

①、静态方法引用。语法格式:ClassName::staticMethodName

//1、静态方法用——ClassName::staticMethodName
@Test
public void test1() {
    // Lambda表达式写法
    Function<String, Integer> function = (a) -> Integer.parseInt(a);
    // 方法引用
    Function<String, Integer> function1 = Integer::parseInt;
    Integer integer = function1.apply("100");
    System.out.println(integer);

    // Lambda表达式
    Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
    // 方法引用
    Comparator<Integer> comparator1 = Integer::compare;
    int result = comparator1.compare(100,10);
    System.out.println(result);

    // Lambda表达式
    BiFunction<Double, Double, Double> biFunction = (x, y) -> Math.max(x, y);
    System.out.println(biFunction.apply(11.1, 22.2));
    System.out.println("-------------");
    // 方法引用
    BiFunction<Double, Double, Double> biFunction1 = Math::max;
    System.out.println(biFunction1.apply(33.3, 44.4));
}

②、实例上的实例方法引用。语法格式:instanceName::methodName

//2、实例上的实例方法引用——instanceName::methodName
@Test
public void test2(){
    Consumer<String> consumer = (str) -> System.out.println(str);
    Consumer consumer1 = System.out::println;

    Person person = new Person("tanghaorong", 20, "China");
    Supplier supplier = () -> person.getName();
    Supplier supplier1  = person::getName;
}

③、类上的实例方法引用。语法格式:ClassName::methodName

//3、类上的实例方法引用——ClassName::methodName
public void test3(){

    BiPredicate<String, String> biPredicate = (x, y) -> x.equals(y);
    BiPredicate<String, String> biPredicate1 = String::equals;

    Function<Person, String> fun = (p) -> p.getName();
    Function<Person, String> fun2 = Person::getName;
}

④、父类上的实例方法引用。语法格式:super::methodName

//4、父类上的实例方法引用——super::methodName
@Test
public void test4(){
    Person person=new Person();
    Supplier supplier = () -> super.toString();
    Supplier supplier1 =super::toString;
}

⑤、构造方法引用。语法格式:ClassName::new

//5、构造方法引用——ClassName::new
@Test
public void test5() {
    Function<String, String> function = (n) -> new String(n);
    String apply = function.apply("Lambda构造方法");
    System.out.println(apply);

    Function<String, String> function1 = String::new;
    String apply1 = function.apply("构造方法引用");
    System.out.println(apply1);
}

⑥、数组构造方法引用。语法格式:TypeName[]::new

//6、数组构造方法引用——TypeName[]::new
@Test
public void test6() {
    Function<Integer, Integer[]> function = (n) -> new Integer[n];
    //Integer integer[]=new Integer[20];
    Integer[] apply = function.apply(3);
    apply[0] = 1;
    for (Integer integer : apply) {
        System.out.println(integer);
    }
    System.out.println("-----------------");

    Function<Integer, Integer[]> function1 = Integer[]::new;
    Integer[] apply1 = function1.apply(5);
    apply1[0] = 11;
    apply1[1] = 22;
    for (Integer integer : apply1) {
        System.out.println(integer);
    }
}

①、方法引用解析

上面的例子看完后你现在的状态可能是这样的😂:

image

你先别激动,下面慢慢来解开你的疑惑。其实仔细一点可以发现它们都是有类似的规律,这里我总结了三点(其中第三点最重要):

  • 必须能够使用Lambda表达式。
  • Lambda表达式中右侧的{}可以省略,即Lambda表达式的方法体只有一个语句。
  • Lambda表达式中引用方法的参数个数、类型,返回值类型和函数式接口中的方法声明要一一对应。

当满足以上条件时,Lambda表达式就可以使用方法引用的简写方式了,下面再用简单例子来走一遍:

//System.out.println()简单举例
@Test
public void test() {
    // Lambda表达式写法
    Function<String, Integer> flb = (a) -> Integer.parseInt(a);
    // 转成方法引用
    Function<String, Integer> fmr = Integer::parseInt;
    Integer integer = fmr.apply("100");
    System.out.println(integer);

    // Lambda表达式写法
    Comparator<Integer> clb = (a, b) -> Integer.compare(a, b);
    // 转成方法引用
    Comparator<Integer> cmr = Integer::compare;
    int result = cmr.compare(100,10);
    System.out.println(result);
}

再次强调第三点:Lambda表达式中引用方法的参数个数、类型,返回值类型和函数式接口中的方法声明要一一对应才行。

首先分别来看下Function函数式接口中的apply方法和Integer类中parseInt这个方法,其中Function这个函数式接口的方法定义如下:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

Integer类中的parseInt方法定义如下:

public static int parseInt(String s) throws NumberFormatException {
    return parseInt(s,10);
}

我们将这两个方法进行对比可以发现Function中的 apply方法参数个数是 1 个,而parseInt方法的参数个数也是 1 个,所以参数个数对应上了。而 apply方法的参数类型是泛型,所以肯定能够与parseInt方法对应上。所以接下来再看它们的返回类型,其中apply方法的返回类型是泛型类型,所以肯定也能和 parseInt方法对应上。这样一来,就可以正确的接收Integer::parseInt的方法引用了,并可以调用Funcitonapply方法,这时候调用到的其实就是对应的 Integer.parseInt方法了。

所以现在可以举一反三,用这套标准套到 Integer::compare方法上,就不难理解为什么可以用Comparator<Integer>来接收了,其中Integer.compare方法定义如下:

public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

两个参数参数类型都是 int,返回值类型也是 int。再来看下Comparator类中的compare方法。

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

可以发现与Integer.compare方法是完全可以对应上的。

tips:其实只要参数类型、个数和返回值类型能够对上的话,那么只要满足要求的函数式接口都能接收。例如上面的compare,输入参数为两个int类型,并且返回的值为int类型的,它不仅可以用Comparator来接收,还可以用 BiFunction,ToIntBiFunction,IntBinaryOperator来接收,因为它们都是接收两个参数,返回一个Int类型,所以调用它们各自的方法都能正确的返回结果,完美接收。

最后来看Comparator和BiFunction,ToIntBiFunction,IntBinaryOperator这些函数式接口定义和其中对应的方法:

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}
    
@FunctionalInterface
public interface IntBinaryOperator {
    int applyAsInt(int left, int right);
}

@FunctionalInterface
public interface IntBinaryOperator {
    int applyAsInt(int left, int right);
}

举例对比:

// Lambda表达式
Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
// 方法引用
Comparator<Integer> comparator1 = Integer::compare;
int result = comparator1.compare(100,10);
System.out.println(result);

// 其它类型进行接收
BiFunction<Integer,Integer,Integer> a = Integer::compare;
IntBinaryOperator b = Integer::compare;
ToIntBiFunction<Integer,Integer> c = Integer::compare;

可以发现用BiFunction,ToIntBiFunction,IntBinaryOperator来接收Integer::compare都能正确的匹配上,其实不仅仅是上面能够看得到接口,只要是满足前面所说的三点就都能够完美匹配。例如现在的Integer::compare只要是在某个函数式接口中声明了这样的方法:两个参数,参数类型是int或者泛型,并且返回值是int或者泛型的,都可以完美接收。

posted @ 2019-09-24 13:36  唐浩荣  阅读(652)  评论(0编辑  收藏  举报