1 函数式接口

函数式接口在Java中是指: 有且仅有一个抽象方法的接口

函数式接口, 即适用于函数式编程场景的接口; 而Java中函数式编程体现就是Lambda, 所以函数式接口就是可以适用于Lambda使用的接口; 只有确保接口中有且仅有一个抽象方法, Java中的Lambda才能顺利地进行推导

备注 : “语法糖"是指使用更加方便, 但是远离不变的代码语法; 例如在遍历集合时使用的for-earch语法, 其实底层实现的仍然是迭代器, 这便是"语法糖”, 从应用层面来讲, Java中的Lambda可以被当做匿名内部类的"语法糖", 但是二者在原理上是不同的

格式 : 只要确保接口中有且仅有一个抽象方法即可

修饰符  interface 接口名称{
    public abstract  返回值类型  方法名称(可选参数信息);
    // 其他非抽象方法内容
}

由于接口中抽象发的public abstract 是可以省略的, 所以定义一个函数式接口很简单

public interface MyFunctionInterface{
    void myMethod();
}

@FunctionalInterface注解

@Override 注解的作用类似, Java8中专门为函数式接口引入了一个新的注解 : @FunctionalInterface, 该注解可用于一个接口的定义上

函数式接口的使用

/* 函数式接口的使用 : 一般作为方法的参数与返回值类型 */
public class Demo{
    // 定义一个方法, 参数使用函数式接口MyFunctionalInterface
    public static void show(MyFunctionalInterface myInter){
        myInter.method()
    }
    
    public static void main(String[] args){
        // 调用show方法, 方法的参数是一个接口,所以可以传递接口的实现类对象
        show(new MyFunctionInterfaceImpl);
        
        // 调用show方法, 方法的参数是一个接口, 所以我们可以传递接口的匿名内部类
        show(new MyFunctionInterface{
            @Override
            public void method(){
                System.out.println("使用匿名内部类重写接口中的抽象方法")
            }
        });
    }
    
    // 调用方法, 方法的参数是一个函数式接口, 所以可以使用Lambda表达式
    
    show(() -> {
        System.out.println("使用Lambda表达式重写接口中的抽象方法")
    });
    
    // Lambda简化
    show( -> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
    
}

2 函数式编程

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);
    }
}

以上的代码问题在于, 当传入的level 不为1时, 还是海鲜拼接字符,造成性能上的浪费

我们要做的是 当第一个参数不满足的shih,不执行后面的操作; 避免性能的浪费

// 接口类
@FunctionalInterface
public interface MessageBuilder{
    // 定义一个拼接消息的抽象方法, 返回被拼接的信息
    public abstract String builderMessaeg();
}


public class Demo02Lambda{
    // 定义一个现实日志的方法, 方法的参数传递日志的等级和MessageBuilder接口
    public static vid showLog(int level,MessageBuilder mb){
        // 对日志的等级记性判断,如果是1级,则调用接口中的方法
        if(level==1){
            System.out.println(mb.builderMessage());
        }
    }
    
    public static void main(String[] args){
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
        
        // 调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以使用Lambda表达式
        shoLog(1, () ->{
            return msgA+msgB+msgC;
        });
    }
    
    // lambda表达式作为参数传递, 仅仅是把参数传递到showLog方法中, 当level=1,才会调用接口中的方法	如果条件不满足,则接口中的方法不会执行, 不存在性能浪费
}

使用Lambda作为参数和返回值

在使用比较器接口的时候, 可以使用Lambda表达式, 避免重写 compare 方法

3 常用函数式接口

Supplier接口

java.util.function.Supplier<T> 接口仅包含一个无参的方法 : T get(); 用来获取一个泛型指定类型的对象数据; 由于这是一个函数式接口, 这也意味着对应的Lambda需要对外提供一个符合泛型类型的对象数据

Supplier<T> 接口被称之为生产型接口, 指定接口的泛型是什么类型, 那么接口中的get方法就会返回什么类型

import java.function.Supplier


public class Demo01Supplier{
    private Static getString(Supplier<String> sup){
        return sup.get();
    }
    
    public static void main(String[] args){
        String msgA = "Hello";
        String msgB = "World";
        System.out.println(getString(() -> msgA+msgB)));
    }
}

练习 : 求数组中元素最大值

