31中级 - Java 8函数式编程

166 阅读7分钟

函数式编程深入浅出

  • 使用函数式编程的原因
    • 减少工作量
    • 提高效率
    • 减少bug

案例,声明一个接口和内部类实现一个过滤接口

 // 过滤姓王的用户
    public static List<User> filterWangUsers(List<User> users) {
        return filter(users, new 姓王的条件());
    }
    // 你可以发现,在上面三个函数中包含大量的重复代码。
    // 请尝试通过Predicate接口将上述代码抽取成一个公用的过滤器函数
    // 并简化上面三个函数

    static class 姓王的条件 implements 条件 {
        @Override
        public boolean 满足当前条件(User user) {
            return user.name.startsWith("王");
        }
    }

    interface 条件 {
        boolean 满足当前条件(User user);
    }
    public static List<User> filter(List<User> users,条件 一个条件 ) {
        List<User> results = new ArrayList<>();
        for (User user : users) {
            if (一个条件.满足当前条件(user)) {
                results.add(user);
            }
        }
        return results;
    }
}
  • 还是觉得啰嗦,第一步简化,去掉啰嗦的接口
 // 过滤姓王的用户
    public static List<User> filterWangUsers(List<User> users) {
        return filter(users, new 姓王的条件());
    }
    // 你可以发现,在上面三个函数中包含大量的重复代码。
    // 请尝试通过Predicate接口将上述代码抽取成一个公用的过滤器函数
    // 并简化上面三个函数

    static class 姓王的条件 implements Predicate<User> {
        @Override
        public boolean test(User user) {
            return false;
        }
    }
    
    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;
    }
  • predicate就是一个判断一个条件,是抽象的是一个接口,必须要传递进去一个实现类
  • java8之前的写法,只能通过接口加匿名类的方式去实现一个抽象的函数式操作
package com.github.hcsp.polymorphism;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class User {
    /** 用户ID,数据库主键,全局唯一 */
    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;
    }

    // 过滤ID为偶数的用户
    public static List<User> filterUsersWithEvenId(List<User> users) {
        return filter(users, new IdIsEvenPredicate());
    }

    static class IdIsEvenPredicate implements Predicate<User> {
        @Override
        public boolean test(User user) {
            return user.id % 2 == 0
        }
    }

    // 过滤姓张的用户
    public static List<User> filterZhangUsers(List<User> users) {

        List<User> results = new ArrayList<>();
        for (User user : users) {
            if (user.name.startsWith("张")) {
                results.add(user);
            }
        }
        return results;
    }


    // 过滤姓王的用户
    public static List<User> filterWangUsers(List<User> users) {
        return filter(users, new 姓王的条件());
    }
    // 你可以发现,在上面三个函数中包含大量的重复代码。
    // 请尝试通过Predicate接口将上述代码抽取成一个公用的过滤器函数
    // 并简化上面三个函数

    static class 姓王的条件 implements Predicate<User> {
        @Override
        public boolean test(User user) {
            return false;
        }
    }

    public static List<User> filter(List<User> users,Predicate<User> 一个条件 ) {
        List<User> results = new ArrayList<>();
        for (User user : users) {
            if (一个条件.test(user)) {
                results.add(user);
            }
        }
        return results;
    }
}
  • java8之后有了很大的改观,lamada表达式
package com.github.hcsp.polymorphism;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class User {
    /** 用户ID,数据库主键,全局唯一 */
    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;
    }

    // 过滤ID为偶数的用户
    public static List<User> filterUsersWithEvenId(List<User> users) {
        return filter(users, user -> user.id % 2 == 0);
    }



    // 过滤姓张的用户
    public static List<User> filterZhangUsers(List<User> users) {

        List<User> results = new ArrayList<>();
        for (User user : users) {
            if (user.name.startsWith("张")) {
                results.add(user);
            }
        }
        return results;
    }


    // 过滤姓王的用户
    public static List<User> filterWangUsers(List<User> users) {
        return filter(users, new Predicate<User>() {
            @Override
            public boolean test(User user) {
                return user.getName().startsWith("王");
            }
        });
    }
    // 你可以发现,在上面三个函数中包含大量的重复代码。
    // 请尝试通过Predicate接口将上述代码抽取成一个公用的过滤器函数
    // 并简化上面三个函数


    public static List<User> filter(List<User> users,Predicate<User> 一个条件 ) {
        List<User> results = new ArrayList<>();
        for (User user : users) {
            if (一个条件.test(user)) {
                results.add(user);
            }
        }
        return results;
    }
}
  • 静态方法也可以转成函数接口
