深入了解函数式接口

Posted paulwang92115

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入了解函数式接口相关的知识,希望对你有一定的参考价值。

前面几个章节我们已经介绍了如何定义函数式接口,以及函数式接口的实例的实现方式。其实 JAVA8 为我们定义好了许多函数式接口,方便我们使用。这些函数式接口都在 java.util.function.Function 包中。还有一些在 JAVA8 之前引入的接口在 JAVA8 引入后也被标记成函数式接口了,比如 Runnalbe 和 Callable。

Consumer

顾名思义,消费者,消费者就是接收一个东西然后消费掉,没有返回。会接受一个输入,并且不返回任何结果。

这个接口中定义了两个方法,一个是抽象方法 accept,接收一个入参,不返回任何结果。

另一个是默认方法 andThen,接收一个 Consumer 接口。先对参数应用当前 Consumer 接口的 accept 方法,然后再对当前参数应用 after Consumer 的 accept 方法。

//源码
@FunctionalInterface
public interface Consumer<T> {


    void accept(T t);

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

例子:

因为这个函数式接口定义没有返回值,我们就传入一个字符串然后打印出来。

public static void main(String[] args) {
  Consumer<String> consumer = (str)-> System.out.println(str);
  //也可以写成 Consumer<String> consumer = System.out::println;
  consumer.accept("hello java8");
}
hello java8

我们再测试一下 andThen 方法,andThen 方法返回一个新的 Consumer。

public static void main(String[] args) {
  Consumer<String> consumer = (str)-> System.out.println(str);
  Consumer<String> newConsumer = consumer.andThen((s)-> System.out.println("after:" +s));
  newConsumer.accept("hello java8");
}
hello java8      
after:hello java8

最终结果,先使用当前的 Consumer 处理输入字符串,然后在使用 andThen 函数中的入参的 Consumer 处理同一个字符串。

Function

接收一个参数,并且生成一个结果。这个接口除了一个抽象方法之外,还有两个 default 方法,还有一个 static 修饰的静态方法。

抽象方法 apply:接收一个参数,返回一个结果,参数和结果都是泛型,可以自己定义。

默认方法 compose,接收一个 Function 接口,返回一个 Function 接口。先对参数应用 before function,得到结果后应用到当前的 function,形成了两个 function 的串联。当然通过多次 compose 可以形成多个 function 的串联。

默认方法 andThen,与 compose 类似,先对参数应用当前 function,得到结果后应用到 after function。与 compose 类型,也可以多次形成串联。

静态方法 identity,返回一个 输入和输出相同的 function。

//源码
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }


    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

例子:

输入一个字符串,将其全部变为大写之后打印出来。

Function<String,String> function = String::toUpperCase;
//使用 Lambda 表达式的写法 Function<String,String> function = str->str.toUpperCase();
System.out.println(f.apply("hello java8"));

我们使用 Function 进行组合,将字符串进行分割和转为大写功能进行组合。andThen 是将 function 的执行结果交给 after function。compose 是先执行 before function,然后将执行结果交给 function。

public static void main(String[] args) {

  Function<String,String> f = String::toUpperCase;
  Function<String,String> after = f.andThen(str->str.substring(0,6));
  System.out.println(after.apply("hello java8"));
}

结果:

//先转为大写,然后在分割字符串
HELLO J

Predicate

Predicate:接收一个输入,根据这个输入进行计算或者评估,返回一个 Boolean 类型的输出。

抽象方法 test:需要我们自己实现的方法,用于根据输入参数进行判断。

默认方法 and:与 && 的效果相同,逻辑与,根据两个 Predicate 的 test 结果进行逻辑与运算。都为 ture 的时候才返回 true。

默认方法 negate:与 !效果相同,否定。对执行 test 之后的结果取反。

默认方法 or:与 || 效果相同,逻辑或,根据两个 Predicate 的 test 结果进行逻辑或运算,其中一个为 true 的时候就返回 true。

