Java はじめて14 〜クラスの作り方〜

イントロダクション

今回は、クラスの作り方をやります。以前似たようなことをやっていますが、違う視点で記載します。

<記載概要>

  • クラスの作り方(書き方)
  • インスタンス化
  • インスタンス化必要の場合

クラスの作り方(書き方)

とりあえずは、構文(書き方)を記載します。

public クラス名 {
   // フィールド変数
    public String  firstName;
   // このフィールドは外部から参照できる
    public int age;
    // これは外部から参照できない
    private int zako;
    /**
     * JavaDoc部分
     */
    public static void main(String[] args) {
        // メインメソッド(クラスのメンバメソッドではない)
        クラス名 clz = new クラス名();
        // アクセス就職しが「public」なので参照可能
        int sabayomi = clz.age - 10;
        // メンバーメソッドの呼び出し
        clz.menberMethod();
        // メソッドの呼び出し2:返り値がINT型になっている
        int res = clz.menberMethod("aaa");
    }
    /** JavaDoc */
    public void menberMethod() {
        // メンバーメソッド(newすると使える)
    }
    /** メソッドのオーバーロード */
    public int menberMethod(String str) {
        // メンバーメソッド(newすると使える)
        return 12;
    }
}

簡単に書くと、上のように書きます。

実際のところ、クラス作って、フィールドとかメソッドとか作ってどうするの?というところが、いまいち理解しづらいと思いました。

クラスを作る=カプセル化の意味

「カプセル化」とか「クラス」という言葉が出て来たときに、基本情報技術者試験やJava Bronze Silverなどの資格試験では「厳密な意味」を求められることがあるので、試験で使用する言葉とこのブログで記載する言葉で混乱しないように、「別物」として認識すうるようにしてください。要約して言葉を使っているためです。

上記の疑問「クラス作って、フィールドとかメソッドとか作ってどうするの?」に関して、クラスを作成しない場合と、作成する場合の違いを見て見るのが一番わかりやすいとお思います。

なので、簡単な「じゃんけんゲーム」を例に比較してみようと思います。

クラス分けしない、じゃんけんゲーム

メインメソッドのみを記載します。(FirstJankenMain)

/**
 * メインメソッド
 * @param args プログラム引数
 */
public static void main(String[] args) {
    FirstJankenMain main = new FirstJankenMain();

    main.createJudgeMap();
    Random random = new Random();

    boolean isJanken= true;
    // 無限ループ
    while(true) {
        main.printJankenAiko(isJanken);
        String input = main.acceptInput();
        if (main.inputCheck(input) == false) {
            System.out.println("0-2の値を入力してください。");
            continue;
        }
        // CPUの手を取得する(JavaSEのAPIを使用するのでテストしない)
        String cpuTe = String.valueOf(random.nextInt(2));
        // 4.「ポン!」or「しょ!」を表示
        main.printPonOrSho(isJanken);
        main.printTe(input, cpuTe);

        int judge = main.judgeWinLoose(input, cpuTe);
        if (main.printJudge(judge)) {
            isJanken = true;
            break;
        } else {
            isJanken = false;
        }
    }
    // 7.じゃんけんゲーム終了
}

この書き方だと、自分の認識では、いまいちなコードです。
理由は、単純に拡張することができないからです。例えば、JavaFXを使用して画面を作成した時にこのクラスを再利用することができません。
<JavaFX: 木琴>※オラクルのサイトからコードをダウンロードできます。

<JavaFXの動画リスト>

じゃんけんゲームの勝敗判定など使える要素はたくさんありますが、拡張する要素にかけるのです。

具体的には、このクラスは、全ての処理が1つのファイルに書いてあるので、クラスを呼び出すのに余計なメインメソッドがついてきます。

なので、メインメソッドはメインメソッド用のクラスを作成した方が無難なのです。そうすれば現状の処理を残したまま別のじゃんけんゲームをすぐに作成することができます。

例えば、現状はシングルスレッドで動きますが、これをマルチスレッド対応にすることができます。
<マルチスレッド対応方法>
メインクラスが独立していれば、「」クラスを継承したクラスを1つ追加して、以下の手順を行えばOKです。

  1. このクラスで、じゃんけんゲームのメイン処理を実装(コピペ)
  2. メインメソッドは、追加したクラスを呼び出すだけ
  3. 追加したクラスのテスト
  4. 起動確認

