美文网首页
Java修炼笔记之函数式编程

Java修炼笔记之函数式编程

作者: 花醉霜寒 | 来源:发表于2020-06-07 17:18 被阅读0次

    函数式编程简介

    \color{green}{函数式编程概要}
    函数式编程是一种编程范式,常见的编程范式还有命令式编程和逻辑式编程,其中命令式编程是对计算机硬件的抽象,就拿Java语言来时,变量对应存储单元、赋值语句对应获取和存储指令、表达式对应内存引用和算数运算指令、控制语句对应着跳转指令等。函数式编程是面向数学的抽象,这个函数不是计算机范畴的函数而是数学范畴的函数。关于逻辑式编程,这里不做介绍,感兴趣可查阅资料资料

    \color{green}{函数式编程的特点}

    • 函数是“一等公民”,函数和其他参数一样,可以赋值给变量,可以作为参数传入另外一个函数,也可以作为别的函数的返回值;
    • 只使用表达式,不使用语句,函数式编程开发的动机就是处理运算不考虑I/O,函数式编程要求把I/O限制到最小,保持运算过程的单纯性;
    • 函数式编程没有“副作用”和不修改状态,函数保持独立,所有功能就是返回一个新的值,没有其他行为,不能修改外部变量的值;
    • 引用透明,函数式编程中函数返回的结果只受传入参数的影响,不受外部状态变化的影响,就是只要传入相同的参数都会返回相同的结果。

    \color{green}{函数式编程的优势}

    • 代码简介,开发迅速;
    • 接近自然语言,便于理解;
    • 方便代码管理;
    • 易于并发编程;
    • 代码热升级等

    Java函数式编程
    JDK1.8对函数式编程进行了支持,引入了注解@FunctionalInterface来标注一个接口为函数式接口,函数式接口要求一个接口只能有一个抽象方法,但是可以有多个默认方法和静态方法。java.util.function包下提供了很多默认的函数式接口,一共有四十多个如下所示,这些接口大致可以分为四大类,分别是Function、Consumer、Supplier和Prediscate的扩展,这四个函数是整个函数式接口的基础。

    \color{green}{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接口是一个运算接口,传入一个参数,并返回一个返回值,该接口共定义了四个方法,一个抽象方法,两个默认方法和一个静态方法:

    • apply(T t),执行Function定义的方法;
    • Function compose(Function before) ,before的返回值作为当前Function的传入参数;
    • Function andThen(Function after),当前Function的执行结果作为after的传入参数;
    • identity(),将输入参数作为返回值。
            //字符串拼接功能
            Function<String, String> function1 = str -> "hello" + str;
            
            //字符串截取
            Function<String, String> function2 = (str) -> str.substring(1);
    
            //返回"helloworld"
            System.out.println(function1.apply("world"));
    
            //返回"orld",字符串被截取
            System.out.println(function2.apply("world"));
    
            //返回"helloorld",先执行function2,再执行function1
            System.out.println(function1.compose(function2).apply("world"));
    
            //返回"elloworld",先执行function1,再执行function2
            System.out.println(function1.andThen(function2).apply("world"));
    
            //输出传入参数
            System.out.println(Function.identity().apply("world"));
            System.out.println(Function.identity().apply(new Object()));
    

    输出结果

    helloworld
    orld
    helloorld
    elloworld
    world
    java.lang.Object@17f052a3
    

    \color{green}{Consumer接口}
    Consumer意思是消费者,该接口表示传入一个参数供消费,并且没有返回值,该接口中定义了两个方法,一个抽象方法和一个默认方法:

    • void accept(T t),执行Consumer定义的消费逻辑;
    • Consumer<T> andThen(Consumer<? super T> after),表示传入一个参数被消费了两次,当前Consumer先执行消费逻辑,然后after执行消费逻辑。
    @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的使用和特性

    Consumer<Integer> addConsumer = param -> System.out.println(param + 100);
    
    Consumer<Integer> consumer = param -> System.out.println(param - 50);
    
    addConsumer.accept(50);
    
    consumer.accept(50);
    
    addConsumer.andThen(consumer).accept(50);
    

    输出结果为

    150
    0
    150
    0
    

    定义了两个Consumer分别进行+100和-50的操作并打印,最后一个输出结果我预测应该输出100,应为前面已经进行了+100的操作,但是返回结果是0,说明传入的参数被操作了两次但是没有进行赋值操作。

    \color{green}{Supplier接口}
    Supplier意为供应,与Consumer相反,Supplier无参数传入,但是有一个返回值,该接口比较简单,只定义了一个抽象方法get(),表示执行Supplier定义的逻辑返回一个对象。

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

    通过下面的示例了解一下Supplier的定义和使用

    Supplier<String> stringSupplier = () -> "hello world";
    
    //打印hello world
    System.out.println(stringSupplier.get());
    

    定义了一个十分简单的Supplier,返回字符串“hello world”,通过get()方法可以获取。

    \color{green}{Predicate接口}
    Predicate意为预测,该接口接收一个参数,返回true或者false,接口定义了五个方法,其中包括一个抽象方法,三个默认方法和一个静态方法:

    • test(T t),执行Predicate中定义的逻辑,返回true或者false;
    • Predicate and(Predicate other),两个Predicate进行&&运算;
    • negate(),当前Predicate执行的结果取反;
    • Predicate or(Predicate other),两个Predicate进行||运算;
    • isEqual
    @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的定义和使用进行简单介绍

    Predicate<String> startWithPredicate = str -> str.startsWith("hello");
    
    Predicate<String> endWithPredicate = str -> str.endsWith("world");
    
    //true
    System.out.println(startWithPredicate.test("hello world"));
    
    //true
    System.out.println(endWithPredicate.test("hello world"));
    
    Predicate<String> andPredicate = startWithPredicate.and(endWithPredicate);
    
    Predicate<String> orPredicate = startWithPredicate.or(endWithPredicate);
    
    //false
    System.out.println(andPredicate.test("hello java"));
    
    //true
    System.out.println(orPredicate.test("hello java"));
    
    Predicate<String> oppPredicate = startWithPredicate.negate();
    
    //false
    System.out.println(oppPredicate.test("hello world"));
    

    定义两个基础的Predicate,startWithPredicate判断字符串是否以“hello”开头,endWithPredicate判断字符串是否以“world”结尾,并对两个Predicate进行了"&&"、"||"以及取反操作,执行结果如上所示。

    总结
    本文对函数式编程进行了简单的介绍,对jdk1.8中四个最重要的函数式接口的定义和使用方法进行了详细的介绍,但是缺乏函数式编程的思想在实际项目中的应用,最近发现,虽然我最近开发的项目中通过lambda表达式使用了一些函数式接口,但是我觉得函数式接口应该是广泛使用在框架代码中的,最近在思考这个问题,后续分享。

    相关文章

      网友评论

          本文标题:Java修炼笔记之函数式编程

          本文链接:https://www.haomeiwen.com/subject/lirftktx.html