夯实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、四大类型接口
为了便于记忆,这里对四大函数式接口的代表接口进行一个总结:
接口类型 | 函数式接口 | 参数类型 | 返回类型 | 方法 |
---|---|---|---|---|
消费型接口 | 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);
}
}
①、方法引用解析
上面的例子看完后你现在的状态可能是这样的😂:
你先别激动,下面慢慢来解开你的疑惑。其实仔细一点可以发现它们都是有类似的规律,这里我总结了三点(其中第三点最重要):
- 必须能够使用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
的方法引用了,并可以调用Funciton
的apply
方法,这时候调用到的其实就是对应的 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或者泛型的,都可以完美接收。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!