Lambda表达式
1.目录
2.什么是Lambda表达式
Lambda是一种语法糖,是为了替代在某些场景下的匿名内部类而存在的,可以大幅度减少没有必要的编码
它是函数式编程的的一个重要特性,标志着Java向函数式编程迈出了重要的第一步
3.Lambda的标准格式
标准格式介绍
(参数类型 参数名称)->{
方法体;
}
格式说明
- (参数类型 参数名称):参数列表
- {代码体;}:方法体
- ->:箭头,分隔参数列表和方法体
4.常见用法
无参数,无返回值
JDK8之前的做法
当需要启动一个线程去完成任务时,通常会通过Runnable接口来定义任务内容,并使用Thread类来启动该线程
/**
* @author sgy
* @date 2022/5/30 14:03
* @description 演示匿名内部类的问题
*/
public class Demo01 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程任务执行");
}
}).start();
}
}
Lambda写法
/**
* @author sgy
* @date 2022/5/30 14:05
* @description 使用Lambda来初步优化匿名内部类
*/
public class Demo02 {
public static void main(String[] args) {
new Thread(() -> System.out.println("新线程任务执行!")).start();
}
}
有参数,有返回值
/**
* @author sgy
* @date 2022/5/30 14:05
* @description 有参数有返回值的Lambda
*/
public class Demo04 {
public static void main(String[] args) {
List<Person> persons = new ArrayList<>();
persons.add(new Person("刘德华", 2, "男"));
persons.add(new Person("张学友", 1, "女"));
persons.add(new Person("刘德华", 4, "男"));
persons.add(new Person("黎明", 9, "男"));
// 原生方式按照从小到大排序(id)
Collections.sort(persons, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getId() - o2.getId();
}
});
for (Person person : persons) {
System.out.println(person);
}
// Lambda排序
Collections.sort(persons, (o1, o2) -> {
return o1.getId() - o2.getId();
});
for (Person person : persons) {
System.out.println(person);
}
}
}
5.Lambda的省略格式
说明
Lambda的标准格式是可以进一步被优化的,只不过要满足一些特殊条件
省略规则
- 小括号内参数的类型可以省略
- 如果小括号内有且仅有一个参数,则小括号可以省略
- 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
省略前代码
(int a)->{
return new person();
}
省略后代码
a->new Person()
6.使用Lambda的前提条件
- 方法的参数或局部变量类型必须为接口才能使用Lambda
- 接口中有且仅有一个抽象方法
7.函数式接口
什么是函数式接口
函数式接口在Java中是指:有且仅有一个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导
FunctionalInterface注解
与@Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface。该注解可用于一个接口的定义上:
@FunctionalInterface
public interface Operator {
void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
8.Lambda和匿名内部类对比
- 所需的类型不一样:
- 匿名内部类,需要的类型可以是类,抽象类,接口
- Lambda表达式,需要的类型必须是接口
- 抽象方法的数量不一样
- 匿名内部类所需的接口中抽象方法的数量随意
- Lambda表达式所需的接口只能有一个抽象方法
9.常用内置函数式接口
介绍
我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口
常用内置函数式接口
它们主要在 java.util.function 包中
- Supplier:一般用于无参创建某对象
- Consumer:进T无返回值
- Function:进T返R
- Predicate:进T返回Boolean,一般用于判断
Supplier接口
介绍
它意味着供给,对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据
代码
@FunctionalInterface
public interface Supplier<T> {
T get();
}
案例
目标
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值
提示:接口的泛型请使用java.lang.Integer类
代码
Consumer接口
介绍
它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定
代码
@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); };
}
}
案例
目标
使用Lambda表达式将一个字符串转成大写的字符串
代码
Function接口
介绍
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值
代码
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
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;
}
}
案例
目标
使用Lambda表达式将字符串转成数字
代码
Predicate接口
介绍
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用它
代码
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
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);
}
}
案例
目标
使用Lambda判断一个人名如果超过3个字就认为是很长的名字
代码
10.方法引用
为什么需要方法引用
进一步简化Lambda表达式
如果我们在Lambda中所指定的功能,已经有其他方法存在相同方案,那是否还有必要再写重复逻辑?可以直接“引用”过去就好了
应用场景
如果Lambda所要实现的方案 ,已经有其他方法存在相同方案,那么则可以使用方法引用
语法定义
- instanceName::methodName 对象::方法名
- ClassName::staticMethodName 类名::静态方法
- ClassName::methodName 类名::普通方法
- ClassName::new 类名::new 调用的构造器
- TypeName[]::new String[]::new 调用数组的构造器
第一种语法
对象::方法名
@Test
public void test01() {
Date now = new Date();
Supplier<Long> supp = () -> {
return now.getTime();
};
System.out.println(supp.get());
Supplier<Long> supp2 = now::getTime;
System.out.println(supp2.get());
}
第二种语法
类名::静态方法
@Test
public void test02() {
Supplier<Long> supp = () -> {
return System.currentTimeMillis();
};
System.out.println(supp.get());
Supplier<Long> supp2 = System::currentTimeMillis;
System.out.println(supp2.get());
}
第三种语法
类名::引用实例方法
Java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是拿第一个参数作为方法的调用者
@Test
public void test03() {
Function<String, Integer> f1 = (s) -> {
return s.length();
};
System.out.println(f1.apply("abc"));
Function<String, Integer> f2 = String::length;
System.out.println(f2.apply("abc"));
BiFunction<String, Integer, String> bif = String::substring;
String hello = bif.apply("hello", 2);
System.out.println("hello = " + hello);
}
第四种语法
类名::new引用构造器
@Test
public void test04() {
Supplier<Person> sup = () -> {
return new Person();
};
System.out.println(sup.get());
Supplier<Person> sup2 = Person::new;
System.out.println(sup2.get());
BiFunction<String, Integer, Person> fun2 = Person::new;
System.out.println(fun2.apply("张三", 18));
}
第五种语法
数组::new 引用数组构造器
@Test
public void test05() {
Function<Integer, String[]> fun = (len) -> {
return new String[len];
};
String[] arr1 = fun.apply(10);
System.out.println(arr1 + ", " + arr1.length);
Function<Integer, String[]> fun2 = String[]::new;
String[] arr2 = fun.apply(5);
System.out.println(arr2 + ", " + arr2.length);
}