LoginSignup
6
2

Java配列を個別インスタンスでまとめて初期化する方法。ただしfor文テメーはダメだ

Last updated at Posted at 2022-08-04

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());
    }
}

でもアナタ、forEachiがループっぽく見えますのよ!残念!!アロー演算子切り!!
ここまでのコードに比べればいくらかお上品ですけれど、私ならばもっと瀟洒なコードを選びますわよ。
次節で見せて差しあげますわ。せっかく!!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

  1. Arrays.fillで小ハマりしつつ、日本語の関連記事がうまく見つけられなかったので書きました。

  2. 書きながらテンションが上がって文体に乱れが出た点や、挑発的なタイトルになった点は書きながらテンションが上がったせいです。

  3. なお私は業務でほとんどJavaを使わないため、エレガントなコード階級はあくまでも自称行為です。

  4. 切腹!!の誤字ではありません。本稿と一切関係ありませんが、令和生まれの読者には波田陽区さんをご存じない方もおられるかもしれませんね。

  5. for文が読めない人も一定数いますが本稿では議論しません

6
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2