Java道|クラス型変数でオブジェクトを渡す方法

オブジェクトは受け渡して連携する。
参照を渡す感覚がつかめると、Javaのクラス型変数はぐっと実践的になります。

ここまでで、クラス型の変数はオブジェクトそのものではなく、オブジェクトを指し示す参照を持っていることを学んできました。
ここからはさらに一歩進んで、その参照をどのように活用するのかを見ていきます。

鬼滅の刃の世界で考えてみましょう。
ある隊士の情報が書かれた記録札があるとします。
その札は、隊士本人そのものではなく、その隊士を指し示すための札です。
そして、その札は別の場所へ渡すことができます。

たとえば、炭治郎の情報を柱に伝えるとき、炭治郎本人を複製するわけではありません。
炭治郎という隊士の情報を指す札を渡して、同じ隊士の情報を共有するイメージです。

Javaのクラス型変数もこれと同じです。
クラス型変数は、オブジェクトを指す参照を持っていて、その参照をフィールドとして持たせたり、メソッドの引数として渡したりできます。

この考え方が分かると、オブジェクト同士を連携させる設計がとても分かりやすくなります。

まず押さえたい「受け渡し」の感覚

クラス型変数を使った受け渡しで一番大切なのは、渡されるのはオブジェクトそのものではなく、参照だということです。

ここを鬼滅の刃風に整理すると、次のようになります。

概念鬼滅の刃でのたとえ
クラス隊士の設計図
オブジェクト実際の隊士
クラス型変数隊士を指し示す札
引数に渡す札を別の人へ手渡す

つまり、クラス型変数の受け渡しとは、実体そのものを丸ごとコピーして渡すのではなく、どのオブジェクトを見ればよいかという情報を渡すことです。

この感覚を持っておくと、

  • なぜ同じオブジェクトを複数の場所で扱えるのか
  • なぜ片方で変えた内容が別の場所でも見えるのか
  • なぜオブジェクト同士を連携させやすいのか

が見えやすくなります。

オブジェクトが不要になったときの finalize の考え方

まずは少しだけ、オブジェクトの終わり方について触れておきます。

オブジェクトが作られるときには、コンストラクタが呼び出されます。
それに対して、オブジェクトが不要になったときには finalize というメソッドが呼び出されることがありました。

ただし、ここには大事な注意があります。

ポイント内容
finalize が呼ばれるか必ず思った通りに呼ばれるとは限らない
呼ばれるタイミングプログラマが直接決められない
判断するのは誰かJava の仕組みが自動で判断する
実務での扱いfinalize に頼る設計はあまり推奨されない

鬼滅の刃でたとえると、ある記録札がいつ本部に回収されるかは、現場の隊士が自由に決めるのではなく、本部の管理側が判断するようなものです。

つまり、オブジェクトがいつ完全に消えるかは、Java が自動で判断します。
そのため、finalize があるからといって、それを前提に重要な処理を書くのは安心できません。

ここで大切なのは、オブジェクトが不要になったときの細かいタイミングは、自分で細密に制御するものではない、という感覚です。

クラス型変数はフィールドとしても使える

クラス型変数は、ローカル変数として使うだけではありません。
フィールドとしても使えます。

今回の話でとても分かりやすいのが、名前を表す String 型です。

String は文字列を表すクラスです。
つまり、name というフィールドは、単なる記号ではなく、文字列オブジェクトを指すクラス型のフィールドです。

鬼滅の刃の世界でたとえると、隊士は剣技の強さや呼吸の力のような数値情報だけを持つのではありません。
名前札も持っています。
そして、その名前札もまたオブジェクトとして扱われるわけです。

整理すると、こうなります。

フィールドの種類意味
基本型のフィールドstrength, breathingPower数値そのものを持つ
クラス型のフィールドname文字列オブジェクトへの参照を持つ

このように、オブジェクトの中に別のオブジェクトへの参照を持たせることができます。
これが分かると、オブジェクト同士がつながる設計の入り口が見えてきます。

クラス型の引数としてオブジェクトを渡せる

今回の中心になるのがここです。

クラス型の変数は、メソッドの引数として渡すことができます。
このとき渡されるのは、オブジェクトそのものではなく、そのオブジェクトを指す参照です。

鬼滅の刃でたとえるなら、隊士本人を直接渡すのではなく、その隊士の情報札を渡して、「この隊士の情報を使ってください」と伝えるようなイメージです。

このしくみがあるおかげで、メソッドは外から渡されたオブジェクトを使って処理できます。

今回の題材では、setName メソッドに String 型の値を渡しています。
String もクラス型なので、ここでも「参照を渡す」という考え方が働いています。

参照渡しを確認する

ここで実際のプログラムを見てみましょう。

ファイル名:Sample8.java

class CorpsSwordsman
{
    private int strength;
    private double breathingPower;
    private String name;

