纹理和基元
 您显然知道什么是延迟加载 ,对吗? 而且您无疑知道缓存 。 据我所知,Java中没有一种优雅的方法来实现它们中的任何一个。 这是我在Cactoos原语的帮助下为自己找到的。 
Matteo Garrone的《 Reality》(2012年)
假设我们需要一个可以加密某些文本的对象。 以一种更加面向对象的方式讲,它将封装文本并成为其加密形式。 这是我们将如何使用它( 首先创建测试 ):
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
纹理和基元