欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java系列笔记第八章:函数式编程

程序员文章站 2024-03-14 23:05:13
...

第八章 函数式编程

1. 函数式接口简介

1.1 概念

有且只有一个抽象方法的接口,叫函数式接口。

因为Java中函数式编程的体现就是Lambda,所以函数式接口就是可以用于lambda使用的接口。

接口中只能有一个抽象方法,可以有其他类型的方法,比如普通私有方法、静态私有方法、默认方法等等。

语法糖:

指的是形式上使用更加简便,但是底层原理不变的代码语法。比如for-each写起来简洁,底层其实还是迭代器。

从应用层面来讲,可以说lambda是匿名内部类的语法糖,但是底层原理并不同。

匿名内部类会在编译时生成一个class文件,而lambda表达式不会,所以会降低开销。

1.2 格式

函数式接口格式:

package FunctionInterface;

public interface MyInterface {
    void method();
}

1.3 @FunctionalInterface注解

加上这个注解后,接口就强制只能有一个抽象方法。如果多写了一个抽象方法或者没抽象方法,编译会失败。

package FunctionInterface;

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

1.4 lambda 的小例子

package FunctionInterface;

public class Demo {
    //参数使用函数式接口
    public static void fun(MyInterface mi) {
        mi.method();
    }

    public static void main(String[] args) {
        //调用fun方法,可以传递接口的实现类方法或者匿名内部类。
        fun(new MyInterface() {
            @Override
            public void method() {
                System.out.println("匿名内部类重写接口的抽象方法。");
            }
        });

        //方法的参数是一个函数式接口,所以可以传递进去lambda表达式
        fun(
                () -> System.out.println("lambda表达式重写接口的抽象方法。")
        );
    }
}

//===============输出===============//
匿名内部类重写接口的抽象方法。
lambda表达式重写接口的抽象方法。

2. 函数式编程

2.1 lambda的延迟执行

有的场景代码执行完毕后,结果不一定会被使用,从而造成性能浪费。而lambda是延迟执行你的,所以可以提升性能。

2.1.1 性能浪费的日志案例

日志记录代码如下:

package FunctionInterface.logDemo;

public class Logger {
    public static void showLog(int level, String msg) {
        if (level == 1) {
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        int level = 2;
        String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        showLog(level, s1 + s2 + s3);
    }
}

代码的问题:
代码中,如果level不是1,字符串的拼接就是无用的,会浪费性能。

使用lambda来优化:

lambda的特点是延迟加载,所以先定义一个函数式接口。

lambda仅仅是传参数进去,如果level不是1,就不会调用接口去拼接字符串。

package FunctionInterface.logDemo;

public class Logger {
    //传递函数式接口
    public static void showLog(int level, LogBuilderInterface logBuilderInterface) {
        //判断日志等级
        if (level == 1) {
            System.out.println(logBuilderInterface.buildMsg());
        }
    }

    public static void main(String[] args) {
        int level = 1;
        String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        showLog(level,
                () -> s1 + s2 + s3
        );
    }
}

2.2 函数式接口的使用

2.2.1作为方法的参数。

java.lang.Runnable是一个函数式接口,假设有一个startThread方法使用Runnable接口作为参数,就可以使用lambda进行传参,效果等同与new Thread(//lambda表达式).start();

package FunctionInterface.InterAsParams;

public class Demo {
    public static void startThread(Runnable runnable) {
        new Thread(runnable).start();
    }

    public static void main(String[] args) {

        //使用Thread的构造方法启动线程。
        new Thread(
                () -> System.out.println(Thread.currentThread().getName() + "线程启动!")
        ).start();

        //startThread的参数是一个接口,可以使用这个接口的匿名内部类。
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "线程启动!");
            }
        });
        //Runnable接口是函数式接口,作为参数,使用lambda表达式启动线程。
        startThread(
                () -> System.out.println(Thread.currentThread().getName() + "线程启动!")
        );
    }
}


//===============输出===============//
Thread-1线程启动!
Thread-2线程启动!
Thread-0线程启动!

2.2.2 作为方法的返回值

最常见的应用就是比较器java.util.Comparator

如果一个方法的返回值类型是一个接口,那么就可以返回一个lambda表达式。

package FunctionInterface.InterAsReturn;

import java.util.Comparator;

public class Demo {
    public static Comparator<Integer> CompareInt(){
        return ((o1, o2) -> o1-o2);
    }
}

3. 常用的函数式接口

java.util.function

3.1 Supplier接口

被称为生产型接口,指定的泛型是什么,接口的get方法就生产什么类型的数据。

package FunctionInterface;

import java.util.function.Supplier;

public class SupplierDemo {
    public static String getMsg(Supplier<String> sup){
        return sup.get();
    }

    public static void main(String[] args) {
        System.out.println(getMsg(()->"a"+"b"));
    }
}

应用:求数组最大值。

package FunctionInterface;

import java.util.function.Supplier;

