Java8:函数式接口

在正式介绍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.使用Lambda
Runnable 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表达式,如下所示。

@FunctionalInterface
public 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来打印列表中的所有元素。

@FunctionalInterface
public 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列表。

@FunctionalInterface
public 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值