什么是Lambda表达式
lambda
表达式是JAVA8
中提供的一种新的特性,它支持JAVA
也能进行简单的"函数式编程"
。- 它是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。
- Lambda 表达式是实现函数式接口的一种方式,可以看做匿名内部类的简写形式:它没有名称,但它有参数列表,函数主体,返回类型,可能还有一个可以抛出的异常列表。
- Lambda 是一个匿名函数,可以把 Lambda表达式 理解为是一段可以传递的代码 (将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升 ,JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
什么是函数式接口
定义:接口中只有一个抽象方法的接口。
函数式接口一般使用 @FunctionalInterface
注解修饰,目的是检查接口是否符合函数式接口规范。
注意点:
- 函数式接口中可以有 默认方法 和静态方法
- 函数式接口重写父类的方法,并不会计入到自己的抽象方法中
函数式接口Runnable
java.lang.Runnable
就是一种函数式接口,在 Runnable 接口中只声明了一个方法 void run()
,我们使用匿名内部类来实例化函数式接口的对象,有了 Lambda 表达式,这一方式可以得到简化。
每个 Lambda 表达式都能隐式地赋值给函数式接口,例如,我们可以通过 Lambda 表达式创建 Runnable 接口的引用。
ini
Runnable r = () -> System.out.println("hello world");
当不指明函数式接口时,编译器会自动解释这种转化:
scss
new Thread(
() -> System.out.println("hello world")
).start();
因此,在上面的代码中,编译器会自动推断:根据线程类的构造函数签名 public Thread(Runnable r) { }
,将该 Lambda 表达式赋给 Runnable 接口。
4类常用函数式接口
- Consumer 消费型接口:接受一个参数并进行逻辑操作,无返回值;
- Supplier 供给型接口:不接受参数,操作后返回一个对象;
- Function<T, R> 函数型接口:接收一个泛型T对象,操作后返回泛型R对象;
- Predicate 断言型接口:接收一个参数,操作后返回一个 boolean 值;
Consumer
接口定义:
csharp
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
消费型接口:接收一个参数进行处理,不返回结果。
Supplier
接口定义:
csharp
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
供给型接口:不接受参数,返回一个泛型类型的对象;
如何使用:使用时提供该接口的实现,并返回一个泛型类型的对象;
Function
接口定义:
vbnet
@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);
}
函数型接口:提供一个 T 类型的参数,返回一个 R 类型的结果。
Predicate
接口定义:
java
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
断言型接口:输入一个 T 类型的参数,返回 boolean 类型的结果。
方法引用
方法引用是进一步简化 Lambda 表达式的写法。
方法引用的格式:类型或者对象 :: 引用的方法
方法引用的四种形式:
- 静态方法的引用;
- 实例方法的引用;
- 特定类型方法的引用;
- 构造器引用;
静态方法的引用
格式: 类名 :: 静态方法名
这里以 List Integer 转 String 为例:
ini
List<Integer> list = new ArrayList<>();
// list.stream().map(s -> String.valueOf(s));
List<String> strs = list.stream().map(String::valueOf).collect(Collectors.toList());
注意点:前后参数一致的静态方法。
实例方法的引用
格式:实例对象 :: 方法名
这里以 System.out 对象的实例方法 println(String x) 为例:
ini
List<String> list = new ArrayList<>();
// lists.forEach(s -> System.out.println(s));
list.forEach(System.out::println);
注意点:前后参数一致。
特定类型的方法引用
要点:参数列表中形参中的第一个参数作为了要调用方法的调用者,并且其余参数作为后面方法的实参,那么就可以用特定类型方法引用了。
rust
String[] strs = new String[]{"James", "AA", "John", "Patricia", "Dlei", "Robert", "Boom", "Cao", "black", "Michael", "Linda", "cao", "after", "sBBB"};
// 按照元素的首字符(忽略大小写)升序排序
Arrays.sort(strs, (s1, s2) -> s1.compareToIgnoreCase(s2));
// 特定类型的方法引用
Arrays.sort(strs, String::compareToIgnoreCase);
当一个对象调用一个方法,方法的参数
中包含一个函数式接口
,该函数式接口的第一个参数类型
是这个对象的类
,那么这个函数式接口
可用方法引用代替
,并且替换用的方法可以不包含函数式接口的第一个参数(调用对象的类)。
构造器引用
dart
s -> new Student(s) => Student::new
创建对象时,前后参数一致。
总结
Lambda表达式是为了简化实现函数式接口的
如果没有Lambda表达式,需要使用匿名内部类实现
typescript
Consumer<String> s1= new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
如果使用Lambda表达式
ini
Consumer<String> s1= s2 -> System.out.println(s2);
既然Lambda表达式是一种实现方式,那函数式接口
是啥呢,顾名思义接口
用来定义行为
,(多说一句,类是抽象属性和行为,即抽象类别的,比如人,飞机等不同类别,接口是抽象行为的,即飞机会飞,人要内卷,飞和内卷都是行为的一种),函数式接口即一个接口只有一个抽象方法,default与static的不算,然后加上注解@FunctionalInterface
。
函数式接口
就是一类行为定义
,Lambda表达式
就是简化实现这类行为的表达式
。等于是有函数式接口才有Lambda表达式,单独成立都没有意义。Lambda表达式本质是一个语法糖,内部还是内部类,只是编译器给我们做了处理。
最后我们总结下什么是方法引用
。
方法引用
其实是Lambda表达式的更简约写法,Lambda表达式需要实现函数式接口(即写方法实现),方法引用则是直接使用
已经存在
的符合函数式接口定义
的方法
。
比如我们要实现一个int转String。
如果没有方法引用
typescript
Function<Integer,String> function=(number)->{return number+"";};
实际上String.valueOf()已经实现了我们要的转换方法,且符合Function的定义,即有一个参数输入,一个参数输出。则我们可以直接使用该方法。
vbnet
Function<Integer,String> function=String::valueOf;
这样极大的简化了我们的代码实现。
再来一个简单例子。
ini
Consumer<String> s1= s2 -> System.out.println(s2);
我们定义了一个传入打印,Lambda写法如上。我们可以使用更简约的写法
ini
Consumer<String> s1= System.out::println;
只要符合我们的函数式接口的方法定义,即是否有返回和参数个数。
scss
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
如上,我们使用这个接口的时候,必须传入一个跟他定义一致的方法,都是没有返回,然后有一个参数传入。