概念
函数式接口在Java中是指:有且仅有一个抽象方法的接口(只能有一个抽象方法,但是可以有其它多个方法比如:默认方法)

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。

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

@FunctionalInterface注解
与@Override 注解作用类似,Java 8专门为函数式接口引入了一个新的注解:@FunctionalInterface。

@FunctionalInterface
public interface MyFunctionalInterface{
     void myMethod();
}

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样

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的指是否满足要求,作为log方法的第二个参数。三个字符串一定会首先被拼接并传入方法内,然后才会进行level值判断,如果level值不符合要求,那么字符串的拼接就白做的,存在性能浪费

Lambda的更优写法
使用Lambda必然需要一个函数式接口:

//函数式接口
@FunctionalInterface
public interface MessageBuilder{
    String builderMessage();
}

//测试类
public class Demo02LoggerLambda{
   private static void log(int level,MessagerBuilder 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);
   }
}

这样一来,只有当level值满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接
tip:实际上使用内部类也可以达到这样的效果,只是将代码操作延迟到了另一个对象当中通过调用方法来完成,而是否调用其所在方法是条件判断之后才执行的

使用Lambda作为参数和返回值
使用Lambda作为参数,上面的案例已经说明,下面说一下函数式接口作为方法的返回值类型。
如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式

//java.util.Comparator接口 是一个函数式接口
import java.util.Arrays;
import java.util.Comparator;

public class Demo03Comparator{
   private static Comparator<String> newComparator(){
       return (a,b) -> b.length()-a.length();//直接返回一个Lambda表达式
   }
   public static void main(String[] args){
      String[] array={"abc","ab","abcd"};
      System.out.println(Arrays.toString(array));//将数组转变为字符串,并输出
      Arrays.sort(array,newComparator());//排序
   }
}

常用函数式接口
JDK提供了大量常用的函数式接口以丰富Lambda的使用场景,它们主要在java.util.function包中被提供

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

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

Consumer接口
java.util.function.Consumer接口,其中包含抽象方法void accept(T t),意为消费一个指定泛型的数据

import java.util.function.Consumer;

public class Demo05Consumer{
    private static void consumerString(Consumer<String> function){
       function.accept("Hello");
    }
    public static void main(String[] args){
       consumerString(s -> System.out.println(s));
    }
}

默认方法:andThen
如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现效果:首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer接口中的default方法andThen。下面是JDK的源代码:

default Consumer<T> andThen(Consumer<? super T> after){
   Objects.requireNonNull(after);//java,util.Objects 的requireNonNull静态方法将会在参数为null时主动抛出NullPointerException异常。
   return (T t)-> {accept(t);after.accept(t);};
}
import java.util.functio.Consumer;

public class Demo06ConsumerandThen{
   private static void consumerString(Consumer<String> one,Consumer<String> two){
        one.andThen(two).accept("Hello");
   }
   public static void main(String[] args){
        consumerString(s-> System.out.println(s.toUpperCase())//把字符串变为大写字母
         s->System.out.println(s.toLowerCase())//把字符串变为小写字母
        );
   }
}

Predicate接口
有时候需要对某中类型的数据进行判断,从而得到一个boolean值结果。这时可以用java.util.function.Predicate接口
抽象方法:boolean test(T t)

import java.util.function.Predicate;

public class Demo07PredicateTest{
   private static void method(Predicate<String> predicate){
      boolean veryLong = predicate.test("HelloWorld");
      System.out.println("字符串很长吗:"+veryLong);
   }
   public static void main(String[] args){
      method(s->s.length()>5);//如果字符串的长度大于5.则返回true
   } 
}

默认方法:and
既然是条件判断的接口,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate条件使用”与“逻辑连接起来实现”并且“的效果时,就可以使用默认方法 and ,其JDK源码为:

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

讲一个实例:判断一个字符串既要包含大写”H“,又要包含大写”W“。

import java.util.function.Predicate;

public class Demo08PredicateAnd{
   private static void method(Predicate<String> one,Predicate<String> two){
      boolean isValid = one.and(two).test("Helloworld");
      System.out.println("字符串符合要求嘛:"+isValid);
   }
   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);
}

默认方法:negate
“非” 取反,其源码为:

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

从源码中可看出,negate方法是执行了test方法之后,对结果boolean值进行取反而已。一定要在test方法调用之前调用negate方法。

import java.util.function.Predicate;

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

Function接口
java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。抽象方法为:R apply(T t) ,根据类型T的参数获取类型R的结果。使用场景例如:将String类型转换为Integer类型

import java.util.function.Function;

public class Demo09FunctionApply{
   private static void method(Function<String,Integer> function){
      int num = function.apply("10");
      System.out.println(num+20);
   }
   public static void main(String[] args){
       method(s-> parseInt(s));/*parseInt() 函数可解析一个字符串,并返回一个整数.parseInt(string, radix),
       string 要被解析的字符串。  radix  当参数 radix 的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。举例,如果 string 以 "0x" 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数。如果 string 以 0 开头,那么 ECMAScript v3 允许 parseInt() 的一个实现把其后的字符解析为八进制或十六进制的数字。如果 string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数。
    */
   }
}

默认方法: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));
}
import java.util.function.Function;

public class Demo10FunctionAndThen{
    private static void method(Function<String,Integer> one, Function<Integer,Integer> two){
        int num = one.andThen(two).apply("10");
        System.out.println(num+20);
    }
    public static void main(String[] args){
        method(str -> parseInt(str)+10,i -> i*=10);
    }
}