Lambda是Java 8引入的一个重要特性,通过Lambda表达式,我们可以以一种更加简洁的方式实现代码编程。但,如果涉及到Exception,应该如何处理?
一般来讲,在Java中,我们都是通过try catch实现异常捕获的,对于Lambda表达式同样也一样,我们先来看一个例子。我们来实现一个简单的网络爬虫,爬虫接受URL列表作为参数,通过遍历URL列表访问对应地址内容,并保存到文本文件中。下面是代码实现:
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.UUID;
public class WebCrawler {
public static void main(String[] args) {
List<String> urlsToCrawl = List.of(“https://masterdevskills.com");
WebCrawler webCrawler = new WebCrawler();
webCrawler.crawl(urlsToCrawl);
}
public void crawl(List<String> urlsToCrawl) {
urlsToCrawl.stream()
.map(urlToCrawl -> new URL(urlToCrawl))
.forEach(url -> save(url));
}
private void save(URL url) throws IOException {
String uuid = UUID.randomUUID().toString();
InputStream inputStream = url.openConnection().getInputStream();
Files.copy(inputStream, Paths.get(uuid + ".txt"), StandardCopyOption.REPLACE_EXISTING);
}
}
代码非常简单,但却无法正确编译,原因在于我们并没有处理CheckedException,new URL(urlsToCrawl)
方法会抛出MalformedURLException,同样的save方法也抛出了IOException,在Lambda表达式中我们都没有对这些异常进行处理。下面我们在Lambda表达式中处理这些异常。
public void crawl(List<String> urlsToCrawl) {
urlsToCrawl.stream()
.map(urlToCrawl -> {
try {
return new URL(urlToCrawl);
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
})
.forEach(url -> {
try {
save(url);
} catch (IOException e) {
e.printStackTrace();
}
});
}
Lambda表达式应该是简洁、明了、纯粹的,但对比上面的代码,这并不满足Lambda表达式的初衷。下面,我们继续重写我们的代码,确保我们的代码可以达到这个目标。
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class WebCrawler {
public static void main(String[] args) {
List<String> urlsToCrawl = List.of("https://masterdevskills.com");
WebCrawler webCrawler = new WebCrawler();
webCrawler.crawl(urlsToCrawl);
}
public void crawl(List<String> urlsToCrawl) {
urlsToCrawl.stream()
.map(this::createURL)
.forEach(this::save);
}
private URL createURL(String urlToCrawl) {
try {
return new URL(urlToCrawl);
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
private void save(URL url) {
try {
String uuid = UUID.randomUUID().toString();
InputStream inputStream = url.openConnection().getInputStream();
Files.copy(inputStream, Paths.get(uuid + ".txt"), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
}
}
再看我们的crawl
方法,这里我们使用了方法引用,结果也是Lambda表达式非常简洁明了,但我们并没有处理异常的问题,只是将处理逻辑移动了位置。而且,这里又引入了一个新的问题,我们通过try-catch的方式捕获异常,但却没有将捕获异常进行向上传递,即调用方法栈中,这就隐藏了异常细节,我们知道,在Java异常处理中,一个基本的准则就是,如果方法无法处理当前异常,就需要继续向上委托。对于,我们代码中引入的新问题,当然,我们可以通过重新抛出RuntimeException
的方式来进行处理,代码可以正常编译,因为,我们无需处理 RuntimeException
。
public void crawl(List<String> urlsToCrawl) {
urlsToCrawl.stream()
.map(this::createURL)
.forEach(this::save);
}
private URL createURL(String urlToCrawl) {
try {
return new URL(urlToCrawl);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
private void save(URL url) {
try {
String uuid = UUID.randomUUID().toString();
InputStream inputStream = url.openConnection().getInputStream();
Files.copy(inputStream, Paths.get(uuid + ".txt"), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
代码可以正常运行,但并没有减少标准框架(try-catch)代码数量,下面我们看下有没有更好的方案,注意,stream的map方法接受函数式接口作为参数,因此,我们可以构建一个类似的函数式接口来处理Checked Exception
@FunctionalInterface
public interface ThrowingFunction<T, R, E extends Throwable> {
R apply(T t) throws E;
}
函数式接口包含了三个通用数据类型,其中一个参数继承Throwable
接口,注意,Java 8 中支持接口中定义静态方法,因此,我们在我们的函数式接口中定义一个静态方法如下:
@FunctionalInterface
public interface ThrowingFunction<T, R, E extends Throwable> {
R apply(T t) throws E;
static <T, R, E extends Throwable> Function<T, R> unchecked(ThrowingFunction<T, R, E> f) {
return t -> {
try {
return f.apply(t);
} catch (Throwable e) {
throw new RuntimeException(e);
}
};
}
}
在上述接口中 ,unchecked
方法接收ThrowingFunction
参数来处理异常,结果抛出RuntimeException
异常,同时返回一个Function
,下面,在我们的代码中使用这个函数式接口。
public void crawl(List<String> urlsToCrawl) {
urlsToCrawl.stream()
.map(ThrowingFunction.unchecked(urlToCrawl -> new URL(urlToCrawl)))
.forEach(this::save);
}
在stream
的map
方法中,ThrowingFunction.unchecked()
负责内部处理异常,并且返回map
方法所需要的参数函数Function
,这样,我们就减少了我们的重复代码,并且在任意需要的地方重用我们的ThrowingFunction
。
对于stream
的forEach
方法,方法接收的是Consumer
作为参数,因此,参照上述代码,这里我们定义一个ThrowingConsumer
函数式接口
public interface ThrowingConsumer<T, E extends Throwable> {
void accept(T t) throws E;
static <T, E extends Throwable> Consumer<T> unchecked(ThrowingConsumer<T, E> consumer) {
return (t) -> {
try {
consumer.accept(t);
} catch (Throwable e) {
throw new RuntimeException(e);
}
};
}
}
同样的,我们的代码中使用如下:
public void crawl(List<String> urlsToCrawl) {
urlsToCrawl.stream()
.map(ThrowingFunction.unchecked(urlToCrawl -> new URL(urlToCrawl)))
.forEach(ThrowingConsumer.unchecked(url -> save(url)));
}
private void save(URL url) throws IOException {
String uuid = UUID.randomUUID().toString();
InputStream inputStream = url.openConnection().getInputStream();
Files.copy(inputStream, Paths.get(uuid + ".txt"), StandardCopyOption.REPLACE_EXISTING);
}
这里,我们代码中没有了try-catch代码块,也没有了其他模板样例代码,我们还可以通过方法引用的方式使我们的Lambda表达式更加简洁。
public void crawl(List<String> urlsToCrawl) {
urlsToCrawl.stream()
.map(ThrowingFunction.unchecked(URL::new))
.forEach(ThrowingConsumer.unchecked(this::save));
}
原文链接
https://dzone.com/articles/how-to-handle-checked-exception-in-lambda-expressi