Java8 函数式编程——基础篇

函数式编程

一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算(lambda calculus)为该语言最重要的基础。

特性

函数是“第一等公民”

函数与其他数据类型一样,可以赋值给其他变量,也可以作为参数,也可以作为返回值

不可变性

像闭包一样,传入的自由变量是不可变的,降低数据的不一致性。同时也只返回新的值,不修改变量状态,没有“副作用”。

为什么使用

  • 代码简洁,开发快速
  • 易于理解,降低风险
  • 易于并行
  • 延迟执行

例: 根据用户名批量并行获取工号

Before

public List<String> listEmployIds(List<String> usernames) throws Exception {
        CountDownLatch countDownLatch = new CountDownLatch(usernames.size());
        ConcurrentHashMap<String, UserDTO> mapping = new ConcurrentHashMap<>();
        for (String username : usernames) {
            ThreadPoolUtil.execute(() -> {
                UserDTO user = userService.getUserDetail(username);
                mapping.put(username, user);
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        List<String> employIds = new ArrayList<>(usernames.size());
        for (UserDTO user : mapping.values()) {
            employIds.add(user.getEmployeeId());
        }
        return employIds;
    }

After

public List<String> listEmployIds(List<String> usernames) throws Exception {
      return usernames.parallelStream().map(userService::getUserDetail).map(UserDTO::getEmployeeId);

基础点

lambda表达式

函数式编程的基础,用于创建函数式接口的实现对象。函数式接口就是只包含一个抽象方法的接口,如Runnable@FunctionalInterface注解用于提供约束。

变量作用域

lambda表达式可以理解为匿名内部类的“语法糖”,但略有些不同。

  • “捕获”传入参数,复制到生成的实例中
  • 自由变量必须为有效final
  • 与内部类不同,lambda表达式方法体中的变量与上层嵌套代码块有着相同的作用域,this也指向外层的对象
public class MyTest {
    public void test() {
        int i = 0;
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 8; // 正确
                System.out.println(this); //com.didi.oe.controller.MyTest$1@16275d20 名字为1的匿名内部类
            }
        }).start();

        new Thread(() -> {
            // int i = 8; // Variable 'i' is already defined in the scope
            System.out.println(this); // com.didi.oe.controller.MyTest@12b968dd
        }).start();
    }
}

Stream

  • 不存储元素,元素依然保存在源集合之中
  • 不改变源集合
  • 延迟执行

并行流

  • 通过parallelStream()stream().parallel()开启并行流,使用sequential()切回串行流
  • 并行返回结果与串行时相同,可通过unordered()放弃有序加快速度。

异常

  • 异常将抛给lambda表达式的调用者
  • 并行流中只要有一个线程抛出异常则强行结束所有线程并抛出异常

开发中的使用场景

Optional

Optional对象是对类型T对象的封装,也可以表示为空对象。可理解为对单对象进行Stream封装。比直接指向T类型的引用更加安全(正确使用的情况下)。

        // 不恰当的使用
        Optional<T> opt = ...;
        if (opt.isPresent()) {
            opt.get().someMethod();
        }
        // 正解
        opt.ifPresent(value -> {
            value.someMethod();
        });

ifNotPresent的处理方式:

    // java9以上
    opt.ifPresentOrElse(value -> value.someMethod(), () -> logger.error("…"));
 
    // java8
    opt.map(value -> {
            value.someMethod();
            return value;
        }).orElseGet(() -> {
            // operation
            return null;
        });

存在问题:表达式中无法直接返回跳出父方法
可通过异常代替。

默认值:

        opt.orElse("");
        opt.orElseThrow(ResourceNotFoundException::new);
		// 注意以下两种写法,前者可以延迟执行
        opt.orElseGet(() -> RandomStringUtils.random(1));
        opt.orElse(RandomStringUtils.random(1));

流式处理

Optional 的 map() 与 flatMap() 方法

简化的源码如下,本质都是为了返回Optional,以支持流式调用。

    public Optional map(Function mapper) {
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

    public Optional flatMap(Function mapper) {
        if (!isPresent())
            return empty();
        else {
            return mapper.apply(value);
        }
    }

示例:

        // 先前的写法
        JSONObject json = new JSONObject();
        JSONObject user = json.getJSONObject("user");
        if (user != null) {
            JSONObject address = json.getJSONObject("address");
            if (user != address) {
                String street = json.getString("street");
                if (street != null) {
                    return street;
                } else {
                    return "not found";
                }
            }
        }
        
        // Optional写法
        return Optional.ofNullable(json).map(jsonObject -> jsonObject.getJSONObject("user"))
            .map(jsonObject ->jsonObject.getJSONObject("address"))
            .map(jsonObject -> jsonObject.getString("street")).orElse("not found");
       // 使用Class解析的情况
        return Optional.ofNullable(resopnse).map(Response::getUser).map(User::getAddress).map(Address::getStreet)
            .orElse("not found");

优势

  • 更加安全,显式地提醒对空指针的处理
  • 简化代码

使用时机

  • Optional的预期用途主要是作为方法返回类型。获取返回值后,可以根据值是否存在进行不同的业务逻辑处理(建议后续业务中统一使用Optional)
  • 不要将其用作类中的字段,及controller类返回值,因为序列化需特殊支持
  • 不要将其用作构造函数和方法的参数,这会导致不必要的复杂代码

聚合之坑

常见使用方式

        List<UserDTO> users = new ArrayList<>();
        users.stream().map(UserDTO::getEmployeeId).collect(Collectors.toList());
        users.stream().map(UserDTO::getEmployeeId).collect(Collectors.joining(","));
        users.stream().collect(Collectors.toMap(UserDTO::getUsername, Function.identity()));

坑1

Collectors.toMap存在多个相同key时,将会抛出IllegalStateException异常。需显式指定:

       users.stream().collect(Collectors.toMap(UserDTO::getUsername, UserDTO::getEmployeeId, (oldValue, newValue) -> newValue));
        

坑2

Collectors.toMap的value为空时抛出NullPointerException异常,这是OpenJDK的已知BUG,解决方案:

		// 自己实现聚合逻辑,还可以替换默认的HashMap类型,如果LinkedHashMap
        users.stream().collect(HashMap::new, (map, user)-> map.put(user.getUsername(), user.getEmployeeId()), HashMap::putAll);

其他

性能

http://worldcomp-proceedings.com/proc/p2015/SER2509.pdf

函数式编程(Functional Programming)与 面向对象编程(Object-oriented Programming)

两种不同的看待事物的方式。
FP以lambda为基础,强调在逻辑处理中不变性的重要性。所谓不变性就是把一切“状态”都消除。由确定的输入经过确定的一组函数处理得到的最终结果

OOP把对象作为程序的基本单元,强调object之间的消息传递。类对象通过继承、多态、消息等机制达到灵活性和扩展性。

没有银弹。

业务领域建模,这个过程就是多个独立的“对象”在相互协作的结果。因此OOP在这个层面上对这个流程进行抽象是很合适的。

当对一组数据做加工时,先查询,然后聚合,聚合后排序,再join,再排序,再聚合,再转换(map)得到最终的结果。这个过程,用FP的函数就很自然。

其他知识点

  • 花样排序(二级排序,null情况等)
  • stream分组、分片
  • 函数式接口类别,实际使用
  • parallelStream机制(forkJoin)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章