    public CorpsSwordsman()
    {
        strength = 0;
        breathingPower = 0.0;
        name = "名無しの隊士";
        System.out.println("隊士を生成しました。");
    }

    public void setSwordsman(int s, double b)
    {
        strength = s;
        breathingPower = b;
        System.out.println("剣技の強さを" + strength + "、呼吸の力を" + breathingPower + "に設定しました。");
    }

    public void setName(String nm)
    {
        name = nm;
        System.out.println("名前を" + name + "にしました。");
    }

    public void show()
    {
        System.out.println("剣技の強さは" + strength + "です。");
        System.out.println("呼吸の力は" + breathingPower + "です。");
        System.out.println("名前は" + name + "です。");
    }
}

class Sample8
{
    public static void main(String[] args)
    {
        CorpsSwordsman tanjiro;
        tanjiro = new CorpsSwordsman();

        tanjiro.show();

        int strength = 9000;
        double breathingPower = 5000.0;
        String swordsmanName = "竈門炭治郎";

        tanjiro.setSwordsman(strength, breathingPower);
        tanjiro.setName(swordsmanName);

        tanjiro.show();
    }
}

このプログラムでは、CorpsSwordsman クラスのオブジェクトを作り、その後で剣技の強さ、呼吸の力、名前を設定しています。

特に注目したいのは、次の部分です。

String swordsmanName = "竈門炭治郎";
tanjiro.setName(swordsmanName);

ここでは、swordsmanName という String 型の変数を setName メソッドへ渡しています。
String はクラス型なので、ここでもクラス型変数の受け渡しが起きています。

Sample8.java の流れを順番に見ていく

このプログラムが何をしているのかを、流れで整理してみます。

順番処理起きていること
1CorpsSwordsman tanjiro;tanjiro というクラス型変数を宣言する
2tanjiro = new CorpsSwordsman();新しい隊士オブジェクトを生成する
3tanjiro.show();初期状態を表示する
4int strength = 9000;剣技の強さを表す値を用意する
5double breathingPower = 5000.0;呼吸の力を表す値を用意する
6String swordsmanName = "竈門炭治郎";名前を表す String オブジェクトを用意する
7tanjiro.setSwordsman(strength, breathingPower);数値情報を設定する
8tanjiro.setName(swordsmanName);名前オブジェクトへの参照を渡す
9tanjiro.show();設定後の状態を表示する

この流れから分かるのは、オブジェクトの中には基本型の値もクラス型の値も持たせられるということです。

setName の引数で何が起きているのか

ここは特に丁寧に見ておきたいところです。

setName メソッドは、次のように定義されています。

public void setName(String nm)
{
    name = nm;
    System.out.println("名前を" + name + "にしました。");
}

そして、呼び出し側はこうです。

String swordsmanName = "竈門炭治郎";
tanjiro.setName(swordsmanName);

このときの対応関係は、次のようになります。

項目内容
実引数swordsmanName
仮引数nm
渡されるものString オブジェクトへの参照
メソッド内の処理nm が指す文字列を name に代入する

つまり、setName(swordsmanName) で渡しているのは、文字列オブジェクトそのものを丸ごと複製したものではなく、その文字列を指している参照です。

鬼滅の刃でたとえると、炭治郎という名前が書かれた名前札を、setName という係に渡しているようなものです。
その係は、その札を受け取り、隊士の名前欄に登録します。

実引数と仮引数を鬼滅の刃風に考える

実引数と仮引数という言葉は、最初は少しかたく感じるかもしれません。
でも、考え方はそんなに難しくありません。

用語意味今回の例
実引数呼び出し側で実際に渡すものswordsmanName
仮引数メソッド側で受け取るための変数nm

鬼滅の刃の世界でたとえるなら、呼び出し側が「この名前札を渡します」と持っていくのが実引数です。
受け取る側が「では、この札をこちらの受け取り箱 nm で受けます」とするのが仮引数です。

このとき受け渡されているのは、文字列オブジェクトを指す参照です。
ここが、クラス型の引数の大事なポイントです。

図:クラス型の引数には参照が渡される

↓クリックすると拡大表示されます。

この図が示していること

この図では、swordsmanName を setName メソッドへ渡したときに、String オブジェクトそのものを丸ごと複製しているのではなく、そのオブジェクトを指す参照が渡されていることを表しています。

左側の実引数 swordsmanName が、中央の矢印を通って、右側の仮引数 nm に受け渡されています。
ただし、両者が見ている先は同じ String オブジェクトです。

ここから分かるのは、クラス型の引数の受け渡しでは、オブジェクトの実体そのものではなく、参照の情報が引き渡されるということです。

クラス型変数をフィールドに持つ意味

今回の Sample8.java では、name が String 型のフィールドになっていました。

private String name;

