【发布时间】:2015-06-11 15:17:27
【问题描述】:
我之前问过一次,我的帖子因为没有提供使用帮助类的代码而被删除。这次我创建了一个完整的测试套件来显示确切的问题。
我认为 Java 的 ZipInputStream 在 InputStream 抽象类方面打破了里氏替换原则 (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中的available方法有类似问题的提示)
现在是演示问题的代码。 (这是我实际尝试做的缩减版,请原谅任何糟糕的风格、多线程问题或流是高级的事实等)。
接受任何 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 存在根本缺陷?
【问题讨论】:
标签: java inputstream liskov-substitution-principle zipinputstream