private static boolean userWithEvenId(User user) {
      return user.getId() % 2 == 0;
}
  • 方法引用的写法,这样的好处是
    • 1.他是有名字的,可以对做什么事情进行解释
    • 2.可以写多行,超过两行就不要写lambada表达式了
private static boolean userWithEvenId(User user) {
    return user.getId() % 2 == 0;
}

// 过滤ID为偶数的用户
public static List<User> filterUsersWithEvenId(List<User> users) {
    return filter(users, User::userWithEvenId);
}
  • 实例方法,也可以作为函数接口
//    实例方法,第一个参数永远存在 User this
    public boolean isWang() {
        return this.name.startsWith("王");
    }
  • 总结三种方法 16.png

什么是函数接口

  • 任何只包含一个抽象方法的接口都可以被自动转换为函数接口
  • abstract 方法,抽象方法就是没有方法体
 // 任何只包含一个抽象方法的接口都可以被自动转换为函数接口
    public static List<User> filter(List<User> users,阿猫阿狗 predicate ) {
        List<User> results = new ArrayList<>();
        for (User user : users) {
            if (predicate.吃骨头(user)) {
                results.add(user);
            }
        }
        return results;
    }

    interface 阿猫阿狗 {
        boolean 吃骨头(User user);
    }

Predicate之外其他的函数接口

  • 在java.util.function包中
  • consumer,输入是一个具体的对象,输出是虚空,把一个对象虚空
  • 例子1
    public static void main(String[] args) {
        List<User> users = new ArrayList<>();
        users.forEach(new Consumer<User>() {
            @Override
            public void accept(User user) {
                System.out.println(user);
            }
        });
    }
  • 变成lambda
public static void main(String[] args) {
    List<User> users = new ArrayList<>();
    users.forEach(user -> System.out.println(user));
}
  • 进一步变成实例方法引用
public static void main(String[] args) {
    List<User> users = new ArrayList<>();
    users.forEach(System.out::println);
}
  • 变成静态方法引用
   public static void main(String[] args) {
        List<User> users = new ArrayList<>();
        users.forEach(User::print);
    }

    private static void print(User user) {

    }

Function接口

  • 用于把一个类型变换成另一个类型
// 输入是一个 隐式的 User this
    public String getName() {
        return name;
    }

    public static void main(String[] args) {
        List<User> users = new ArrayList<>();
        users.forEach(System.out::println);
        map(new User(1, "name"), User::getName);
    }

    public static String map(User user, Function<User, String> function) {
        return function.apply(user);
    }

Supplier接口

  • consumer的逆操作,从虚空变出来一个东西
public static String map(User user, Function<User, St ring> function) {
        create(Object::new);
        create(()->"");
        create(()->new User(1, "name"));
    }

    private static Object create(Supplier<Object> supplier) {
        return supplier.get();
    }

除了常见的这些,还有三套平行的Double int lang

  • 二元运算符,一元云算符

传统方法实现比较器

public static void main(String[] args) throws IOException {
        List<Point> points =
                Arrays.asList(
                        new Point(2, 0),
                        new Point(-1, 1),
                        new Point(1, -1),
                        new Point(2, 1),
                        new Point(2, -1));
        Collections.sort(points, new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                if (o1.x < o2.x) {
                    return 1;
                } else if (o1.x > o2.x) {
                    return -1;
                }

                if (o1.y < o2.y) {
                    return 1;
                } else if (o1.y > o2.y) {
                    return -1;
                }
                return 0;
            }
        });
    }
  • java8的写法
// 先按照x反序排,再按y的反序排
Collections.sort(points, comparing(Point::getX)
                .reversed()
                .thenComparing(comparing(Point::getY).reversed()));

课后练习题

  • 1
package com.github.hcsp.functional;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;

public class RefactorToSupplier {
    private static int randomInt() {
        return new Random().nextInt();
    }

    static int a = 0;

    public static void main(String[] args) {
        System.out.println(createObjects());
        System.out.println(createStrings());
        System.out.println(createRandomIntegers());
    }

    private static Object getObject() {
        return new Object();
    }

    private static String getString() {
        return "" + a++;
    }

