系统环境:
- Java JDK 版本:1.8
参考地址:
- Java 8 Lambda 表达式
- Jdk 8 新特性 04 方法引用与构造器引用
- Java 8 新特性:Lambda 表达式之方法引用
一、Lambda 表达式简介
1、什么是 Lambda 表达式
Lambda 表达式是在 JDK 8 中引入的一个新特性,可用于取代大部分的匿名内部类。使用 Lambda 表达式可以完成用少量的代码实现复杂的功能,极大的简化代码代码量和代码结构。同时,JDK 中也增加了大量的内置函数式接口供我们使用,使得在使用 Lambda 表达式时更加简单、高效。
2、为什么需要 Lambda 表达式
谈起为什么需要 Lambda 表达式,那得从函数式编程开始说起。函数式编程可以简单说是一种编程规范,也就是如何编写程序的方法论。它属于结构化编程的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。函数式编程有很多优点,其中包括:
- 易于并发编程;
- 代码的热升级;
- 更方便的代码管理;
- 代码简洁,开发快速;
- 接近自然语言,易于理解;
函数式编程在 C#、Python、JavaScript中都得到充分体现,在 Java 8 版本中也得到了支持。最明显的就是对 Lambda 表达式的支持。很多种迹象表明未来编程语言将是逐渐融合各自的特性,而不是单纯的声明式语言函数编程语言。将来声明式编程语言借鉴函数编程思想,函数编程语言融合声明式编程特性,这几乎是一种必然趋势。
在 Java 中主要引入 Lambda 表达式的作用是对现有编码语义的优化,减少语法冗余。轻量级的将代码封装为数据,使代码简洁,易于理解。
二、函数式接口和定义
1、什么是函数式接口
函数式接口(Functional Interface)是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。Java 中函数式接口被隐式转换为 Lambda 表达式,只有保证接口类中有且只有一个抽象方法,Java 中的 Lambda 表达式才能对该方法进行推导。
2、函数式接口格式
在 Java 函数式接口类中,需要满足接口类中只能有一个抽象方法。总结如下:
- 接口有且仅有一个抽象方法;
- 允许定义静态方法;
- 允许定义默认方法;
- 允许 java.lang.Object 中的 public 方法;
在创建函数式接口时,可以在接口类上面加上@FunctionalInterface注解,这时编译器就可以对接口结构进行强制检查是否符合函数式接口规则,如果不符合规则就显示错误。当然,这个注解只是用于检查,即使不加上该注解,只要符合函数式接口规则一样也是函数式接口。
下面创建个演示的函数式接口,如下:
// @FunctionalInterface 注解说明: // 使用该注解来定义接口,编译器会强制检查接口是否符合函数式接口规则(有且仅有一个抽象方法),如果不符合则会报错。 @FunctionalInterface public interface MyInterface{ /** * 抽象方法(Jdk 8 以后接口类中方法可以省去 public abstract) */ public abstract [返回值类型] [方法名称](参数列表); /** * 其它方法(Jdk 8 以后允许接口类中添加"默认方法"与"静态方法" ) */ ...(略) }
按照上面函数式接口,定义一个示例的函数式接口类,代码如下:
@FunctionalInterface public interface MyCollection { void push(List list); }
3、函数式接口和 Lambda 表达式的关系
函数式接口和 Lambda 表达式的关系可以总结为:
- 函数式接口只包含一个操作方法;
- Lambda 表达式只能操作一个方法;
- Java 中的 Lambda 表达式核心就是一个函数式编程接口的实现。
4、当前 JDK 8 中存在的函数式接口类
在 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 8 中新增了函数接口 java.util.function 包,里面包含了很多用来支持 Java 的函数式编程的接口类,如下:
类名称
描述信息
5、JDK 中常见的函数式接口类
上面 java.util.function 包提供了众多的函数式接口,其中常用的有:
- java.util.function.Predicate<T>:接收参数对象 T,返回一个 boolean 类型结果。
- java.util.function.Comsumer<T>:接收参数对象 T,不返回结果。
- java.util.function.Function<T,R>:接收参数对象 T,返回结果对象 R。
- java.util.function.Supplier<T>:不接收参数,提供 T 对象的创建工厂。
- java.util.function.UnaryOperator<T>:接收参数对象 T,返回结果对象 T。
- java.util.function.BinaryOperator<T>:接收两个 T 对象,返回一个 T 对象结果。
注:为了使易懂,下面示例中 Lambda 表达式没有使用的最简易写法,而是使用比较繁琐的写法。
(1)、java.util.function.Predicate<T>
接口类作用: 接收参数对象T,返回一个 boolean 类型结果。
接口类源码:
@FunctionalInterface public interface Predicate<T> { /** abstract 方法,接收一个参数, 判断这个参数是否匹配某种规则, 然后返回布尔值结果 */ boolean test(T t); /** default 方法,接收另外一个 Predicate<T> 类型参数进行"逻辑与"操作,返回一个新的 Predicate */ default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } /** default 方法,接收另外一个 Predicate<T> 类型参数进行"逻辑或"操作,返回一个新的 Predicate */ default Predicate<T> negate() { return (t) -> !test(t); } /** default 方法,返回当前 Predicate 取反操作之后的 Predicate */ default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } }
使用示例:
public class PredicateExample { /** 这里创建一个 Prodicate,设置验证秘钥的一个逻辑,然后返回并输出验证结果 */ public static void main(String[] args) { // 创建 Predicate 及 Lambda 表达式与待实现的逻辑 Predicate<String> validation = (String secret) -> { return "123456".equals(secret); }; // 调用 Predicate 提供的 test 方法并输出结果 System.out.println(validation.test("123")); System.out.println(validation.test("123456")); } }
日常开发中,需要对某个值进行判断操作,并且返回判断结果,这时可以考虑使用 Predicate 接口,以及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
(2)、java.util.function.Comsumer<T>
接口类作用: 接收参数对象 T,不返回结果。
接口类源码:
@FunctionalInterface public interface Consumer<T> { /** abstract 方法,接收一个参数,执行消费逻辑 */ void accept(T t); /** default 方法,将两个 Consumer 连接到一起,再进行消费 */ default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
使用示例:
/** 这里创建一个 Consumer,模拟发送消息并打印内容 */ public class ConsumerExample { public static void main(String[] args) { // 创建 Consumer 及 Lambda 表达式与待实现的逻辑 Consumer<String> consumer = (String message) -> { System.out.println("发送消息内容:" + message); }; // 调用 Consumer 提供的 accept 方法 consumer.accept("测试消息"); } }
日常开发中,需要对某个类型进行公共处理,并且不需要任何返回值,这时可以考虑使用 Consumer 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
(3)、java.util.function.Function<T,R>
接口类作用: 接收参数对象 T,返回结果对象 R。
接口类源码:
@FunctionalInterface public interface Function<T, R> { /**abstract 方法,接收一个参数进行处理,然后返回处理结果 R */ R apply(T t); /** default 方法,先执行参数(Function)的,再执行调用者(Function) */ default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } /** default 方法,先执行调用者,再执行参数,和compose相反 */ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } /** 返回当前正在执行的方法 */ static <T> Function<T, T> identity() { return t -> t; } }
使用示例:
/** 这里创建一个 Function,对传入的参数进行验证,如果包含 a 字符就返回1,否则返回0 */ public class FunctionExample { public static void main(String[] args) { // 创建 Function 及 Lambda 表达式与待实现的逻辑 Function<String, Integer> function = (String str) -> { return str.contains("a") ? 1 : 0; }; // 调用 Function 提供的 apply 方法 System.out.println(function.apply("abcd")); System.out.println(function.apply("efgh")); } }
日常开发中,需要对某个类型数据进行操作,经过一系列逻辑后转换为一个新的类型进行返回,这时可以考虑使用 Function 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
(4)、java.util.function.Supplier<T>
接口类作用: 不接收参数,提供 T 对象的创建工厂。
接口类源码:
@FunctionalInterface public interface Supplier<T> { /** abstract 方法,设置业务逻辑,获取逻辑中创建的对象 */ T get(); }
使用示例:
/** 这里创建一个 Supplier,用于生成随机ID,通过 get 方法获取生成的随机ID值 */ public class SupplierExample { public static void main(String[] args) { // 创建 Supplier 及 Lambda 表达式与待实现的逻辑 Supplier<String> supplier = () -> { return UUID.randomUUID().toString(); }; // 调用 Supplier 提供的 get 方法 System.out.println(supplier.get()); } }
日常开发中,需要创建一个统一的工厂用于生成特定的产物完成特定的目标,这时就可以考虑使用 Supplier 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
(5)、java.util.function.UnaryOperator<T>
接口类作用: 接收参数对象 T,返回结果对象 T。
接口类源码:
// 可以看到 UnaryOperator 继承了 Function 接口 @FunctionalInterface public interface UnaryOperator<T> extends Function<T, T> { /** static 方法,接收一个参数,然后对其处理后再返回 */ static <T> UnaryOperator<T> identity() { return t -> t; } }
使用示例:
/** 这里创建一个 UnaryOperator,接收一个字符串进行加工处理后返回新字符串 */ public class UnaryOperatorExample { public static void main(String[] args) { // 创建 UnaryOperator 及 Lambda 表达式与待实现的逻辑 UnaryOperator<String> unaryOperator = (String str) -> { return "[" + str + "]"; }; // 调用 UnaryOperator 继承的 Function 提供的 apply 方法 System.out.println(unaryOperator.apply("hello")); } }
日常开发中,我们经常要对一个已有的对象进行操作修改,然后返回修改后的对象,这时就可以考虑使用 UnaryOperator 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
(6)、java.util.function.BinaryOperator<T>
接口类作用: 接收两个 T 对象,返回一个 T 对象结果。
接口类源码:
@FunctionalInterface public interface BinaryOperator<T> extends BiFunction<T,T,T> { /** abstract 方法,通过比较器Comparator来比较两个元素中较小的一个作为返回值返回 */ public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) <= 0 ? a : b; } /** 通过比较器Comparator来比较两个元素中较大的一个作为返回值返回 */ public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b; } }
使用示例:
/** 这里创建一个 BinaryOperator,比较传入的两个参数哪个值最大,返回最大值 */ public class BinaryOperatorExample { public static void main(String[] args) { // 创建 BinaryOperator 及 Lambda 表达式与待实现的逻辑 BinaryOperator<Integer> binaryOperator = (Integer t1, Integer t2) -> { return t1 > t2 ? t1 : t2; }; // 调用 BinaryOperator 继承的 BiFunction 提供的 apply 方法 System.out.println(binaryOperator.apply(1, 2)); } }
在使用这几种基本函数接口时传入参数 T 不能是基本类型,如 BinaryOperator 中 T 不能设置为 int,只能使用 Integer 包装类,这也限制了 Lambda 表达式中设置参数时候,使用包装类替换基本类型。
日常开发中,我们有时候会对两个对象进行操作,执行一些操作逻辑后返回结果,这时就可以考虑使用 BinaryOperator 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。
三、Lambda 表达式基本语法
1、Lambda 表达式的组成
Lambda 表达式的组成可以拆分为:
- 声明: 与 Lambda 表达式绑定的接口类型。
- 参数: 参数包含在一对 () 中,和绑定的接口中的抽象方法中的参数个数及顺序一致。
- 操作符: ->
- 执行代码块: 执行代码块包含在一对 {} 中,出现在操作符的右侧。
[接口声明] = (参数) -> {执行代码块}
2、Lambda 表达式的格式
Lambda 表达式可以分为下面几种格式:
- 无参数,无返回值;
- 有一个参数,无返回值;
- 左侧只有一个参数,小括号可以省略不写;
- 有两个以上参数,有返回值,并且Lambda 体中有多条语句;
- 若右侧Lambda体中,只有一条语句,return 和大括号都可以省略不写;
- Lambda 表达式的参数列表的数据类型可以省略不写,jvm编译器会进行上下文推断出,数据类型“类型推断”;
(1)、无参数,无返回值
() -> System.out.println("测试");
(2)、有一个参数,无返回值
(x) -> System.out.println(x);
(3)、左侧只有一个参数,小括号可以省略不写
x -> System.out.println(x);
(4)、有两个以上参数,有返回值,并且Lambda 体中有多条语句
Comparator<Integer> comparator = (x, y) -> { System.out.println("测试"); return Integer.compare(x,y); };
(5)、若右侧Lambda体中,只有一条语句,return 和大括号都可以省略不写
Comparator<Integer> Comparator = (x, y) -> Integer.compare(x, y);
(6)、Lambda 表达式的参数列表的数据类型可以省略不写,JVM 在运行时,会自动根据绑定的抽象方法中的参数,进行数据类型推导
(Integer x, Integer y) -> Integer.compare();
四、Lambda 表达式中变量作用域
Java 中的变量捕获与变量隐藏:
- 变量捕获: 局部类和匿名内部类可以访问被 final 修饰的封闭块内的局部变量。
- 变量隐藏: 在一个类中,子类中的成员变量如果和父类中的成员变量同名,那么即使他们类型不一样,只要名字一样,父类中的成员变量都会被隐藏。
在局部类和匿名内部类都存在 变量捕获 与 变量隐藏,而在 Lambda 表达式中则只支持 变量捕获。
下面是对这作用域得演示示例:
(1)、匿名内部类:
public class VariableExample { /** 成员变量 */ String str1 = "成员变量"; public void innerClass() { // 方法内部变量 String str2 = "方法内部变量"; // 使用匿名内部类创建线程 new Thread(new Runnable() { // 匿名内部类内部变量 String str3 = "匿名内部类内部变量"; @Override public void run() { /* 访问变量 */ System.out.println("匿名内部类输出:" + str1); System.out.println("匿名内部类输出:" + str2); System.out.println("匿名内部类输出:" + str3); /* 修改变量 */ str1 = "修改访问成员变量"; // str2 = "修改访问方法内部变量"; // 不能进行修改,默认推导变量的修饰符 final str3 = "修改访问匿名内部类内部变量"; /* 在匿名内部类中定义和类外部变量一样名称的变量 */ String str1 = "重新命名成员变量"; String str2 = "重新命名方法内部变量"; } }).start(); } /** Main 方法 */ public static void main(String[] args) { VariableExample variableExample = new VariableExample(); // 匿名内部类 variableExample.innerClass(); } }
(2)、Lambda 表达式:
public class VariableExample { /** 成员变量 */ String str1 = "成员变量"; public void lambdaExpression() { // 方法内部变量 String str2 = "方法内部变量"; new Thread(()->{ // Lambda 内部变量 String str3 = "Lambda 内部变量"; /* 访问变量 */ // 访问成员变量 System.out.println("Lambda 表达式输出:" + str1); // 访问方法内部变量 System.out.println("Lambda 表达式输出:" + str2); // 访问匿名内部类内部变量 System.out.println("Lambda 表达式输出:" + str3); /* 修改变量 */ str1 = "修改访问成员变量"; // str2 = "修改访问方法内部变量"; // 不能进行修改,默认推导变量的修饰符 final str3 = "修改访问匿名内部类内部变量"; /* 在 Lambda 中定义和类外部变量一样名称的变量 */ String str1 = "重新命名成员变量"; // String str2 = "重新命名方法内部变量"; // 不能命名,lambda 不支持变量隐藏 }).start(); } /** Main 方法 */ public static void main(String[] args) { VariableExample variableExample = new VariableExample(); // 匿名内部类 variableExample.innerClass(); // Lambda 表达式 variableExample.lambdaExpression(); } }
五、Lambda 表达式方法重载问题
当使用 Lambda 表达式,调用一个类中的重载方法,且方法中的参数为都为函数接口,函数接口中定义的方法接收的参数类型相同,这时候 Lambda 是无法推断出要调用哪个方法。
函数接口A:
@FunctionalInterface interface MyInterfaceA { void push(String param); }
函数接口B:
@FunctionalInterface interface MyInterfaceB { void pull(String param); }
示例,实现方法重载与测试的 Main 方法:
public class LambdaOverloadExample { // 重载方法A public static void method(MyInterfaceA myInterfaceA) { myInterfaceA.push("hello 1"); } // 重载方法B public static void method(MyInterfaceB myInterfaceB) { myInterfaceB.pull("Hello 2"); } /** Main 方法*/ public static void main(String[] args) { // 使用匿名内部类 method(new MyInterfaceA() { @Override public void push(String param) { System.out.println(param); } }); method(new MyInterfaceB() { @Override public void pull(String param) { System.out.println(param); } }); // 使用 Lambda 表达式 //method(param -> System.out.println(param)); // 编译器提示错误,表示无法推断使用哪个参数 } }
上面注掉的那部分代码,在编辑器中直接提示错误,很显然 Lambda 表达式无法直接推断出使用哪个类中的重载方法。其实,只要明确告诉 Lambda 表达式使用哪个参数,就可以很简单的解决问题,比如以上面的例子,在 Lambda 表达式使用 method 方法时,将参数类型转换为对应的要使用的类型就可以解决这个问题,代码如下:
// 转换参数为 MyInterfaceA method((MyInterfaceA)param -> System.out.println(param)); // 转换参数为 MyInterfaceB method((MyInterfaceB)param -> System.out.println(param));
按上面进行修改后就可以正常使用 Lambda 表达式了,如果不习惯也可以使用匿名内部类进行方法调用,内名内部类是没有相关问题的。
六、Lambda 表达式方法引用
方法引用本质上就是对方法调用的简化,方法引用和函数式接口绑定,在使用过程中会创建函数式接口的实例,是结合 Lambda 表达式的一种特性。在应用过程中,方法引用常分为:
- 静态方法引用
- 实例方法引用
- 构造方法引用
- 特定类型的任意对象实例方法引用
注意:在使用 Lmabda 方法引用时虽然能够简化代码,但是在实际开发中不可因需要简化代码而过度使用方法引用,因为他会在很大程度上降低代码可读性。
1、创建示例的实体类
为了下面示例方便,我们首先创建一个 Person 实体类,如下:
public class Person { /** 姓名 */ private String name; /** 岁数 */ private int age; public Person() { } public Person(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 "Person{name=" + name + ",age=" + age + "}"; } }
2、静态方法引用示例
静态方法的引用的使用: 静态方法所在类.方法名称() –> 静态方法所在类 :: 方法名称
创建一个使用静态方法引用的示例类:
public class StaticMethodExample { /** 测试的静态方法 */ public static int compareAge(Person p1, Person p2) { return p1.getAge() - p2.getAge(); } public static void main(String[] args) { // 创建 Person 集合 List<Person> personList = new ArrayList<>(); personList.add(new Person("Wangqin",26)); personList.add(new Person("Liming",22)); personList.add(new Person("Alisi",18)); personList.add(new Person("Jerry",31)); // 按岁数进行排序,使用静态方法引用 personList.sort(StaticMethodExample::compareAge); } }
3、实例方法引用示例
实例方法的引用的使用:创建类型对应一个对象 –> 对应应用 :: 实例方法名称
创建一个封装实例方法的类:
public class PersonUtil{ /** 测试的实例方法 */ public int compareAge(Person p1, Person p2) { return p1.getAge() - p2.getAge(); } }
创建一个使用实例方法引用的示例类:
public class InstanceMethodExample { public static void main(String[] args) { // 创建 Person 集合 List<Person> personList = new ArrayList<>(); personList.add(new Person("Wangqin",26)); personList.add(new Person("Liming",22)); personList.add(new Person("Alisi",18)); personList.add(new Person("Jerry",31)); // 按岁数进行排序, PersonUtil personUtil = new PersonUtil(); // 引用实例方法 personList.sort(personUtil::compareAge); } }
4、构造方法引用示例
构造方法的引用的使用:绑定函数式接口
创建一个函数式接口,且设置接收参数和 Person 的构造方法相同,返回 Person 对象的方法定义:
@FunctionalInterface public interface PersonConstructor{ Person initInstance(String name, int age); }
创建一个使用构造方法引用的示例类
public class LambdaConstructorMethodExample { public static void main(String[] args) { // 创建 Person 集合 List<Person> personList = new ArrayList<>(); personList.add(new Person("Wangqin", 26)); personList.add(new Person("Liming", 22)); personList.add(new Person("Alisi", 18)); personList.add(new Person("Jerry", 31)); // 构造方法引用 PersonConstructor personConstructor = Person::new; Person person = personConstructor.initInstance("linda", 18); System.out.println(person); } }
5、特定类型的任意对象实例方法引用示例
特定类型的任意对象实例方法引用示例:特定类型 :: 特定类型的方法
以下是对特定类型的任意对象的实例方法的引用的示例:
public class LambdaExample { public static void main(String[] args) { // 创建字符串集合 List<String> strList = new ArrayList<>(); strList.add("Jerry"); strList.add("Mini"); strList.add("Kary"); strList.add("walls"); // 使用集合的sort方法,按照String的compareToIgnoreCase进行排序(比较字符串hash值) strList.sort(String::compareToIgnoreCase); System.out.println(strList); } }
这里根据定义的集合 strList 去推导目标类型参数值,如果不符合后面传入的方法引用所对应的类型,将报错。该方法参考等效 Lambda 表达式 String::compareToIgnoreCase 的参数列表 (String a, String b),其中 a 和 b 是用于更好地描述这个例子中的任意名称。方法引用将调用该方法 a.compareToIgnoreCase(b)。
到此这篇关于Java8如何使用Lambda表达式简化代码的文章就介绍到这了,更多相关Java8用Lambda表达式简化代码内容请搜索易盾网络以前的文章或继续浏览下面的相关文章希望大家以后多多支持易盾网络!