Lambda 表达式即函数式编程,可以将行为进行传递,可以在以后执行一次或多次。使写出更简洁、灵活、紧凑的代码。
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable
接口来定义任务内容,并使用java.lang.Thread
类来启动该线程。
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("执行线程任务");
}
}).start();
}
执行线程任务
我们可以看到真正希望做到的事情是:将 run 方法体内的代码传递给 Thread 类使用。
把函数作为一个方法的参数(函数作为参数传递进方法中)。
借助 Java8 的全新语法,上述Runnable
接口的匿名内部类写法可以通过更简单的 Lambda 表达式达到等效
public static void main(String[] args) {
new Thread(() -> System.out.println("执行线程任务")).start();
}
执行线程任务
这段代码和刚才执行的效果是完全一致的,可以看出我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
Lambda 的格式由3个部分组成
(参数类型 参数名称 …) -> { 代码语句 }
->
是新引入的语法格式,代表指向动作。当编译器可以自动推导出这个方法的参数类型及返回值时,就可以进行省略。
(参数名称 …) -> { 代码语句 }
public static void main(String[] args) {
Comparator comparator;
// 原写法
comparator = new Comparator() {
@Override
public int compare(String first, String second) {
int lenNum = first.length() - second.length();
if (lenNum > 0) return 1;
if (lenNum < 0) return 0;
return 0;
}
};
// Lambda 写法
comparator = (first, second) ->{
int lenNum = first.length() - second.length();
if (lenNum > 0) return 1;
if (lenNum < 0) return 0;
return 0;
};
}
}
参数名称 -> { 代码语句 }
public class Lam1 {
public static void main(String[] args) {
Comparable comparable;
// 原写法
comparable = new Comparable() {
@Override
public int compareTo(Object o) {
if (null == o){
return 0;
}
return 1;
}
};
// Lambda 写法
comparable = o -> {
if (null == o){
return 0;
}
return 1;
};
}
}
(参数类型 参数名称 …) -> 代码语句
public class Lam1 {
public static void main(String[] args) {
Comparator comparator;
// 原写法
comparator = new Comparator() {
@Override
public int compare(String o1, String o2) {
return o1.length()-o2.length();
}
};
comparator = ((o1, o2) -> o1.length()-o2.length());
}
}
Lambda 表达式不能独立存在,总是会转换为函数式接口的实例。
函数接口是有且仅有一个抽象方法的接口,无论是Runnable
、Comparator
接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才能用作 Lambda 表达式。
函数式接口 入参参数类型 返回类型 抽象方法名 描 述 示例
Supplier 无 T get 提供一个 T 类型的值 类似工厂方法
Consumer T void accept 处理一个 T 类型的值 用作打印入参
Function T R apply 有一个 T 类型参数的函数 获取person的姓名
UnaryOperator T T apply 类型 T 上的一元操作 对输入的数字自增
BinaryOperator T,T T apply 类型 T 上的二元操作 对输入的两个数字相乘
Predicate T boolean test 布尔值函数 判断person的年龄是否大于18
static class Person {
private String name;
private Integer age;
// 忽略 get set toString
}
public static void main(String[] args) {
// 返回个 Person 对象
Supplier supplier = () -> new Person("旺财", 18);
// 用作打印入参
Consumer consumer = s -> System.out.println(s);
// 获取 person 对象的名字
Function function = person -> person.getName();
// 输入的数字自增1
UnaryOperator unaryOperator = i -> ++i;
// 输入的两个数字做乘法
BinaryOperator binaryOperator = (x, y) -> x * y;
// 判断 person 对象的年龄是否大于18岁
Predicate predicate = person -> person.getAge() > 18;
Person person = new Person("旺财", 18);
System.out.println("①Supplier ==>生成 person 对象 :" + supplier.get());
System.out.print("②consumer ==>打印入参 :"); consumer.accept("莱纳,你坐啊");
System.out.println("③function ==>获取 person 对象的姓名 :" + function.apply(person));
System.out.println("④unaryOperator ==>输入的值自增1 :" + unaryOperator.apply(123456));
System.out.println("⑤binaryOperator ==>输入的两个值相乘 :" + binaryOperator.apply(1234, 5678));
System.out.println("⑥predicate ==>判断 person 对象年龄是否大于18岁 :" + predicate.test(person));
}
①Supplier ==>生成 person 对象 :Person(name=旺财, age=18)
②consumer ==>打印入参 :莱纳,你坐啊
③function ==>获取 person 对象的姓名 :旺财
④unaryOperator ==>输入的值自增1 :123457
⑤binaryOperator ==>输入的两个值相乘 :7006652
⑥predicate ==>判断 person 对象年龄是否大于18岁 :false
实际上也是有且仅有一个抽象方法的自定义的接口。
可以使用 @FunctionalInterface 注解修饰需要的接口,编译器会检测该接口是否只有一个抽象方法,否则,会报错。可以有多个默认方法,静态方法。
@FunctionalInterface
interface TestInterface {
void getMaxNum(Integer a, Integer b);
}
public static void main(String[] args) {
TestInterface testInterface = (a, b) -> a >= b ? a : b;
Integer maxNum = testInterface.getMaxNum(255, 666);
System.out.println("最大值 maxNum :" + maxNum);
// 666
}
方法引用相当于简写 了 Lambda 表达式中已存在的方法,可以把 Lambda 表达式重写为方法引用,通过使用操作符 :: 直接访问类或实例的已存在的方法或构造方法。
方法引用可以理解为 Lambda 表达式的另外一种表现形式。
通俗的说就是 Lambda 所要重写的内容和其他已存在的方法体一样,就可以直接拿来使用。
类型 语法 对应的Lambda表达式
静态方法引用 类名::staticMethod (args) -> 类名.staticMethod(args)
实例方法引用 inst::instMethod (args) -> inst.instMethod(args)
对象方法引用 类名::instMethod (inst,args) -> inst 的类名.instMethod(args)
构建方法引用 类名::new (args) -> new 类名(args)
public static void main(String[] args) {
// 函数式接口,其需要重写的方法为: T apply(T t, T u);
BinaryOperator binaryOperator = (a, b) -> (a >= b) ? a : b;
}
假设我们要实现的 BinaryOperator 的逻辑内容为输出最大的那个数。
这里我们可以改写成
public static void main(String[] args) {
BinaryOperator binaryOperator = (a, b) -> (a >= b) ? a : b;
// 这两个是等价的
binaryOperator = Math::max;
}
这里其实引用的是 Math 类下的静态方法
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
我们发现我们要实现的比较的方法在 Math 类中已经有现成的逻辑内容结构了,所以我们直接引用过来使用。
这里的 Math::max 等价于 (a, b) -> (a >= b) ? a : b
这里我们先建立一个用于引用的类及对应的方法。
class TestInstanceFun {
public int isNull(Object obj) {
if (null == obj)
return 0;
else
return 1;
}
}
具体调用
public static void main(String[] args) {
Comparable comparable = o -> {
if (null == o) {
return 0;
} else {
return 1;
}
};
TestInstanceFun testInstanceFun = new TestInstanceFun();
comparable = testInstanceFun::isNull;
}
这里想要实现对比的 Comparable 的逻辑为 当对象为 null 时,返回0,其他就返回1,
这个和静态方法引用类似,使用 TestInstanceFun 对象的实例去引用即可。
这里我们先建立一个用于测试的函数式接口
@FunctionalInterface
interface TestInterface {
simpleSubString(String originalString, int a, int b);
}
具体调用
public static void main(String[] args) {
// 假设具体的逻辑为 将字符串进行首尾切割。
TestInterface testInterface = (originalString, a, b) -> originalString.substring(a, b);
// 这里可以转换使用 String 的对象方法引用
testInterface = String::substring;
}
引用的 String 的 substring(int beginIndex, int endIndex) 方法为
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen);
}
第一个入参的 originalString 调用其 substring 方法,后面的两个参数 a 和 b,作为 substring 的入参,因此可以转换成 String::substring ,相当于 originalString.substring(a, b) 来使用。
构造器引用与方法引用很类似,只不过方法名为 new。被引用的类必须存在一个构造方法与函数式接口的抽象方法参数列表一致,以及回参为该类的实例。
测试类
class Person {
String name;
String age;
public Person() {
this.name = "default";
this.age = "18";
}
public Person(String name, String age) {
this.name = name;
this.age = age;
}
}
测试函数式接口
@FunctionalInterface
interface TestStructureFun{
Person getPerson(String a, String b);
}
具体调用
public static void main(String[] args) {
TestStructureFun testStructureFun = (a, b) -> new Person(a, b);
// 可以转为这种写法
testStructureFun = Person::new;
}
这里的 Person::new 是 Person 构造器的一个引用。根据上下文推测出,这里引用的是 Person 中两个参数的构造器。
方法引用不能独立存在,总是会转换为函数式接口的实例。
如果有多个同名的重载方法, 编译器就会尝试从上下文中找出你指的那一个方法。
例如该文章中的 静态方法引用 示例中:
Math.max 方法有四个版本,有用于整数的, 有用于 double 值。选择哪一个版本取决于 Math::max 转换为哪个函数式接口的方法参数。
示例里 Math::max 检测到当前调用的方法的两个入参都为 Integer 类型,就转换为对应的方法参数 --> public static int max(int a, int b)