零、Lambda表达式基础语法
Java8引入了一个新的操作符 “->”,该操作符称为箭头操作符或Lambda操作符
左侧:Lambda表达式的参数列表
右侧:Lambda表达式中所需执行的功能,即Lambda体
语法格式一: 无参数,无返回值
()-> System.out.println("Hello Lambda")
语法格式二:有1个参数,无返回值类型
(x) -> System.out.println(x)
语法格式三:若只有一个参数,小括号可以省略不写
x -> System.out.println(x)
语法格式四:有两个以上的参数,有返回值,并且Lambda体中有多条语句
Comparator<Integer> com = (x,y) -> {
System.out.println("函数式接口");
return Integer.compare(x,y);
};
语法格式五:若Lambda体中只有一条语句,return和大括号可以省略不写
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
语法格式六:Lambda 表达式的参数列表数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
(Integer x, Integer y) -> Integer.compare(x,y)
左右遇一括号省 (左边省略小括号,右边省略大括号)
左侧推断类型省
能省则省
一、函数式接口:
1.1概念:
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
1.2格式:
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
由于接口当中抽象方法的public abstract 是可以省略的,所以定义一个函数式接口很简单:
public interface MyFunctionalInterface {
void myMethod();
}
//不是一个函数式接口,因为有两个抽象方法
public interface MyFunctionalInterface {
void myMethod();
public abstract void myMethod2();
}
//不是一个函数式接口,因为没有1个抽象方法
public interface MyFunctionalInterface {
}
1.3 @FunctionalInterface注解
与@Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解 : @FunctionalInterface 。该注解可用于一个接口的定义上:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
1.4 自定义函数式接口
对于刚刚定义好的MyFunctionalInterface 函数式接口,典型使用场景就是作为方法的参数:
public class Demo09FunctionalInterface {
// 使用自定义的函数式接口作为方法参数
private static void doSomething(MyFunctionalInterface inter) {
inter.myMethod(); // 调用自定义的函数式接口方法
}
public static void main(String[] args) {
// 调用使用函数式接口的方法
doSomething(() ‐> System.out.println("Lambda执行啦!"));
}
}
1、函数式接口作为方法的参数
package com.aspirecn.jdk8.function;
@FunctionalInterface
public interface MyFunctionInterface {
void myMethod();
}
package com.aspirecn.jdk8.function;
public class MyFunctionInterfaceImpl implements MyFunctionInterface {
@Override
public void myMethod() {
System.out.println("实现了抽象类");
}
}
package com.aspirecn.jdk8.function;
/**
* 函数式接口的使用:一般可以作为方法的参数和返回值类型
*/
public class Demo {
//定义一个方法,参数使用函数式接口MyFunctionInterFace
public static void show(MyFunctionInterface myFunctionInterface){
myFunctionInterface.myMethod();
}
public static void main(String[] args) {
//调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
show(new MyFunctionInterface() {
@Override
public void myMethod() {
System.out.println("使用匿名内部类重写接口中的抽象方法");
}
});
//调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
show(new MyFunctionInterfaceImpl());
//调用show方法,方法的参数是一个接口
show(()->{
System.out.println("使用b表达式重写接口中的抽象方法");
});
//简化Lambda表达式
}
}
二、函数式编程:
2.1 Lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
性能浪费的日志案例
注:日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:
public class Demo01Logger {
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
}
这段代码存在问题:无论级别是否满足要求,作为log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
备注:SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进行字符串拼接。例如: LOGGER.debug(“变量{}的取值为{}。”, “os”, “macOS”) ,其中的大括号{} 为占位符。如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字符串拼接。这也是一种可行解决方案,但Lambda可以做到更好。
体验Lambda的更优写法
使用Lambda必然需要一个函数式接口:
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
然后对log 方法进行改造:
public class Demo02LoggerLambda {
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, () ‐> msgA + msgB + msgC );
}
}
这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。
证明Lambda的延迟
下面的代码可以通过结果进行验证:
public class Demo03LoggerDelay {
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(2, () ‐> {
System.out.println("Lambda执行!");
return msgA + msgB + msgC;
});
}
}
从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。
扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的。
2.2 使用Lambda作为参数和返回值
如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。
例如java.lang.Runnable 接口就是一个函数式接口,假设有一个startThread 方法使用该接口作为参数,那么就可以使用Lambda进行传参。这种情况其实和Thread 类的构造方法参数为Runnable 没有本质区别。
package com.aspirecn.jdk8.lambda;
/**
* 例如java.lang.Runnable接口就是一个函数式接口
* 结社有一个startThread的方法使用该接口作为参数,那么就可以使用lambda进行传参
* 这种情况其实和Thread类的构造方法参数为Runnable没有本质区别
*/
public class Demo01Runnable {
public static void startThread(Runnable run) {
//开启多线程
new Thread(run).start();
}
public static void main(String[] args) {
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"启动了");
}
});
//调用startThread方法参数是一个函数式接口,所以可以传递Lambda表达式
//Runnable的run方法没有参数,所以传一个(),大括号代表run方法的方法体
startThread(()->{
System.out.println(Thread.currentThread().getName()+"启动了");
});
//优化Lambda
startThread(()->System.out.println(Thread.currentThread().getName()+"启动了"));
}
}
类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取。
package com.aspirecn.jdk8.lambda;
import java.util.Arrays;
import java.util.Comparator;
/**
* 如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式
* 当需要通过一个方法获取一个java.util.Comparator接口类型的对象作为排序其时,就可以调该方法获取
**/
public class Demo02Comparator {
// 定义一个方法,方法的返回值类型使用函数式接口Comparator
public static Comparator<String> getComparator() {
// 方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
// return new Comparator<String>() {
// @Override
// public int compare(String o1, String o2) {
// return o2.length() - o1.length();
// }
// };
//也可以使用Lambda表达式
/*return (String o1,String o2) ->{
return o1.length() - o2.length();
};*/
//继续优化Lambda表达式
return (o1,o2) -> o1.length()-o2.length();
}
public static void main(String[] args) {
//创建一个字符串数组
String[] stringArr = {"aaa","bbbb","ccccccc","dd"};
System.out.println(Arrays.toString(stringArr));
//调用Arrays中的sort方法,对字符串数组进行排序
Arrays.sort(stringArr,getComparator());
System.out.println(Arrays.toString(stringArr));
}
}
其中直接return一个Lambda表达式即可
三、 常用函数式接口
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function 包中被提供。下面是最简单的几个接口及使用示例。
3.1 Supplier接口
java.util.function.Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
package com.aspirecn.jdk8.supplier;
/**
* @Title: Demo01Supplier
* @Package: com.aspirecn.jdk8.supplier
* @Description:
* @Author: sunkuan
* @Date: 2020/6/28 - 14:40
*/
import java.util.function.Supplier;
/**
* 常用的函数时接口
* java.util.function.Supplier<T>接口仅包含一个无参的方法:T get().用来获取一个泛型参数指定类型的对象数据
*
* Supplier<T> 接口被称之为生产型接口,指定接口的泛型是什么类型,接口中的get方法就生产什么类型的数据
*/
public class Demo01Supplier {
//定义一个方法,方法的参数传递Supplier<接口>,泛型执行String,get()方法就会返回一个String
public static String getString(Supplier<String> supplier) {
return supplier.get();
}
public static void main(String[] args) {
//调用getString()方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
String s = getString(()->{
return "hupu";
});
System.out.println(s);
//简化 可以省略大括号和return
System.out.println(getString(()-> "hupu"));
}
}
3.2 练习:求数组元素最大值
使用Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用java.lang.Integer 类。
解答
package com.aspirecn.jdk8.supplier;
import java.util.function.Supplier;
/**
* @Title: Demo02GetArrayMax
* @Package: com.aspirecn.jdk8.supplier
* @Description:
* @Author: sunkuan
* @Date: 2020/6/28 - 15:36
*/
public class Demo02GetArrayMax {
public static int getMax(Supplier<Integer> supplier) {
return supplier.get();
}
public static void main(String[] args) {
int[] arr = {10,76,43,99,33,-30};
//调用getMax方法,方法的参数Supplier是一个函数时接口,所以可以传递Lambda表达式
int maxValue = getMax(() -> {
int max = arr[0];
for (int i : arr) {
if (i>=max){
max = i;
}
}
return max;
});
System.out.println(maxValue);
}
}
3.3 Consumer接口
java.util.function.Consumer 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
抽象方法:accept
Consumer 接口中包含抽象方法void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:
package com.aspirecn.jdk8.consumer;
/**
* @Title: ConsumerDemo
* @Package: com.aspirecn.jdk8.consumer
* @Description:
* @Author: sunkuan
* @Date: 2020/6/28 - 15:51
*/
import java.util.function.Consumer;
/**
* 消费性接口,泛型指定什么类型,就可以使用accept方法消费什么类型的数据
*/
public class ConsumerAcceptDemo {
/*
定义一个方法
方法的参数传递一个字符串的姓名
方法的参数传递Consumer接口,泛型使用String
可以使用Consumer接口雄安飞字符串的姓名
*/
public static void method(String name, Consumer<String> consumer){
consumer.accept(name);
}
public static void main(String[] args) {
//调用method方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以出啊降低lambda表达式
method("赵丽颖",(name) -> {
System.out.println(name);
String newName = new StringBuffer(name).reverse().toString();
System.out.println(newName);
});
}
}
默认方法:andThen
如果一个方法的参数和返回值全都是Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer 接口中的default方法andThen 。下面是JDK的源代码:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t); };
}
备注: java.util.Objects 的requireNonNull 静态方法将会在参数为null时主动抛出
NullPointerException 异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个Lambda表达式即可,而andThen 的语义正是“一步接一步”操作。例如两个步骤组
合的情况:
package com.aspirecn.jdk8.consumer;
/**
* @Title: AndThenDemo
* @Package: com.aspirecn.jdk8.consumer
* @Description:
* @Author: sunkuan
* @Date: 2020/6/28 - 16:03
*/
import java.util.function.Consumer;
/**
* Consumer接口的默认方法andThen
* 作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费
* <p>
* 例如:
* Consumer<String> con1
* Consumer<String> con2
* String s = "hello"
* con1.accept(s)
* con2.accept(s)
* <p>
* 连接两个Consumer接口,再进行消费
* con1.andThen(con2).accept(s) 谁写前边谁先消费
*/
public class AndThenDemo {
//定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串
public static void method(String s, Consumer<String> consumer1, Consumer<String> consumer2) {
// consumer1.accept(s);
// consumer2.accept(s);
// con2连接con1,先执行con2消费数据,再执行con1消费数据
consumer2.andThen(consumer1).accept(s);
}
public static void main(String[] args) {
method("Hello",
(String string) -> {
//消费方式:把字符串转换为大写输出
String string1 = string.toUpperCase();
System.out.println(string1);
},
(String string) -> {
//消费方法:把字符串装华为小写输出
String string2 = string.toLowerCase();
System.out.println(string2);
}
);
//简化
method("Hbase",
string ->
//消费方式:把字符串转换为大写输出
System.out.println(string.toUpperCase())
,
string ->
//消费方法:把字符串装华为小写输出
System.out.println(string.toLowerCase())
);
}
}
3.4 练习:格式化打印信息
题目
下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。”的格式将信息打印出来。要求将打印姓名的动作作为第一个Consumer 接口的Lambda实例,将打印性别的动作作为第二个Consumer 接口的Lambda实例,将两个Consumer 接口按照顺序“拼接”到一起。
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
}
解答
package com.aspirecn.jdk8.consumer;
import java.util.function.Consumer;
public class ConsumerDemo {
/**
* 此方法先将整个的逻辑写好
*
* @param consumer1 consumer1
* @param consumer2 consumer2
* @param array 数组
*/
public static void printInfo(Consumer<String> consumer1, Consumer<String> consumer2, String[] array) {
for (String s : array) {
consumer1.andThen(consumer2).accept(s);
}
}
public static void main(String[] args) {
String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
// 调用方法只需要写函数式接口的实现即可,因为整个的逻辑已写好
printInfo((String string) -> {
System.out.println(string.split(",")[0]);
},
(String string) -> {
System.out.println(string.split(",")[1]);
},
array
);
//写Lambda表达式时候主要关注 重写的方法的参数
}
}
3.5 Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用
java.util.function.Predicate 接口。
抽象方法:test
Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景:
package com.aspirecn.jdk8.predicate;
import java.util.function.Predicate;
/**
* java.util.fuction.Predicate<T接口>
* 作用:对某种数据基类型的的数据进行判断,返回一个boolean值
* <p>
* predicate 接口包含一个抽象方法
* boolean test(T t) :用来对指定数据类型数据进行判断的方法
* 结果:符号条件,返回true
* 不符合条件,返回false
*/
public class PredicateTestMethod {
/**
* 定义一个方法
* 参数传递一个String类型的字符串
* 传递一个Predicate接口,泛型使用String
* 使用Predicate的方法test对字符串进行判断,并把判断的结果返回
*/
public static boolean checkString(String arg, Predicate<String> predicate) {
return predicate.test(arg);
}
public static void main(String[] args) {
String s = "aaaaa";
boolean flag = checkString(s, (arg) -> {
if (arg.length() > 2) {
return true;
} else {
return false;
}
});
System.out.println(flag);
//优化
boolean flag2 = checkString(s,arg -> arg.length()>2);
System.out.println(flag2);
}
}
默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法and 。其JDK源码为:
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
如果要判断一个字符串既要包含字母“a”,长度又要大于5,那么:
package com.aspirecn.jdk8.predicate;
import java.util.function.Predicate;
/**
* 逻辑表达式:可以来凝结郭个判断的条件
* &&: 与运算符,有false则false
* ||: 或运算符,有true则true
* !: 取反
*
* 需求:判断一个字符串,有两个判断的条件
* 1、判断字符串的长度是否大于5
* 2、判断字符串中是否包含a
*
* Predicate接口中有一个方法and,表示并且关系,也可以用于连接两个判断条件
* default Predicate<T> and(Predicate<? super T> other) {
* Objects.requireNonNull(other);
* return (t) -> test(t) && other.test(t);
* }
*/
public class PredicateAndMethod {
/**
* 定义一个方法,方法的参数,传递一个字符串
* 传递两个Predicate接口
* 一个用于判断字符串的长度是否大于5
* 另一个用于判断字符串中是否包含a
*/
public static boolean checkString(String s,Predicate<String> predicate1,Predicate<String> predicate2) {
//return predicate1.test(s) && predicate2.test(s);
//等价于return predicate1.test(s) && predicate2.test(s);
return predicate1.and(predicate2).test(s);
}
public static void main(String[] args) {
boolean flag = checkString("abcdef",s -> s.length()>5,s -> !s.contains("a") );
System.out.println(flag);
}
}
默认方法:or
与and 的“与”类似,默认方法or 实现逻辑关系中的“或”。JDK源码为:
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可,其他都不变:
package com.aspirecn.jdk8.predicate;
import java.util.function.Predicate;
/**
* @Title: PredicateOrMethod
* @Package: com.aspirecn.jdk8.predicate
* @Description:
* @Author: sunkuan
* @Date: 2020/6/29 - 11:03
*/
public class PredicateOrMethod {
/**
* 定义一个方法,方法的参数,传递一个字符串
* 传递两个Predicate接口
* 一个用于判断字符串的长度是否大于5
* 另一个用于判断字符串中是否包含a
* 只要有一个满足条件就可以
*/
public static boolean checkString(String s, Predicate<String> predicate1, Predicate<String> predicate2) {
//return predicate1.test(s) || predicate2.test(s);
// 等价于return predicate1.test(s) || predicate2.test(s);
return predicate1.or(predicate2).test(s);
}
public static void main(String[] args) {
boolean flag = checkString("abcdef",s -> s.contains("W"),s -> s.contains("H") );
System.out.println(flag);
}
}
默认方法:negate
“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法negate 的JDK源代码为:
default Predicate<T> negate() {
return (t) -> !test(t);
}
从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在test 方法调用之前调用negate 方法,正如and 和or 方法一样:
package com.aspirecn.jdk8.predicate;
import java.util.function.Predicate;
public class PredicateNegate {
public static boolean checkString(String s, Predicate<String> predicate1) {
//return !predicate1.test(s);
// 等价于return !predicate1.test(s);
return predicate1.negate().test(s);
}
public static void main(String[] args) {
boolean flag = checkString("abcdef",s -> s.length()>5);
System.out.println(flag);
}
}
3.6 练习:集合信息筛选
题目
数组当中有多条“姓名+性别”的信息如下,请通过Predicate 接口的拼装将符合要求的字符串筛选到集合ArrayList 中,需要同时满足两个条件:
- 必须为女生;
- 姓名为4个字。
public class DemoPredicate {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
}
}
解答
package com.aspirecn.jdk8.predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
/**
* 练习:集合信息筛查
* 数组当中有多条"姓名+性别"的信息如下
* String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
*
* 请通过Predicate 接口的拼装将符合要求的字符串筛选到集合ArrayList 中,需要同时满足两个条件:
* 1. 必须为女生;
* 2. 姓名为4个字。
*
* 分析:
* 1.有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断
* 2.必须同事满足两个条件,所以可以使用and方法连接两个判断
*/
public class PredicateDemo {
public static List<String> filter(String[] arr, Predicate<String> predicate1, Predicate<String> predicate2) {
List<String> list = new ArrayList<>();
for (String s : arr) {
boolean result = predicate1.and(predicate2).test(s);
if (result) {
list.add(s);
}
}
return list;
}
public static void main(String[] args) {
String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女"};
List<String> stringList = filter(array, (string) -> {
return "女".equals(string.split(",")[1]);
}, (string) -> {
return string.split(",")[0].length() == 4;
});
for (String s : stringList) {
System.out.println(s);
}
}
}
3.7 Function接口
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
抽象方法:apply
Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。
使用的场景例如:将String 类型转换为Integer 类型。
package com.aspirecn.jdk8.function;
import java.util.function.Function;
/*
java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,
前者称为前置条件,后者称为后置条件。
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
使用的场景例如:将String类型转换为Integer类型。
*/
public class ApplyDemo {
public static void change(String s,Function<String,Integer> function) {
Integer apply = function.apply(s);
System.out.println(apply);
}
public static void main(String[] args) {
String s = "1234";
change(s,str->Integer.parseInt(str) + 9);
}
}
默认方法:andThen
Function 接口中有一个默认的andThen 方法,用来进行组合操作。JDK源代码如:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) ‐> after.apply(apply(t));
}
该方法同样用于“先做什么,再做什么”的场景,和Consumer 中的andThen 差不多:
package com.aspirecn.jdk8.function;
import jdk.nashorn.internal.ir.FunctionCall;
import java.util.function.Function;
/*
Function接口中的默认方法andThen:用来进行组合操作
需求:
把String类型的"123",转换为Inteter类型,把转换后的结果加10
把增加之后的Integer类型的数据,转换为String类型
分析:
转换了两次
第一次是把String类型转换为了Integer类型
所以我们可以使用Function<String,Integer> fun1
Integer i = fun1.apply("123")+10;
第二次是把Integer类型转换为String类型
所以我们可以使用Function<Integer,String> fun2
String s = fun2.apply(i);
我们可以使用andThen方法,把两次转换组合在一起使用
String s = fun1.andThen(fun2).apply("123");
fun1先调用apply方法,把字符串转换为Integer
fun2再调用apply方法,把Integer转换为字符串
*/
public class AndThenDemo {
public static void change(String s, Function<String, Integer> function1, Function<Integer, String> function2) {
String ss = function1.andThen(function2).apply(s);
System.out.println(ss);
}
public static void main(String[] args) {
change("12345", string -> Integer.parseInt(string) + 5, i -> String.valueOf(i));
}
}
第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过andThen 按照前后顺序组合到了一起。
请注意,Function的前置条件泛型和后置条件泛型可以相同。
3.8 练习:自定义函数模型拼接
题目
请使用Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:
String str = “赵丽颖,20”;
- 将字符串截取数字年龄部分,得到字符串;
- 将上一步的字符串转换成为int类型的数字;
- 将上一步的int数字累加100,得到结果int数字。
解答
package com.aspirecn.jdk8.function;
import java.util.function.Function;
/*
String str = "赵丽颖,20";
1. 将字符串截取数字年龄部分,得到字符串;
2. 将上一步的字符串转换成为int类型的数字;
3. 将上一步的int数字加100,得到结果int数字。
*/
public class FunctionDemo {
public static void change(String s, Function<String, String> function1, Function<String, Integer> function2, Function<Integer, Integer> function3) {
function1.andThen(function2).andThen(function3).apply(s);
}
public static void main(String[] args) {
String str = "赵丽颖,20";
change(str,
//"赵丽颖,20"->"20"
(string) -> string.split(",")[1],
//"20"->20
ageStr -> Integer.parseInt(ageStr),
//20->120
age -> age + 100);
}
}
四、方法引用于构造器与构造器引用
1、方法引用:
若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”,可以理解为方法引用是Lambda表达式的另外一种表现形式
主要有三种语法格式:
对象::实例方法名
类::静态方法名
类::实例方法名
注意:
Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
若Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用 className::实例methodName
首先有一个Employee实体类
package com.aspirecn.jdk8.method_ref;
import lombok.*;
import sun.reflect.CallerSensitive;
@Data
public class Employee {
private String name;
@NonNull Integer age;
@NonNull Double salary;
public Employee() {
}
public Employee(String name, @NonNull Integer age, @NonNull Double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee(@NonNull Integer age, @NonNull Double salary) {
this.age = age;
this.salary = salary;
}
public Employee(@NonNull Integer age) {
this.age = age;
}
}
1.1 对象:: 实例方法名
// 对象:: 实例方法名
@Test
public void test() {
Consumer<String> con = string -> System.out.println(string);
con.accept("hehe");
/*
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
*/
// System.out 对象的println方法的参数为1个返回值为void,与accept方法保持一致
Consumer<String> con1 = System.out::println;
con1.accept("heihei");
Employee employee = new Employee(13, 20D);
Supplier<Integer> supplier = () -> employee.getAge();
Integer integer = supplier.get();
System.out.println(integer);
/**
* employee的getAge方法与Supplier()的参数和返回值保持一致
*/
Supplier<Integer> supplier1 = employee::getAge;
Integer integer1 = supplier1.get();
System.out.println(integer1);
}
1.2 类::静态方法名
/**
* 类::静态方法名
*/
@Test
public void test2() {
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
/**
* Integer的compare的参数列表与返回值类型
* 和
* Comparator的compare的参数列表和返回值一致
*/
Comparator<Integer> com1 = Integer::compare;
}
1.3 类::实例方法名
若Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,
可以使用className::实例methodName
/**
* 类::实例方法名
*/
@Test
public void test4() {
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
BiPredicate<String,String> bp1 = String::equals;
}
2、构造器引用:
格式:
ClassName::new
注意:
需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。
2.1 构造器引用 (无参) className::new
/**
* 构造器引用 (无参)
* className::new
*/
@Test
public void test5(){
Supplier<Employee> supplier = () -> new Employee();
Employee employee = supplier.get();
System.out.println(employee);
/**
* 需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
* 此函数式接口没有参数,则 Employee::new会默认调用无参构造方法
*/
Supplier<Employee> supplier1 = Employee::new;
Employee employee1 = supplier.get();
System.out.println(employee1);
}
2.2 构造器引用 (1参) className::new
/**
* 构造器引用 (1参)
* className::new
*/
@Test
public void test6(){
Function<Integer,Employee> function = (x)->new Employee(x);
System.out.println(function.apply(12));
/**
* 需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
* 则函数式接口只有一个参数,则Employee::new默认会调用一个参数的构造方法
*/
Function<Integer,Employee> function1 = Employee::new;
System.out.println(function1.apply(12));
}
2.3 构造器引用 (多参) className::new
/**
* 构造器引用 (多参)
* className::new
*/
@Test
public void test7(){
/**
* 需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
* 则函数式接口有两个参数,则Employee::new默认会调用两个参数的构造方法
*/
BiFunction<Integer,Double,Employee> biFunction = (age,salary) -> new Employee(age,salary);
BiFunction<Integer,Double,Employee> biFunction1 = Employee::new;
Employee apply = biFunction1.apply(20, 12000d);
System.out.println(apply);
}
3、数组引用 Type[]::new
格式:
Type[]::new
3.1数组引用
/**
* 数组引用
* Type[]::new
*/
@Test
public void test8(){
Function<Integer, String[]> function = (x) -> new String[x];
String[] strings1 = function.apply(10);
System.out.println(strings1.length);
Function<Integer, String[]> function1 = String[]::new;
String[] strings2 = function1.apply(20);
System.out.println(strings2.length);
}