vlambda博客
学习文章列表

聊聊Java 8的Lambda表达式和函数式接口

Java 8作为Java的一个里程碑版本,提供了很多特性,本文主要介绍Java 8的Lambda表达式及函数式接口的内容。

Lambda表达式

Java引入Lambda表达式是为了简化代码,使代码更加简洁优雅。因此,一般使用Lambda表达式的代码也是比较简短的。那么Lambda表达式的代码可以写的多简洁呢?我们看看下面的几个例子。

public void fun1() {
 Thread t = new Thread(() -> System.out.println("Hello"));
 t.start();
}

public void fun2() {
 Integer[] arr = {1438692705};
 Arrays.sort(arr, (x, y) -> x - y);
 System.out.println(Arrays.toString(arr));
}

上面的代码是两个经典的Lambda表达式的使用方法,分别选取了线程创建和排序的代码。如果不用Lambda表达式,很多人使用匿名内部类完成会多很多行冗余代码,但是像上面这样() -> {}的形式就简洁多了。

下面介绍一下Lambda表达式的几种常见的形式。

//没有参数,没有返回值
() -> System.out.println("hello")

//没有参数,返回值是10
() -> 10

//有一个参数
x -> x

//有两个参数,编译器自动判断参数类型
(x, y) -> x + y

//有两个int型参数
(int x, int y) -> x + y

//有多条语句,需要加上大括号
(x, y) -> {int sum = x + y; System.out.println(sum);}

根据上面的这些例子我们可以总结一下Lambda的特性:

  • 可以不加参数类型,编译器会自动识别。

  • 只有一个参数小括号加不加都可以,但如果不止一个参数就一定需要小括号。

  • 如果主体代码只有一条语句,大括号加不加都可以,若超过一条语句则必须加上大括号。

函数式接口

说完了Lambda表达式,我们来讲讲Lambda表达式的实现基础——函数式接口。函数式接口首先肯定是一个接口,这个接口有且仅有一个抽象方法,但是可以有多个非抽象方法。

我们可以看一下Runnable接口的定义,@FunctionalInterface修饰的是函数式接口,这里@FunctionalInterface不是必须的,只是方便编译器进行识别。Runnable接口只有一个run()方法,因此Runnable接口是一个非常典型的函数式接口。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

看到这里,会有细心的朋友发现一个奇怪的问题,为啥同样是函数式接口,Comparator接口的抽象方法却没有abstract修饰,并且有两个方法都没有方法体。这里需要引入一个知识点,接口除了default修饰的默认方法,其他都是默认由public abstract修饰的抽象方法,因此其实接口里面的方法其实是省略这些修饰的。而由于boolean equals(Object obj)这个方法是Object类的一个方法,接口默认继承Object类,所以这个方法是不算入接口的抽象方法计数的。

@FunctionalInterface
public interface Comparator<T{
 int compare(T o1, T o2);
 boolean equals(Object obj);
 ...
}

下面我们来看看Java 8提供的几个常见的函数式接口。

Supplier接口

@FunctionalInterface
public interface Supplier<T{
    get();
}

先从最简单的Supplier接口说起,这个接口只有一个get()方法,该方法没有参数,但是有返回值。下面是测试的demo,代码会打印test supplier的字符串。

public void testSupplier() {
 Supplier<String> supplier = () -> "test supplier";
 System.out.println(supplier.get());
}

Consumer接口

@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); };
    }
}

Consumer接口和Supplier接口的思路完全不同,我们可以理解为Supplier是数据的提供者,而Consumer是数据消费者,需要我们提供数据由Consumer消费,另外Consumer还有一个addThen方法可以控制消费者的执行顺序。

public void testConsumer() {
 Consumer<String> name = n -> System.out.println(
   "get name: " + n.split(",")[0]);
 Consumer<String> phone = p -> System.out.println(
   "get phone number: " + p.split(",")[1]);
 name.andThen(phone).accept("zhangsan,13912345678");
}

代码会打印下面的字符串。

get name: zhangsan
get phone number: 13912345678

Predicate接口

@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);
    }
}

Predicate是一个断言型接口,接受一个输入参数,返回一个布尔值。我们尝试实现一个demo,有两个Predicate对象,只要满足其中一个条件就返回true,代码最后会打印true。

public void testPredicate() {
 Predicate<String> s1 = s -> "predicate".equals(s);
 Predicate<String> s2 = s -> "consumer".equals(s);
 System.out.println(s1.or(s2).test("predicate"));
}

Function接口

@FunctionalInterface
public interface Function<TR{

    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;
    }
}

最后一个是函数型接口,接受参数且有返回值。下面演示一个复合函数的demo,f1的作用是求数字的2倍,f2的作用是拼接字符串和数字,最后会打印the result is 10

public void testFunction() {
 Function<Integer, Integer> f1 = t -> 2 * t;
 Function<Integer, String> f2 = t -> "the result is " + t;
 System.out.println(f2.compose(f1).apply(5));
}