使用supplier 接口作为方法参数类型, 通过Lambda表达式求出int数组中的最大值, 提示 : 接口的泛型请使用java.lang.Integer

public class Demo02Test{
    public static int getMax(Supplier<Integer> sup){
        return sup.get();
    }
    
    public static vid main(String[] args){
        int arr[] = {2,56,48,3,9,64};
        
        // 调用getMax方法,参数传递Lambda
        int manNum = getMax(() -> {
            // 计算数组中的最大值
            int max = arr[0];
            // 遍历数组
            for(int i:arr){
                if(i>max){
                    max = i;
                }
            }
            return max;
        });
        System.out.println("数组中最大的元素"+max);
    }
}

Consumer 接口

java.util.function.Consumer<T> 接口则正好与Supplier相反, 它不是生产一个数据, 而是消费一个数据, 其数据类型由类型决定

抽象方法 :accept
Consumer 接口中包含抽象方法 void accept(T t), 意为消费一个指定泛型的数据,基本使用

import javautil.function.Consumer

public class Demo01Consumer {
	
	private static void consumeString(String name,Consumer<String> con){
        con.accept(name);
	}

	public static int method(Supplier<Integer> sup){
		consumeString(s -> System.out.println(s));
        
	}
}

更好地方法是使用 方法引用

默认方法 : andThen()

如果一个方法的参数和返回值都是Consumer类型., 那么就可以实现效果 : 消费数据的时候, 实现先做一个操作, 然后在做一个操作, 实现组合; 而这个方法就是接口中的默认方法 andThen, 源码是

default Consumer<T> andThen(Consumer<? super T> after){
    Objects.requireNonNull(after);
    return (T t) -> {accept(t);after.accept(t);};
}

备注 : java.util.ObjectsrequireNonNull 静态方法将会在参数为null时 主动抛出NullPointerException 异常, 这省去了重复编写if语句和抛出空指针异常的麻烦

要想实现组合, 需要两个或多个Lambda表达式即可, 而 andThen的语义正式"一步接一步"操作, 例如两个步骤组合的情况:

public class Demo02AndThen{
    public static void method(String s,Consumer<String> con1,Consumer<String> con2){
 		// 连接两个consumer,再消费
 		con1.andThen(con2).accept(s);
	}
	
	public static void main(String[] args){
        meth("hello",(t) ->{
            System.out.println(t.toUpperCase());
        }, (t) ->{
            System.out.println(t.toLowerCase());
        })
	}
}

格式化打印信息

public class Demo03Test{
    public static void printInfo(String[] arr,Consumer<String> con1,Consumer<String> con2){
    // 遍历字符串数组
    for(String message: arr){
        con1.andThen(con2).accept(messge);
    }
        
    }
    
    public static void main(String[] args){
    	// 定义一个字符串类型的数组
    	String[] arr = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男"};
    	
    	printInfo(arr,(message)->{
            String name = meaasge.split(",")[0];
            System.out.print("姓名: "+name);
    	},(meaasge)->{
            String age = messaeg.aplit(",")[1];
            System.out.println("年龄 :"+age);
    	})
        
    }
}

Predicate接口

有时我们需要对某种类型数据进行判断, 从而得到一个boolean值结果, 这时就可以使用jav.util.function.Predicate<T> 接口

抽象方法 : test

Predicate 接口中包含一个抽象方法 :boolean test\<T t>, 用于条件判断场景

public class DemoPredicateTest{
    public static void method(Predicate<String> preficate){
        boolean veryLong = predicate.test("HelloWorld");
        System.out.println("字符串很长吗 :"+veryLong);
    }
    
    public static void main(String[] args){
        method(s -> s.length() >5);
    }
}

条件判断的标准是传入的Lambda表达式逻辑, 只要字符串长度大于5 则认为很长

默认方法 : and

既然是条件判断, 就会存在与, 或, 非三种常见的逻辑关系; 其中将两个predicate 条件使用"与"逻辑连接起来实现"并且"的效果时, 就可以使用default方法 and ,JDK源码为

default Predicate<T> and(Predicate<? super T> other){
    Objects.requireNonNull(other);
    return (t) ->test(t) && other.test(t);
}

如果要判断一个字符既要包含大写"H", 又要包含大写"W", 那么:

