浅析Java8新特性 函数式编程(一)初探函数式编程

272 阅读5分钟

Java 8 的Lambda表达式简化了匿名委托的使用,让你让代码更加简洁,优雅。

那么什么是表达式,什么是语句呢?

"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。

表达式与语句

为了更加清楚的表现表达式与语句的区别,下面请看语句相关的栗子:

//一个表达式之后跟一个分号,就形成了一个可执行的语句。
"muxia".toUpperCase(); <= 这里

//如果某个语句是以  {}  表征作用范围,则大括号之后可以不用写分号。
public String doSomething(){
    //一顿操作
}

表达式 "muxia".toUpperCase() 代表单纯的运算操作。

好的ヽ( ̄▽ ̄)و大概了解了表达式和语句的区别了,那么我们直接进入正题吧。

	//正常匿名内创建线程
	new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("木下z");
            }
        });

				//lambda创建线程
        new Thread(() -> {
            System.out.println("木下z");
        });

上面的代码明显看起来很冗余,我实际想要的只有 System.out.println("木下z"); 一行代码,但是却多了三行无用的代码。Lambda表达式的出现就是为了简化这样的流程,我们只要重点关心行为就可以了。

当然,Lambda表达式的使用也是有要求的,只有满足函数式的接口才可以使用。

函数式接口

那么什么样才是满足函数式的接口呢?

  1. 使用@FunctionalInterface 标注的接口。
  2. 仅有一个未实现的方法。(default方法 和 static方法不在其中)

继续举栗子

@FunctionalInterface
public interface TestInterface {

    default void doSomething(){
        System.out.println("I do something");
    }

    static void doStaticThings() {
        System.out.println("I did some static things");
    }

    int add(int param1,int param2);
}

    public static void main(String[] args) {

        //实现的是add接口
        TestInterface testInterface = (p1,p2) -> {
            return p1 + p2;
        };

        int res = testInterface.add(1, 3);
        System.out.println(res);
        /*** 4 ***/
    }

大概了解了函数式接口,那我们看看Java8新特性用到了哪些函数式接口呢?♪(^∇^*)

java.util.function 包最基本的四种原型如下
  • Consumer<T> 消费者函数
  • Predicate<T> 判断(断言)函数
  • Supplier<T> 生产者函数
  • Function 函数

这些函数我们先了解一下,因为在Java8非常实用的 OptionalStream 的实现均与其有关,这些我会在后面介绍。

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

accept 方法是接收一个参数并执行重写后的逻辑。

Consumer<Integer> consumerA = a -> System.out.println("consumerA消费了:" + a) ;
Consumer<Integer> consumerB = b -> System.out.println("consumerB消费了:" + b) ;
Consumer<Integer> consumerC = c -> System.out.println("consumerD消费了:" + c) ;

consumerA.accept(5);
consumerB.accept(10);
consumerC.accept(15);

Consumer<User> consumerD = u -> {
      System.out.println("consumerC消费了:" + u);
      u.setUserName("我改名咯^_^");
} ;

User user = new User("id", "木下z");

consumerD.accept(user);
System.out.println(user);

输出结果如下:

consumerA消费了:5
consumerB消费了:10
consumerB消费了:15
consumerC消费了:User(userId=id, userName=木下z)
User(userId=id, userName=我改名咯^_^)

使用时,我们需要指定泛型T的类型,方便我们在lambda表达式中使用。

andThen

andThen 是当前方法执行完成后在执行andThen里的Comsumer对象

consumerA.andThen(consumerC).andThen(consumerB).accept(5);

输出结果如下:

consumerA消费了:5
consumerC消费了:5
consumerB消费了:5

先执行了consumerA后执行了consumerC最后执行了consumerD。

查看源码得知return (T t) -> { accept(t); after.accept(t); }; 这个是符合顺序的按照连接的顺序分别消费。

Predicate
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

上面Consumer讲了default 方法,但是其实我们最常用的还是这个函数接口的未实现方法。所以后面我会把默认实现的方法和静态方法过滤掉,有兴趣的朋友可以去看看function这个包下的源码。(#^.^#)

这个函数主要适用于Stream.filter 和 Optional.filter 用于筛选符合条件的对象。

User user = new User("id", "木下z");
Predicate<User> predicate = u -> u.getUserId().equals("id");
System.out.println(predicate.test(user));

输出结果为

true

没错,这个就是判断型接口,主要告诉你这个对象是否符合条件。这时候肯定有人觉得上面两个函数式命名几行代码就可以解决的事情,为什么搞得这么复杂呢?不要着急,万事皆有存在的理由,如果我们连函数式都不能理解,那么Java8新特性Stream和Optional也很难上手的,因为这些实用的类就是基于这些函数接口的。

那我们继续吧(╹▽╹)

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

这个类似于BeanFactory,根据不同需求,实现该接口返回所需的实体对象。

        Supplier<User> supplier = () -> {
            return  new User("newId","newName");
        };

        User newUser = supplier.get();
        System.out.println(newUser);
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函数和Consumer类似,但又不太相同,Function能通过 composeandThen 满足大部分的数学逻辑运算。这里我就不写栗子了。

最后总结:

虽然我介绍的这些东西我们不可能直接在项目使用,但是我们可以学习这种思想,造属于自己轮子或者查看别人源码的时候能理解他的设计理念。后面在Optional和Stream的介绍中,你会发现函数编程的灵活之处。

参考资料

函数式编程初探 阮一峰

Java多核编程-5-函数式接口与function包