默认方法 isEqual:返回一个能判断两个参数是否 equal 的函数式接口。根据 Objects 的 equal 的方法。这个方法的意义在于我们可以根据传入的 Object 类型来进行 equals 判断,传入的是 String 就用 String 的 equals,传入对象就用对象的 equals。

//源码
@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

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

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

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


    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

例子:

判断一个字符串的长度是否大于 5。

public static void main(String[] args) {
  Predicate<String> predicate = p->p.length()>5;
  System.out.println(predicate.test("hello"));
}
//hello 的长度为 5,结果返回 false
false

打印出集合中的偶数。

public class PredicateTest {

    public static void main(String[] args) {
        List<Integer> list  = Arrays.asList(1,2,3,4,5,6,7,8);
        PredicateTest test = new PredicateTest();
        test.conditionFilter(list,item->item%2==0);
    }


    public void  conditionFilter(List<Integer> list, Predicate<Integer> predicate){
        for(Integer i : list){
            if(predicate.test(i)){
                System.out.println(i);
            }
        }
    }

}

判断两个字符串是否相等:

//静态方法直接使用接口调用即可
//结果返回 true
System.out.println(Predicate.isEqual("test").test("test"));

//结果返回 false
Predicate<String> p = Predicate.isEqual("hello");
System.out.println(p.test("hello1"));

Supplier

不接受输入,返回一个输出。以前写的没有输入参数的工厂的情况就可以用 Supplier 替代。

抽象方法 get:直接获取输出。

//源码
@FunctionalInterface
public interface Supplier<T> {

    T get();
}

例子:

Supplier<List<String>> supplier = ()->new ArrayList<>();
List<String> l = supplier.get();

当然也可以使用方法引用的方式:

Supplier<List<String>> s = ArrayList::new;
List<String> l = s.get();

获取到返回值之后我们就可以使用了。

其他

除了这些函数式接口之外,JAVA8 还为我们提供了很多函数式接口来满足我们不同的需求。

比如我们需要两个输入参数和一个返回值该怎么办那?Function 接口目前只支持一个参数一个返回值啊,不用担心,JDK 的设计者们早就想好了,它们定义了 BiFunction 这个函数式接口来满足两个输入参数一个返回值的情况。

BinaryOperator接收两个操作数,是 BiFunction 在操作数和结果相同时的特例。Consumer 接口也有接收两个操作数的函数式接口BiConsumer

除此之外,上面介绍的四大种类的函数式接口也有更具体的实现,比如 Function 有 DoubleFunction(参数是 double),IntFunction(参数是 int),LongFunction(参数是 long),ToDoubleFunction(返回值是 double),ToIntFunction返回值是 int),ToLongFunction返回值是 long)。Consumer,Predicate 和 Supplier 也是同理,就不再赘述了。我们可以根据实际情况选择更为具体的函数式接口。

默认方法与静态方法

通过上面的源码我们会发现接口中有了非抽象方法,默认方法和静态方法,分别用 default 和 static 修饰。

其实这样设计是为了在增加新功能的同时考虑到向后兼容性。这样就能够在顶层接口中定义一些共同的非抽象方法操作。比如 forEach, stream 等等。

默认方法对应实例方法,静态方法对应类方法。

//Collection 接口
default Stream<E> stream() {
  return StreamSupport.stream(spliterator(), false);
}
//Iterable 接口
default void forEach(Consumer<? super T> action) {
  Objects.requireNonNull(action);
  for (T t : this) {
    action.accept(t);
  }
}

以上是关于深入了解函数式接口的主要内容,如果未能解决你的问题,请参考以下文章

函数式接口原理都讲烂了,还不来了解...

Java8实践指南 代表性的函数式接口

Java8函数式接口以及lambda表达式实践

Java8函数式接口以及lambda表达式实践

Java8函数式接口以及lambda表达式实践

深入,把函数式接口再次深入玩转!!