Java8:当 Lambda 遇上受检异常

12 篇文章 1 订阅

在java8的Stream中使用lambda表达式编程,如下:

static void test0() throws Exception {
		long count = Files.walk(Paths.get("E:/ws_test"))// 获得项目目录下的所有目录及文件
				.filter(file -> !Files.isDirectory(file)) // 筛选出文件
				.filter(file -> file.toString().endsWith(".java"))// 筛选出 java 文件
				.flatMap(file -> Files.lines(file)) // 按行获得文件中的文本
				.filter(line -> !line.trim().isEmpty()) // 过滤掉空行
				.count();
		
		System.out.println("代码行数:" + count);
	}

补充:

  • Files.walk(Path) 在 JDK1.8 时添加,深度优先遍历一个 Path (目录),返回这个目录下所有的Path(目录和文件),通过 Stream<Path> 返回;
  • Files.lines(Path) 也是在 JDK1.8 时添加,功能是返回指定Path(文件)中所有的行,通过 Stream<String> 返回;

上述代码编译报错,因为 Files.lines(Path) 会抛出 IOException。及时将上述代码修改成如下,仍然编译报错:

static void test0() throws Exception {
    try{
		long count = Files.walk(Paths.get("E:/ws_test"))// 获得项目目录下的所有目录及文件
				.filter(file -> !Files.isDirectory(file)) // 筛选出文件
				.filter(file -> file.toString().endsWith(".java"))// 筛选出 java 文件
				.flatMap(file -> Files.lines(file)) // 按行获得文件中的文本
				.filter(line -> !line.trim().isEmpty()) // 过滤掉空行
				.count();
		
		System.out.println("代码行数:" + count);
    } catch(Exception e){

    }
}

原因是:在JDK类中,lambda不在您编写的位置评估,而是在某些完全不相关的位置评估。修改成如下代码就可编译通过:

static void test1() throws Exception {
		long count = Files.walk(Paths.get("E:/ws_test"))// 获得项目目录下的所有目录及文件
				.filter(file -> !Files.isDirectory(file)) // 筛选出文件
				.filter(file -> file.toString().endsWith(".java"))// 筛选出 java 文件
				.flatMap(file -> {
					try {
						return Files.lines(file);
					} catch (IOException e) {
						return Stream.empty();
					}
				}) // 按行获得文件中的文本
				.filter(line -> !line.trim().isEmpty()) // 过滤掉空行
				.count();

		System.out.println("代码行数:" + count);
}

虽然可以编译、运行通过,但是这样写法不够简洁,而且不符合lambda的one-liner expression

解决方法一:

新建一个方法,在其中处理各种异常,getLines 方法中抛出异常时我们输出了异常,并返回一个空的 Stream:

static void test2() throws IOException {
    long count = Files.walk(Paths.get("E:/ws_test")) // 获得项目目录下的所有文件
            .filter(file -> !Files.isDirectory(file)) // 筛选出文件
            .filter(file -> file.toString().endsWith(".java")) // 筛选出 java 文件
            .flatMap(file -> getLines(file)) // 按行获得文件中的文本
            .filter(line -> !line.trim().isEmpty()) // 过滤掉空行
            .count();

    System.out.println("代码行数:" + count);
}
private static Stream<String> getLines(Path file) {
    try {
        return Files.lines(file);
    } catch (IOException ex) {
        ex.printStackTrace(System.err);
        return Stream.empty();
    }
}

这种方法和最上面的try-catch一样,不具有通用性。

方法二:

将会抛出异常的函数进行包装,使其不抛出受检异常。

如果一个 FunctionInterface 的方法会抛出受检异常(比如 Exception),那么该 FunctionInterface 便可以作为会抛出受检异常的 Lambda 的目标类型

我们定义如下一个受检的 FunctionInterface:

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

那么该 FunctionalInterface 便可以作为类似于file -> File.lines(file) 这类会抛出受检异常的 Lambda 的目标类型,此时 Lambda 中并不需要捕获异常(因为目标类型的 apply 方法已经将异常抛出了)。

