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

オブジェクトは作るだけでは終わりません。参照を渡してつなげる感覚がつかめると、Javaのクラス型変数は一気に実践的になります。

ここまでで、クラス型の変数はオブジェクトそのものではなく、オブジェクトを指し示す参照を持っていることを学んできました。

ここからはさらに一歩進んで、その参照をどのように活用するのかを見ていきます。

ドラゴンボールの世界で考えてみましょう。

ある戦士の情報が書かれた登録札があるとします。
その札は、戦士本人そのものではなく、その戦士を指し示すための札です。
そして、その札は別の場所へ渡すことができます。

たとえば、悟空の情報を修行管理係へ伝えるとき、悟空本人を複製するわけではありません。
悟空という戦士の情報を指す札を渡して、同じ戦士の情報を共有するイメージです。

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

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

今回は、クラス型変数でオブジェクトを渡す方法を、ドラゴンボール風の世界観でやさしく整理していきます。特に、クラス型変数で受け渡されるのは何なのか、フィールドにクラス型を持つとはどういうことか、メソッドの引数にクラス型を使うと何が起きるのかを、Sample8.java を使って丁寧に見ていきます。

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

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

ドラゴンボール風に整理すると、次のようになります。

概念ドラゴンボール風のたとえ
クラス戦士の設計図
オブジェクト実際の戦士
クラス型変数戦士を指し示す登録札
引数に渡す札を別の係へ手渡す

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

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

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

が見えやすくなります。

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

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

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

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

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

ドラゴンボール風にたとえると、ある戦士の記録札が、いつ本部の整理対象になるかは、現場の戦士が自由に決めるのではなく、本部の管理側が判断するようなものです。

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

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

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

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

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

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

ドラゴンボールの世界観でたとえると、戦士は戦闘力や気の力のような数値情報だけを持つのではありません。
名前札も持っています。
そして、その名前札もまたオブジェクトとして扱われるわけです。

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

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

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

図:クラス型変数の受け渡しは参照を渡す

この図が示していること

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

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

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

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

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

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

ドラゴンボール風にたとえるなら、戦士本人を直接渡すのではなく、その戦士の情報札を渡して、この戦士の情報を使ってください、と伝えるようなイメージです。

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

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

参照渡しを確認する

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

ファイル名:Sample8.java

class SaiyanWarrior
{
    private int battlePower;
    private double kiPower;
    private String name;

    public SaiyanWarrior()
    {
        battlePower = 0;
        kiPower = 0.0;
        name = "名無しの戦士";
        System.out.println("戦士を生成しました。");
    }

    public void setWarrior(int b, double k)
    {
        battlePower = b;
        kiPower = k;
        System.out.println("戦闘力を" + battlePower + "、気の力を" + kiPower + "に設定しました。");
    }

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

    public void show()
    {
        System.out.println("戦闘力は" + battlePower + "です。");
        System.out.println("気の力は" + kiPower + "です。");
        System.out.println("名前は" + name + "です。");
    }
}

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

        goku.show();

        int battlePower = 9000;
        double kiPower = 5000.0;
        String warriorName = "孫悟空";

        goku.setWarrior(battlePower, kiPower);
        goku.setName(warriorName);

        goku.show();
    }
}

このプログラムでは、SaiyanWarrior クラスのオブジェクトを作り、その後で戦闘力、気の力、名前を設定しています。

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

String warriorName = "孫悟空";
goku.setName(warriorName);

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

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

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

順番処理起きていること
1SaiyanWarrior goku;goku というクラス型変数を宣言する
2goku = new SaiyanWarrior();新しい戦士オブジェクトを生成する
3goku.show();初期状態を表示する
4int battlePower = 9000;戦闘力を表す値を用意する
5double kiPower = 5000.0;気の力を表す値を用意する
6String warriorName = "孫悟空";名前を表す String オブジェクトを用意する
7goku.setWarrior(battlePower, kiPower);数値情報を設定する
8goku.setName(warriorName);名前オブジェクトへの参照を渡す
9goku.show();設定後の状態を表示する

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

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

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

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

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

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

String warriorName = "孫悟空";
goku.setName(warriorName);

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

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

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

ドラゴンボール風にたとえると、孫悟空という名前が書かれた名前札を、setName という係に渡しているようなものです。
その係は、その札を受け取り、戦士の名前欄に登録します。

実引数と仮引数をドラゴンボール風に考える

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

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

ドラゴンボール風にたとえるなら、呼び出し側がこの名前札を渡しますと持っていくのが実引数です。
受け取る側が、ではこの札をこちらの受け取り箱 nm で受けますとするのが仮引数です。

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

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

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

private String name;

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

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

フィールド名役割
battlePowerint戦闘力を表す
kiPowerdouble気の力を表す
nameString戦士の名前を表す文字列オブジェクトを指す

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

ドラゴンボール風にたとえると、戦士の記録には、

  • 戦闘力
  • 気の力
  • 名前札

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

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

この図が示していること

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

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

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

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

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

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

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

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

ドラゴンボール風にたとえると、悟空の情報札を別の係や別の戦士へ渡して、同じ悟空の情報をそれぞれの場面で活用するようなものです。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

図:クラス型変数でオブジェクトを渡して連携する

この図が示していること

この図では、warriorName を setName メソッドへ渡し、その参照が仮引数 nm で受け取られ、最終的に name フィールドへ代入される流れを表しています。

ここから分かるのは、クラス型変数の受け渡しでは、オブジェクト本体を丸ごと移動させたり複製したりしているのではなく、参照を受け渡しながら処理を連携させているということです。

このしくみがあるからこそ、Javaではオブジェクト同士をつなげた設計がしやすくなります。

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

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

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

ドラゴンボール風にたとえるなら、戦士の記録は単独で存在するだけではありません。
名前札を持ち、情報札を受け渡し、別の係や別の場面へつながっていきます。

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

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