[Java 8] (6) Lambda与资源管理
资源处理
Java本身自带了垃圾回收(Garbage Collection)功能。可是仅仅有垃圾回收的目标是内部资源(Internal Resource),典型的比方堆上分配的内存区域等。对于外部资源(External Resource),如数据库连接,文件句柄,套接字等资源,还是须要在程序中进行显式回收的。
使用Lambda表达式能够实现一种叫做Execute Around的模式,用来处理外部资源的回收。关于Execute Around模式,能够參考这个链接。
回收资源
以下是一个利用FileWriter完毕消息写入的样例:
public class FileWriterExample {
private final FileWriter writer;
public FileWriterExample(final String fileName) throws IOException {
writer = new FileWriter(fileName);
}
public void writeStuff(final String message) throws IOException {
writer.write(message);
}
public void finalize() throws IOException {
writer.close();
}
//...
} public static void main(final String[] args) throws IOException {
final FileWriterExample writerExample = new FileWriterExample("peekaboo.txt");
writerExample.writeStuff("peek-a-boo");
}
可是执行以上的main方法后会发现。文件peekaboo.txt尽管被创建了,可是它是空的。出现这样的情况的原因在于文件并没有被关闭,也就是说finalize方法没有被调用。
这种方法是由JVM负责调用的。这里没有调用是由于JVM觉得此刻还有足够的内存,不须要执行finalize操作用来回收。毕竟垃圾回收操作也是须要消耗时间的。并且还是一种“Stop-the-world”(停下全部正在执行的应用程序代码)的方式。
关于垃圾回收的基础知识,能够參考这篇文章。
实际上,在《Effective Java》这本书中。明白的指出了不要依赖于finalize方法来运行资源的回收。以上的代码违背这一准则。
关闭资源
更好的方式是直接调用资源的close方法用来回收外部资源:
public void close() throws IOException {
writer.close();
} final FileWriterExample writerExample = new FileWriterExample("peekaboo.txt");
writerExample.writeStuff("peek-a-boo");
writerExample.close();
调用以上的代码后,文件里确实有内容了。可是这样的做法还是有问题。假设在调用writeStuff方法的时候就发生了异常,那么close方法就没有机会被运行了。
确保资源的关闭
能够将close方法的调用放到finally语句中:
final FileWriterExample writerExample = new FileWriterExample("peekaboo.txt");
try {
writerExample.writeStuff("peek-a-boo");
} finally {
writerExample.close();
}
这样的写法也是眼下十分主流的写法,非常多代码都是这样处理外部资源的。可是不认为这段代码噪声过多了。不够简洁吗?针对这样的问题,Java 7中引入了自己主动资源管理(ARM,Automatic Resource Management)这一特性。
它使用了一种特殊形式的try语句,编译器会自己主动地将包括close方法调用的finally语句块插入到try的最后。以下是一个样例:
try(final FileWriterARM writerARM = new FileWriterARM("peekaboo.txt")) {
writerARM.writeStuff("peek-a-boo");
System.out.println("done with the resource...");
}
当try语句块运行完成之后,writeARM这一资源就会被关闭。
然而并非全部的资源都可以利用ARM进行自己主动回收的,须要该资源类实现AutoCloseable接口,当中值包括了一个方法:close()。在Java 8中,Stream接口实现了AutoCloseable接口,也就意味着基于I/O的Stream也可以利用ARM来实现资源的自己主动回收。
为了使用ARM,又一次实现的FileWriterARM例如以下:
public class FileWriterARM implements AutoCloseable {
private final FileWriter writer;
public FileWriterARM(final String fileName) throws IOException {
writer = new FileWriter(fileName);
}
public void writeStuff(final String message) throws IOException {
writer.write(message);
}
public void close() throws IOException {
System.out.println("close called automatically...");
writer.close();
}
//...
}
ARM确实简化了代码,可是仍然须要开发者去显示的调用它。
假设没有调用。程序除了不会关闭资源外。也不会出现什么其它错误。因此。能够对它进行进一步的优化。
使用Lambda表达式来回收资源
之前介绍的ARM有两个基本的缺点:
- 资源须要实现AutoCloseable接口
- 须要显式地使用它
以下我们看看怎样使用Lambda表达式结合Execute Around模式来进行优化:
public class FileWriterEAM {
private final FileWriter writer;
private FileWriterEAM(final String fileName) throws IOException {
writer = new FileWriter(fileName);
}
private void close() throws IOException {
System.out.println("close called automatically...");
writer.close();
}
public void writeStuff(final String message) throws IOException {
writer.write(message);
}
//...
}
能够发现。这个资源类的构造函数被声明成私有的了。也就意味着外部代码不能直接创建这样的资源。
close方法也被声明为私有的。仅仅有writeStuff是公有的方法。
我们须要一个工厂方法来得到该资源类的实例。这一点能够通过静态方法结合Lambda表达式来办到:
public static void use(final String fileName,
final UseInstance<FileWriterEAM, IOException> block) throws IOException {
final FileWriterEAM writerEAM = new FileWriterEAM(fileName);
try {
block.accept(writerEAM);
} finally {
writerEAM.close();
}
} @FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
void accept(T instance) throws X;
}
这个静态工厂方法和传统意义上的静态工厂方法不太一样。它并没有返回被创建的实例,而是马上在方法中使用了被创建的实例。
use方法接受的第二个參数是UseInstance类型的函数接口,它和JDK中的Consumer很类似。仅仅只是它可以抛出一个异常。关于这一点,在之前的文章中进行了介绍。
另外还能够将ARM融合到上面的代码中:
public static void use(final String fileName,
final UseInstance<FileWriterEAM, IOException> block) throws IOException {
try(final FileWriterEAM writerEAM = new FileWriterEAM(fileName)) {
block.accept(writerEAM);
}
}
仅仅只是此时须要FileWriterEAM实现AutoCloseable接口,并将之前的close方法訪问级别从私有变成公有。
使用它也很easy:
// case 1
FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet")); // case 2
FileWriterEAM.use("eam2.txt", writerEAM -> {
writerEAM.writeStuff("how");
writerEAM.writeStuff("sweet");
});
这样的模式克服了之前提到的首要缺点。即须要显式调用try with resource语句进行资源回收。而且它对资源对象的生命周期也进行了非常好的控制,因此它也实现了Loan模式。仅仅有在须要使用一个资源的时候才会创建它,而且在利用完成之后马上将它标记为回收。
锁管理
在并发程序中,锁是一类相当重要的资源,以下我们看看Lambda表达式怎样处理锁资源。
历史悠久的synchronized代码块实际上就是一个典型的Execute Around模式的实现。synchronized关键词的出现能保证同一时刻至多仅仅有一个线程可以执行这段代码。
可是synchronizedkeyword也有其缺点:
- synchronized代码块难以进行超时处理
- synchronized代码块难以进行单元測试
因此为了解决这些问题,Lock接口应运而生。Lock接口可以处理超时的情况。而且由于其本身是一个接口,也easy被Mocking而完毕单元測试。可是天下没有免费的午餐。使用Lock时须要显式地进行加锁和解锁操作。
可是在Java 8中,能够使用Lambda表达式结合前面提到的Execute Around模式来轻松解决这一类问题,以下是一段使用了Lock的代码:
public class Locking {
Lock lock = new ReentrantLock(); //or mock
protected void setLock(final Lock mock) {
lock = mock;
}
public void doOp1() {
lock.lock();
try {
//...critical code...
} finally {
lock.unlock();
}
}
//...
}
上述的doOp1方法噪声太多。过多的加锁解锁和try finally语句块让代码的意图不够清晰。为了使用Lambda表达式。我们能够首先设计一段代码:
public class Locker {
public static void runLocked(Lock lock, Runnable block) {
lock.lock();
try {
block.run();
} finally {
lock.unlock();
}
}
}
上述代码将加锁解锁操作和固定的try finally语句块给抽象成一个方法,然后将真正须要在锁环境中执行的代码通过一个Runnable參数传入。这样一来,其他须要锁环境的操作就能够这样实现了:
public void doOp2() {
runLocked(lock, () -> {/*...critical code ... */});
}
public void doOp3() {
runLocked(lock, () -> {/*...critical code ... */});
}
public void doOp4() {
runLocked(lock, () -> {/*...critical code ... */});
}
[Java 8] (6) Lambda与资源管理的更多相关文章
- java 8 中lambda表达式学习
转自 http://blog.csdn.net/renfufei/article/details/24600507 http://www.jdon.com/idea/java/10-example-o ...
- Lambda 表达式,Java中应用Lambda 表达式
一.Lambda 表达式 简单来说,编程中提到的 lambda 表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数. 链接:知乎 先举一个普通的 Python 例 ...
- Java 终于有 Lambda 表达式啦~Java 8 语言变化——Lambda 表达式和接口类更改【转载】
原文地址 en cn 下载 Demo Java™ 8 包含一些重要的新的语言功能,为您提供了构建程序的更简单方式.Lambda 表达式 为内联代码块定义一种新语法,其灵活性与匿名内部类一样,但样板文件 ...
- Java 8里面lambda的最佳实践
Java 8已经推出一段时间了,越来越多开发人员选择升级JDK,这条热门动弹里面看出,JDK7最多,其次是6和8,这是好事! 在8 里面Lambda是最火的主题,不仅仅是因为语法的改变,更重要的是带来 ...
- JAVA基础知识|lambda与stream
lambda与stream是java8中比较重要两个新特性,lambda表达式采用一种简洁的语法定义代码块,允许我们将行为传递到函数中.之前我们想将行为传递到函数中,仅有的选择是使用匿名内部类,现在我 ...
- 理解和运用Java中的Lambda
前提 回想一下,JDK8是2014年发布正式版的,到现在为(2020-02-08)止已经过去了5年多.JDK8引入的两个比较强大的新特性是Lambda表达式(下文的Lambda特指JDK提供的Lamb ...
- 8000字长文让你彻底了解 Java 8 的 Lambda、函数式接口、Stream 用法和原理
我是风筝,公众号「古时的风筝」.一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...
- Java 8中Lambda表达式默认方法的模板方法模式,你够了解么?
为了以更简单的术语描述模板方法,考虑这个场景:假设在一个工作流系统中,为了完成任务,有4个任务必须以给定的执行顺序执行.在这4个任务中,不同工作流系统的实现可以根据自身情况自定义任务的执行内容. 模板 ...
- Java中的lambda匿名函数使用
Java中的lambda匿名函数使用 lambda匿名函数的使用是为了满足某些情况下需要临时定义函数,或者事先定义,需要时才使用.在python里面,lambda表达式的表达方式为:lambda 参数 ...
随机推荐
- QTP实践总结
QTP实践总结 查询数据库修改freq 1.Testcasetable创建查询select * from testcasetable order by fseq desc 2.设计表-选项-修改自动递 ...
- arm:jlink调试和直接烧写运行的不同 [mdk s3c2440]
1.对全局变量的初始化. 2.还没发现的事例. /*************************************************/ 先上连接文件sct LR_ROM1 0x3000 ...
- javascript每日一练(十二)——运动框架
运动框架 可以实现多物体任意值运动 例子: <!doctype html> <html> <head> <meta charset="utf-8&q ...
- CentOS6.5+php5.3听云安装过程
听云安装过程请参考听云安装指导页面:http://doc.tingyun.com/help/html/doc/server_novice.html 根据自己的服务器系统和语言包下载对应的 安装包 安装 ...
- 【译】在Asp.Net中操作PDF - iTextSharp - 利用列进行排版
原文 [译]在Asp.Net中操作PDF - iTextSharp - 利用列进行排版 在使用iTextSharp通过ASP.Net生成PDF的系列文章中,前面的文章已经讲述了iTextSharp所涵 ...
- Hibernate 3中如何获得库表所有字段的名称
15问:Hibernate 3中如何获得库表所有字段的名称 答:可以使用以下的程序获得. Configuration conf = new Configuration(); conf.configur ...
- mysql basic operation,mysql总结
mysql> select * from wifi_data where dev_id like "0023-AABBCCCCBBAA" ; 1.显示数据库列表.show d ...
- cocos2d-x3.2中加入Android手机震动
本人宣布从此博文发出后,我的cocos2dx的引擎从cocos2dx3.1.1跳到cocos2dx3.2,哈哈,事实上变化不大的,不碍事~~~ 以下来说说在cocos中加入Android手机震动的功能 ...
- PHP - Windows安装Pear
1. 打开命令窗口,切换到php的安装目录,执行以下命令(你也可以添加一个php的环境变量,就不用如此麻烦的切换目录,但是我安装了多个版本的php,所以就没有添加环境变量). 2. 当出现下面这句话时 ...
- [poj 1904]King's Quest[Tarjan强连通分量]
题意:(当时没看懂...) N个王子和N个女孩, 每个王子喜欢若干女孩. 给出每个王子喜欢的女孩编号, 再给出一种王子和女孩的完美匹配. 求每个王子分别可以和那些女孩结婚可以满足最终每个王子都能找到一 ...