该文章Github地址:https://github.com/AntonyCheng/java-notes【有条件的情况下推荐直接访问GitHub以获取最新的代码更新】
在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template【有条件的情况下推荐直接访问GitHub以获取最新的代码更新】& CSDN文章地址:https://blog.csdn.net/AntonyCheng/article/details/136555245),该模板集成了最常见的开发组件,同时基于修改配置文件实现组件的装载,除了这些,模板中还有非常丰富的整合示例,同时单体架构也非常适合SpringBoot框架入门,如果觉得有意义或者有帮助,欢迎Star & Issues & PR!
上一章:由浅到深认识Java语言(44):Junit单元测试
50.Lambda表达式
函数式编程思想
引例如下:
我们创建一个线程,来打印 hello lambda;
普通做法:
package top.sharehome.Project;
import org.junit.Test;
public class Demo {
@Test
public void Test() {
Runnable tt = new ThreadTest();
Thread t = new Thread(tt);
t.start();
}
}
class ThreadTest implements Runnable {
@Override
public void run() {
System.out.println("hello lambda");
}
}
函数式编程做法:
package top.sharehome.Project;
import org.junit.Test;
public class Demo {
@Test
public void testStart() {
new Thread(() -> {
System.out.println("hello lambda");
}).start();
}
}
打印效果如下:
即:我们只需要关注具体要做的事情,不用思考具体是由谁(哪个对象)来做,这样就能极大的简化我们的代码,但是这样做是由前提的,那就是保证一个接口中只有一个抽象方法即可,比如这里调用的 Thread() 对象所实现的 Runnable 接口就只有一个抽象方法 run();
函数式接口及其分布
lambda表达式其实就是实现SAM接口的语法糖,所谓SAM接口就是Single Abstract Method,即该接口中只有一个抽象方法需要实现,当然该接口可以包含其他非抽象方法;
其实只要满足“SAM”特征的接口都可以称为函数式接口,都可以使用Lambda表达式,但是如果要更明确一点,最好在声明接口时,加上@FunctionalInterface。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错;
之前学过的SAM接口中,标记了@FunctionalInterface的函数式接口的有:Runnable,Comparator,FileFilter;
Java8在java.util.function新增了很多函数式接口:主要分为四大类,消费型、供给型、判断型、功能型。基本可以满足我们的开发需求。当然你也可以定义自己的函数式接口;
自定义函数式接口
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
接口当中抽象方法的 public abstract 是可以省略的;
示例如下:
package top.sharehome.Project;
public class Demo {
public static void main(String[] args) {
CalcTest(1,2,(int a,int b)->{
return a+b;
});
}
public static void CalcTest(int a,int b,Calc calc){
System.out.println("计算结果为 "+calc.calc(a,b));
}
}
//这就是自定义的一个函数接口
interface Calc {
int calc(int a, int b);
}
打印效果如下:
消费型接口Consumer
消费型接口的抽象方法特点:有形参,但是返回值类型是void;
接口名 | 抽象方法 | 描述 |
---|---|---|
Consumer | void accept(T t) | 接收一个对象用于完成功能 |
BiConsumer<T,U> | void accept(T t, U u) | 接收两个对象用于完成功能 |
DoubleConsumer | void accept(double value) | 接收一个double值 |
IntConsumer | void accept(int value) | 接收一个int值 |
LongConsumer | void accept(long value) | 接收一个long值 |
ObjDoubleConsumer | void accept(T t, double value) | 接收一个对象和一个double值 |
ObjIntConsumer | void accept(T t, int value) | 接收一个对象和一个int值 |
ObjLongConsumer | void accept(T t, long value) | 接收一个对象和一个long值 |
供给型接口Supplier
这类接口的抽象方法特点:无参,但是有返回值;
接口名 | 抽象方法 | 描述 |
---|---|---|
Supplier | T get() | 返回一个对象 |
BooleanSupplier | boolean getAsBoolean() | 返回一个boolean值 |
DoubleSupplier | double getAsDouble() | 返回一个double值 |
IntSupplier | int getAsInt() | 返回一个int值 |
LongSupplier | long getAsLong() | 返回一个long值 |
断言型接口Funtion
这里接口的抽象方法特点:有参,但是返回值类型是boolean结果;
接口名 | 抽象方法 | 描述 |
---|---|---|
Predicate | boolean test(T t) | 接收一个对象 |
BiPredicate<T,U> | boolean test(T t, U u) | 接收两个对象 |
DoublePredicate | boolean test(double value) | 接收一个double值 |
IntPredicate | boolean test(int value) | 接收一个int值 |
LongPredicate | boolean test(long value) | 接收一个long值 |
功能型接口Predicate
这类接口的抽象方法特点:既有参数又有返回值;
接口名 | 抽象方法 | 描述 |
---|---|---|
Function<T,R> | R apply(T t) | 接收一个T类型对象,返回一个R类型对象结果 |
UnaryOperator | T apply(T t) | 接收一个T类型对象,返回一个T类型对象结果 |
DoubleFunction | R apply(double value) | 接收一个double值,返回一个R类型对象 |
IntFunction | R apply(int value) | 接收一个int值,返回一个R类型对象 |
LongFunction | R apply(long value) | 接收一个long值,返回一个R类型对象 |
ToDoubleFunction | double applyAsDouble(T value) | 接收一个T类型对象,返回一个double |
ToIntFunction | int applyAsInt(T value) | 接收一个T类型对象,返回一个int |
ToLongFunction | long applyAsLong(T value) | 接收一个T类型对象,返回一个long |
DoubleToIntFunction | int applyAsInt(double value) | 接收一个double值,返回一个int结果 |
DoubleToLongFunction | long applyAsLong(double value) | 接收一个double值,返回一个long结果 |
IntToDoubleFunction | double applyAsDouble(int value) | 接收一个int值,返回一个double结果 |
IntToLongFunction | long applyAsLong(int value) | 接收一个int值,返回一个long结果 |
LongToDoubleFunction | double applyAsDouble(long value) | 接收一个long值,返回一个double结果 |
LongToIntFunction | int applyAsInt(long value) | 接收一个long值,返回一个int结果 |
DoubleUnaryOperator | double applyAsDouble(double operand) | 接收一个double值,返回一个double |
IntUnaryOperator | int applyAsInt(int operand) | 接收一个int值,返回一个int结果 |
LongUnaryOperator | long applyAsLong(long operand) | 接收一个long值,返回一个long结果 |
BiFunction<T,U,R> | R apply(T t, U u) | 接收一个T类型和一个U类型对象,返回一个R类型对象结果 |
BinaryOperator | T apply(T t, T u) | 接收两个T类型对象,返回一个T类型对象结果 |
ToDoubleBiFunction<T,U> | double applyAsDouble(T t, U u) | 接收一个T类型和一个U类型对象,返回一个double |
ToIntBiFunction<T,U> | int applyAsInt(T t, U u) | 接收一个T类型和一个U类型对象,返回一个int |
ToLongBiFunction<T,U> | long applyAsLong(T t, U u) | 接收一个T类型和一个U类型对象,返回一个long |
DoubleBinaryOperator | double applyAsDouble(double left, double right) | 接收两个double值,返回一个double结果 |
IntBinaryOperator | int applyAsInt(int left, int right) | 接收两个int值,返回一个int结果 |
LongBinaryOperator | long applyAsLong(long left, long right) | 接收两个long值,返回一个long结果 |
Lambda表达式语法
Lambda表达式是用来给【函数式接口】的变量或形参赋值用的。
其实本质上,Lambda表达式是用于实现【函数式接口】的“抽象方法”
Lambda表达式语法格式
(形参列表) -> {Lambda体}
说明:
- (形参列表)它就是你要赋值的函数式接口的抽象方法的(形参列表),照抄
- {Lambda体}就是实现这个抽象方法的方法体
- ->称为Lambda操作符(减号和大于号中间不能有空格,而且必须是英文状态下半角输入方式)
优化:Lambda表达式可以精简
- 当{Lambda体}中只有一句语句时,可以省略{}和{;}
- 当{Lambda体}中只有一句语句时,并且这个语句还是一个return语句,那么return也可以省略,但是如果{;}没有省略的话,return是不能省略的
- (形参列表)的类型可以省略
- 当(形参列表)的形参个数只有一个,那么可以把数据类型和()一起省略,但是形参名不能省略
- 当(形参列表)是空参时,()不能省略
示例代码:
public class TestLambdaGrammer {
@Test
public void test1(){
//用Lambda表达式给Runnable接口的形参或变量赋值
/*
* 确定两件事,才能写好lambda表达式
* (1)这个接口的抽象方法是否需要传入参数
* public void run()
* (2)这个抽象方法的实现要干什么事
* 例如:我要打印“hello lambda"
*/
Runnable r = () -> {System.out.println("hello lambda");};
}
@Test
public void test2(){
//lambda体省略了{;}
Runnable r = () -> System.out.println("hello lambda");
}
@Test
public void test3(){
String[] arr = {"hello","Hello","java","chai"};
//为arr数组排序,但是,想要不区分大小写
/*
* public static <T> void sort(T[] a,Comparator<? super T> c)
* 这里要用Lambda表达式为Comparator类型的形参赋值
*
* 两件事:
* (1)这个接口的抽象方法: int compare(T o1, T o2)
* (2)这个抽象方法要做什么事?
* 例如:这里要对String类型的元素,不区分大小写的比较大小
*/
// Arrays.sort(arr, (String o1, String o2) -> {return o1.compareToIgnoreCase(o2);});
//省略了{return ;}
// Arrays.sort(arr, (String o1, String o2) -> o1.compareToIgnoreCase(o2));
//省略了两个String
Arrays.sort(arr, (o1, o2) -> o1.compareToIgnoreCase(o2));
for (String string : arr) {
System.out.println(string);
}
}
@Test
public void test4(){
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("world");
/*
* JDK1.8给Collection系列的集合,准确的讲是在Iterable接口中,增加了一个默认方法
* default void forEach(Consumer<? super T> action)
* 这个方法是用来遍历集合等的。代替原来的foreach循环的。
*
* 这个方法的形参是Consumer接口类型,它是函数式接口中消费型接口的代表
* 我现在调用这个方法,想要用Lambda表达式为Consumer接口类型形参赋值
*
* 两件事:
* (1)它的抽象方法: void accept(T t)
* (2)抽象方法的实现要完成的事是什么
* 例如:这里要打印这个t
*/
// list.forEach((String t) -> {System.out.println(t);});
//省略{;}
// list.forEach((String t) -> System.out.println(t));
//省略String
// list.forEach((t) -> System.out.println(t));
//可以省略形参()
list.forEach(t -> System.out.println(t));
}
}