配列を作って、その中身を表示しようと思って、
int[] array = {1,2,3,4,5};
System.out.println(array);
のように、配列をそのまま引数にして標準出力に渡してしまう人、初心者で意外にいるようだ。
この出力結果(例)は
[I@1540e19d
のように、わけのわからないものになる。
これを見て、「うまく動かない」などと質問されることが…
なぜこんなことになるのか?
System.out.printlnとは
System → java.lang.Systemクラス
out → SystemクラスのPrintStream型のstatic変数
つまり、printlnはPrintStreamクラスのメソッドなのである。
そこでPrintStream#printlnを見てみると、いくつかのオーバーロードが存在する。
その多くは各種プリミティブ型に対応するためのオーバーロードなので、それ以外のものを挙げると、
println(char[] x)
println(Object x)
println(String x)
これらが存在する。プリミティブ型を含め、これらは引数を文字列化したものを出力する。
char[]の場合は各要素の文字をつなげて出力し、Stringもその内容をそのまま出力する。
Objectの場合はどうかというと、引数をString.valueOf
に渡した結果の文字列が出力される。
Object引数の文字列表現を返します。
パラメータ:
obj - Object。
戻り値:
引数がnullの場合は"null"に等しい文字列。それ以外の場合はobj.toString()の値が返される。
そのままtoString()
にしないのはnullセーフのため。
ではそのtoString()
とはどうなっているのか?
オブジェクトの文字列表現を返します。一般に、toStringメソッドは、このオブジェクトを「テキストで表す」文字列を返します。この結果は、人間が読める簡潔で有益な情報であるべきです。すべてのサブクラスで、このメソッドをオーバーライドすることをお薦めします。
クラスObjectのtoStringメソッドは、オブジェクトがインスタンスになっている元のクラスの名前、アットマーク文字「@」、およびオブジェクトのハッシュ・コードの符号なし16進数表現から構成される文字列を返します。つまり、このメソッドは次の値と等しい文字列を返します。
getClass().getName() + '@' + Integer.toHexString(hashCode())
戻り値:
このオブジェクトの文字列表現。
となっている。ここで最初の出力を見てみよう。
[I@1540e19d
お分かりだろうか?この@以降の文字列はInteger.toHexString(hashCode())
の結果、つまりハッシュコードの16進表記ということである。しかし、この数字は見ても人にとっては意味がない。
メソッドの説明に「この結果は、人間が読める簡潔で有益な情報であるべきです。すべてのサブクラスで、このメソッドをオーバーライドすることをお薦めします。」とある通り、オーバーライドして使うのがこのメソッドの本来の使い方である。基底クラスであるObjectでは「とりあえず呼ばれたらこう出力する」とだけ決めてあって、内容がどうこうといった情報は入っていないのである。
#@の前の文字列 - 何のクラスのオブジェクト?
1つだけ有益な情報があるとすれば、それは**@の前の文字列**である。この例で言えば[I
だ。
これが何…?そう思ったら、もう一度説明を読もう。
@の前の文字列、それはgetClass().getName()
の結果である。
ではそちらはどういう仕様になっているのか?
getClass()の返り値の型Classについては詳しく説明するのも理解するのも難しいので、とりあえず「どのクラスのインスタンスかを表すオブジェクト」という程度に認識してもらいたい。
このClassオブジェクトが表すエンティティ(クラス、インタフェース、配列クラス、プリミティブ型、またはvoid)の名前を、Stringとして返します。
『Java(tm)言語仕様』で規定されているように、このクラス・オブジェクトが配列型ではない参照型を表す場合は、クラスのバイナリ名が返されます。
このクラス・オブジェクトがプリミティブ型またはvoidを表す場合、返される名前はプリミティブ型またはvoidに対応するJava言語キーワードと等価なStringです。
このクラス・オブジェクトが配列のクラスを表す場合、名前の内部形式は、配列の入れ子の深さを表す1つ以上の[文字、要素のタイプの名前という順序で構成されます。要素のタイプの名前のエンコーディングは、次のとおりです。
要素のタイプ エンコーディング
boolean型 Z
byte B
char C
classまたはinterface Lclassname;
double D
float F
int I
long J
short S
クラス名またはインタフェース名のclassnameは、上記の例のようにクラスのバイナリ名で指定されます。
例
String.class.getName()
returns "java.lang.String"
byte.class.getName()
returns "byte"
(new Object[3]).getClass().getName()
returns "[Ljava.lang.Object;"
(new int[3][4][5][6][7][8][9]).getClass().getName()
returns "[[[[[[[I"
簡単に言えば、そのインスタンスの型を表現する文字列である。
注目してほしいのは配列の時の場合。配列の次元数だけ[
が並び、そのあとにクラス名(プリミティブ以外ならLが挟まる)が続く形式となっている。
例の最後の場合、配列が7次元になっているため、[
が7個並び、そのあとにint配列であることを表すIが連なって表現されている。
つまり[I
は、このオブジェクトが1次元のint配列である、ということを表現していたのだ。
以上のように、最初の一見意味不明な出力は、きちんと仕様に従って出力されていたものなのだ。意味不明な文字列だからといってバグと決めつけるのではなく、きちんと本来の動きを調べよう。
ちなみに、配列の中身をきちんと出力したいのなら、Arrays.toString()(多次元配列ならArrays.deepToString())に渡して文字列化するとか、ループを使って出力するとかしましょう。