Java学习笔记之 Lambda表达式
Posted JMW1407
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java学习笔记之 Lambda表达式相关的知识,希望对你有一定的参考价值。
Java Lambda表达式
Lambda表达式
1、引言
在 Java 8 以前,若我们想要把某些功能传递给某些方法,总要去写匿名类。以前注册事件监听器的写法与下面的示例代码就很像:
manager.addScheduleListener(new ScheduleListener() {
@Override
public void onSchedule(ScheduleEvent e) {
// Event listener implementation goes here...
}
});
这里我们添加了一些自定义代码到 Schedule 监听器中,需要先定义匿名内部类,然后传递一些功能到 onSchedule 方法中。
正是 Java 在作为参数传递普通方法或功能的限制
,Java 8 增加了一个全新语言级别的功能,称为 Lambda 表达式。
2、为什么 Java 需要 Lambda 表达式
在java中我们很容易将一个变量赋值,比如int a =0;int b=a;但是我们如何将一段代码和一个函数赋值给一个变量?这个变量应该是什么的类型?
Java 是面向对象语言,除了原始数据类型之处,Java 中的所有内容都是一个对象。而在函数式语言中,我们只需要给函数分配变量,并将这个函数作为参数传递给其它函数就可实现特定的功能。javascript 就是功能编程语言的典范(闭包)。
Lambda 表达式的加入,使得 Java 拥有了函数式编程的能力。在其它语言中,Lambda 表达式的类型是一个函数;但在 Java 中,Lambda 表达式被表示为对象,因此它们必须绑定到被称为功能接口的特定对象类型
。
Lambda可以让你简单的传递一个行为或者代码。可以把lambda看作是匿名函数,是没有声明名称的方法,但和匿名类一样,也可以作为参数传递给一个方法。
3、Lambda 表达式的结构
Lambda 表达式是一个匿名函数(对于 Java 而言并不很准确,但这里我们不纠结这个问题)。简单来说,这是一种没有声明的方法,即没有访问修饰符,返回值声明和名称。
在仅使用一次方法的地方特别有用,方法定义很短。它为我们节省了,如包含类声明和编写单独方法的工作。
Java 中的 Lambda 表达式通常使用语法是 (argument) -> (body)
,比如:
Lambda表达式的基本结构是:
- 左侧:指定了 Lambda 表达式需要的参数列表
- 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。
(arg1, arg2...) -> { body }
(type1 arg1, type2 arg2...) -> { body }
3.1、Lambda 表达式:语法
Lambda 表达式的结构:
- Lambda 表达式可以具有零个,一个或多个参数。
- 可以显式声明参数的类型,也可以由编译器自动从上下文推断参数的类型。例如 (int a) 与刚才相同 (a)。
- 参数用
小括号
括起来,用逗号分隔。例如(a, b)
或(int a, int b)
或 (String a, int b, float c)。空括号用于表示一组空的参数
。例如() -> 42
。 - 当有且
仅有一个参数
时,如果不显式指明类型,则不必使用小括号
。例如a -> return a*a
。 Lambda表达式的正文可以包含零条,一条或多条语句。 - 如果 Lambda 表达式的
正文只有一条语句,则大括号可不用写
,且表达式的返回值类型要与匿名函数的返回类型相同。 - 如果 Lambda 表达式的
正文有一条以上的语句必须包含在大括号(代码块)中
,且表达式的返回值类型要与匿名函数的返回类型相同。
4、在哪里使用lambda?
4.1、函数式接口
函数式接口就是只定义一个抽象方法
的接口。
- 不包含object中的方法
这个需要说明一点,就是在Java中任何一个对象都来自Object 所有接口中自然会继承自Object中的方法,但在判断是否是函数式接口的时候要排除Object中的方法,下面举几个例子如下:
//这个是函数式接口
interface eat
{
void eatFood();
}
//这个不是是函数式接口
interface eat
{
default void eatFood()
{
System.out.println("hello");
};
}
//这个是一个函数式接口
interface eat
{
void eatFood();
String toString();
}
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。具体来说,Lambda表达式是函数是接口的具体实现的实例
。
通过匿名内部类也可以完成同样的事情,但是比较笨拙:需要提供一个实现,然后在直接内联将它的实现实例化。如下面的例子所示,可以清楚地看到lambda的简洁:
public class TestLambdaAndAnonymity {
/**
* 执行方法
* @param r
*/
public static void process(Runnable r){
r.run();
}
public static void main(String[] args) {
// 使用匿名类
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("this is r1");
}
};
// 使用Lambda
Runnable r2 = ()-> System.out.println("this is r2");
process(r1);
process(r2);
// 直接将lambda作为参数传递
process(()-> System.out.println("this is r3"));
}
}
4.2、函数描述符?
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符
。
举个例子:
Runnable 接口可以看作一个什么也不接受什么也不返回( void )的函数的签名,因为它只有一个叫作 run 的抽象方法,这个方法什么也不接受,什么也不返回( void )。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
如上个小节的例子:“()-> System.out.println("this is r3")
”, () -> void
就是这个函数的签名, 代表了参数列表为空,且返回 void 的函数。这正是 Runnable 接口所代表的。
举另一个例子, (Apple,Apple) -> int
代表接受两个 Apple 作为参数且返回 int 的函数。
4.3、@FunctionalInterface 又是怎么回事?
如果你去看看新的Java API,会发现函数式接口带有 @FunctionalInterface 的标注。这个标注用于表示该接口会设计成一个函数式接口。如果你用 @FunctionalInterface 定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。如下:
@FunctionalInterface
private interface ITest{
void test();
void test1();
}
表示该接口存在多个抽象方法。
@FunctionalInterface 不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。
4.3、简单使用
下面我们写一段lambda简单的代码,找出指定的身份证号码,并打印出来。
最终的调用:
对于上面的代码实现,在我们调用excutor方法前,并不知道findName的实现方法,直到在最后把一个方法作为参数传入到excutor方法中
。
反思:函数式接口NameCheckInterface,是不是可以用来表示所有返回值为bool类型的,有两个形参(类型是passager 和String类型)的lambda表达式?
如果我们再配合泛型的话,是不是我们只需要定义一个通用的函数式接口?下面我们改写下代码:
public class SocketTest {
public static void main(String[]args)throws Exception{
List<Passager> passagerList = new ArrayList<>();
passagerList.add(new Passager("李四", "123456789"));
passagerList.add(new Passager("张三", "123456789"));
passagerList.add(new Passager("王二", "123456789"));
excutor(passagerList,(p,str)->p.getPassagerName().equals(str),str-> System.out.println(str));
}
private static void excutor(List<Passager> passagerList, NameCheckInterface<Boolean,Passager,String> checker, PrintInterface<String> printer)
{
for (Passager p : passagerList) {
if (checker.findName(p,"李四")){
printer.printName(p.getPassagerNo());
}
}
}
}
class Passager{
public String name;
String passagerNo;
public Passager(String name,String passagerNo){
this.name=name;
this.passagerNo=passagerNo;
}
public String getPassagerNo(){
return passagerNo;
}
public String getPassagerName(){
return name;
}
}
interface NameCheckInterface<T,T1,T2>
{
T findName(T1 passager,T2 name);
}
@FunctionalInterface
interface PrintInterface<T>
{
void printName(T name);
}
5、Lambda 表达式的使用
5.1、使用函数式接口
函数式接口定义且只定义了一个抽象方法。函数式接口很有用,因为抽象方法的签名可以描述Lambda表达式的签名。函数式接口的抽象方法的签名称为函数描述符。所以为了应用不同的Lambda表达式,你需要一套能够描述常见函数描述符的函数式接口。
Java 8的库设计师帮你在 java.util.function 包中引入了几个新的函数式接口。
Runnable r = () -> System.out.printf("say hello");//没有输入参数,也没有输出
Supplier<String> sp = () -> "hello";//只有输出消息,没有输入参数
Consumer<String> cp = r -> System.out.printf(r);//有一个输入参数,没有输出
Function<Integer, String> func = r -> String.valueOf(r);//有一个输入参数 有一个输出参数
BiFunction<Integer, Integer, String> biFunc = (a, b) -> String.valueOf(a + b);//有两个输入参数 有一个输出参数
BiConsumer<Integer, Integer> biCp = (a, b) -> System.out.printf(String.valueOf(a + b));//有两个输入参数 没有输出参数
PS:上面是基本的方法,其他的都是基于这几个扩展而来
如果上面的代码使用jdk中的函数式接口的话,就不用额外的定义NameCheckInterface和PrintInterface 接口了。根据上面的参数和返回值的形式,可以使用BiFunction和Consumer直接改写excutor方法:
private void excutor(List<passager> passagerList, BiFunction<passager,String,Boolean> checker, Consumer<String> printer) {
for (passager p : passagerList) {
if (checker.apply(p,"李四")){
printer.accept(p.getPassagerNo());
}
}
}
5.2、Consumer
java.util.function.Consumer<T>
定义了一个名叫 accept
的抽象方法,它接受泛型 T
的对象,没有返回( void )
。你如果需要访问类型 T 的对象,并对其执行某些操作,就可以使用这个接口。
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
如下例子,分别打印每个值:
public class TestConsumer {
public static void main(String[] args) {
forEach(Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println(i));
}
public static <T> void forEach(List<T> list, Consumer<T> c) {
for (T t : list) {
c.accept(t);
}
}
}
5.3、Function
java.util.function.Function<T, R>
接口定义了一个叫作 apply
的方法,它接受一个泛型 T 的对象,并返回一个泛型 R 的对象
。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口。
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
如下所示,将String列表的长度映射到Integer列表:
public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
List<R> results = new ArrayList<>();
for (T t : list) {
results.add(function.apply(t));
}
return results;
}
public static void main(String[] args) {
List<Integer> map = map(
Arrays.asList("Tom", "Jerry", "XiaoMing"),
(String s) -> s.length()
);
System.out.println(map);
}
6、方法引用
6.1、方法引用简介
方法引用是某些Lambda的快捷写法。让你可以重复使用现有的方法定义,并像Lambda一样传递它们。
当你需要使用方法引用时,目标引用放在分隔符 :: 前,方法的名称放在后面。如下面的例子:
Apple::getWeight
Apple::getWeight
就是引用了 Apple 类中定义的方法 getWeight
,方法引用就是Lambda表达式 (Apple a) -> a.getWeight()
的快捷写法。
6.2、构建方法引用
方法引用主要有三类:
1)指向静态方法的方法引用
如下所示,指向静态方法parseInt。
//指向任意实例类型方法
Function<String, Integer> function = Integer::parseInt;
Integer apply = function.apply("100");
System.out.println(apply);
//lambda对比
Function<String, Integer> function = s -> parseInt(s);
Integer apply = null;
try {
apply = function.apply("100");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(apply);
2)指向任意类型实例方法的方法引用
如下所示,指向String的length方法
//指向任意实例类型方法
Function<String,Integer> function1 = String::length;
Integer hello_world = function1.apply("hello world");
System.out.println(hello_world);
//lambda对比
Function<String,Integer> function1 = s -> s.length();
Integer hello_world = null;
try {
hello_world = function1.apply("hello world");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(hello_world);
可以这样理解这句话:你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。
3)指向现有实例对象的方法的方法引用
如下所示:
@Data
static class Student{
private String name;
private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
}
//指向现有对象的方法
Student student = new Student("Jack",10);
Supplier<String> supplier = student::getName;
String s = supplier.get();
System.out.println(s);
与第二条不同,这个方法引用的对象时外部已经存在的对象,如上述例子中的student。
6.3 、构造函数引用
对于一个现有构造函数,你可以利用它的名称和关键字 new 来创建它的一个引用:ClassName::new 。它的功能与指向静态方法的引用类似。
有如下实体类:
@Data
static class Student {
private String name;
private Integer age;
private String address;
public Student() {
}
public Student(String name) {
this.name = name;
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public Student(String name, Integer age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
}
实例:
/**
* description: 自定义三个参数的接口
* @return:
* @author: weirx
* @time: 2021/10/19 18:03
*/
interface ThreeParamsFunction<T, U, P, R> {
R apply(T t, U u, P p);
}
public static void main(String[] args) {
// 无构造参数 ()->new Student() 转化成 Student::new
Supplier<Student> supplier = Student::new;
Student student = supplier.get();
// 一个构造参数 (name)->new Student(name) 转化成 Student::new
Function<String, Student> function = Student::new;
function.apply("JACK");
// 两个构造参数 (name,age)->new Student(name,age) 转化成 Student::new
BiFunction<String, Integer, Student> biFunction = Student::new;
biFunction.apply("JACK", 10);
// 三个构造参数,没有提供三个构造参数的,需要自己写个接口
// (name,age,address)->new Student(name,age,address) 转化成 Student::new
ThreeParamsFunction<String, Integer, String, Student> threeParamsFunction = Student::new;
threeParamsFunction.apply("JACK", 10, "Haerbin");
}
7、案例总结
使用List的sort方法对苹果进行排序:
void sort(Comparator<? super E> c)
根据上面的sort方法构成,我们需要一个Comparator对象对苹果进行比较。在没有java8之前,我们需要这样做,传递代码的方式:
7.1 、传递代码
package test2;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class LambdaTest {
public static class Apple {
public static void main(String[] args) {
List<Apple> apples = Arrays.asList(
new Apple(10),
new Apple(19),
new Apple(9),
new Apple(22)
);
apples.sort(new CompareApple());
for( Apple apple : apples) {
System.out.println(apple.getWeight());
}
}
private Integer weight;
public Apple(Integer weight) {
this.weight = weight;
}
public Integer getWeight() {
return weight;
}
}
public static class CompareApple implements Comparator<Apple> {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
}
}
输出
没有java8: 9
没有java8: 10
没有java8: 19
没有java8: 22
7.2 、匿名类
package test2;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class LambdaTest {
public static class Apple {
public static void main(String[] args) {
List<Apple> apples = Arrays.asList(
new Apple(10),
new Apple(19),
new Apple(9),
new Apple(22)
);
apples.sort(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});
for( Apple apple : apples) {
System.out.println("匿名类: " + apple.getWeight());
}
}
private Integer weight;
public Apple(Integer weight) {
this.weight = weight;
}
public Integer getWeight() {
return weight;
}
}
}
输出:
匿名类: 9
匿名类: 10
匿名类: 19
匿名类: 22
7.3、 lambda表达式
package test2;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class LambdaTest {
public static class Apple {
public static void main(String[] args) {
List<Apple> apples = Arrays.asList(
new Apple(10),
new Apple(19),
new Apple(9),
new Apple(22)
);
apples.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight()));
for( Apple apple : apples) {
System.out.println("lambda表达式: " + apple.getWeight());
}
}
private Integer weight;
public Apple(Integer weight) {
this.weight = weight;
}
public Integer getWeight() {
return weight;
}
}
}
输出:
lambda表达式: 9
lambda表达式: 10
lambda表达式: 19
lambda表达式: 22
7.4 、方法引用
package test2;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class LambdaTest {
public static class Apple {
public static void main(String[] args) {
List<Apple> apples = Arrays.asList(
new Apple(10),
new Apple(19),
new Apple(9),
new Apple(22)
);
apples.sort(Comparator.comparing(Apple::getWeight));
for( Apple apple : apples) {
System.out.println("方法引用: " + apple.getWeight());
}
}
private Integer weight;
public Apple(Integer weight) {
this.weight = weight;
}
public Integer getWeight() {
return weight;
}
}
}
输出
方法引用: 9
方法引用: 10
方法引用: 19
方法引用: 22
参考
1、Java 8 Lambda 表达式详解
2、java8(二)lambda表达式手把手学习
3、Java SE8 之 Lambda 表达式详解
4、Java8 lambda表达式详解
5、java8 之如何使用函数引用
6、【java8新特性】方法引用
以上是关于Java学习笔记之 Lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章
Java学习笔记3.9.1 Lambda表达式 - Lambda表达式入门