lambda表达式
Lambda表达式的基本语法::(parameters) -> expression 或 (parameters) ->{ statements; }
- () -> 代表了 lambda的一个表达式
- 单行代码无需写return (无论函数式接口有没有返回值),花括号
- 多行代码必须写花括号,有返回值的一定要写返回值
- 单行代码且有参数的情况下可以不写 () 如 s->System.out.println(s)
- (T t)中的参数类型可写可不写
例子
// 1. 不需要参数,返回值为 5 () -> 5 // 2. 接收一个参数(数字类型),返回其2倍的值 x -> 2 * x // 3. 接受2个参数(数字),并返回他们的差值 (x, y) -> x – y // 4. 接收2个int型整数,返回他们的和 (int x, int y) -> x + y // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void) (String s) -> System.out.print(s)
主要使用场景
1. 简化匿名类的编码
2. 减少不必要的方法创建
3. 事件处理
4. stream中使用
函数式编程
函数式接口
1 .概念
函数式接口在java中是指:有且仅有一个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口。而java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
备注:“语法糖"是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。
2. 格式
接口中只能存在一个抽象方法
@FunctionalInterface interface Callback { void callback(); }
3.@FunctionalInterface注解
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。(该接口是一个标记接口)
- 被@FunctionalInterface注释的接口,满足函数式接口的约束。
- 没有被@FunctionalInterface注释的接口,但是满足函数式接口的约束。
函数式的约束:
- 接口有且只能有个一个抽象方法,只有方法定义,没有方法体 。
- 在接口中覆写Object类中的public方法,不算是函数式接口的方法。
- 在接口中的default方法,不算是函数式接口的方法。
- 在接口中的static方法,不算是函数式接口的方法。
常用函数式接口
Consumer<T>:消费型接口
代表了接受一个输入参数并且无返回的操作
Consumer<String> testConsumer = param -> System.out.println(param);
testConsumer.accept("testConsumer");
Consumer默认提供了andThen
作用:用于连接两个Consumer接口,一个是调用andThen方法的Consumer接口this,一个是andThen方法的参数after。
con1.andThen(con2).accept(s);
等价于con1.accept(s);con2.accept(s);
Supplier<T>:供给型接口
无参数,返回一个结果。
Supplier<String> testSupplier = () -> String.valueOf(Math.random());
System.out.println(testSupplier.get());
Function<T, R>:函数型接口
接受一个输入参数,返回一个结果
Function<Integer, Integer> testFunction = s -> s * 2;
System.out.println(testFunction.apply(6));
默认提供的一些方法
Predicate<T>:断言型接口
接受一个输入参数,返回一个布尔值结果。
Predicate<String> testPredicate = s -> s.equals("test");
System.out.println(testPredicate.test("test"));
默认提供的一些方法
注意:
- and方法与逻辑运算符&&功能相同
- or方法与逻辑运算符||功能相同
- negate方法与逻辑运算符!功能相同
每种接口都有一些默认的方法,可以根据业务需求组合出更多的效果
扩展函数接口
参数个数上扩展:
比如接收双参数的,有 Bi 前缀, 比如 BiConsumer<T,U>, BiFunction<T,U,R> ;
特殊常用的变形:
比如 BinaryOperator , 是同类型的双参数 BiFunction<T,T,T> ,二元操作符 ; UnaryOperator 是 Function<T,T> 一元操作符。
类型上扩展:
比如接收原子类型参数的,比如 [Int|Double|Long] [Function|Consumer|Supplier|Predicate]
为什么要有基本类型扩展
只有对象类型才能作为泛型参数,对于基本类型就涉及到装箱拆箱的操作,虽然是自动的
但是这不可避免给内存带来了额外的开销,装箱和拆箱都会带来开销
所以为了减小这些性能开销 对基本类型进行类型扩展
Stream 类的某些方法对基本类型和装箱类型做了区分
Java 8中,仅对 整型、长整型和双浮点型做了特殊处理 因为它们在数值计算中用得最多
对基本类型做特殊处理的方法在命名上有明确的规范
- 如果参数是基本类型,则不加前缀只需类型名即可
- 如果方法返回类型为基本类型,则在基本类型前再加上一个 To
总结一句话:加了类型前缀[Int|Double|Long] 表示参数是基本类型, 如果在此基础上又加上了To 表示返回类型是基本类型
如有可能,应尽可能多地使用对基本类型做过特殊处理的方法,进而改善性能
其它
方法引用
方法引用可以在某些条件成立的情况下,更加简化lambda表达式的声明。方法引用语法格式有以下三种:
- objectName::instanceMethod
- ClassName::staticMethod
- ClassName::instanceMethod
前两种方式类似,等同于把 lambda 表达式的参数直接当成 instanceMethod/staticMethod 的参数来调用。比如 System.out::println 等同于 x->System.out.println(x);Math::max 等同于 (x, y)->Math.max(x,y) 。
最后一种方式,等同于把lambda表达式的第一个参数当成 instanceMethod 的目标对象,其他剩余参数当成该方法的参数。比如 String::toLowerCase 等同于 x -> x.toLowerCase()。
//Funciton Lambda表达式: (Apple a) -> a.getWeight() 等价的方法引用: Apple::getWeight //Conusmer Lambda表达式: () -> Thread.currentThread().dumpStack() 等价的方法引用: Thread.currentThread()::dumpStack //BiFunction Lambda表达式: (str, i) -> str.substring(i) 等价的方法引用: String::substring //Function Lambda表达式: (String s) -> System.out.println(s) 等价的方法引用: System.out::printl
只有当lambda表达式的体只调用一个方法而不做其他操作时,才可以把lambda表达式重写为方法引用。如以下表达式:
s -> s.length == 0
这里有一个方法调用,但是还有一个比较,因而这里不能使用方法引用。
构造器引用
构造器引用语法如下:ClassName::new,把lambda表达式的参数当成ClassName构造器的参数 。例如BigDecimal::new等同于x->new BigDecimal(x)。3
//无参构造函数 Supplier<Apple> c1 = () -> new Apple(); Supplier<Apple> c1 = Apple::new; //一元构造函数 Function<Integer, Apple> c2 = (weight) -> new Apple(weight); Function<Integer, Apple> c2 = Apple::new; //二元构造函数 BiFunction<Integer, String, Apple> c3 =(weight, color) -> new Apple(weight, color); BiFunction<Integer, String, Apple> c3 = Apple::new;
Lambda表达式和匿名内部类的区别
一、所需类型不同:
- 匿名内部类可以是:接口、抽象类、或者具体类
- Lambda 表达式只能是:接口
二、使用限制不同:
- 匿名内部类:接口中可以多个或者一个方法
- Lambda 表达式要求:接口中的只能有一个方法
三、实现原理不同:
查看项目文件夹中的时候可以看到
- 匿名内部类:编译之后会产生单独的 .class 文件
- Lambda 表达式:编译之后不会有单独的 .class 文件出现,对应的字节码会在运行的时候动态生成。
其它
在lamada表达式中使用循环的时候,不能使用break和continue,使用return(相当于同样的功能break);
public static void main(String[] args) throws Exception { List<String> ss = new ArrayList<>(); ss.add("aa"); ss.add("bb"); ss.add("cc"); ss.forEach(c -> { System.out.println(c); return; }); System.out.println("执行完成"); }
执行结果
转载
https://blog.csdn.net/wanghao112956/article/details/91865095
https://www.cnblogs.com/dgwblog/p/11739500.html
https://cloud.tencent.com/developer/article/1333532
https://www.cnblogs.com/hellovan/p/13528201.html