1.函数式接口
1.1 概念
函数式接口在java中是指只有一个抽象方法的接口。
函数式接口,就是适用于函数式编程场景的接口。在java中函数式编程就体现在Lambda,因此函数式接口就是能够适用于lambda使用的接口。只有确保接口中有且仅有一个抽象方法,lambda才能进行顺利的推导。
1.2 格式
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
因为接口中抽象方法的修饰符可以省略。例;
public interface MyFunctionalInterface {
void myMethod();
}
1.3 @FunctionalInterface注解
此注解和@override注解的作用类似。该住处应用于函数式接口的定义上。
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
一旦使用了该注解来定义函数式接口,编译器就会检查该接口是否是有且仅有一个抽象方法。
1.4 自定义函数式接口
将函数式接口作为方法的参数。
public class Demo {
private static void dos(FunctionInterface fi){
fi.method();
}
public static void main(String[] args) {
Demo.dos(()->{System.out.println("lambda表达式");});
}
}
2.函数式编程
2.1 lambda的延迟执行
有些代码执行后,结果不一定会被使用,进而就是造成资源的浪费,lambda表达式是延迟执行的,这就可以可以解决这种资源浪费的问题。
性能浪费的日志案例:
日志可以帮助我们快速的解决定位的问题,记录长须过程中的情况,以便瞠目的监控和优化。
一种典型的场景的就是对参数进行有条件的使用。比如在对日志信息进行拼接之后,在满足条件的情况下进行打印输出:
public class DemoLogger {
public static void log(int level,String msg){
if(level==1){
System.out.println(msg);
}
}
public static void main(String[] args) {
String m="你好";
String s="日志";
String g="吗?";
log(1,m+s+g);
}
}
这段代码就存在资源浪费的问题,因为不管level是够满足要就,系统会都会把这三个字符串拼接起来,作为参数,传到方法中。
lambda表达式的更优写法
lambda表达式的使用前提必须要有函数式接口。
public class DemoLogger1 {
private static void log(int level,Message msg){
if(level==1){
System.out.println(msg.pinjie());
}
}
public static void main(String[] args) {
String m="你好";
String s="日志";
String g="吗?";
log(1,()->{return m+s+g;});
}
}
这个代码只有在level满足要求时,才会对三个字符串进行拼接。
验证Lambda表达式的延迟性
public class DemoLogger1 {
private static void log(int level,Message msg){
if(level==1){
System.out.println(msg.pinjie());
}
}
public static void main(String[] args) {
String m="你好";
String s="日志";
String g="吗?";
log(2,()->{
System.out.println("lambda表达式执行了");
return m+s+g;});
}
}
从上述代码可以看出,level不满足条件,lambda表达式没有执行。
2.2 lambda表达式作为参数和返回值
lambda表达式的原理实际上就是匿名对象。如果方法的参数是一个函数接口,那就该函数接口就可以被lambda表示替代。
例如:java.lang.Runnable接口就是一个函数式接口。现在有一个方法使用该接口作为参数,那么就可以使用lambda表示进行提到。
public class DemoRunnable {
private static void startThread(Runnable runnable){
new Thread(runnable).start();
}
public static void main(String[] args) {
startThread(()->{
System.out.println("线程执行了!");
});
}
}
如果一个方法的返回值类型是函数式接口,那么就可以直接返回一个lambda表达式。
例如,需要一个方法来获取java.util.Comparator接口类型的对象作为排序器时候,就可以调用该方法获取。
public class DemoComparator {
private static Comparator<String> newComparator(){
return (a,b)->{return a.length()-b.length();};
}
public static void main(String[] args) {
String[] arr={"abc","acvd","a"};
System.out.println(Arrays.toString(arr));
Arrays.sort(arr,newComparator());
System.out.println(Arrays.toString(arr));
}
}
3.常用函数式接口
3.1 Supplier接口
java.util.function.Supplier<T>接口仅包含一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。这是一个函数式接口,对应的lambda表达式就需要“对外提供”一个符合泛型类型的对象数据。
public class DemoSupplier {
private static String getstring(Supplier<String> supplier){
return supplier.get();
}
public static void main(String[] args) {
String msg="你好";
String getstring = getstring(() -> { return msg; });
System.out.println(getstring);
}
}
3.2 练习:求数组元素的最大值
使用Supplier接口作为方法的参数,通过lambda表达式求出int数组中的最大值。
public class DemoSupplier {
private static int getmax(Supplier<Integer> supplier){
return supplier.get();
}
public static void main(String[] args) {
int[] arr={1,3,4,6,9};
Integer getmax = getmax(() -> {
int max=arr[0];
for (int i = 0; i < arr.length; i++) {
if(max>arr[i]){
max=max;
}else{
max=arr[i];
}
}
return max;
});
System.out.println(getmax);
}
}
3.3 Consumer接口
java.util.function.Consumer<T>接口正好与Supplier接口相反,他不是生产一个数据,而是消费一个数据,其数据类型根据泛型决定。
抽象方法:accept
Consumer接口中包含抽象方法void accpet(T t),消费一个指定的泛型。
public class Democonsume {
private static void consunmerString(Consumer<String> f){
f.accept("你好");
}
public static void main(String[] args) {
consunmerString((s)->{System.out.println(s);});
}
}
默认方法:andThen
如果一个方法的参数和返回值都是Consumer类型。 那么在消费数据时,会做一个操作,然后在做一个操作,实现组合。这个方法就是Consumer接口中的default方法andThen。
要实现组合,就需要两个或者多个lambda表达式,andThen的语义就是一步一步的执行;
public class DemoandThen {
private static void consumerstring(Consumer<String> c1,Consumer<String> c2){
c1.andThen(c2).accept("nihao");
}
public static void main(String[] args) {
consumerstring((s)-> System.out.println(s.toUpperCase()),
s -> System.out.println(s.toLowerCase()));
}
}
运行结果是先输出大写的nihao,然后打印小写的nihao。
3.4 格式化打印
下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。”的格式将信息打印出来。要求将打印姓名的动作作为第一个Consumer 接口的Lambda实例,将打印性别的动作作为第二个Consumer 接口的Lambda实例,将两个Consumer 接口按照顺序“拼接”到一起。
public class DemoandThen {
private static void consumerstring(Consumer<String> c1,Consumer<String> c2,String[] arr){
for (String s : arr) {
c1.andThen(c2).accept(s);
}
}
public static void main(String[] args) {
String[] arr={"路飞,男","娜美,女","索隆,男","乔巴,动物"};
consumerstring(s -> System.out.print(s.split(",")[0]+"性别:"),
s -> System.out.println(s.split(",")[1]),
arr);
}
}
路飞性别:男
娜美性别:女
索隆性别:男
乔巴性别:动物
3.5 Predicate接口
java.util.function.Predicate<T> 接口,对某种类型的数据进行判断,从而得到一个boolean值结果。
抽象方法:test
Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景:
public class DemoPredicate {
public static void predicate(Predicate<String> predicate){
boolean test = predicate.test("我是字符串");
System.out.println(test);
}
public static void main(String[] args) {
predicate(s->s.length()>3);
}
}
true
条件判断的标准是传入的Lambda表达式逻辑,只要字符串长度大于3则true.
默认方法:and
predicate是条件判断,就会有三种逻辑关系,与、或、非。其中将两个Predicate 条件使用“与”逻辑连接起来实
现“并且”的效果时,可以使用default方法and 。
public class DemoPredicate {
public static void predicate(Predicate<String> p1,Predicate<String> p2){
boolean test = p1.and(p2).test("我是字符串");
System.out.println(test);
}
public static void main(String[] args) {
predicate(s->s.length()>3,
s->s.length()<6);
}
}
true
默认方法:or
与and方法同类型
public class DemoPredicate {
public static void predicate(Predicate<String> p1,Predicate<String> p2){
boolean test = p1.or(p2).test("我是字符串");
System.out.println(test);
}
public static void main(String[] args) {
predicate(s->s.length()>3,
s->s.length()>6);
}
}
true
默认方法:negate
它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在test 方法调用之前
调用negate 方法,正如and 和or 方法一样:
public class DemoPredicate {
public static void predicate(Predicate<String> p1){
boolean test = p1.negate().test("我是字符串");
System.out.println(test);
}
public static void main(String[] args) {
predicate(s->s.length()>3
);
}
}
false
3.6 集合信息筛选
数组当中有多条“姓名+性别”的信息如下,请通过Predicate 接口的拼装将符合要求的字符串筛选到集合ArrayList 中,需要同时满足两个条件:
1. 必须为女生;
2. 姓名为4;
public class DemoPredicate {
public static ArrayList<String> predicate(Predicate<String> p1, Predicate<String> p2, String[] arr){
ArrayList<String> a=new ArrayList<>();
for (String s : arr) {
if(p1.and(p2).test(s)){
a.add(s);
}
}
return a;
}
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
ArrayList<String> pre = predicate(s -> "女".equals(s.split(",")[1]),
s -> s.split(",")[0].length() == 4,
array
);
System.out.println(pre);
}
}
[迪丽热巴,女, 古力娜扎,女]
3.7 Function 接口
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
抽象方法:apply
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
例如:将String类型转换成Integer类型。
public class DemoFunction {
public static void method(Function<String,Integer> f){
Integer apply = f.apply("100");
System.out.println(apply);
}
public static void main(String[] args) {
method(s-> Integer.parseInt(s));
}
}
100
默认方法:andThen
Function 接口中有一个默认的andThen 方法,用来进行组合操作,该方法同样用于“先做什么,再做什么”的场景,和Consumer 中的andThen 差不多:
public class DemoFunction {
public static void method(Function<String,Integer> f1,Function<Integer,Integer> f2){
Integer apply = f1.andThen(f2).apply("100");
System.out.println(apply);
}
public static void main(String[] args) {
method(s-> Integer.parseInt(s), s->s*=100);
}
}
1000
3.8 自定义函数模型拼接
请使用Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:
String str = "赵丽颖,20";
1.将字符串截取数字年龄部分,得到字符串;
2. 将上一步的字符串转换成为int类型的数字;
3. 将上一步的int数字累加100,得到结果int数字。
public class DemoFunction {
public static int method(Function<String,String> f1,
Function<String,Integer> f2,
Function<Integer,Integer> f3,String str){
return f1.andThen(f2).andThen(f3).apply(str);
}
public static void main(String[] args) {
String str="赵丽颖,20";
int method = method(s ->s.split(",")[1],
s -> Integer.parseInt(s),
s -> s += 100,
str
);
System.out.println(method);
}
}
120