TL;DR;
配列要素にインスタンスを埋めて初期化する時は、無難にfor
文を使うかArrays#setAll
を使おう。
Arrays#fill
だとインスタンスが同一になる罠があるから気をつけてね。1
この記事を作った経緯(長い茶番とも言う)
本稿には挑戦的なタイトル2や言葉の乱れがございますので、我慢するかブラウザバックをお願いいたします。
for文なんてダセーよなー
自作クラスの配列を初期化する時、どうしますか?
Javaの教本ではfor
文使うじゃないですか。
例えば★こんな風★に。
public class Sample {
public static void main(String[] args) {
Sample[] samples = new Sample[3];
// ★こんな風★
for(int i = 0; i < 3; i++) {
samples[i] = new Sample();
}
}
}
ですけどねぇ…エレガントなコード階級3になりますとね、まるで我慢ならないのよ。
こんなJava1.4臭がするロートルなコードは。
ぐるぐるループ回してて、全然スタイリッシュじゃありませんわー。
ブレークポイント当てたら3回も止まるんですのよ!
おお嫌だ嫌だ。こんなコードはどこかに捨てておしまい。
コピペじゃない、コピペじゃない、Don'tのコードさ
だったらこうすればいい?
Sample[] samples = new Sample[] { new Sample(), new Sample(), new Sample() };
おおお…肌が粟立って二の句が継げないわ。
「for
文使うな」と言われて「はいはい、じゃあコピペしてやりますよ」とかトンチキなトンチを利かせたつもりかしら?
これはトンチじゃなくてトンマって言うのよ、この○○○○○!
いいこと?
仕様変更で配列が3つから30個に増えたらどうするつもりかしら。
new Sample();
を無作為の回数コピペするコーダーと、コピペされた個数を数えてからクレンジングするデータサイエンティストを投入しないといけないわ。
そしたらfor
文でも1人で済んでた作業に10人くらい必要かもしれないわね。100倍ですわよ100倍!
IntStream.rangeすればいいと思った?
まあ、Java8に慣れてきたご令嬢は★ナウなヤング風★にIntStream
を使うらしいじゃない?
import java.util.stream.IntStream;
public class Sample {
public static void main(String[] args) {
Sample[] samples = new Sample[3];
// ★ナウなヤング風★
IntStream.range(0, 3).forEach(i -> samples[i] = new Sample());
}
}
でもアナタ、forEach
のi
がループっぽく見えますのよ!残念!!アロー演算子切り!!
ここまでのコードに比べればいくらかお上品ですけれど、私ならばもっと瀟洒なコードを選びますわよ。
次節で見せて差しあげますわ。せっかく!!4ですので。
Arrays.fillじゃダメだったよ
私がこの記事を書く経緯となった、ご自慢のコードは★こちら★ですわ~!
import java.util.Arrays;
public class Sample {
public String name; // publicフィールドはお行儀悪いけどごめんあそばせ
public static void main(String[] args) {
Sample[] samples = new Sample[3];
// ★こちら★
Arrays.fill(samples, new Sample());
samples[0].name = "hoge";
samples[1].name = "fuga";
samples[2].name = "piyo";
for(Sample sp: samples) {
System.out.println(sp.name);
}
}
}
ほらごらんなさい!
なんてミスする余地なく、可読性に優れた素晴らしいコードなのかしら。
Arrays#fill
を使えば配列の各要素を同一の内容でフィりますのよ!
例えば下のようにコーディングすれば801
個のarr
変数すべてに11
が入るのですわよ。
int[] arr = new int[801];
Arrays.fill(arr, 11);
つまり簡単に、やおい じゅういち ができるのよ。
満を持して先ほどのSample
コードを実行しますわよ…それ!
実行結果
piyo
piyo
piyo
ゆーふぉー!
ななな、なぜ?どうして?Why!Java needs Why!
アカン考察法
こほん。
私の心酔しているStackOverflow様によると、Arrays.fill
は第二引数のインスタンスをすべての配列要素に等しく詰め込んでるようですわ。
public static void fill(Object[] a, Object val) {
for (int i = 0, len = a.length; i < len; i++)
a[i] = val;
}
これでは同一のインスタンスを見てしまいますわね…。
まるで複数のインスタンスを作ったつもりが、インスマスみたく架空のインスタンスで満たされたかのようですわ。
あら?この例えでは何言ってるか分からない?
平気ですわ。私も分かりませんもの。
Collections.nCopiesも参照先は同一っぽい
ちなみに下記のように書き換えてもぜんぶpiyoりましたわ。
import java.util.Collections;
(中略)
// これを
Arrays.fill(samples, new Sample());
// こうする
samples = Collections.nCopies(samples.length, new Sample()).toArray(new Sample[samples.length]);
実行結果
piyo
piyo
piyo
Collections#nCopies
してもシャローコピーなのは意外でしたの。
結局のところ、シャロー様とは浅薄な関係だったってことなのね。
まあ、実在しない人名をかたってもこの記事の情報量は砂糖一粒ほども増えませんけど。
成功事例
Stream.generateで解決できる
試行錯誤の過程で、下のように書き換えてみましたわ。
import java.util.stream.Stream;
(中略)
// これを
Sample[] samples = new Sample[3];
Arrays.fill(samples, new Sample());
// こうする
Sample[] samples = Stream.generate(Sample::new).limit(3).toArray(Sample[]::new);
実行結果
hoge
fuga
piyo
やりました!やりましたわー!
やっぱり時代はメソッドチェーンですのね!
Stream#generate
ってSupplier
関数型インタフェースを使うと無限にインスタンスを作れますのね。
末端の技術者にとっては、ブロックチェーンみたいな難しい理論よりもメソッドチェーンの方が安くて速くて旨いサプライチェーンやーですわ!
ですけど、わざわざlimit
してtoArray
するよりも簡潔な方法はないかしら。
もうArrays.setAllでいいんじゃないかな
そんなこんなであれやこれやがてんやわんやした結果、★かくかくしかじか★に落ち着きましたわ。
import java.util.Arrays;
public class Sample {
public String name; // publicフィールドはお行儀悪いけどごめんあそばせ
public static void main(String[] args) {
Sample[] samples = new Sample[3];
// ★かくかくしかじか★
Arrays.setAll(samples, i -> new Sample());
samples[0].name = "hoge";
samples[1].name = "fuga";
samples[2].name = "piyo";
for(Sample sp: samples) {
System.out.println(sp.name);
}
}
}
実行結果
hoge
fuga
piyo
エレガントでしょう?おーっほほほほ!
Arrays#setAll
を使うことで、第二引数のIntFunction
ごとに独立したインスタンスを作成できますのよ!
え?アロー演算子切り?
そんな昔のことは忘れましたわよ!
これを使えばワンライナーで初期化できますし、きめ細かな処理すら可能ですわ。
これを活用すれば、for
文を使った初期化と同等の初期化処理ができますわね!
この記事の結論をズバリ言いますわよ!!
結局のところfor
文も捨てたものじゃありませんわね…ほとんど誰でも読めて保守性が高いですし。5
そしてArrays#fill
みたいな使いなれないコードを無意味に使うと、罠にハマって無駄に疲れることがありますのでお気をつけあそばせ。
そう、謎のお嬢様言葉を使って無駄に疲弊している今の私のように!
参考資料
Arrays.fill() uses the same object all time
Create an array with n copies of the same value/object?
Stream.generate
IntStream.range
Arrays.fill
Arrays.setAll
Collections.nCopies