您显然知道什么是延迟加载 ,对吗? 而且您无疑知道缓存 。 据我所知,Java中没有一种优雅的方法来实现它们中的任何一个。 这是我在Cactoos原语的帮助下为自己找到的。
假设我们需要一个可以加密某些文本的对象。 以一种更加面向对象的方式讲,它将封装文本并成为其加密形式。 这是我们将如何使用它(让我们先创建测试 ):
interface Encrypted {String asString() throws IOException;
}
Encrypted enc = new EncryptedX("Hello, world!");
System.out.println(enc.asString());
现在,让我们以一个非常原始的方式用一个主要的构造函数来实现它。 加密机制只会在传入数据中的每个字节上加上+1
,并且会假定加密不会破坏任何内容(一个非常愚蠢的假设,但是对于本示例而言,它将起作用):
class Encrypted1 implements Encrypted {private final String text;Encrypted1(String txt) {this.data = txt;}@Overridepublic String asString() {final byte in = this.text.getBytes();final byte[] out = new byte[in.length];for (int i = 0; i < in.length; ++i) {out[i] = (byte) (in[i] + 1);}return new String(out);}
}
到目前为止看起来正确吗? 我对其进行了测试,并且可以正常工作。 如果输入是"Hello, world!"
,输出将为"Ifmmp-!xpsme\""
。
接下来,假设我们希望我们的类接受InputStream
和String
。 我们想这样称呼它,例如:
Encrypted enc = new Encrypted2(new FileInputStream("/tmp/hello.txt")
);
System.out.println(enc.toString());
这是最明显的实现,具有两个主要的构造函数(同样,实现是原始的,但是可以工作):
class Encrypted2 implements Encrypted {private final String text;Encrypted2(InputStream input) throws IOException {ByteArrayOutputStream baos =new ByteArrayOutputStream();while (true) {int one = input.read();if (one < 0) {break;}baos.write(one);}this.data = new String(baos.toByteArray());}Encrypted2(String txt) {this.text = txt;}// asString() is exactly the same as in Encrypted1
}
从技术上讲,它是可行的,但流读取是在构造函数内部进行的,这是一种不好的做法 。 主要构造函数只能执行属性分配,而次要构造函数只能创建新对象。
让我们尝试重构并引入延迟加载:
class Encrypted3 {private String text;private final InputStream input;Encrypted3(InputStream stream) {this.text = null;this.input = stream;}Encrypted3(String txt) {this.text = txt;this.input = null;}@Overridepublic String asString() throws IOException {if (this.text == null) {ByteArrayOutputStream baos =new ByteArrayOutputStream();while (true) {int one = input.read();if (one < 0) {break;}baos.write(one);}this.text = new String(baos.toByteArray());}final byte in = this.text.getBytes();final byte[] out = new byte[in.length];for (int i = 0; i < in.length; ++i) {out[i] = (byte) (in[i] + 1);}return new String(out);}
}
效果不错,但看起来很丑。 最丑陋的部分当然是这两行:
this.text = null;
this.input = null;
它们使对象可变,并且使用NULL 。 丑陋,相信我。 不幸的是,延迟加载和NULL引用总是在经典示例中并存 。 但是,有一种更好的方法来实现它。 让我们重构类,这次使用Cactoos的 Scalar
:
class Encrypted4 implements Encrypted {private final IoCheckedScalar<String> text;Encrypted4(InputStream stream) {this(() -> {ByteArrayOutputStream baos =new ByteArrayOutputStream();while (true) {int one = stream.read();if (one < 0) {break;}baos.write(one);}return new String(baos.toByteArray());});}Encrypted4(String txt) {this(() -> txt);}Encrypted4(Scalar<String> source) {this.text = new IoCheckedScalar<>(source);}@Overridepublic String asString() throws IOException {final byte[] in = this.text.value().getBytes();final byte[] out = new byte[in.length];for (int i = 0; i < in.length; ++i) {out[i] = (byte) (in[i] + 1);}return new String(out);}
现在看起来好多了。 首先,只有一个主要构造函数和两个次要构造函数。 其次,对象是不可变的 。 第三,还有很多改进的余地:我们可以添加更多的构造函数来接受其他数据源,例如File
或byte数组。
简而言之,应该以“惰性”方式加载的属性在对象内部表示为“功能”(Java 8中的lambda表达式 )。 在我们触摸该属性之前,不会加载该属性。 一旦需要使用它,函数便会执行并得到结果。
这段代码有一个问题。 每当我们调用asString()
,它将读取输入流,这显然是行不通的,因为只有第一次流才会有数据。 在每个后续调用中,流都将为空。 因此,我们需要确保this.text.value()
仅执行一次封装的Scalar
。 所有以后的调用都必须返回先前计算的值。 因此,我们需要对其进行缓存 。 方法如下:
class Encrypted5 implements Encrypted {private final IoCheckedScalar<String> text;// same as above in Encrypted4Encrypted5(Scalar<String> source) {this.data = new IoCheckedScalar<>(new StickyScalar<>(source));}// same as above in Encrypted4
此StickyScalar
将确保仅对其方法value()
的第一次调用将传递给封装的Scalar
。 所有其他呼叫将接收第一个呼叫的结果。
要解决的最后一个问题是关于并发性。 我们上面的代码不是线程安全的。 如果创建Encrypted5
的实例并将其传递给同时调用asString()
两个线程,则结果将是不可预测的,这仅仅是因为StickyScalar
不是线程安全的。 不过,还有另一种可以帮助我们的原语,称为SyncScalar
:
class Encrypted5 implements Encrypted {private final IoCheckedScalar<String> text;// same as above in Encrypted4Encrypted5(Scalar<String> source) {this.data = new IoCheckedScalar<>(new SyncScalar<>(new StickyScalar<>(source)));}// same as above in Encrypted4
现在我们很安全,设计优雅。 它包括延迟加载和缓存。
我现在在许多项目中都使用这种方法,它看起来很方便,清晰且面向对象。
您可能还会发现这些相关的帖子很有趣: 为什么InputStream设计错误 ; 尝试。 最后。 如果。 不。 空值。 ; 每个私有静态方法都是新类的候选人 ; 我将如何重新设计equals() ; 对象行为不可配置 ;
翻译自: https://www.javacodegeeks.com/2017/10/lazy-loading-caching-via-sticky-cactoos-primitives.html