クラス分けをする。じゃんけんゲーム

メインメソッドのみを記載します。(SecondJankenMain)

/**
 * メインメソッドの実装をオブジェクト指向プログラミングっぽく
 * クラスの継承を使用して実装しなおしてみる。
 * @param args
 */
public static void main(String[] args) {
    SecondJankenMain main = new SecondJankenMain();
    Random random = new Random();

    boolean isJanken= true;
    // 無限ループ
    while(true) {
        main.printJankenAiko(isJanken);
        String input = main.acceptInput();
        if (main.inputCheck(input) == false) {
            System.out.println("0-2の値を入力してください。");
            continue;
        }
        // CPUの手を取得する(JavaSEのAPIを使用するのでテストしない)
        String cpuTe = String.valueOf(random.nextInt(2));
        // 4.「ポン!」or「しょ!」を表示
        main.printPonOrSho(isJanken);
        main.printTe(input, cpuTe);
        JankenConst judge = main.judgeWinLoose(input, cpuTe);
        main.printJudge(judge);
        if (main.printJudge(judge)) {
            isJanken = true;
            break;
        } else {
            isJanken = false;
        }
    }
    // 7.じゃんけんゲーム終了
}

この場合だと、上に記載した実装を行うことができます。
詳細に関してはGitリポジトリを見ていただきたいのですが、SecondJankenMainがメインメソッドを持っているので、このクラスの他にクラスを追加。。。(上記参照)

といった具体に実装が可能です。

他にも、
FirstJankenMainクラスには、printXXXの他にもメソッドが定義されていますが、SecondJankenMainクラスには、printXXXX()というメソッドのみが定義されている状態です。

SecondJankenMainに書いていないコードは、このクラスのスーパークラス(親クラス)に書いてあるので、このクラスには書かなくて良いのです。

しかし、この継承関係はあまりよくありません。というか意味がありません。

フィールド変数に、継承しているクラスを保持する方が読みやすいし、修正もしやすいです。

継承を実装することが目的にあったのでこのようになりました。継承関係をなくすのも簡単です。「extends XXXX」を削除するだけです。

インスタンス化

単純に「new」することです。この「new」を行うとクラスの中の「メンバ」が使用可能になります。
具体的には、以下の通りです。

public class MainClass {
    public static void main(String[] args) {
        // インスタンス化
        ClassA clsA = new ClassA();
    }
}

public class ClassA {
    private int age;
    private String name;
    /** コンストラクタ */
    public ClassA() {
        age = 39;
        name = "Takunoji";
    }
    /** メソッド */
    public void say() {
        System.out.println("I am " + name + ". " + age + "years old.");
    }
}

MainクラスとClassAを記載しています。MainクラスでClassAをnewしています。この状態では、ClassAのフィールド変数に決まった値しか設定することができません。

それでは、下のような実装ではどうでしょうか?

public class MainClass {
    public static void main(String[] args) {
        // インスタンス化
        ClassA clsA = new ClassA("10", "Takunoji_Jr");
    }
}

public class ClassA {
    private int age;
    private String name;
    /** コンストラクタ */
    public ClassA() {
        age = 39;
        name = "Takunoji";
    }
    /** コンストラクタのオーバーロード */
    public ClassA(int age, String name) {
        this.age = age;
        this.name = name;
    } 
    /** メソッド */
    public void say() {
        System.out.println("I am " + name + ". " + age + "years old.");
    }
}

このようにすると、コンストラクタに引数を渡した時は「デフォルト値」でのClassAのインスタンスを生成します。
そして、引数を与えた場合は引数に対応したインスタンスが作成されます。

メインメソッドのみを書くと下のようになります。
==インスタンスを2つ作る==

public static void main(String[] args) {
    ClassA defaultClassA = new ClassA();
    ClassA customClassA = new ClassA(10, "Takunoji_Jr");
    defaultClassA.say();
    customClassA.say();
}

表示内容にはそれぞれのフィールドに設定された値が表示されます。

この場合は、ClassAのインスタンスが2つアプリケーション(メインメソッドの処理内)にできていて、それぞれメモリ上にデフォルト値と引数に設定した値が保持されています。

インスタンス化不要の場合

メインメソッドがインスタンス化しなくても使用できます。
本当は「static」がついているので「静的メソッド」とか呼ばれるメソッドですが、後々にこの部分は記載します。