    private static int getRandom() {
        return randomInt();
    }

    // 请尝试使用函数式接口Supplier对三个方法进行重构,消除冗余代码
    // 并尽量尝试使用lambda表达式和方法引用来传递参数
    public static List<Object> create(Supplier<Object> supplier) {
        List<Object> result = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            result.add(supplier.get());
        }
        return result;
    }


    public static List<Object> createObjects() {
        return create(RefactorToSupplier::getObject);
    }

    public static List<Object> createStrings() {
        return create(RefactorToSupplier::getString);
    }

    public static List<Object> createRandomIntegers() {
        return create(RefactorToSupplier::getRandom);
    }
}

  • 2
package com.github.hcsp.functional;

public class PriceCalculator {
    public static void main(String[] args) {
        int originalPrice = 100;
        User vipUser = User.vip("张三");
        // 不打折
        calculatePrice((price, user) -> price, originalPrice, vipUser);
        // 全场95折
        calculatePrice((price, user) -> (int) (price * 0.95), originalPrice, vipUser);
        // 只有VIP打95折,其他人保持原价
        calculatePrice(
                (price, user) -> user.isVip() ? (int) (price * 0.95) : price,
                originalPrice,
                vipUser);
    }
    // 还记得策略模式么?有了函数式接口之后,策略模式的实现就更加简单了
    // 使用函数式接口重构这个方法,将原先的三种策略作为参数传入
    //
    // 你可以选择自己声明一个接口,例如DiscountStrategy:

    interface DiscountStrategy {
        int discount(int price, User user);
    }

    static int calculatePrice(DiscountStrategy strategy, int price, User user) {
        return strategy.discount(price, user);
    }

    //
    // 或是使用JDK自带的函数式接口BiFunction
    //
    // static int calculatePrice(BiFunction<Integer,User,Integer> strategy, int price, User user)

//    public static int calculatePrice(String discountStrategy, int price, User user) {
//        switch (discountStrategy) {
//            case "NoDiscount":
//                return price;
//            case "Discount95":
//                return (int) (price * 0.95);
//            case "OnlyVip":
//                {
//                    if (user.isVip()) {
//                        return (int) (price * 0.95);
//                    } else {
//                        return price;
//                    }
//                }
//            default:
//                throw new IllegalStateException("Should not be here!");
//        }
//    }
}
  • 3
package com.github.hcsp.functional;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;

public class Order {
    // 订单编号,全局唯一
    private Integer id;
    // 下单时间
    private Instant orderTime;
    // 是否开启,true为开启,false为关闭
    private boolean open;
    // 订单金额
    private BigDecimal amount;

    public Order(Integer id, Instant orderTime, boolean open, BigDecimal amount) {
        this.id = id;
        this.orderTime = orderTime;
        this.open = open;
        this.amount = amount;
    }

    public Integer getId() {
        return id;
    }

    public Instant getOrderTime() {
        return orderTime;
    }

    public boolean isOpen() {
        return open;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    @Override
    public String toString() {
        return "Order{"
                + "id="
                + id
                + ", orderTime="
                + orderTime
                + ", open="
                + open
                + ", amount="
                + amount
                + '}';
    }

    // 请尝试编写一个方法,输入一个订单列表,输出一个TreeSet,TreeSet中订单的排序规则是:
    // 1.首先按照是否关闭排序,未关闭的订单靠前;
    // 2.然后按照订单金额排序,订单金额大的靠前;
    // 3.然后按照下单时间排序,下单时间早的靠前
    public static TreeSet<Order> toTreeSet(List<Order> orders) {
        TreeSet orderTreeSet = new TreeSet<>(Comparator.comparing(Order::isOpen)
                .thenComparing(Order::getAmount)
                .reversed()
                .thenComparing(Order::getOrderTime)
                .thenComparing(Order::getId));
        orderTreeSet.addAll(orders);
        return orderTreeSet;
    }

    public static void main(String[] args) {
        Instant now = Instant.now();
        System.out.println(
                toTreeSet(
                        Arrays.asList(
                                new Order(1, now, false, new BigDecimal("1")),
                                new Order(2, now.minusSeconds(1), true, new BigDecimal("2")),
                                new Order(3, now.minusSeconds(-1), true, new BigDecimal("3")),
                                new Order(4, now.minusSeconds(2), false, new BigDecimal("4")))));
    }
}