ByteBufferの複製には、バイトオーダーの再設定が必要

バイト列を扱う際、バイトオーダーを意識しないといけない場合があります。

このような場合、Javaでは、[ByteBuffer](https://docs.oracle.com/javase/jp/12/docs/api/java.base/java/nio/ByteBuffer.html)クラスを使用すると便利です。ByteBufferにはバイトオーダーを設定でき、複数バイトから構成される値を簡単に扱えるためです。例えば、次のコードでは、リトルエンディアンの値を扱っています。

```java:LittleEndianSample.java
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class LittleEndianSample {
    public static final void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES);

        // リトルエンディアンの16ビット整数。下位バイトが0x11、上位バイトが0x22。
        buffer.order(ByteOrder.LITTLE_ENDIAN)
            .put((byte) 0x11)
            .put((byte) 0x22)
            .rewind();

        System.out.printf("0x%x\n", buffer.getShort());
    }
}
```

実行結果は次の通りです。

```
0x2211
```

ところが、上記のByteBufferを複製すると、バイトオーダーが想定外のものになってしまいます。例えば、リトルエンディアンのByteBufferを返すメソッドを書いているとします。その呼び出し元がByteBufferの内容を変更できないように、読み取り専用で返すことにします。しかし、次のようなコードでは、呼び出し元がビッグエンディアンのByteBufferを受け取ってしまいます。

```java:BadSample.java
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class BadSample {
    private static class BufferObject {
        private final ByteBuffer buffer;

        public BufferObject() {
            buffer = ByteBuffer.allocate(Short.BYTES);

            // リトルエンディアンの16ビット整数。下位バイトが0x11、上位バイトが0x22。
            buffer.order(ByteOrder.LITTLE_ENDIAN)
                .put((byte) 0x11)
                .put((byte) 0x22)
                .rewind();
        }

        private ByteBuffer getBuffer() {
            // 読み取り専用で複製（誤り。ビッグエンディアンになる）
            return buffer.asReadOnlyBuffer();
        }
    }

    public static final void main(String[] args) {
        BufferObject bufferObject = new BufferObject();

        ByteBuffer buffer = bufferObject.getBuffer();

        System.out.printf("0x%x\n", buffer.getShort());
    }
}
```

実行結果は次のようになります。上位バイトと下位バイトとが入れ替わり、ビッグエンディアンになっていることがわかります。

```
0x1122
```

なぜこのような挙動となるのでしょうか。

OpenJDKのIssue Trackerに投稿されたIssue [JDK-7178109](https://bugs.openjdk.java.net/browse/JDK-7178109)によると、この挙動は仕様とのことです。

新規に作成されたByteBufferのバイトオーダーは、常にビッグエンディアンとなります。つまり、allocate()やallocateDirect()以外にも、下記のメソッドから返されるByteBufferのバイトオーダーは、元のByteBufferのバイトオーダーにかかわらず、常にビッグエンディアンです。

* duplicate()
* asReadOnlyBuffer()
* wrap()
* slice()
* alignedSlice()

Java SEでは、[バージョン9のAPIドキュメント](https://docs.oracle.com/javase/9/docs/api/java/nio/ByteBuffer.html)から、上記の各メソッドに、作成されたByteBufferのバイトオーダーがビッグエンディアンになることが記されています。しかし、[AndroidのAPIドキュメント](https://developer.android.com/reference/java/nio/ByteBuffer?hl=en)では、現時点（2019/7/9）ではそのような記述はありません。

以上から、バイトオーダーを保ったままByteBufferを複製するには、複製後にorder()でバイトオーダーを再度設定する必要があります。

```java:DuplicationSample.java
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class DuplicationSample {
    private static class BufferObject {
        private final ByteBuffer buffer;

        public BufferObject() {
            buffer = ByteBuffer.allocate(Short.BYTES);

            // リトルエンディアンの16ビット整数。下位バイトが0x11、上位バイトが0x22。
            buffer.order(ByteOrder.LITTLE_ENDIAN)
                .put((byte) 0x11)
                .put((byte) 0x22)
                .rewind();
        }

        private ByteBuffer getBuffer() {
            // 読み取り専用で複製。バイトオーダーを設定する必要がある。
            return buffer.asReadOnlyBuffer().order(buffer.order());
        }
    }

    public static final void main(String[] args) {
        BufferObject bufferObject = new BufferObject();

        ByteBuffer buffer = bufferObject.getBuffer();

        System.out.printf("0x%x\n", buffer.getShort());
    }
}
```

実行結果は次の通りです。バイトオーダーが正しくリトルエンディアンになりました。

```
0x2211
```