Java8:函数式接口

来源:互联网 发布:php替换删除符号 编辑:程序博客网 时间:2024/06/08 15:56

在正式介绍Lambda表达式之前有一些概念是我们需要了解的。

函数式接口:仅仅声明了一个抽象方法的接口。

说明:函数式接口中除了抽象方法外,还可以有其他方法,但是必须被static或者default中修饰,且要有方法体。

Java API中常见的一些函数式接口

public interface Comparator<T> {    // 抽象方法    int compare(T o1, T o2);    // 除了抽象,默认或者静态方法外,函数式接口中还可以有Object类中public修饰的方法    boolean equals(Object obj);    // 默认方法,含有方法体    default Comparator<T> reversed() {        return Collections.reverseOrder(this);    }    // ...}
public interface Runnable{    void run();}
public interface Callable<V>{    V call();}

Java8自带了一些常用的函数式接口,放在 java.util.function包里。
这里写图片描述
这里写图片描述

为了避免装箱操作,装箱拆箱都有一定的性能损耗。对Predicate< T>和Function< T, R>等通用函数式接口的原始类型特化:IntPredicate、IntToLongFunction等。

函数描述符是什么?

函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符

例如,Runnable接口可以看作一个什么也不接受什么也不返回(void)的函数的签名,因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void)。

() -> void 代表了参数列表为空,且返回void的函数
(Apple,Apple) -> int 代表接受两个Apple作为参数且返回int的函数。

函数式接口可以干什么?

Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例,具体来说就是函数式接口的一个具体实现的实例。

当然匿名内部类也可以完成同样的事情,只不过比较笨拙。
举例:

// 1.使用LambdaRunnable r1 = () -> System.out.println("Hello World 1");new Thread(r1).start();// 2.使用匿名内部类Runnable r2 = new Runnable(){    public void run(){        System.out.println("Hello World 2");    }};new Thread(r2).start();

函数式接口和Lamdba表达式的关系?

只有在接受函数式接口的地方才可以使用Lamdba表达式。
Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。

使用函数式接口

函数式接口定义且只定义了一个抽象方法。函数式接口很有用,因为抽象方法的签名可以描述Lambda表达式的签名。函数式接口的抽象方法的签名称为函数描述符。所以为了应用不同的Lambda表达式,你需要一套能够描述常见函数描述符的函数式接口。
Java API中已经有了几个函数式接口,比如之前见到的Comparator、Runnable和Callable。

1. Predicate

java.util.function.Predicate< T > 接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。
函数描述符:(T)-> boolean
在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。比如,你可以定义一个接受String对象的Lambda表达式,如下所示。

@FunctionalInterfacepublic interface Predicate<T>{    boolean test(T t);}public static < T > List< T > filter(List< T> list, Predicate< T> p) {    List< T> results = new ArrayList<>();    for(T s: list){        if(p.test(s)){        results.add(s);        }    }    return results;}Predicate< String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();List< String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);

2. Predicate

java.util.function.Consumer< T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。
函数描述符: (T)-> void
你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。比如,你可以用它来创建一个forEach方法,接受一个Integers的列表,并对其中每个元素执行操作。在下面的代码中,你就可以使用这个forEach方法,并配合Lambda来打印列表中的所有元素。

@FunctionalInterfacepublic interface Consumer<T>{    void accept(T t);}public static <T> void forEach(List<T> list, Consumer<T> c){    for(T i: list){        c.accept(i);    }}forEach(Arrays.asList(1,2,3,4,5),(Integer i) -> System.out.println(i));

3.Function

java.util.function.Function< T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。
函数描述符:(T) -> R
如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。在下面的代码中,我们向你展示如何利用它来创建一个map方法,以将一个String列表映射到包含每个String长度的Integer列表。

@FunctionalInterfacepublic interface Function<T, R>{    R apply(T t);}public static <T, R> List<R> map(List<T> list,Function<T, R> f) {    List<R> result = new ArrayList<>();    for(T s: list){        result.add(f.apply(s));    }    return result;}// [7, 2, 6]List<Integer> l = map(Arrays.asList("lambdas","in","action"),(String s) -> s.length());

关于接口中default和static方法的一些延伸:

问题:Java8中为什么要加入defalut方法呢?
答案:举例,在Java8之前,List接口中并没有stream()或者 parallelStream()方法,它实现的collection接口也没有,因为当初设计接口的时候还没有想到这些方法,最简单的方案就是Java8的设计者把steam方法加入collection中。可是如果这样做的话对于用户来说就是噩梦了,因为给接口加入一个方法,意味所有实体类都必须为其提供一个实现。
Java8中引入了支持defalut修饰的方法,这样方法就有了默认实现而不用实体类去实现这个方法。

0 0