之所以原来的 Lambda 需要捕获异常,就是因为在流式操作 flatMap 中使用的 java.util.function 包下的 Function<T, R> 没有抛出异常:

java.util.function.Function

那我们如何使用 CheckedFunction 到流式操作的 Lambda 中呢?
首先我们定义一个 Attempt 接口,它的 apply 静态方法提供将 CheckedFunction 包装为 Function 的功能:

public interface Attempt {
   static <T, R> Function<T, R> apply(CheckedFunction<T, R> function) {
        Objects.requireNonNull(function);//如果为空会抛nullpoint异常

        return t -> {
            try {
                return function.apply(t);
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        };
    }
}

然后在原先的代码中,我们使用 Attempt.apply 方法来对会抛出受检异常的 Lambda 进行包装:

static void test3() throws IOException {
		long count = Files.walk(Paths.get("E:/ws_test")) // 获得项目目录下的所有文件
				.filter(file -> !Files.isDirectory(file)) // 筛选出文件
				.filter(file -> file.toString().endsWith(".java")) // 筛选出 java 文件
				.flatMap(Attempt.apply(file -> Files.lines(file)))  // 按行获得文件中的文本
				.filter(line -> !line.trim().isEmpty()) // 过滤掉空行
				.count();
		
		System.out.println("代码行数:" + count);
}

此时,我们便可以选择是否去捕获异常(RuntimeException)。这种解决方法下,我们一般不关心抛出异常的情况 —— 比如自己写的小例子,抛出了异常程序就该终止;或者你知道这个 Lambda 确实 100% 不会抛出异常。

不过我更倾向于抛出异常时,我们来指定处理的方式:

public interface Attempt {
   static <T, R> Function<T, R> apply(CheckedFunction<T, R> function) {
        Objects.requireNonNull(function);//如果为空会抛nullpoint异常

        return t -> {
            try {
                return function.apply(t);
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        };
    }
    static <T, R> Function<T, R> apply(CheckedFunction<T, R> function,Function<Throwable, R> handler) {
        Objects.requireNonNull(function);
        Objects.requireNonNull(handler);

        return t -> {
            try {
                return function.apply(t);
            } catch (Throwable e) {
                return handler.apply(e);
            }
        };
    }
}

比如我们前面的例子,如果 file -> Files.lines(file) 抛出异常了,说明在访问 file 类的时候出了问题,我们可以就假设这个文件的行数为 0 ,那么默认值就是个空的 Stream<String>(当然你也可以选择顺手记录一下异常):

static void test4() throws IOException {
		long count = Files.walk(Paths.get("E:/ws_test")) // 获得项目目录下的所有文件
				.filter(file -> !Files.isDirectory(file)) // 筛选出文件
				.filter(file -> file.toString().endsWith(".java")) // 筛选出 java 文件
				.flatMap(Attempt.apply(file -> Files.lines(file),ex -> Stream.empty()))  // 按行获得文件中的文本
				.filter(line -> !line.trim().isEmpty()) // 过滤掉空行
				.count();

		System.out.println("代码行数:" + count);
}

注意使用 CheckedFunction这种方式更为通用 —— 类似的,我们可以包装:

  • CheckedConsumer 为 java.util.function.Consumer,
  • CheckedSupplier 为 java.util.function.Suppiler...
public interface Attempt {

    ......

    /**
     * 包装受检的 Consumer
     */
    static <T> Consumer<T> accept(CheckedConsumer<T> consumer) {
        Objects.requireNonNull(consumer);

        return t -> {
            try {
                consumer.accept(t);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        };
    }

    /**
     * 包装受检的 Consumer,并自定义异常处理
     */
    static <T> Consumer<T> accept(CheckedConsumer<T> consumer, Consumer<Throwable> handler) {
        Objects.requireNonNull(consumer);
        Objects.requireNonNull(handler);

        return t -> {
            try {
                consumer.accept(t);
            } catch (Throwable e) {
                handler.accept(e);
            }
        };
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值