• lambda stream流处理异常的方法/不终止stream流处理异常


    lambda stream流处理异常的方法/Either不终止stream流处理异常

    1、直接用try/catch捕获

    1.1 stream流中使用try/catch

    案例如下,在list中存在可能引发空指针的异常,当我需要使用stream流处理list时需要使用try/catch来捕获异常,当然你也可以对任何类型进行处理并捕获任何异常,这里只是举例了List

    public class StreamHandle {
        public static void main(String[] args) {
            new StreamHandle().myFunction();
        }
    
        public void myFunction() {
            List<Integer> list = Arrays.asList(1, 2, null, 4, 5, 6, 7, 8);
            list.stream()
                    .map(value -> {
                        try {
                            return value + 1;
                        } catch (NullPointerException e) {
                            e.printStackTrace();
                            throw new NullPointerException();
                        }
                    })
                    .forEach(System.out::println);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    当我们捕获到异常时可以打印异常信息或者直接throw抛出异常来终止stream流到操作。

    2
    3
    java.lang.NullPointerException
    
    • 1
    • 2
    • 3

    1.2 提取到方法中捕获再调用

    将lambda代码块中的代码抽到方法:增加代码的可读性

    public void myFunction() {
        List<Integer> list = Arrays.asList(1, 2, null, 4, 5, 6, 7, 8);
        list.stream()
                .map(this::doSomeThing)
                .forEach(System.out::println);
    }
    
    private int doSomeThing(int value) {
        try {
            return value + 1;
        } catch (NullPointerException e) {
            e.printStackTrace();
            throw new NullPointerException();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    现在你可以方法中捕获到异常并做一些需要的处理。

    2、改造-包装成运行时异常RuntimeException

    对于很多异常处理,我们会包装成运行时异常。

    2.1 为什么要将异常包装成运行时异常?

    常用的异常继承关系图:
    异常
    因为一些异常比如IO异常不是继承自RuntimeException,而是直接继承自Exception,对于继承自RuntimeException的异常我们当然可以直接抛出:

    private <T> void doSomeThing(T value) {
        throw new NullPointerException();
    }
    
    • 1
    • 2
    • 3

    但是直接继承自Exception的异常我们就不能直接抛出而是需要用throws在方法头处理向上抛出:否则会编译不通过

    private <T> void doSomeThing(T value) throws FileNotFoundException {
        throw new FileNotFoundException();
    }
    
    • 1
    • 2
    • 3

    不过还有另一种常用的解决方案就是将异常包装为运行时异常,这样就可以不增加方法头的处理直接抛出:

    private <T> void doSomeThing(T value) {
        FileNotFoundException e = new FileNotFoundException();
        throw new RuntimeException(e);
    }
    
    • 1
    • 2
    • 3
    • 4

    综上所诉,如果你不想在方法调用链中写许多重复的throws,包装成运行时异常抛出是非常优雅的做法!

    1.2 在lambda调用链中将可能抛出的异常包装成运行时异常

    因为lambda中调用的方法可能会抛出各种异常,你如果每次都使用try/catch包装成RuntimeException会显得很麻烦,我们可以为要调用的可能抛出异常的方法做一个自定义方法接口:

    @FunctionalInterface
    public interface CheckedFunction<T,R> {
        R apply(T t) throws Exception;
    }
    
    • 1
    • 2
    • 3
    • 4

    然后我们可以写一个通用的工具函数包装定义的检查函数,在这个函数中使用try/cat捕获异常并转换成RuntimeException运行时异常抛出(或者其他的uncheckedException)

    public static <T,R> Function<T,R> wrap(CheckedFunction<T,R> checkedFunction) {
        return t -> {
            try {
                return checkedFunction.apply(t);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    现在在stream流中我们可以对任何可能抛出异常的方法,使用自定义的检查函数来包裹处理了

    public void myFunction() {
        List<Integer> list = Arrays.asList(1, 2, null, 4, 5, 6, 7, 8);
        list.stream()
                .map(wrap(this::doSomeThing))
                .forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3、 Either-在不终止stream流的情况下处理异常

    使用方法二虽然可以方便的包裹可能产生的异常,但是当异常抛出时stream流也会立刻终止,当异常出现时,如果你不想停止stream流并继续执行后面的流,就要引入Either了。

    3.1 构造Either类

    Either是函数式语言里很常见的类型,但是目前java还没有。和Optional类型相似,Either是对两种结果的一种包装。它可以是左值的(Left)或者是右值的(Right),也可以都不是。左值和右值都可以是任意类型的。举个例子,如果我们有个Either类型,它的值可以是一个String或者Integer, Either

    根据这个原理,我们可以用Either来实现处理,把异常信息存到左值,正常处理的数据存放到右值。
    实现Either类:

    import org.apache.commons.lang3.tuple.Pair;
    
    import java.util.Optional;
    import java.util.function.Function;
    
    /**
     * 处理函数的包装器 处理stream流中可能产生的异常*
     * @author hexuan*
     * @param 
     * @param 
     */
    public class Either<L, R> {
        /**
         * 左-存储异常信息*
         */
        private final L left;
        /**
         * 右-存储正常的CheckedFunction处理结果*
         */
        private final R right;
    
        private Either(L left, R right) {
            this.left = left;
            this.right = right;
        }
    
        public static <T, R> Function<T, Either> liftWithValue(CheckedFunction<T, R> function) {
            return t -> {
                try {
                    return Either.Right(function.apply(t));
                } catch (Exception ex) {
                    return Either.Left(Pair.of(ex, t));
                }
            };
        }
    
        public static <T, R> Function<T, Either> lift(CheckedFunction<T, R> function) {
            return t -> {
                try {
                    return Either.Right(function.apply(t));
                } catch (Exception ex) {
                    return Either.Left(ex);
                }
            };
        }
    
        public static <L, R> Either<L, R> Left(L value) {
            return new Either(value, null);
        }
    
        public static <L, R> Either<L, R> Right(R value) {
            return new Either(null, value);
        }
    
        public <T> Optional<T> mapLeft(Function<? super L, T> mapper) {
            if (isLeft()) {
                return Optional.of(mapper.apply(left));
            }
            return Optional.empty();
        }
    
        public <T> Optional<T> mapRight(Function<? super R, T> mapper) {
            if (isRight()) {
                return Optional.of(mapper.apply(right));
            }
            return Optional.empty();
        }
    
        @Override
        public String toString() {
            if (isLeft()) {
                return "Left(" + left + ")";
            }
            return "Right(" + right + ")";
        }
    
        public Optional<L> getLeft() {
            return Optional.ofNullable(left);
        }
    
        public Optional<R> getRight() {
            return Optional.ofNullable(right);
        }
    
        public boolean isLeft() {
            return left != null;
        }
    
        public boolean isRight() {
            return right != null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    3.2 将异常和正确数据左右分离

    我们先看这个函数:通过在Either里添加这个静态的lift方法,我们现在可以简单地“改进”一个抛出checkedException的函数,让它返回一个Either对象。如果我们现在回到最原始的问题上,我们现在拿到的是一个Either的流(Stream),而不是一个包含RuntimeException的不稳定的流(Stream)。

    public static <T, R> Function<T, Either> lift(CheckedFunction<T, R> function) {
        return t -> {
            try {
                return Either.Right(function.apply(t));
            } catch (Exception ex) {
                return Either.Left(ex);
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    调用方式:

    public void myFunction() {
        List<Integer> list = Arrays.asList(1, 2, null, 4, 5, 6, 7, 8);
        list.stream()
                .map(Either.lift(this::doSomeThing))
                .forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    执行结果:现在正确数据都存放到了Right,而错误的数据则是Left,我们甚至可以根据需要分开处理了

    Right(2)
    Right(3)
    Left(java.lang.NullPointerException)
    Right(5)
    Right(6)
    Right(7)
    Right(8)
    Right(9)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    比如可以对异常进行日志操作(可以封装为方法),正确返回的数据按需要处理:

    public void myFunction() {
        List<Integer> list = Arrays.asList(1, 2, null, 4, 5, 6, 7, 8);
        list.stream()
                .map(Either.lift(this::doSomeThing))
                .peek(either -> {
                    if (either.getLeft().isPresent()) {
                        System.out.println(JSONObject.toJSONString(either.getLeft().get()));
                    }
                })
                .filter(either -> either.getRight().isPresent())
                .forEach(either ->  System.out.println(either.getRight().get()));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    输出结果:

    2
    3
    {"@type":"java.lang.NullPointerException","stackTrace":[{"className":".........
    5
    6
    7
    8
    9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.3 当出现异常时保存原始参数

    引入了Pari类,这是一个能保存两个值的类型,我上面是直接引入了现成的,你也可以自己定义这个能保存两个值的简单类型,它大概长这样:

        public class Pair<F,S> {
            public final F fst;
            public final S snd;
            private Pair(F fst, S snd) {
                this.fst = fst;
                this.snd = snd;
            }
            public static <F,S> Pair<F,S> of(F fst, S snd) {
                return new Pair<>(fst,snd);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    函数定中异常处理的部分就再用Pari包一层异常和原数据就可以啦:

    public static <T, R> Function<T, Either> liftWithValue(CheckedFunction<T, R> function) {
        return t -> {
            try {
                return Either.Right(function.apply(t));
            } catch (Exception ex) {
                return Either.Left(Pair.of(ex, t));
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    总结

    当你在使用一个抛出checkedException的函数式,如果你想要在lambda里使用它,你需要做一些额外的工作,比如将异常包装成RuntimeException是一种可行的方案。这种方法非常适合创建一个简单的工具包装函数,然后每次你只需要调用这个检查函数而不必再写try/catch。

    如果你想进一步控制异常并在不终止stream的需求下,可以使用Either来再对函数包装,把异常和正确执行结果分开处理。

  • 相关阅读:
    使用docker搭建owncloud && Harbor && 构建镜像
    勇敢的人先享受世界?
    使用Java语言做几个小小练习题吧
    HashMap-红黑树插入平衡、左旋、右旋源码解析
    去中心化标志符在DID中的核心地位
    【教程】Derby数据库安装与使用
    数据分析面试手册《统计篇》
    Elasticsearch:构建地图以按国家或地区比较指标
    c编译器学习02:chibicc文档翻译
    微信小程序自定义导航
  • 原文地址:https://blog.csdn.net/a2272062968/article/details/127819549