三种方式实现函数式编程

三种方式实现函数式编程

摘要

\quad 在Java8引入函数式编程之后,很多复杂的代码可以大大的简化,甚至可以简化成一句话。这里就主要通过一个例子,分析使用使用Lambda表达式、静态方法以及实例方法简化代码的优缺点。

目录

(点击可直接跳转)
*1.前言
*2.使用Lambda表达式
*3.使用静态方法
*4.使用实例方法
*5.总结

1.前言

\quad 通过这个例子,一方面可以认识到,自己以前写过的代码可读性是多么的差,另一方面可以知道如何优雅的使用接口。以前使用接口的时候,只知道实现了这个接口,就代表这个类具有某种能力。但是通过这个例子之后,我看到了接口还可以用在方法中。实现接口的时候,只需要在lambda表达式中提供对应的(输入/返回值)类型即可。

2.使用Lambda表达式

先看下面这段代码:
将map中的k,v值分别用不同的连接符打印出来
public static void printWithComma(Map<String, String> map1, Map<String, String> map2) {
        for (Map.Entry<String, String> entry : map1.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "," + value);
        }

        for (Map.Entry<String, String> entry : map2.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "," + value);
        }
    }

    public static void printWithDash(Map<String, String> map1, Map<String, String> map2) {
        for (Map.Entry<String, String> entry : map1.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "-" + value);
        }

        for (Map.Entry<String, String> entry : map2.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "-" + value);
        }
    }

    public static void printWithColon(Map<String, String> map1, Map<String, String> map2) {
        for (Map.Entry<String, String> entry : map1.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + ":" + value);
        }

        for (Map.Entry<String, String> entry : map2.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + ":" + value);
        }
    }
    
复制代码

看完后发现,里面非常多的重复代码,自然而然的想到,可以将其中的公共代码抽取出来。并且函数是输入两个map,得到一个void,这符合BiConsumer接口设定。所以,可以在抽取的公共方法中实现BiConsumer接口,实现代码复用。
代码如下:

public static void printWithConsumer(
         Map<String, String> map1,
         Map<String, String> map2,
         BiConsumer<String, String> consumer) {
     map1.forEach(consumer);
     map2.forEach(consumer);
 }
复制代码

打印方法就可以被简化成一句话了:

public static void printWithComma(Map<String, String> map1, Map<String, String> map2) {
        printWithConsumer(map1, map2, (s, s2) -> System.out.println(s+","+s2));
    }
复制代码

3.使用静态方法

\quad 同样使用第二个例子,对第二种方法进行简化。

 public static void printWithDash(Map<String, String> map1, Map<String, String> map2) {
        printWithConsumer(map1, map2, RefactorToConsumer::staticPrintWithDash);
    }

    public static void staticPrintWithDash(String s1, String s2) {
        System.out.println(s1 + "-" + s2);
    }
复制代码

\quad定义的静态方法消耗两个String,得到一个void,符合BiCounsumer的约定,所以可以使用静态方法引用的方式简化代码。
\quad分析二者优缺点:
1.使用静态方法可以给方法取名字,这样更加直观,更容易被理解。
2.使用静态方法可以在方法中写较为复杂的代码,而Lambda中一般就是一两句话。所以需要较多的代码实现的时候,最好还是在静态方法中声明。

4.使用实例方法

\quad 为了便于理解,这里使用另外一个简单的例子: 对一个User类实现过滤
代码如下:

public class User {
   
    private final Integer id;
    private final String name;

    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }
    
    // 过滤姓张的用户
    public static List<User> filterZhangUsers(List<User> users) {
        List<User> list = new ArrayList<>();
        for (User user:users
             ) {
            if (user.name.startsWith("张")){
                list.add(user);
            }
        }
        return list;
    }
}
复制代码

\quad 为了减少排版,我这里只写了一个过滤器,但是可以想象如果需要过滤姓王的,ID为偶数的User...我们就可以将其抽取出来,形成一个公共的抽取方法。无论用前面的Lambda表达式也好,用静态方法也可以,实现Predicate接口即可。除此之外,我们还发现这个判定的过程其实就是User类型转换为boolean类型的过程,而恰好方法就是在User类中定义的。所以不必将方法定义成static类型,使用实例方法即可。
代码如下:

public static List<User> filterZhangUsers(List<User> users) {
       return filter(users,User::filterNameStartWithZhang);
    }
    
    public boolean filterNameStartWithZhang(){
        return this.name.startsWith("张");
    }

    public static List<User> filter(List<User> users, Predicate<User> predicate) {
        List<User> results = new ArrayList<>();
        for (User user : users
        ) {
            if (predicate.test(user)) {
                results.add(user);
            }
        }
        return results;
    }
复制代码

\quad由于 filterNameStartWithZhang()方法是非静态的,在方法形参表中看似是空的,实际上形参是(User this),符合Predicate的从某一类型到boolean的设定。

5.总结

总结:
\quad1.将大量的重复代码进行重构,是一件非常有必要的事情。不仅有形的减少代码量,而且在无形中减少了出bug的机率,并且大大的增加了代码的可读性。
\quad 2.使用Lambda表达式需要注意,在表达式中引用的变量都需要是effectively final类型的,否则会报
“Variable used in lambda expression should be final or effectively final”。

解决方法:
1.可以使用Atomic类型的参数。2.可以将参数声明为全局类型的。
详细参考这篇文章:zhuanlan.zhihu.com/p/82921974

猜你喜欢

转载自juejin.im/post/5d819a03e51d4561c83e7d08
今日推荐