以前にこれを尋ねたところ、ヘルパー クラスを使用するコードを提供していないために投稿が削除されました。今回は、正確な問題を示す完全なテスト スイートを作成しました。
Java の ZipInputStream は、InputStream 抽象クラスに関して Liskov Substitution Principle (LSP) を破っていると私は考えています。ZipInputStream を InputStream のサブタイプにするには、プログラム内のタイプ InputStream のオブジェクトを、そのプログラムの望ましいプロパティ (正確性、実行されるタスクなど) を変更することなく、タイプ ZipInputStream のオブジェクトに置き換えることができます。
ここで LSP に違反する方法は、読み取りメソッドです。
InputStream.read(byte[], int, int) は、次を返すと述べています。
バッファに読み込まれた合計バイト数、またはストリームの終わりに達したためにデータがなくなった場合は -1。
ZipInputStream の問題は、-1 の戻り値の意味が変更されていることです。それは述べています:
読み取られた実際のバイト数、またはエントリの最後に達した場合は -1
(実際には、Android のドキュメントhttp://developer.android.com/reference/java/util/zip/ZipInputStream.htmlで利用可能なメソッドに同様の問題へのヒントがあります)
次に、問題を示すコードについて説明します。(これは私が実際にやろうとしていたことの縮小版ですので、貧弱なスタイル、マルチスレッドの問題、またはストリームが進んでいるという事実などを許してください)。
ストリームの SHA1 を生成するために任意の InputStream を受け入れるクラス:
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()));
}
}
}
問題に戻ります... InputStream の場合はストリームの最後まで読み取ることができますが、ZipInputStream の場合は読み取れないため、LSP に違反しています。もちろん、これにより、そのような方法で使用しようとするメソッドの正確性プロパティが壊れます。 .
これを達成できる方法はありますか、それとも ZipInputStream に根本的な欠陥がありますか?