Java Lambda表达式
概念
函数式接口是指:有且仅有一个抽象方法的接口。
函数式接口即适用于函数式编程场景的接口。而Java中函数式编程体现的就是Lambda,所以Lambda接口就是适用于Lambda使用的接口。有且只有一个抽象方法,Java中的Lambda才能顺利推导。
格式
只要确保接口的有且仅有一个抽象方法即可:
修饰符 interface 接口{
public abstract 返回值类型 方法名称(可选参数信息);
//其他非抽象方法
}
接口中的public abstract可以省略,所以格式可以简化:
修饰符 interface 接口{
返回值类型 方法名称(可选参数信息);
//其他非抽象方法
}
函数式接口可以作为方法的参数和返回值类型
@FunctionalInterface注解
@FunctionalInterface
修饰符 interface 接口{
返回值类型 方法名称(可选参数信息);
//其他非抽象方法
}
检测该函数是否是函数式接口,是否有且仅有一个抽象方法。
自定义函数式接口
public class LambdaTest{
public static void useLambdaInterfaceMethod(LambdaInterface lifm){
lifm.LambdaInterfaceMethod();
}
public static void main(String[] args){
//传入接口的实现类:
//useLambdaInterfaceMethod(new 接口的实现类);
//传入接口的匿名内部类
/*
useLambdaInterfaceMethod(new LambdaInterface(){
public void LambdaInterfaceMethod(){
System.out.println("这里传入的是接口的实现匿名内部类");
}
});
*/
//抽象方法有且仅有一行语句,可以省略大括号
//ustLambdaInterfaceMethod(() -> System.out.println("..."));
useLambdaInterfaceMethod(() -> {
System.out.println("使用Lambda表达式,重写接口中的抽象方法。")
}
);
}
}
//注解FunctionanInterface检测是否为函数式接口
@FunctionalInterface
interface LambdaInterface{
void LambdaInterfaceMethod();
}
函数式编程
Lambda的延迟执行
有些场景代码执行以后,结果不一定使用,从而造成浪费。而Lambda表达式是延迟执行的,可以作为解决方案,提升性能。
性能浪费的日志案例
日志可以快速定位问题,记录程序运行中的情况,以便项目的监控和优化。
一种场景是对参数进行有条件的使用,例如在日子消息拼接以后,在满足条件时打印输出。
/*
下面存在性能浪费的问题
调用log方法的时候,传入第二个参数是一个拼接后的字符串
是先把字符串拼接好,然后再调用log方法
log方法中的日志等级不是1级,那么就不会是如此拼接后的字符串
这里的字符串就白拼接了,造成浪费
*/
public class LoggerTest{
public static void log(int level, String msg){
if(level == 1){
System.out.println(msg);
}
}
public static void main(String[] args){
String str1 = "hello";
String str2 = "java";
log(1, str1 + str2);
}
}
体验Lambda的更优写法
/*
使用Lambda优化日志:
延迟加载
使用前提:必须存在函数式接口
使用Lambda表达式作为参数传递,仅仅是把参数传递到log方法中,
只有满足条件,日志的等级是1级
才会调用接口LambdaInterface方法中的msgAdd()方法
然后进行方法的拼接
如果条件不满足,那么接口LambdaInterface方法中的msgAdd()方法不会执行
拼接字符串的代码也不会执行,不会存在性能的浪费
*/
public class LoggerTest{
public static void log(int level, LambdaInterface li){
if(level == 1){
String str = li.msgAdd();
System.out.println(str);
}
}
public static void main(String[] args){
String str1 = "hello";
String str2 = "java";
log(1, () -> {
//返回一个拼接好的字符串
return str1 + str2;
});
}
}
@FunctionalInterface
interface LambdaInterface{
//定义一个拼接消息的方法,返回拼接的消息
String msgAdd();
}
证明Lambda的延迟
public static void main(String[] args){
String str1 = "hello";
String str2 = "java";
log(1, () -> {
//没有满足条件这里不会执行
System.out.println("不满足条件");
//返回一个拼接好的字符串
return str1 + str2;
});
}
使用Lambda作为参数和返回值
Java的Lambda表达式可以被当做匿名内部类的替代品,如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。
例如:Runnable接口就是一个函数式接口
public class Demo{
private static void startThread(Runnable runab){
new Thread(runab).start();
}
public static void main(String[] args){
startThread(() -> System.out.println(...));
}
}
public class DemoComparator{
//返回值类型是一个函数式接口
//Comparator只有一个抽象方法,所以是函数式接口
private static Comparator<String> newComparator(){
return (a, b) -> b.length() - a.length();
}
public static void main(String[] args){
String[] arr = {"a", "abs", "dfae"};
//排序
Arrays.sort(arr, newComparator());
}
}
如果返回值类型是一个函数式接口,就可以直接返回一个Lambda表达式,如上程序。
注:Comparator有两个抽象方法,int cimpare(T o1, T o2)和boolean equals(Object obj),如果接口声明了一个覆盖java.lang.Object的全局方法之一的抽象方法,那么它不会计入接口的抽象方法数量中,因为接口的任何实现都将具有java.lang.Object或其他地方的实现。
常用的函数式接口
Supplier接口
java.util.function.Supplier
import java.util.function.Supplier;
public class SupplierClass{
public static String getString(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args){
String str1 = "hello";
String str2 = "java";
String getStringMethod = getString(() -> str1 + str2);
System.out.println(getStringMethod);
}
}
Supplier
求数组最大值练习
import java.util.function.Supplier;
public class SupplierClass{
//返回的是Integer类型
public static Integer getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args){
int[] iArr = {12, 252, -12, 435};
int getMaxInteger = getMax(() -> {
int max = iArr[0];
//遍历数组
for(int i : iArr){
//如果数组有一个值大于max,则把这个值赋值给max
if(i > max){
max = i;
}
}
//返回数组中的最大值
return max;
});
System.out.println(getMaxInteger);
}
}
Consumer接口
java.util.function.Consumer
import java.util.function.Consumer;
public class ConsumerClass{
public static void consumerUseMethod(Consumer<String> com){
con.accept("被使用的字符串");
}
public static void main(String[] args){
consumerUseMethod((x) -> System.out.println(x));
}
}
抽象方法:Accept
import java.util.function.Consumer;
public class ConsumerClass{
public static void consumerUseMethod(String name,Consumer<String> con){
con.accept(name);
}
public static void main(String[] args){
consumerUseMethod("lilei", (x) -> System.out.println(x));
}
}
抽象默认方法:andThen
如果一个方法的参数和返回值全是Consumer类型,就可以实现这个效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer接口中default方法antThen:
default Consumer<T> andThen(Consumer<? super T> after){
Objects.requireNonNull(after);
return (T t) -> {
accept(t);
after.accept(t);
};
}
java.util.Objects和requireNonNull静态方法将会在参数为null时主动抛出异常。省去重写if语句和抛出空指针异常的麻烦。
import java.util.function.Consumer;
/*
Consumer接口默认方法andThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,对数据进行消费
例如:
Consumer<String> con1
Consumer<String> con2
String s = “hello”;
con1.accept(s);
con2.accept(s);
连接两个Consumer接口,再进行消费
con1.andThen(con2).accept(s);
*/
public class ConsumerClass{
public static void consumerUseMethod(String name,
Consumer<String> con, Consumer<String> con2){
//con.accept(name);
//con2.accept(name);
//使用andThen方法替代上面代码:
con.andThen(con2).accept(name);
//con链接的con2,所以先执行con,再执行con2
}
public static void main(String[] args){
consumerUseMethod(
"lilei",
//第一个Lambda表达式
(x) -> System.out.println(x),
//第二个Lambda表达式
(x) -> System.out.println("第二个Lambda表达式")
);
}
}
如果需要多个Consumer接口,使用andThen方法,就是con1.andThen(con2).andThen(con3)...andThen(conN).accept(t);
Predicate接口
包含一个抽象方法:boolean test(T t),用于条件判断场景
符合条件:返回true
不符合:返回false
import java.util.function.Predicate;
public class PredicateClass{
public static boolean checkString(String s, Predicate<String> pre){
//传入要判断的文本
return pre.test(s);
}
public static void main(String[] args){
String str = "guess how long";
//判断字符串长度是否大于5,并返回结果
boolean b = checkString(str, (String s) -> {return str.length() > 5;});
System.out.println(b);
}
}
省略一些代码
//省略String s 中类型,可以直接推导
//只有一行语句,省略大括号和return关键字
boolean b = checkString(str, s-> str.length() > 5);
默认方法and(与)
将两个Predicate条件使用与逻辑连接起来实现并且的效果,使用默认方法and
default Predicate<T> and(Predicate<? super T> other){
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
实现:
import java.util.function.Predicate;
public class PredicateClass{
public static void checkString(String str, Predicate<String> preOne, Predicate<String> preTwo){
//preOne.test("str")&&preTwo.test("str");
boolean b = preOne.and(preTwo).test(str);
System.out.println(b);
}
public static void main(String[] args){
String strName = "helloworld";
checkString(strName, s-> s.contains("H"),s-> s.contains("W"));
}
}
默认方法or(或)
default Predicate<T> or(Predicate<? super T> other){
Objects.requireNonNull(Other);
return (t) -> test(t) || other.test(t);
}
实现:
import java.util.function.Predicate;
public class PredicateClass{
public static boolean checkString(String str, Predicate<String> preOne, Predicate<String> preTwo){
//等价于preOne.test(t) || preTwo.test(t);
return preOne.or(preTwo).test(str);
}
public static void main(String[] args){
String strName = "helloworld";
boolean b = checkString(strName, s-> s.contains("h"),s-> s.contains("W"));
System.out.println(b);
}
}
默认方法negate(非)
default Predicate<T> negate(){
retuan (t) -> !test(t);
}
//取反
return preOne.negate(preTwo).test(str);
Function接口
用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
抽象方法apply
Function最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
import java.util.function.Function;
public class FunctionClass{
//Function<原始类型, 要转换的类型>
public static void applyMethod(String str, Function<String, Integer> fun){
//字符串类型转换成int类型
int num = fun.apply(str);
System.out.println(num + 20);
}
public static void main(String[] args){
String str = "100";
applyMethod(str, s -> Integer.parseInt(s));
}
}
默认方法andThen
//用来组合操作
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after){
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
实现代码:
import java.util.function.Function;
public class FunctionClass{
//andThen组合操作
public static void applyMethod(String str, Function<String, Integer> funOne, Function<Integer, String> funTwo){
//字符串类型转换成int类型,加10
//int num = fun.apply(str) + 10;
//然后转换成String类型
//String str = funTwo.apply(num);
//组合,先实现funOne,再实现funTwo
String strr = funOne.andThen(funTwo).apply(str);
System.out.println(strr);
}
public static void main(String[] args){
String str = "100";
//先把字符串转换为integer,结果加10
//把前面funOne的结果转换成String类型
applyMethod(str, (String s) -> Integer.parseInt(s) + 10, (Integer in) -> String.valueOf(in));
}
}
Lambda方法引用
方法引用符号
双冒号::是为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达是函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
语义分析
例如:System.out对象中有一个重载的println(String)方法恰好就是我们所需要的,那么对于该方法的函数式接口参数,以下两种方式等价:
Lambda表达式: s -> System.out.println(s);
方法引用写法: System.out::println
第一种语义是:拿到参数之后经Lambda手,继而传递给System.out.println方法去处理;
第二种是:直接让System.out中的println方法来取代Lambda。
两种写法的执行效果完全一样。而第二种方法引用复用了已有方案,更加简洁。
Lambda中传递的参数一定是方法引用中那个方法可以接收的类型,否则抛出异常。
推导与省略
如果使用Lambda,那么根据可推导就是可省略的原则,无需指定参数类型,也无需指定重载形式--它们都是自动推导,而如果使用方法引用,也是同样可以根据上下文进行推导。
函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。
通过对象名引用成员方法
/*
通过对象名引用成员方法
使用前提:对象名已经存在,成员方法已经存在
*/
public class LambdaObjectUseMethod{
//定义一个方法
public static void printString(Printable p){
p.print("hello");
}
public static void main(String[] args){
//Lambda表达式
printString(x -> {
MethodUpper mu = new MethodUpper();
mu.printUpperCaseString(x);
});
//方法引用
//对象必须已经存在
MethodUpper mu2 = new MethodUpper();
printString(mu2::printUpperCaseString);
}
}
//函数式接口
@FunctionalInterface
interface Printable{
void print(String s);
}
//将字符串全改为大写并输出
class MethodUpper{
public void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
通过类名称引用静态方法
/*
通过对象名引用成员方法
使用前提:类已经存在,静态方法已经存在
*/
public class LambdaObjectUseMethod{
//定义一个方法
public static void printString(Printable p){
p.print("hello");
}
public static void main(String[] args){
//通过类名引用静态方法
printString(MethodUpper::printUpperCaseString);
}
}
//函数式接口
@FunctionalInterface
interface Printable{
void print(String s);
}
//将字符串全改为大写并输出
class MethodUpper{
//静态方法
public static void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
通过super引用成员方法
//函数式接口
@FunctionalInterface
interface Greetable{
void greet();
}
//父类
class FatherClass{
public void showMethod(){
System.out.println("我是你爸爸");
}
}
public class SonClass extends FatherClass{
public void showMethod(){
System.out.println("不符solo!!!");
}
public void useGreetable(Greetable gt){
gt.greet();
}
public void show(){
/*
useGreetable(() -> {
FatherClass fc = new SonClass();
super.showMethod();
});
*/
//super引用成员方法
useGreetable(super::showMethod);
}
public static void main(String[] args){
new SonClass().show();
}
}
通过this引用成员方法
//函数式接口
@FunctionalInterface
interface Greetable{
void greet();
}
//父类
class FatherClass{
public void showMethod(){
System.out.println("我是你爸爸");
}
}
public class SonClass extends FatherClass{
public void showMethod(){
System.out.println("不符solo!!!");
}
public void useGreetable(Greetable gt){
gt.greet();
}
public void show(){
//this引用本类成员方法
useGreetable(this::showMethod);
}
public static void main(String[] args){
new SonClass().show();
}
}
类构造器的引用
构造器引用使用 类名称::new 的格式
//类构造器引用
class Person{
String name;
public Person(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
@FunctionalInterface
interface CreatePerson{
Person returnPerson(String name);
}
public class PersonFunctionClass{
public static void method(String name, CreatePerson cp){
Person p = cp.returnPerson(name);
//Person p =
System.out.println(p.getName());
}
public static void main(String[] args){
/*Lambda表达式
method("lilei",(String name) -> {
return new Person(name);
});
*/
/*简化版的Lambda表达式
method("lilei", name -> new Person(name));
*/
//构造器引用
method("lilei", Person::new);
}
}
数组构造器的引用
//数组的构造器引用
@FunctionalInterface
interface ArrayBuiler{
//创建int数组类型的方法
int[] createIntArray(int length);
}
//数组的构造器引用
public class ArrayTestClass{
//定义一个方法,方法参数传递创建数组的长度和ArrayBuiler接口
//方法内部根据传递的长度创建数组
public static int[] createArray(int length, ArrayBuiler ab){
return ab.createIntArray(length);
}
public static void main(String[] args){
int[] arr1 = createArray(10, (int i) -> {
return new int[i];
});
int[] arr2 = createArray(12, len -> new int[len]);
//引用数组的构造器
int[] arr3 = createArray(13, int[]::new);
}
}