我之前问过一次,我的帖子因为没有提供使用帮助类的代码而被删除。这次我创建了一个完整的测试套件来显示确切的问题。
我认为 Java 的 ZipInputStream 打破了关于 InputStream 抽象类的 Liskov 替换原则 (LSP)。如果 ZipInputStream 是 InputStream 的子类型,那么程序中 InputStream 类型的对象可以被 ZipInputStream 类型的对象替换,而不会改变该程序的任何所需属性(正确性、执行的任务等)。
这里违反 LSP 的方式是针对 read 方法。
InputStream.read(byte[], int, int) 声明它返回:
读入缓冲区的总字节数,如果由于到达流的末尾而没有更多数据,则为 -1。
ZipInputStream 的问题在于它修改了 -1 返回值的含义。它指出:
读取的实际字节数,如果到达条目的末尾,则为 -1
(实际上,Android 文档http://developer.android.com/reference/java/util/zip/ZipInputStream.html中的可用方法有类似问题的提示)
现在来看演示问题的代码。(这是我实际尝试做的缩减版,请原谅任何糟糕的风格、多线程问题或流是高级的事实等)。
接受任何 InputStream 以生成流的 SHA1 的类:
public class StreamChecker {
private byte[] lastHash = null;
public boolean isDifferent(final InputStream inputStream) throws IOException {
final byte[] hash = generateHash(inputStream);
final byte[] temp = lastHash;
lastHash = hash;
return !Arrays.equals(temp, hash);
}
private byte[] generateHash(final InputStream inputStream) throws IOException {
return DigestUtils.sha1(inputStream);
}
}
单元测试:
public class StreamCheckerTest {
@Test
public void testByteArrayInputStreamIsSame() throws IOException {
final StreamChecker checker = new StreamChecker();
final byte[] bytes = "abcdef".getBytes();
try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes)) {
Assert.assertTrue(checker.isDifferent(stream));
}
try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes)) {
Assert.assertFalse(checker.isDifferent(stream));
}
// Passes
}
@Test
public void testByteArrayInputStreamWithDifferentDataIsDifferent() throws IOException {
final StreamChecker checker = new StreamChecker();
byte[] bytes = "abcdef".getBytes();
try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes)) {
Assert.assertTrue(checker.isDifferent(stream));
}
bytes = "123456".getBytes();
try (final ByteArrayInputStream stream = new ByteArrayInputStream(bytes)) {
Assert.assertTrue(checker.isDifferent(stream));
}
// Passes
}
@Test
public void testZipInputStreamIsSame() throws IOException {
final StreamChecker checker = new StreamChecker();
final byte[] bytes = "abcdef".getBytes();
try (final ZipInputStream stream = createZipStream("test", bytes)) {
Assert.assertTrue(checker.isDifferent(stream));
}
try (final ZipInputStream stream = createZipStream("test", bytes)) {
Assert.assertFalse(checker.isDifferent(stream));
}
// Passes
}
@Test
public void testZipInputStreamWithDifferentEntryDataIsDifferent() throws IOException {
final StreamChecker checker = new StreamChecker();
byte[] bytes = "abcdef".getBytes();
try (final ZipInputStream stream = createZipStream("test", bytes)) {
Assert.assertTrue(checker.isDifferent(stream));
}
bytes = "123456".getBytes();
try (final ZipInputStream stream = createZipStream("test", bytes)) {
// Fails here
Assert.assertTrue(checker.isDifferent(stream));
}
}
private ZipInputStream createZipStream(final String entryName,
final byte[] bytes) throws IOException {
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final ZipOutputStream stream = new ZipOutputStream(outputStream)) {
stream.putNextEntry(new ZipEntry(entryName));
stream.write(bytes);
return new ZipInputStream(new ByteArrayInputStream(
outputStream.toByteArray()));
}
}
}
回到问题上来……违反了 LSP,因为您可以读取 InputStream 的流末尾,但不能读取 ZipInputStream ,当然这会破坏任何尝试以这种方式使用它的方法的正确性属性.
有什么方法可以实现这一点,还是 ZipInputStream 从根本上存在缺陷?