资源处理

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有两个基本的缺点:

  1. 资源须要实现AutoCloseable接口
  2. 须要显式地使用它

以下我们看看怎样使用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也有其缺点:

  1. synchronized代码块难以进行超时处理
  2. 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与资源管理的更多相关文章

  1. java 8 中lambda表达式学习

    转自 http://blog.csdn.net/renfufei/article/details/24600507 http://www.jdon.com/idea/java/10-example-o ...

  2. Lambda 表达式,Java中应用Lambda 表达式

    一.Lambda 表达式 简单来说,编程中提到的 lambda 表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数. 链接:知乎 先举一个普通的 Python 例 ...

  3. Java 终于有 Lambda 表达式啦~Java 8 语言变化——Lambda 表达式和接口类更改【转载】

    原文地址 en cn 下载 Demo Java™ 8 包含一些重要的新的语言功能,为您提供了构建程序的更简单方式.Lambda 表达式 为内联代码块定义一种新语法,其灵活性与匿名内部类一样,但样板文件 ...

  4. Java 8里面lambda的最佳实践

    Java 8已经推出一段时间了,越来越多开发人员选择升级JDK,这条热门动弹里面看出,JDK7最多,其次是6和8,这是好事! 在8 里面Lambda是最火的主题,不仅仅是因为语法的改变,更重要的是带来 ...

  5. JAVA基础知识|lambda与stream

    lambda与stream是java8中比较重要两个新特性,lambda表达式采用一种简洁的语法定义代码块,允许我们将行为传递到函数中.之前我们想将行为传递到函数中,仅有的选择是使用匿名内部类,现在我 ...

  6. 理解和运用Java中的Lambda

    前提 回想一下,JDK8是2014年发布正式版的,到现在为(2020-02-08)止已经过去了5年多.JDK8引入的两个比较强大的新特性是Lambda表达式(下文的Lambda特指JDK提供的Lamb ...

  7. 8000字长文让你彻底了解 Java 8 的 Lambda、函数式接口、Stream 用法和原理

    我是风筝,公众号「古时的风筝」.一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...

  8. Java 8中Lambda表达式默认方法的模板方法模式,你够了解么?

    为了以更简单的术语描述模板方法,考虑这个场景:假设在一个工作流系统中,为了完成任务,有4个任务必须以给定的执行顺序执行.在这4个任务中,不同工作流系统的实现可以根据自身情况自定义任务的执行内容. 模板 ...

  9. Java中的lambda匿名函数使用

    Java中的lambda匿名函数使用 lambda匿名函数的使用是为了满足某些情况下需要临时定义函数,或者事先定义,需要时才使用.在python里面,lambda表达式的表达方式为:lambda 参数 ...

随机推荐

  1. BZOJ 4145: [AMPPZ2014]The Prices( 状压dp + 01背包 )

    我自己只能想出O( n*3^m )的做法....肯定会T O( nm*2^m )做法: dp( x, s ) 表示考虑了前 x 个商店, 已买的东西的集合为s. 考虑转移 : 先假设我们到第x个商店去 ...

  2. Python 第七篇:socket编程

    一:socket基础: 1.1:Socket基础: socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用[打开][读写][关闭]模式来操作.socket就是该模 ...

  3. Java运行时内存

    对于java程序员来说,并不必显示地对内存进行管理,一切都交给java虚拟机去做吧,而且,你也不一定做得比java虚拟机来得专业.好像所有内存管理都交给虚拟机去做就万事大吉了,但是,事实有时并非如此, ...

  4. C++界面库

    刚开始用C++做界面的时候,根本不知道怎么用简陋的MFC控件做出比较美观的界面,后来就开始逐渐接触到BCG  Xtreme ToolkitPro v15.0.1,Skin++,等界面库,以及一些网友自 ...

  5. 基于visual Studio2013解决C语言竞赛题之0804成绩筛选

     题目

  6. MinGW是什么

    MinGW是什么? MinGW是建立在gcc和binutils项目上的,用来编译和连接代码,使之运行在windows系统上: 提供c.c++和fortran编译器和相关工具: MinGW=Minima ...

  7. Creating Spatial Indexes(mysql 创建空间索引 The used table type doesn't support SPATIAL indexes)

    For MyISAM tables, MySQL can create spatial indexes using syntax similar to that for creating regula ...

  8. js导出table到excel,同时兼容FF和IE

    前台调用(第一个参数是table的id): <input value="导出" type="button" /> function toExcel( ...

  9. MongoDB(二)——安装配置了解

    前边介绍了MongoDB的大概理论知识,这篇来对MongoDB进行一下安装使用,支持安装在windows和linux上,当然了很多其它情况下我们是安装在linux上,由于毕竟server用linux的 ...

  10. Xcode6使用storyboard在TabBarController上建立三个以上Item

    在Xcode5上做以上的操作没有问题,这次是要在Xcode6上实现之,特记录以备用. 首先新建一个storyboard文件.取名Custom.storyboard.拖动菜单添加一个TabBarComt ...