例えば、チェック処理を行おうとした時に、クラスのインスタンスの生成が必要ない場合。ちょっと極端ですが、下のようなメソッドがあったとします。

public class ClassA {
    public boolean isBoolean(boolean b) {
        return b;
    }
}

このメソッドは、フィールド変数を使わないので、インスタンス化する必要がありません。なので下のように修正し「静的メソッドにします。」

public class ClassA {
    public static boolean isBoolean(boolean b) {
        return b;
    }
}

このようにすると、下のように「new」しなくても呼び出すことができます。

public static void main(String[] args) {
    ClassA.isBoolean(true);
}

インスタンス化が必要になる場合

メンバXXXはインスタンス化が必要です。具体的には上のフィールド変数、メンバーメソッドはインスタンス化しないと使用できません。「なんでか」って?

インスタンス化(コンストラクタを起動)することで、クラスがただの「型」だったのがインスタンス(メモリ領域を確保)して、フィールド変数、メンバメソッドを使用するためのメモリ領域にデータを設定、インスタンスとしてクラス・オブジェクトを1つ生成するので、使用可能になります、。。

上の==インスタンスを2つ作る==を見ていただければわかる通り、同じクラスでも、フィールドの値が違います。
なので、sayメソッドで出力する結果も違います。

テキストRPGの場合

例えば、テキストRPGを作成しようと考えたとします。
そうした時に、プレーヤーの中で「勇者」とか「戦士」など作成したいと思います。

このような時に、プレーヤークラスを作成しておき「職業」というフィールド変数にそれぞれの職業を設定してやればクラスを2つ作らなくてもよくなります。 ※極端な話ですが。。。

UMLを見る

クラスとクラスの関係を描くのに UMLというもの(言語)があります。これを使うとイメージ的に理解しやすいと思います。

早い話が、クラスを絵にするので、関係が見やすいということです。

インスタンスを使う、具体的な例

あるウェブサーバーでユーザーがログインする時のケースで考えます。
この時に、ユーザークラスは「ログイン」する前はインスタンスがありませんが、ログインする時に、ユーザーの情報をDBから取得し、インスタン化してデータをクラスにセットします。
これは、ユーザーがログインした分だけ(つまりユーザーの数だけ)インスタンスが生成されます。

つまり、ウェブサーバーにログインした人はサーバー側では1つのインスタンスとして管理されるわけです。
当然、ログアウトしたときには、ログイン情報のインスタンスは削除されます。 ※再度ログインしたら、インスタンスが再度作成されます。
インスタンスが、作成される。というのは、例えば下のようなコードです。

// ユーザー名とセッションIDを保持する形でインスタンスを作成
LoginInfo login = new LogiinInfo(userName, sessionId)

インスタンスについて

はじめに「new」するとインスタンスができるということを話しましたが、3回LoginInfoクラスを「new」するとどうなるでしょうか?
LoginInfoのインスタンスが3つ存在することになりますが、クラスは同じものです。
しかし、ログインしている人が違うので、LoginInfoのユーザー名とかパスワードは別物になります。
コードで書くと次のような感じです。
<LoginInfo>

public LoginInfo() {
   private String userName;
   private String passwd;

   public void setUserName(String name) {
       this.userName = name;
   }
   public void setPasswd(String pass) {
       this.passwd = pass;
   }
}

それぞれログインしたときにフィールド変数に値(ユーザー名)などがセットされ、かつ、インスタンス化されるので各インスタンスが保持している情報(フィールド変数の値)は別物がセットされているというわけです。

問題

  1. メインメソッドを持つ、クラス「Yusha」を作成し、「Hello World」をコンソールに出力するクラスを作成してください。

  2. 上で作成したクラスに「kogeki()」メソッドを追加して、同様にコンソールに「勇者の攻撃!はぐれプログラマに8のダメージ。」と出力してください。

<<< 前回 次回 >>>

投稿者:

takunoji

音響、イベント会場設営業界からIT業界へ転身。現在はJava屋としてサラリーマンをやっている。自称ガテン系プログラマー(笑) Javaプログラミングを布教したい、ラスパイとJavaの相性が良いことに気が付く。 Spring framework, Struts, Seaser, Hibernate, Playframework, JavaEE6, JavaEE7などの現場経験あり。 SQL, VBA, PL/SQL, コマンドプロント, Shellなどもやります。

コメントを残す