2

我之前问过一次,我的帖子因为没有提供使用帮助类的代码而被删除。这次我创建了一个完整的测试套件来显示确切的问题。

我认为 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 从根本上存在缺陷?

4

1 回答 1

2

我没有看到任何 LSP 违规。文档ZipInputStream.read(byte[], int, int)说“从当前 ZIP 条目读取到字节数组”。

在任何时候,ZipInputStream它实际上是条目的输入流,而不是整个 ZIP 文件。并且很难看出ZipInputStream.read()除了 return -1 之外还有什么可以在输入结束时做的。

这将破坏任何尝试以这种方式使用它的方法的正确性属性

很难看出这个方法怎么会知道。

于 2015-04-07T06:56:48.747 回答