これはとても大切な書き方です。
なぜなら、オブジェクトの中に別のオブジェクトへの参照を持たせているからです。

これを整理すると、次のようになります。

フィールド名役割
strengthint剣技の強さを表す
breathingPowerdouble呼吸の力を表す
nameString隊士の名前を表す文字列オブジェクトを指す

このように、1つのオブジェクトは複数のデータを持てますが、その中には基本型だけでなくクラス型も含められます。

鬼滅の刃でたとえると、隊士の記録には、

  • 剣技の強さ
  • 呼吸の力
  • 名前札

のように、数値情報と別オブジェクトへの参照が一緒に入っている感じです。

なぜクラス型の引数が重要なのか

ここまでの内容を踏まえると、クラス型の引数がなぜ大切なのかが見えてきます。

クラス型の引数を使えると、メソッドにオブジェクトの情報を渡して、別の処理に活用できるようになります。

これには、次のような利点があります。

利点内容
オブジェクトを別の処理へ渡せる必要なメソッドにオブジェクト情報を渡して使える
同じデータを共有しやすい同じオブジェクトを複数の場所で扱える
設計を柔軟にできるオブジェクト同士を連携させやすい
再利用しやすい同じ処理を別のオブジェクトにも使いやすい

鬼滅の刃でたとえると、炭治郎の情報札を別の係や別の柱に渡して、同じ炭治郎の情報をそれぞれの場面で活用するようなものです。

本人を複製して増やすのではなく、同じ情報を指す札を受け渡すからこそ、連携がしやすくなります。

オブジェクトそのものではなく参照を渡す意味

ここは少し本質的な話ですが、とても大切です。

Javaでクラス型の引数を渡すときに重要なのは、
メソッドの中でも外でも、同じオブジェクトを土台にして話が進むことです。

今回の setName の例では String を扱っていますが、感覚としては他のクラス型でも同じです。

整理すると、こうなります。

観点内容
渡しているものオブジェクトへの参照
コピーされるもの参照の情報
メソッド内で扱う対象呼び出し元と同じオブジェクトを指すことになる
設計上の意味オブジェクト連携がしやすくなる

この考え方が分かると、今後学ぶ

  • オブジェクトを別のメソッドへ渡す処理
  • オブジェクトを配列やコレクションに入れる処理
  • 複数オブジェクトの連携

などが、かなり理解しやすくなります。

図:オブジェクトの中に別のオブジェクトを持てる

↓クリックすると拡大表示されます。

この図が示していること

この図では、CorpsSwordsman オブジェクトの中に、基本型のフィールドだけでなく、String 型のフィールド name があることを表しています。

strength と breathingPower は数値そのものを持つ基本型フィールドです。
一方、name は String オブジェクトを指すクラス型フィールドです。

ここから分かるのは、オブジェクトの中には別のオブジェクトへの参照を持たせることができるということです。
これがあるからこそ、Javaではオブジェクト同士をつなげた柔軟な設計ができます。

Sample8.java から見えてくる設計の感覚

このプログラムは一見シンプルですが、Javaの大事な考え方がいくつも入っています。

特に意識したいのは、次の点です。

見るポイント内容
クラス型変数を使っているtanjiro と name がクラス型に関係している
フィールドにクラス型があるname は String 型のフィールド
メソッドの引数にクラス型があるsetName(String nm)
参照が受け渡されているswordsmanName から nm へ参照が渡る
オブジェクト同士がつながるCorpsSwordsman の中に String オブジェクトが関係する

つまり、この1本のプログラムの中で、

  • クラス型変数
  • フィールドとしてのクラス型
  • 引数としてのクラス型
  • 参照の受け渡し

がきれいにつながっています。

ここで押さえておきたい重要ポイント

最後に、今回のテーマで大切なポイントを整理しておきます。

ポイント内容
クラス型変数はフィールドにも使える例:String 型の name
クラス型はメソッドの引数として渡せる例:setName(String nm)
渡されるのはオブジェクトそのものではない参照が渡される
実引数と仮引数で受け渡しが行われるswordsmanName から nm へ渡る
オブジェクトの中に別のオブジェクト参照を持てるname が String オブジェクトを指す
finalize はタイミングを制御できない実務では finalize に頼る設計は推奨されにくい

鬼滅の刃でたとえるなら、隊士の記録は単独で存在するだけではありません。
名前札を持ち、情報札を受け渡し、別の係や別の場面へつながっていきます。

Javaのクラス型変数も同じです。
参照を持ち、その参照を渡すことで、オブジェクト同士が連携できるようになります。

この「参照を渡す」という感覚がつかめると、Javaの設計は一気に見通しがよくなります。
オブジェクトはただ作るだけでなく、受け渡して活かすものだと分かってくると、クラス型変数の使い方がぐっと実践的になります。