public class SupplierDemo {
    public static Integer getMax(Supplier<Integer> sup) {
        return sup.get();
    }

    public static void main(String[] args) {
        int[] ints = {1, 2, 3, 4, 5};
        System.out.println(getMax(
                () -> {
                    int max = ints[0];
                    for (int i : ints) {
                        max = Math.max(i, max);
                    }
                    return max;
                }));
    }
}

3.2 Consumer接口

消费一个数据,类型由泛型决定。

void accept(T t)方法。

具体怎么使用,需要自定义。

package FunctionInterface;

import java.util.function.Consumer;

public class ConsumerDemo {
    public static void fun(String str, Consumer<String> con) {
        con.accept(str);
    }

    public static void main(String[] args) {
        String s = "abc";
        fun(s,
                (str) -> System.out.println("传递进来的字符串是:" + str)
        );
    }
}

消费者接口的另一种写法:方法引用

fun(s, System.out::println);


Consumer接口有一个默认方法:andThen

需要两个Consumer接口,可以把两个Consumer接口组合在一起。

谁写在前面谁先消费。

例子:定义一个方法,传进去一个字符串和两个 Consumer接口。

  • 要么调用两次accept方法。

  • 要么使用andThen方法。

package FunctionInterface.logDemo;

import java.util.function.Consumer;

public class ConsumerAndThenDemo {
    public static void fun(String str, Consumer<String> consumer1, Consumer<String> consumer2) {
//        consumer1.accept(str);
//        consumer2.accept(str);
        consumer1.andThen(consumer2).accept(str);
    }

    public static void main(String[] args) {
        String s = "Hello World!";
        fun(s,
                (str) -> System.out.println(str.toLowerCase()),
                (str) -> System.out.println(str.toUpperCase())
        );
    }
}

练习:格式化打印信息。

package FunctionInterface.logDemo;

import java.util.ArrayList;
import java.util.function.Consumer;

public class ConsumerAndThenDemo {
    public static void printInfo(Consumer<String> c1, Consumer<String> c2, String[] array) {
        for (String str : array) {
            c1.andThen(c2).accept(str);
        }
    }

    public static void main(String[] args) {
        String[] strings = {"张三,22", "李四,21"};
        printInfo(
                (s -> System.out.println("姓名:" + s.split(",")[0])),
                (s -> System.out.println("年龄:" + s.split(",")[1])),
                strings
        );
    }

}

//===============输出===============//
姓名:张三
年龄:22
姓名:李四
年龄:21

3.3 Predicate接口

3.3.1 接口的使用

对某种类型的数据进行判断,返回一个boolean值。boolean test(T t)

package FunctionInterface;

import java.util.function.Predicate;

public class PredicateDemo {
    public static boolean fun(String s, Predicate<String> predicate) {
        return predicate.test(s);
    }

    public static void main(String[] args) {
        String s = "abc";
        boolean b = fun(s, 
                (o) -> o.contains("A")
        );
        System.out.println("字符串中含有A吗?:" + b);
    }
}

3.3.2 接口中的三个默认方法

  1. and 方法。

传入两个Predicate接口。

package FunctionInterface.PredicateDemo;

import java.util.function.Predicate;

public class PredicateAndDemo {
    public static boolean check(String str, Predicate<String> p1, Predicate<String> p2) {
        return p1.and(p2).test(str);
    }

    public static void main(String[] args) {
        String s = "Abc";
        boolean b = check(s,
                (o -> s.contains("A")),
                (o -> s.length() > 5)
        );
        System.out.println("字符串中既有A,长度又大于5吗?:" + b);
    }
}
  1. or 方法。

return p1.or(p2).test(str);

  1. negate 非操作
public static boolean check(String str, Predicate<String> p) {
    return p.negate().test(str);
}

3.4 Function接口

转换类型的接口。把一个类型的数据转换为另一个类型。

Function<T, R>,把T类型转换为R类型。

package FunctionInterface.FunctionDemo;

import java.util.function.Function;

public class Demo {
    public static void fun(String s, Function<String, Integer> fun) {
        System.out.println(fun.apply(s));
    }

    public static void main(String[] args) {
        String s = "12";
        fun(s,
                (str) -> Integer.parseInt(str)
        );
        
        //方法引用
        fun(s,
                Integer::parseInt
        );
    }
}

Function接口的默认方法:andThen。用来进行组合操作。

示例:将传进来的字符串转成数字,加上10,再转为字符串返回。

package FunctionInterface.FunctionDemo;

import java.util.function.Function;

public class andThenDemo {
    public static String fun(String str, Function<String, Integer> f1, Function<Integer, String> f2) {
        return f1.andThen(f2).apply(str);
    }

    public static void main(String[] args) {
        String s1 = "123";
        String s2 = fun(s1,
                (s -> Integer.parseInt(s) + 10),
                (i -> String.valueOf(i))
        );
        System.out.println(s2);
    }
}

(i -> String.valueOf(i))这行代码可以使用方法引用改写为:
(String::valueOf)