Java函数式编程入门学习举例与优点详解

Java技术 潘老师 2年前 (2021-12-14) 1887 ℃ (0) 扫码查看

在JDK1.8之后,Java引入了函数式编程,可以大大简化代码,提高开发效率,下面针对Java函数式编程入门学习进行一些举例,并详解一下它的优点。

一、什么是函数式编程

我们最常用的面向对象编程(Java)属于命令式编程(Imperative Programming)这种编程范式。常见的编程范式还有逻辑式编程(Logic Programming),函数式编程(Functional Programming)。

函数式编程作为一种编程范式,在科学领域,是一种编写计算机程序数据结构和元素的方式,它把计算过程当做是数学函数的求值,而避免更改状态和可变数据。简单来说:一切都是数学函数。函数式编程语言里也可以有对象,但通常这些对象都是恒定不变的 —— 要么是函数参数,要什么是函数返回值。函数式编程语言里没有 for/next 循环,因为这些逻辑意味着有状态的改变。相替代的是,这种循环逻辑在函数式编程语言里是通过递归、把函数当成参数传递的方式实现的。

二、函数式接口

1)函数式接口概念

首先需要清楚一个概念:函数式接口——它指的是有且只有一个未实现的方法的接口,一般通过@FunctionalInterface这个注解来表明某个接口是一个函数式接口。函数式接口是Java支持函数式编程的基础。比如如下接口就是一个函数式接口:

@FunctionalInterface
public interface FunctionTest {
    void method();
}

2)函数式接口与其他普通接口的区别

  • 函数式接口中只能有一个抽象方法(这里不包括与Object的方法重名的方法)
  • 接口中唯一抽象方法的命名并不重要,因为函数式接口就是对某一行为进行抽象,主要目的就是支持 Lambda 表达式
  • 自定义函数式接口时,应当在接口前加上@FunctionalInterface标注(虽然不加也不会有错误)。编译器会注意到这个标注,如果你的接口中定义了第二个抽象方法的话,编译器会抛出异常。

至于Lambda 表达式可以详细参考:

Lambda表达式使用详解教程

Lambda表达式是JDK8推出一个重要的新特性,虽然看着很高大上,其实Lambda表达式的本质只是一个 […]

3)JDK内置函数式接口

(1)JDK 1.8之前已有的函数式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

(2)JDK 1.8 新增加的函数接口:
java.util.function 包下,包含了很多接口,用来支持Java8的函数式编程,该包中的常用的函数式接口有:
Java函数式编程入门学习举例与优点详解

三、Java8函数式编程语法入门

Consumer消费型接口作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出。现在我们要定义/创建一个Consumer对象,传统的方式是这样定义的:

Consumer c = new Consumer() {
    @Override
    public void accept(Object o) {
        System.out.println(o);
    }
};

而在Java8中,针对函数式编程接口,可以这样定义:

Consumer c = (o) -> {
    System.out.println(o);
};  

结合Lambda 表达式特性,我们还可以继续简化如下:

// 简化-只有一条语句,可以去掉{}
Consumer c = (o) -> System.out.println(o);  
// 再简化-它表示的意思就是针对输入的参数将其调用System.out中的静态方法println进行打印。
Consumer c = System.out::println;  

使用:

// 最后我们只要调用Consumer里的accept方法就相当于调用了System.out.println方法,如:
c.accept("hello consumer");
//相当于
System.out.println("hello consumer");

通过最后两段代码,我们可以简单的理解函数式编程,Consumer接口直接就可以当成一个函数了,这个函数接收一个输入参数,然后针对这个输入进行处理;当然其本质上仍旧是一个对象,但我们已经省去了诸如老方式中的对象定义过程,直接使用一段代码来给函数式接口对象赋值。而且最为关键的是,这个函数式对象因为本质上仍旧是一个对象,因此可以做为其它方法的参数或者返回值,可以与原有的代码实现无缝集成!

四、Java函数式接口介绍

1)Consumer

Consumer是一个函数式编程接口, 顾名思义,Consumer的意思就是消费,即针对某个东西我们来使用它,因此它包含有一个有输入而无输出的accept接口方法,除accept方法,它还包含有andThen这个方法。其定义如下:

default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
}

可见这个方法就是指定在调用当前Consumer后是否还要调用其它的Consumer;使用示例:

public static void consumerTest() {
    Consumer f = System.out::println;
    Consumer f2 = n -> System.out.println(n + "-F2");
    System.out.println("单次执行------");
    f.accept("F1");
    System.out.println("组合执行------");
    //执行完F后再执行F2的Accept方法
    f.andThen(f2).accept("F1");
    System.out.println("连续执行------");
    //连续执行F的Accept方法
    f.andThen(f).andThen(f).accept("F1");
}

输出结果:

单次执行------
F1
组合执行------
F1
F1-F2
连续执行------
F1
F1
F1

2)Function

Function也是一个函数式编程接口,它代表的含义是“函数”,而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出。
除apply方法外,它还有compose与andThen及indentity三个方法,其使用见下述示例:

/**
 * Function测试
 */
public static void functionTest() {
    Function<Integer, Integer> f = s -> ++s;
    Function<Integer, Integer> g = s -> s * 2;

    /**
     * 下面表示在执行F时,先执行G,并且执行F时使用G的输出当作输入。
     * 相当于以下代码:
     * Integer a = g.apply(1);
     * System.out.println(f.apply(a));
     */
    System.out.println(f.compose(g).apply(1));

    /**
     * 表示执行F的Apply后使用其返回的值当作输入再执行G的Apply;
     * 相当于以下代码
     * Integer a = f.apply(1);
     * System.out.println(g.apply(a));
     */
    System.out.println(f.andThen(g).apply(1));

    /**
     * identity方法会返回一个不进行任何处理的Function,即输出与输入值相等; 
     */
    System.out.println(Function.identity().apply("a"));
}

输出结果:

3
4
a

3) Predicate

Predicate为函数式接口,predicate的中文意思是“断定”,即判断的意思,判断某个东西是否满足某种条件; 因此它包含test方法,根据输入值来做逻辑判断,其结果为true或者false。它的使用方法示例如下:

/**
 * Predicate测试
 */
private static void predicateTest() {
    Predicate<String> p = o -> o.equals("test");
    Predicate<String> g = o -> o.startsWith("t");

    /**
     * negate: 用于对原来的Predicate做取反处理;
     * 如当调用p.test("test")为True时,调用p.negate().test("test")就会是False;
     */
    Assert.assertFalse(p.negate().test("test"));

    /**
     * and: 针对同一输入值,多个Predicate均返回True时返回True,否则返回False;
     */
    Assert.assertTrue(p.and(g).test("test"));

    /**
     * or: 针对同一输入值,多个Predicate只要有一个返回True则返回True,否则返回False
     */
    Assert.assertTrue(p.or(g).test("ta"));
}

4)Supplier

Supplier也是一个函数式编程接口,是供给型接口,它代表了这样的一类函数:无入参,有一个返回值

/**
 * Supplier测试
 */
private static void supplierTest() {
    Supplier<String> supplier = () -> {
        return "厨师不要钱,并向吃货扔了一个包子";
    };
    System.out.println(supplier.get());
}

输出结果:

厨师不要钱,并向吃货扔了一个包子

六、总结

通过Java函数式编程入门举例的学习,我们一家基本掌握了函数式编程的概念与基本使用,以及JDK8常见的函数式接口,使用它们的优点也是显而易见,后续还有一些高级的应该有机会再讲,比如Stream流式编程等。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/java/4027.html
喜欢 (1)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】