Android
Kotlin

Kotlinのdata classに配列を入れるとtoString()に配列の中身が全部表示されて困る話

More than 1 year has passed since last update.

Kotlinのdata classはとても便利だけど,微妙にかゆいところに手が届かないときもあるね,という話.

TL;DR (長い! 3行で!)

  • data classにbyte配列を格納したい
  • でもtoString()に配列の中身が全部表示されて,logcat1行文のバッファにすら入り切らん
  • 配列を格納するだけの別classを作って入れ子にしてやるといい感じ

はじめに

Kotlinのdata classで撮影した写真のJPEG Bufferと保存先File名やMeta Dataなど一式を格納するようなコンテナクラス↓を作って,Kotlinのdata classはラクでいいなぁと思ってました.

PhotoData.kt
data class PhotoData(val filepath: String, val jpeg: ByteArray, val meta: String) {
}

しかし,保存ThreadでPhotoData#toString()を使ってLog出力をしたところ,
default_log.png
のように,byteひとつひとつがIntに変換されて,全要素が表示されるようになっていて,配列の終端かlogcatの1行文のバッファがなくなるかまで表示されてしまいます.
JPEGデータが同一なのか別モノなのか,が知りたいだけなのに,さすがにコレでは困るのでなんとかしよう,というお話です.

やりたいこと

data class にbyte配列などを格納したときでも,配列全部を表示するのでなく,配列が同一のものなのか別モノなのか,が分かる程度にtoString()でヨロシクlog出力したい.

Kotlinのdata classがJavaにどう変換されているか?

というわけで,data classで自動生成されているtoString()がJAVA変換後にどういうCodeになっているかを見てみます.

↓ のサイトを参考に,KotlinのCodeをJAVAに変換してみます.
http://tech.actindi.net/2016/12/15/kotlin.html

PhotoData.kt
data class PhotoData(val filepath: String, val jpegBuffer: ByteArray) {
}

このdata class内に自動生成されるtoString()のCodeはJAVAに変換後,↓ のようになります.

PhotoData.java
public final class PhotoData
{
    public String toString()
    {
        return (new StringBuilder())
                .append("PhotoData(fileFullPath=")
                .append(fileFullPath)
                .append(", jpegBuffer=")
                .append(Arrays.toString(jpegBuffer))
                .append(")")
                .toString();
    }
}

このなかの
Arrays.toString(jpegBuffer)
が配列全出しの要因のようですね.

Array#toString()の仕様を調べると,↓ のようになっていて,byteひとつひとつをStringBuilder使って文字列に詰めています.

Arrays.java
public static String toString(byte[] array) {
    if (array == null) {
        return "null";
    }
    if (array.length == 0) {
        return "[]";
    }
    StringBuilder sb = new StringBuilder(array.length * 6);
    sb.append('[');
    sb.append(array[0]);
    for (int i = 1; i < array.length; i++) {
        sb.append(", ");
        sb.append(array[i]);
    }
    sb.append(']');
    return sb.toString();
}

byte配列かArraysか何かに細工して挙動変えられないかなーと思ってたのですが,無理そうですね.
(じつはできるぜ! みたいなのあったら知りたいです!)

ByteArrayを格納するためだけのclassを作る

byte配列を素のままdata classに格納するとArrays#toString()が使われてしまうので,いっそ配列だけを格納するclassを別に作って入れ子にして,そのclassのtoString()をoverrideしてやれば,byte配列がそのまま出力されることを回避できるはず.

↓ こんな感じ

PhotoData.kt
class ByteBuffer(val buffer: ByteArray) {
    override fun toString(): String {
        return "${buffer.hashCode()}"
    }
}

data class PhotoData(val fileFullPath: String, val jpeg: ByteBuffer) {
}

この構成で実際にlog catに出力させてみると,
modified.png

PhotoData(
    fileFullPath=/storage/emulated/0/2017-07-29_151933.JPG,
    jpeg=164835622
)

ってな感じの出力になって,同じ配列かどうか判定ができる程度のいい感じのlog catになっています.

まとめ

data classに長い配列を入れた時でも,log catが荒らされることが無いようにすることができました.
Kotlinのdata classはとても便利なのにArrayを入れたときのtoString()の標準挙動だけは残念な感じですね.

他にもっといい方法がありそうなきがするなーと思いつつ.

---///