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);
    }
}

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

2
3
java.lang.NullPointerException

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();
    }
}

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

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

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

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

常用的异常继承关系图:

lambda stream流处理异常的方法/不终止stream流处理异常
因为一些异常比如IO异常不是继承自RuntimeException,而是直接继承自Exception,对于继承自RuntimeException的异常我们当然可以直接抛出:
private <T> void doSomeThing(T value) {
    throw new NullPointerException();
}

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

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

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

private <T> void doSomeThing(T value) {
    FileNotFoundException e = new FileNotFoundException();
    throw new RuntimeException(e);
}

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

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

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

@FunctionalInterface
public interface CheckedFunction<T,R> {
    R apply(T t) throws Exception;
}

然后我们可以写一个通用的工具函数包装定义的检查函数,在这个函数中使用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);
        }
    };
}

现在在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);
}

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;

public class Either<L, R> {

    private final L left;

    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;
    }
}

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);
        }
    };
}

调用方式:

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);
}

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

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

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

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()));
}

输出结果:

2
3
{"@type":"java.lang.NullPointerException","stackTrace":[{"className":".........

5
6
7
8
9

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);
        }
    }

函数定中异常处理的部分就再用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));
        }
    };
}

总结

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

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

Original: https://blog.csdn.net/a2272062968/article/details/127819549
Author: WalkingWithTheWind~
Title: lambda stream流处理异常的方法/不终止stream流处理异常

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/657503/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球