Java8: Functional Interface and Lambda Expression

写在前面: 最近在看<<Java8实战>>,感觉这本书的排版,纸质,内容真的都超级棒,个人觉得这本书还是很值得一读.本文简单或详细介绍一下Java8的FunctionalInterface和Lambda表达式.

What is Functional Interface?

函数式接口(Functional Interface)是在Java8才开始引入的.首先看一下Java普通接口和函数式接口的区别:

  • Java普通接口: 指的是一些具有相同属性和行为的类的抽象.
  • 函数式接口: 也是同样的理解方式,它是对一些相似的方法的抽象.

How can we say those methods are similar methods?

1.如果定义的是一个泛型的函数式接口的话,比如:

@FunctionalInterface
public interface MyFunction1<T, R> {
    R calculate(T t);
}

那么所有只有一个参数并有返回值的函数都是MyFunction1的一个实例.也就是说这些方法都是相似的方法.

2.如果定义的是一个具体的函数式接口,比如:

@FunctionalInterface
public interface MyFunction2 {
    void print(Integer i);
}

那么所有参数是Integer类型的并且没有返回值的函数都是MyFunction2的一个实例.也就是说这些方法都是相似的方法.

Java的接口可以当做函数的参数,那么函数式接口自然也可以,这就叫做行为参数化.

How to define and use a funtional interface?

在Java8中已经定义了好多的函数式接口,比如:

  • java.lang.Runnable
  • java.util.Comparator
  • java.util.function.Predicate
  • java.util.function.Consumer

那么定义一个函数式接口,有哪些需要注意的地方呢?

  • 1.添加@FunctionalInterface注解.
  • 2.有且只有一个抽象方法.可以有多个default方法和static方法,但是这些方法都必须实现.

对于@FunctionalInterface注解,其实不加也可以,完全是可以通过编译的,但前提是这个接口需要满足上面的第二个注意点.

但又是为什么需要@FunctionalInterface?首先我们先看一个例子:

假设我们定义了一个函数式接口,但是忘记了添加了@FunctionInterface,同时这个函数式接口还没被其他地方引用,如果这个时候我们又添加了一个抽象方法,是不会报错的.因为这个接口有了多个抽象方法,所以这个接口就是一个普通的接口,而不是我们所期望的函数式接口.

这时候,如果添加了@FunctionalInterface注解的话,就会报错,提示说这个接口具有多个抽象方法.实际上,个人理解@FunctionalInterface就是手动添加约束,说明这个接口就是函数式接口,只能有一个抽象方法.

接下来编写并使用一个自定义函数式接口

// define a functional interface
@FunctionalInterface
public interface MyFunction<T, R> {
    R convert(T t);
}

// use a function interface
public class Test {
    public static void main(String[] args) {
        MyFunction<Long, String> f = (Long l) -> String.valueOf(l);
        System.out.println(f.convert(10L));  // f: convert a Long to String 
        MyFunction<String, Long> g = (String s) -> Long.parseLong(s);
        System.out.println(g.convert("10")); // g: convert a String to Long
    }
}

What is Lambda expression?

咱们可以看一看<<Java8实战>>这本书中的定义:

可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式: 它没有名称,但它有参数列表,函数主体,返回类型,可能还有一个可以抛出的异常列表.

  • 匿名: 是因为它不像普通的方法有一个明确的名称
  • 函数: 是因为Lambda函数不像方法那样属于某个特定的类,但又和方法一样,有参数列表,函数主体,返回值类型,还可能有可以抛出的异常列表
  • 传递: Lambda表达式可以作为参数传递给方法或存储在变量中
  • 简洁: 无须像匿名类那样写很多模板代码

个人理解,Lambda表达式完全是服务于函数式接口的,就是为了在创建一个函数式接口实例时更加的直观,简洁.比如咱们要创建一个匿名类对象时,可以看一下普通接口和函数式接口的区别:

// a common interface
public interface Common {
    void f();
    void g();
}

@FunctionalInterface
public interface MyFunction<T, R> {
    R convert(T);
}

public class Test {
    public static void main(String[] args){
        // create a instance of common interface
        Common c = new Common() {
            @Override
            public void f() {}
            @Override
            public void g() {}
        };
        // create a instance of functional interface
        MyFunction<Long, String> f = (Long l) -> String.valueOf(l);
    }
}

Method Reference

说起方法引用之前,咱们先看一个例子

    // 1
    MyFunction<Long, String> f = (Long l) -> String.valueOf(l);
    // 2: 通过Lambda的上下文推断出l的数据类型
    MyFunction<Long, String> f = l -> String.valueOf(l);  // 
    // 3
    MyFunction<Long, String> f = String::valueOf;

第一种,第二种和第三种写法的作用完全是一样的,但显然第三种的写法更加简洁.形如xxx::xxx这样的表达式就是方法引用.方法引用可以被看做仅仅调用特定方法的Lambda的一种快捷写法.

How many ways to replace lambda expression with method reference?

总共有四种方法引用,下面咱们根据一个实例来说明并解释这几种方法引用.

// common class
public class FunctionInstance {
    public Integer getLength(String str) {
        return Optional.ofNullable(str).orElse("").length();  // Optional也是Java8引入的
    }
}
// test class
public class MethodReference {
    public static void main(String[] args) {
        // 1.指向静态方法的方法引用
        //Function<String, Integer> f1 = s -> Integer.parseInt(s);
        Function<String, Integer> f1 = Integer::parseInt;

        // 2.指向任意类型实例方法的方法引用,s为内部对象
        //Predicate<String> f2 = s -> s.isEmpty();
        Predicate<String> f2 = String::isEmpty;

        // 3.指向现有对象(外部对象)的实例方法的方法引用, instance为外部对象
        //Function<String, Integer> f3 = s -> instance.getLength(s);
        FunctionInstance instance = new FunctionInstance();
        Function<String, Integer> f3 = instance::getLength;

        // 4.构造函数引用
        //Supplier<MethodReference> f4 = () -> new MethodReference();
        Supplier<MethodReference> f4 = MethodReference::new;
    }
}

2和3有人可能会混淆, 反正一开始看的时候我是没区分出来.其实就看参数是为内部对象还是外部对象.内部对象就是用2,外部对象就是用3

  • 内部对象: 指Lambda表达式中的对象,比如上面的s, 传入的参数,属于内部对象
  • 外部对象: 指的是Lambda表达式外的对象,比如上面的instance

Summary

函数式接口: 就是一个特殊的接口,只能有一个抽象方法.所以可以将函数式接口想象成是一个函数类型(比如Predicate,Consumer或自定义的函数式接口).

Lambda表达式: 则是简化了匿名类对象的创建.正如我在上文所说的,Lambda表达式完全是服务于函数式接口的,正是因为了函数式接口的特殊性,所以才使用了Lambda表达式来创建一个函数类型实例,而不是像匿名类那样写一堆无用的模板代码.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值