public class DemoPredicateAnd{
    public static void method(Predicate<String> one,Predicate<String> two){
    boolean isValid = one.and(two).test("HelloWorld");
    System.out.println("字符串很长吗 :"+isVaild);
    
    public static void main(String[] args){
        method(s -> s.contains("H"),s -> s.contains("W"));
    }
   }
}

默认方法 : 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即可, 其他不变

public class DemoPredicateOr{
    public static void method(Predicate<String> one,Predicate<String> two){
    boolean isValid = one.or(two).test("HelloWorld");
    System.out.println("字符串很长吗 :"+isVaild);
    
    public static void main(String[] args){
        method(s -> s.contains("H"),s -> s.contains("W"));
    }
   }

默认方法 : negate

与 或已经了解了, 剩下的 非(取反)也非常简单, 默认发 negate的 JDK源码为

default Predicate<T> negate(){
    return (t) -> !test(t);
}

从实现中很容易看出, 它执行了test方法之后, 对结果boolean值进行 ! 取反而已; 一定要在 test 方法调用之前调用 negate 方法, 正如and 和 or方法一样

public class DemoPredicateNegate{
    public static boolean checkString(String s,Predicate<String> pre){
        return !pre.test(s);
    }
    
    public static void main(String[] args){
    	String = "abc"
        boolean b = checkString(s,(String str)-> {
        return str.length > 5
        });
    }
}

练习 : 集合信息筛选

数组当中有多条 “姓名+性别” 的信息如下, 请通过Pridicate 接口的拼接将符合要求的字符串筛选到集合ArrayList 中, 需要同时满足下面两个条件:

  1. 必须为女生
  2. 姓名为4个字
public class DemoPredicate{
	public static void main(String[] args){
        String[] arra = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"}
	}  
}
public class DemoTest{
    public class ArrayList<String> filter(String[] arr,Predicate<String> pre1,Predicate<String> pre1){
        ArrayList<String> list = new ArrayList<>();
        
        for(String a:arr){
            // 使用接口之间中的test方法对获取的字符进行判断
            boolean b = pre1.and(pre2).test(s);
            if(b){
                list.add(s)
            }
        }
        return list
    }
    
    public static void main(String[] args){
        String[] array = {"迪丽热巴,女","古力娜扎,女","马尔扎哈,男","赵丽颖,女"}
		// 调用filterffa
		filter(array,(String s) ->{
            s.split(",")[1].equals("女");
		},(String s) ->{
            s.split(",")[0].length == 4;
		});
		// 遍历集合
		for(String s:list){
            System.out.println(s);
		}
	}  
    
}

Function接口

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个数据的数据, 前者称为前置条件, 后者称为后置条件

抽象方法 : apply

Function 接口中最主要的抽象方法为 : R apply(T t) , 根据类型的T的参数获取类型R的结果

使用的场景 : 例如将 String 类型转换为 Integer 类型

public class DemoFunctionApply{
    private static void change(Function<String,Integer> function){
        int num = function.apply(s);
        System.out.println(num);
    }
    
    public static void main(String[] args){
    	String s = "1234";
        change(s,(String str) -> return Integer.parseInt(s));
    }
}

默认方法 : andThen

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差不多:

public class DemoFunctionAndThen{
    public static void change(String s,Function<String,Integer> fun1,Function<String,Integer> fun2){
        String ss = fun1.andThen(fun2).apply(s);
        System.out.println(ss);
    }
    
    public static void main(String[] args){
        String = "123";
        change(s,(String str) ->{
            return Integer.parseInt(str)+10
        },(Integer i) ->{
            return i+"";
        });
    }
}

练习: 自定义函数类型拼接

请使用Function 进行函数模型的拼接, 按照顺序需要执行的多个函数操作为

String str = “赵丽颖,20”;

  1. 将字符串截取数字年龄部分, 得到字符串;
  2. 将上一步的字符串转换为int类型的数字;
  3. 将上一步的int数字累加100, 得到结果 int 数字;
public class Demo03Test{
    public static int change(String s,Function<String,String> fun1,Function<String,String> fun2,Function<String,String> fun3){
        // 使用andThen方法将三个组合到一起
        return fun1.andThen(fun2).andThen(fun3).apply(s);
    }
    
    public static void main(String[] args){
        String str = "赵丽颖,20";
        int num =change(str,(String s)->{
            return s.split(",")[1];
        },(String s)->{
            return Integer.parseInt(s);
        },(Integer i)->{
            return i+100
        });
        System.out.println(num);
    }
}