Java はじめて18 〜設計後の部品を実装する2〜

イントロダクション

役割分担などのクラスを扱う、プログラム設計をする。ということをやってきたので、今度は実際にプログラムを作成しなが
設計から実装までをやってみたいと思います。

そのため、前書きというか能書きが長くなります。申し訳ない・・・

設計したプログラム

設計したプログラムは「コーダー銀行アプリ」です。単純に標準入力から金額を入力してそれを保持します。
お金を入金すれば、加算され、引き出せば減算されるというようなプログラムです。

このプログラムの実装には、以下のクラスを使用しています。※Githubにソースがあります

クラス図 ※利用していないものもあります。

  • MainBankクラス: メインメソッドがある、プログラムを起動するクラス
  • Calcurationクラス: 細かい計算などを行うためのクラス
  • InputCheckerクラス: 入力値などをチェックするためのクラス

それぞれ上のように役割分担をしました。

MainBankクラスの設計

  • このクラスは、メインメソッドを実行しATMの機能を実装したatm()メソッドを起動する。
  • atmメソッドは、以下の処理を行う
    • ATMの受付表示
    • ATMの終了・入金・引出し操作

Calcurationクラス

  • このクラスは、ATMに貯金されている金額の管理を行う ※預金額は1000円スタート
  • 入金処理と引出し処理を行う

InputCheckerクラス

  • このクラスは、入力した値が適当かどうか判定する

設計後の実装

今回は、部品の設計を行った後の、実装を行います。(設計で)作成したクラスは以下の通りです。

  1. MainBankクラス
  2. Calcurationクラス

設計レベル1

作成するクラスは上記の通りですので、これらをシンプルに実装します。

MainBakクラス

筆者が実装したメインメソッドは、以下になります。

public static void main(String[] args) {
    MainBank main = new MainBank();
    main.atm();
}

2行で終わりました。。。デワデワ。。。

というのは冗談として、最終的なソースはGitにコミットしておきます。(本当はPUSHですが。。。)
mainメソッドで、「ATIMを起動する」という言葉をmain.atm()と実装しています。次は、ATMを起動したら、「ATMの処理内容」を実装します。以下は、その実装例になります。これを参考にオリジナル仕様で作成してください。

  1. 「いらっしゃいませ」などのメッセージを表示する
  2. 入力を促す文言の表示
  3. 入力を受付て「入金」「引出」を選択する
  4. 上記と同様に、「入金」「引出」の処理を実行する
  5. ATMを終了する入力を受けたらプログラム終了

ここまできたら、あとは創意工夫がモノを言うので、ここでは参考程度に、筆者が実装した結果を記載したいと思います。

作成したコード

部品一覧

一覧と言っても、2つのクラスしかないので、大したことはないのですが、簡単に説明だけつけておきます。

MainBankクラス
・メインメソッ: このクラスのatm()メソッドを呼び出します。これだけです。
・フィールド: cal=Calcurationクラス
・コンストラクタ: 引数なしのコンストラクタで、Calcurationクラスのインスタンスを生成する
・atm()メソッド: コンソール表示を行い、入金、引き出しのどちらかの処理を呼び出す。
そして、想定外の入力があった時はエラーメッセージを出力して再度入力を促す
Calcurationクラス
・フィールド: 預金額を保持
・預金額のGetterとSetterを持っている
・入金・引き出し処理メソッド: 第二引数のbooleanで入金か引き出しか判定する。あとは入金なら足し算、引き出しなら引き算を行う。
他のメソッドは細かい入力チェック、計算処理

これらのクラス関係

MainBankクラスはフィールドにCalcurationクラスを持っている。このクラスがアプリケーションのメインを担当する。

これに対して、Calcurationクラスは、入金、引き出し処理の担当を行い。フィールドには預金額を保持するようにしている。入金、引き出しの処理で預金額が加算されたり、減算される。

コンストラクタについて

コンストラクタはクラスのインスタンスを作る(newする)ときに動くメソッドのようなものでクラスの初期化を行う。
そして、引数のあるなしによりコンストラクタは区別される。具体的には下のようなコードです。

public class MainBank {
    MainBank() {
       // コンストラクタA
    }
    MainBank(int yokingaku) {
       // コンストラクタB
    }
}

上の2つのコンストラクタはJVMによって区別されます。つまりは、両方とも定義(コードに書くことができる)ということです。
例えば、メインメソッドでコンストラクタを呼び出したときに処理が走る場所が変わります。サンプルコードをいかに記載しいます。

public static void main(String[] args) {
   // コンストラクタAが起動する
   MainBank main = new MainBank();
   // コンストラクタBが起動する
   MainBank main = new MainBank(1008);
}

このようにコンストラクタを複数作るのは、初期化するパターンを何個か作成したいからです。
もし、「このパターンはいらんな。。。」という場合はこのようにコンストラクタを複数作らなければ良いのです。

そして、上のように「引数あり」と「引数なし」のコンストラクタ(メソッド)を作成することを「オーバーロード」と言いいます。
のちに、「オーバーライド」という言葉も出てくるので混同しないようにしましょう。

とりあえずは、クラスを作った後に、どのようにしようするのか?という部分について記載しました。今後は、作成したクラスにいろいろな使い方ができるように、次を意識しながら実装していきます。

  • コンストラクタでは何をする?
  • インスタンスの数は?
  • 処理の流れはこれでよいか?
  • わかりやすいコードになっているか?

でわでわ。。。

<<< 前回 次回 >>>

<Java関連の動画リスト>



Java はじめて17 〜設計後の部品を実装する1〜

イントロダクション

今までに、クラスを作成して、使用するということを学習しました。クラスを作成して使用するというのは、単純に書くクラスに役割を与えてその役割ごとにメインメソッドから必要なクラスを呼び出しプログラムを動かすということです。

具体的には、JavaAPIですでに実装されていますが、文字列を扱うのであれば、Stringクラスがその役割を果たします。
また、独自に作成したクラスがその役割を果たすのであれば、そのクラスを呼び出し、実行してやればそれで目的は果たせるというわけです。

実例としてじゃんけんゲームを考えてみます。

クラスの作り方

<その1>

<その2>

じゃんけんゲームの場合

じゃんけんゲームと言っても、コンソールアプリを実装(作成)するとします。
必要な処理としては、以下のようになります。

  1. じゃんけんの手を入力する。(プレーヤーの手を入力)
  2. CPUの手を取得する
  3. プレーヤーとCPUの勝敗判定を行う
  4. 結果を表示する

※下の動画の場合は、じゃんけんの手を示すヘルプを下のように表示しています。

**************
グー  *   0 *
チョキ*   1 *
パー  *   2 *
**************

役割分担

ここで、役割分担のことを考えてみます。※あくまでも、筆者の考えたものです
上のような処理を行うのであれば、役割として次のような分担を行うことができます。

  • プログラムを実行する役割(メインメソッドのあるクラス)
  • CPUの手を取得したり、勝敗判定を行う役割(Utilityを提供するクラス)

そうすると、次のように2つのクラスを作成することができます。

  • JankenMain: じゃんけんゲームを起動するクラス(メインメソッドのあるクラス)
  • JankenUtil: じゃんけんゲームのユーティリティー(細かい機能)を提供するクラス

実装例

メインメソッドのみの実装でいくと下のような実装イメージです。

public static void main(String[] args) {
    // ユーティリティークラス
    JankenUtil util = new JankenUtil();
    // 標準入力(コンソールからの入力)
    Scanner scan = new Scanner(System.in);
    // ユーザー入力
    String input = scan.nextLine();
    // CPUの手
    String cpuTe = util.getCpuTe();

    if (util.judge(input, cpuTe)) {
        System.out.println("WIN");
    } else {
        System.out.println("LOOSE");
    }
}

フローチャートにすると下のようになります。プログラム的なプロセスと人間的なプロセスがあります。
プログラム的なプロセスに関しては、学習すればすぐにわかることなのでよいのですが、
人間的なプロセスに関しては、ほぼ無限大にパターンがあるので、頭をひねる必要があります。

設計後の部品

今回は、役割分担を行なった後にクラス(部品)をどのように組み合わせてアプリを作成するか?について記載したいと思います。
作成するのは、子供銀行とでも言いましょうか?お金の入金、引き出しができるなんちゃってATMアプリを作成したいと思います。
【補足】
アプリの規模がとても小さく、処理内容も少ないのでちゃんとした「設計書」の類は作成しません。

<ユースケース>
【前提】
コンソール画面のATMとします。
ユーザー認証は行いません。
単純に金額を入金、引き出しができるものを作ります。

【仕様】
アプリを起動してコンソールから「引き出す」「入金」を選択する
それぞれの処理に対して「預金額」から足し算、引き算を行いその結果を表示する

<クラス図>

<部品候補>

  1. メインメソッドを持ち、入力の受付も行うクラス
  2. 入金、引き出しを行う計算処理クラス

部品を作る

<部品候補>からクラスを2つ作成します。クラス名はアルファベットで作成する必要がある(全角の文字でも作成できますが、なんとなく嫌なので。。。)

  1. MainBankクラス(メインメソッドを持つ)
  2. Calcurationクラス(引出し、入金を管理する)

上のように名前をつけます。このように「役割分担」をしてやれば、いざ修正するときにどちらかのクラスの一部を修正してやれば、デグレート(修正したらおかしな事になる)する事もないように作ることができます。

ここのポイントとしては、書く処理の依存度をなるべく低くしてやることです。依存度が高いと下のようなコードになります。

public static void main(String[] args) {
   MainBank main = new MainBank();
   Scanner input = new Scanner(System.in);
   String inStr = input.nextLine();
   if ("in".equals(inStr)) {
       // 入金処理を行う
   }
      ・
      ・
      ・
}

入力を受けたあとに入金処理を同じクラスの中に書き始めると、結局のところは全部の処理をMainBankクラスに書くことになります。
ソースを読む時は全部読まないといけないし、修正するときも、あちこち直さなくてはいけません。。。
残業パラダイス必至のコードです。平和のためにこのような作成方法はやめましょう。。。

依存度を下げるために

クラスを複数作成し、役割分担をしてやります。
今回のコンソールアプリを作るのには以下のような2つの処理が必要と筆者は考えました。

自分で考えて「このように役割分担をする」という風に考える練習をすることが大切です。上記は筆者が考えたものですので、参考程度にしてください。

  1. コーダー銀行の受付(入金、引出し)を行う(判定)する処理
  2. 入金処理、引出し処理のそれぞれを行う。

ポイント

  1. 受付部分は共通なので、MainBankで行う
  2. 入金処理、引出し処理は独立させることができるので分割する

そんなわけで

  1. MainBankクラス
  2. Calcurationクラス

上記のクラスを作成することにしました。

MainBankクラス

アプリケーションの起動とコーダー銀行の受付処理を行います。
処理の詳細に関しては、以下の通りになります。

  1. コーダー銀行の受付文言をコンソールに表示
  2. デフォルトの預金金額¥1000-を表示する
  3. 入金(in)、引出し(out)の判定を行い、処理終了する時は「bye」の入力で処理を終了する
  4. そのほかの入力はエラーメッセージを表示してサイド入力を促す表示を行う

Calcurationクラス

入金、引出し処理を担当するクラスです。このクラスには預金額を管理するためのフィールド変数が必要になります。
そして、入金処理、引出し処理では以下のような処理が必要になります。

  1. 入金・引出し処理を行う旨を表示する。
  2. それぞれの処理を行った後の預金額を表示する。
  3. 受付〜各処理への状態が変化していることをプログラム的に区別する必要がある。

上の3番に関して、「プログラム的に〜」と記載している部分は「状態」を管理することにより処理クラスを変更、管理しやすいのです。この設計手法に関してはGOFのデザインパターンなどを読んでみるととても参考になります。
良いデザイン(設計)をできるようになると、色々と楽ができます。そして、仲間内で「あーだこーだ」と議論するのも楽しいと思います。

でわでわ。。。

<<< 前回 次回 >>>

<Java関連の動画リスト>



Java はじめて16 〜クラス設計から実装〜

クラス設計から実装

クラスの設計〜実装までの簡単な流れを記載したいと思います。いよいよ基本レベル2のプログラミングの始まりです。

余談

よく世間巷では「オブジェクト指向言語」などという言葉が使われていますが、これは「オブジェクト指向で作りやすい」という意味で使用します。オブジェクト指向という言葉がいろんな意味で解釈されているので(同様に、関数型も。。。)使用しません。逆に使用されていたら無視してください。

基本レベル2

筆者の考えですが、プログラミングの学習を行うのには、3つの段階があると思っています。その段階は次のようになると考えます。

  1. 基本文法の理解をする段階
    基本レベル1
  2. クラスとクラスの関係を作り、一つの仕組みを作るための知識を得る段階
    基本レベル2
  3. 新しい仕組みをくみ上げる、新しいテクノロジーを追求する段階
    応用編

そして、今回は基本レベル2の段階に学習を進めようというところです。
まずは、クラスの継承方法、抽象クラス、インターフェースの作り方を覚えましょう。
次に、ポリモーフィズムを理解しましょう。

まずは、クラスを作るときの基本的な考え方を理解しましょう。

よくないプログラムの例

public class Sample_Array {
    public static void main(String[] args) {
        // 1次元配列
        String[] lv1Array = new String[] {"a", "b", "c"};
        // 2次元配列
        String[][] lv2Array = new String[][] {{"a", "b", "c"}, {"d", "e", "f"}};
        // 3次元配列
        String[][][] lv3Array = new String[][][] {
            {{"a", "b", "c"}, {"d", "e", "f"}}
            ,{{"g", "h", "i"}, {"j", "k", "l"}}
            ,{{"m", "n", "o"}, {"p", "q", "r"}}
            };
        // 1次元配列を表示する
        System.out.println("*** 1次元配列を表示 ***");
        for (String nakami : lv1Array) {
            printf("", nakami);
        }
        // 2次元配列
        System.out.println("\n*** 2次元配列を表示 ***");
        for (String[] nakamiLv2 : lv2Array) {
            for (String nakami : nakamiLv2) {
                printf("", nakami);
            }
        }
        // 3次元配列
        System.out.println("\n*** 3次元配列を表示 ***");
        for (String[][] nakamiLv3 :lv3Array) {
            for (String[] nakamiLv2 : nakamiLv3) {
                for (String nakami : nakamiLv2) {
                    printf("", nakami);
                }
            }
        }
    }

    private static void printf(String label, String value) {
        System.out.print(label + "" + value);
    }
}

ポイントとしては、以下の部分が良くないプログラムです。

  1. プログラムのほとんどを1つのメソッドに書いているので、変更を加えようとすると必ずこのファイルを修正しないといけません。
  2. 処理の内容が少ないので問題ありませんが、処理が分割されておらず「〜の処理はXXXにまとめる」というように処理が分割されていない。

結局は小さなプログラムではあまり効果がないのですが、何かのアプリケーションを作成しようとした時に威力を発揮します。

簡単なアプリを作る

コンソールアプリになりますが、簡単に作成して行こう思います。まずは設計です。

設計を行うことで、「プログラムをどのように組むか?」というところを考えます。はっきり言って答えがないので考え続けることになると思います。
しかし、これができれば、プログラミングはもっと面白くなり、世間にも貢献できるようになるかもしれません。
まずは、考えるための材料をゲットしましょう。※知識を取得しましょうという意味です。

クラス図について知識をゲットしましょう。

UMLで設計関連のページ

詳細部分に関しては上記のリンク先を参照していただきたく思います。が簡単に「ATMのようなアプリケーション」を作成してみようと思います。

全体の流れ(ユースケース)

「全体の流れ」と記載しましたが、「人が使うときの流れ=ユースケース」になります。
実際のATMとはかけ離れてしまいますが。。。

<ユースケース>
【前提】

  1. コンソール画面のATMとします。
  2. ユーザー認証は行いません。
  3. 単純に金額を入金、引き出しができるものを作ります。

【仕様】

  1. アプリを起動してコンソールから「引き出す」「入金」を選択する
  2. それぞれの処理に対して「預金額」から足し算、引き算を行いその結果を表示する

仕様としては単純なものです。これをサンプルとして作成することを考えます。

必要な機能を考える

はっきり言ってこの部分には「正解」というものがありません。
ある意味「なんでも良い」のかもしれませんが、なるべく効率の良いものを作りたいと思うのが人情です。
そのために必要なことは以下のようになります。

無駄な処理を行わない

ということです。そのためにクラスを作成し役割分担を行います。
上のケースで行くと、以下の要件が出てきます。

  1. 「引き出し」と「入金」の2つをハンドルする処理が必要
  2. 「引き出し時の処理」と「入金時の処理」が必要
  3. 最終的にコンソールへ出力する処理が必要

大雑把に3つの要件が出てきます。

要件を満たす部品を考える

【前提】
コンソールアプリなのでメインメソッドを動かす中で全ての作業(入金、引き出しなど)を表現します。
<部品候補>

  1. メインメソッドを持ち、入力の受付も行うクラス
  2. 入金、引き出しを行う計算処理クラス

2つの部品を作る事で、役割を分担し、要件を満たすことができそうです。しかし、これでよいのかどうか?自分で考えてみましょう。

筆者が作成したのは、こんな感じです。ソースはGithubにあります。
結局、5個くらいのクラスを作成した形になりました(笑)。

<動画その1>

<動画その2>

でわでわ。。。

<<< 前回 次回 >>>



Java はじめて15 〜クラス型変数の使い方〜

イントロダクション

Javaはクラスをプログラムの単位としています。具体的にはJavaのプログラムを起動するのには下のようなコマンドを実行します。

java クラス名[プログラム引数]

つまり、プログラムを実行するのに「クラス」が必要なわけです。正確にはクラス内にある「メインメソッド」を検索して実行するのですが、「クラスが基本単位なんだな」という理解で十分です。

そして、クラスに役割を与えてそれぞれの役割でプログラムを実行するように作成すれば、デバック、プログラムの改修が楽になります。つまりより良いプログラムが作成しやすくなります。

クラスの扱い方イメージ

イメージとしては、演劇を考えるとわかりやすいかもしれません。
例えとして、役者一人一人が「クラス」であり、それぞれの演目の役(役割)を果たすことで「物語をリアルに再現できる」というのが「実行結果」になります。

今回の学習ポイント

今回は、クラスを使うための学習、練習を行います。なのである部隊の役者を一人引っ張って着て「~やって!」と指示するようなイメージです(笑)

プログラムでクラスを使うときには、以前学習したように基本的にはインスタンス化して実行することが多いです。
なので、変数にインスタンスをセットして実行します。

自作のクラスを使う場合はこんな感じでした。

public class Sample1() {
    public static void main(String[] args) {
        Sample1 main = new Sample1();
        main.hello();
    }
    public void hello() {
        System.out.println("Morning!");
    }
}

クラス変数の使い方

Javaで作成したクラスを使う方法を記載します。クラス・インスタンスの作成は、以下のようにコードを書くと作れます。今回の内容はクラス名 変数名 = new クラス名();のように使用するときの話です。

クラス定義の書き方
フィールド変数は「カプセル化」の観点から「private」にしましょう。もし「クラス変数.フィールド変数名」のように操作したいときは「public」にしてやればよいのですが、セキュリティ的によろしくないのでフィールド変数にアクセスるときは「getter, setter」を使いましょう。

/**
 * クラスのJavaDoc
 */
public クラス名 {
    // フィールド変数
    public String  firstName;
    // publicは外部から参照可能
    public int age;
    // privateは外部から参照できない
    private int zako;
    /**
    * メインメソッドのJavaDoc部分
    */
    public static void main(String[] args) {
        // メインメソッド(クラスのメンバメソッドではない)
    }
    /** メンバメソッドのJavaDoc */
    public void menberMethod() {
        // メンバーメソッド(newすると使える)
    }
}

クラス変数

今回は、作成したクラスの中にメインメソッドを作り、自分のクラスを変数に代入して使用します。
下にサンプルコードを示します。

  • データ型:プリミティブ型・参照型を含めて変数の型を意味します。例:「int型、String型、(作成した)Sample1型」

<通常の変数の書き方>

データ型 変数名の形で書く、SampleCls型の変数mainを宣言する
SampleCls main;

<クラスをインスタンス化して変数にセット>

同様に、初期化する場合
SampleCls main = new SampleCls();

サンプルコード1: メンバメソッドについて

クラスをインスタンス化して使用するときに呼び出すメソッドは基本的に「メンバメソッド(インスタンスメソッド)」です。
他にもstaticメソッドとかありますが、呼び名が複数あるので「static」のついたメソッドとそうでないメソッド(通常のメソッド)があると思っていただければよろしいと思います。

/**
 * クラスのJavaDoc
 */
public SampleCls {
    // フィールド変数
    public String  firstName;
    public int age;
    // これは外部から参照できない
    private int zako;
    /**
    * メインメソッドのJavaDoc部分
    */
    public static void main(String[] args) {
        // メインメソッド(クラスのメンバメソッドではない)
        // クラス型の変数main: データ型 変数名の形で書く
        SampleCls main = new SampleCls();
        main.menberMethod();
    }
    /** メンバメソッドのJavaDoc */
    public void menberMethod() {
        // メンバーメソッド(newすると使える)
        System.out.println("Hello MemberMethod!);
    }
    /** staticメソッド */
    public void staticMethod() {
    // クラス名.メソッド名で呼び出せる ※クラスに一つだけ定義できる
        System.out.println("Hello StaticMethod!);
    }
}

<通常のメソッド>

呼び出し方
main.menberMethod();

<staticメソッド>

呼び出し方
SampleCls.staticMethod();

シンプルにこんな形でコードを作成しました。メインメソッドは、「static」がつくので、クラスの中にあっても「new」演算子でのインスタンス化が必要になります。
「書き方」と同じ構成なのでわかりやすいと思いますが、いかがでしょうか?細かい説明は省きますが、メインメソッドの場合は、クラスをnewして使用しないとクラス内のメソッドを使用することができません。これはメンバメソッドはインスタンス化しないと使用できないためです。
逆に、メソッドにstaticをつけてやれば、メインメソッドから使用することができます。

サンプルコード2: staticメソッド

/**
 * クラスのJavaDoc
 */
public SampleCls {
    // フィールド変数
    public String  firstName;
    public int age;
    // これは外部から参照できない
    private int zako;

    /**
    * メインメソッドのJavaDoc部分
    */
    public static void main(String[] args) {
        SampleCls main = new SampleCls();
        // メインメソッド(クラスのメンバメソッドではない)
        main.menberMethod();
        // スタティックメソッドの実行
        staticMethod();
    }

/** メンバメソッドのJavaDoc */
    public void menberMethod() {
        // メンバーメソッド(newすると使える)
        System.out.rintln("Hello MemberMethod!);
    }

    public static void staticMethod() {
        // スタティックメソッド
        System.out.rintln("Hello StaticMethod!);
    }
}

つまりメインメソッド(staticメソッド)以外のメソッドにはクラスをnewしないとアクセスできません。

理屈としては、メインメソッドはstaticメソッドなので、通常のメソッド(メンバ・メソッド)とは独立したメンバ(クラスの要素)になります。

staticとメンバについて

メンバ・メソッドと同様にメンバ・変数というのもあります。フィールド変数の事です。「メンバ」といいう言葉は、JavaだけでなくC言語、それに連なる言語でよく使用されます。

「メンバ」とはクラスの中に定義するフィールド変数とメソッドの事です。ただし、staticと修飾子が付くと話は変わります。

メンバはクラスのインスタンスを生成して使用するルールになっていますが、staticメソッドはクラスのインスタンスを必要としません

なので呼び出し方が下の様に別にになります。ここがインスタンスとオブジェクトの違いになります。※深く考える必要はありません。後々に学びます。

/** サンプルメソッド */
public void sample() {
    ClassA clsA = new ClassA();
    // メンバメソッド
    clsA.memberMethod();
    // staticメソッド
    ClassA.staticMethod();
}

クラスからクラスを呼ぶ

上のコードともう1つ作成して見ます。

public SecondSampleCls {
    // フィールド変数
    public String  firstName;
    public int age;
    // これは外部から参照できない
    private int zako;
    /** JavaDoc */
    public void secondMethod() {
        // メンバーメソッド(newすると使える)
        System.out.rintln("Hello MemberMethod!);
    }
}

はじめのクラスに「メインメソッド」がないだけの同じうようなクラスです。
そして、はじめのクラスをちょっと修正します。

public SampleCls {
    // フィールド変数
    public String  firstName;
    public int age;
    // これは外部から参照できない
    private int zako;
    /**
    * JavaDoc部分
    */
    public static void main(String[] args) {
        // メインメソッド(クラスのメンバメソッドではない)
        SampleCls main = new SampleCls();
        main.menberMethod();
        SecondSampleCls second = new SecondSampleCls();
        second.sendMethod();
    }
    /** JavaDoc */
    public void sendMethod() {
        // メンバーメソッド(newすると使える)
        System.out.rintln("This is SecondMethod!);
    }
}

このような形でメインメソッドから他の作成したクラスを呼び出します。当然java.lang.XXXのようなクラスもこれと同じようにメソッドを呼び出し使用することができます。

String(文字列)クラスを使ってみる

例えば、以下のコードはjava.lang.Stringクラスの「equals()」を使用しています。

public static void main(String[] args) {
    String str = "test";
    if ("test".equals(str) {
        System.out.println("testです");
    } else {
        System.out.println("testではありません");
    }
}

<復習用動画>

<注意点:リテラルを意識しましょう>

  • 「"(ダブルクォーテーション)」で囲った文字はStringクラスとみなされます。
  • 「'(シングルクォーテーション)」で囲った文字はchar型とみなされます。
  • 何もつけない「1」は数値としてみなされます。
String str = "文字列(String)";
char ch = 'も'; // 1文字のみ
int num = 1; // 数値

こんな感じです。

JavaAPIを使ってみる

「JavaAPI」というのは、JDKにい含まれているJavaのクラス群のことです。パッケージ名で言うならば以下のようなもののことです。

  • java.lang.*;
  • java.util.*;
  • java.nio.*;

詳細はこちらのオラクルのJavaaDovページを参照ください。
クラスパッケージはこちらです。

File読み込み処理

クラスの扱い方の一歩目として、File読み込み処理を学習します。
文字列や、配列(リスト)などは、すぐになれると思いますので、まずは飛ばします。

File IO(ファイル入出力)

このファイル入出力処理は、データ(文字列や配列)の扱いと少し違います。以下の操作を行うときには「ストリーム」を使用します。

  • ファイルを開く
  • データを読み込む
  • データをファイルに書き込む

ストリームという言葉

このストリームという言葉は、動画配信などで使用する「ストリーミング」とは別物です。
こちらのページによると下のような意味が書かれていました。

ストリームとは、小川、流れ、連続などの意味を持つ英単語で、ITの分野では連続したデータの流れや、データの送受信や処理を連続的に行うことなどを意味する。

ここでの「ストリーム」とは「プログラムとファイルの間を行き来するデータの流れる道」のことです。
なので、「ストリームを開く」とか「ストリームを閉じる」などというものの言い方をします。
ファイルの読み込みを行うときには、「ストリームを開きファイルにアクセス」してデータを読み取ります。また、読み込みや書き込みが終わったら「ストリームを閉じます。

ファイル読み込み処理

ファイル読み込み処理を実装してみます。ゲームっぽく入力 -> 表示を繰り返す形のプログラムを作りました。
このプログラムは「file」と入力すると「resources/FirstStage.txt」を開き、ファイルを読み込み、記述している内容を出力するというものです。

プログラムコードは下のようなものです。「readFile()」メソッドががいるの読み込み処理を実装したものです。
クラスの中で、ストリームを開くとか閉じるなどの処理を行っているので、プログラム上ではストリームを開くなどの処理を行っています。
なので、プログラム的には次のような形の説明ができます。

  • <ストリームを開く>:new File(fileName);の部分で引数に渡されている「resources/FirstStage.txt」を開く
  • <ファイルを読み込む>: buf.readLine()でファイルを1行ずつ読込む
  • <ストリームを閉じる>: buf.close();でファイルを閉じる(ストリームを閉じる)

使用している、APIはBufferedReaderを主軸にしています。これは、コンストラクタの引数に「Reader」クラスを渡してファイル操作を行う準備をします。詳細はこちらのJavaDocを見てください。

文字、配列、行をバッファリングすることによって、文字型入力ストリームからテキストを効率良く読み込みます。
バッファのサイズは、デフォルト値のままにすることも、特定の値を指定することもできます。デフォルト値は、通常の使い方では十分な大きさです。

一般的に、Readerに対して読込み要求が出されると、それに対応するベースとなる文字型ストリームまたはバイト・ストリームへの読込み要求が発行されます。このため、FileReaderやInputStreamReaderのようにread()オペレーションの効率の良くないReaderでは、その周りをBufferedReaderでラップすることをお薦めします。次に例を示します。

 BufferedReader in
   = new BufferedReader(new FileReader("foo.in"));

筆者が実装したコード

public class NinethProgram {
        public static void main(String[] args) {
            jp.zenryoku.sample.basic.NinethProgram main = new jp.zenryoku.sample.basic.NinethProgram();
            Scanner scan = new Scanner(System.in);

            while(true) {
                System.out.print("入力してください: ");
                String in = scan.next();

                if("bye".equals(in)) {
                    System.out.println("アプリ終了");
                    break;
                }
                String[] tmp = new String[] {"aaa"};
                try {
                    if (in.equals("test")) {
                        System.out.println("*** Testing now! ***");
                    } else if ("file".equals(in)) {
                        main.readFile("resources/FirstStage.txt");
                    } else if (tmp.length == 1) {
                        System.out.println("*** Not Error! ***");
                    }
                } catch (FileNotFoundException e) {
                    System.out.println("*** No File! ***");
                } catch (IOException e) {
                    System.out.println("*** What's up!? ***");
                }
            }
        }
        private void readFile(String fileName) throws FileNotFoundException, IOException {
            File f = new File(fileName);
            BufferedReader buf = new BufferedReader(new FileReader(f));
            String line = null;
            while((line = buf.readLine()) != null) {
                System.out.println(line);
            }
            buf.close();
        }
}

上記のBufrrerdReaderクラスの場合でもそうですがJavaDocにJavaAPIの使い方など全て書かれていますので、それを読んで理解できるようになれば、Javaを極めたといっても過言ではありません

筆者も学習中です。そのほか「デザインパターン」が実装済みのAPIもありますので、眺めてみるのもよいと思います。

でわでわ。。。

<Java関連の動画リスト>

<<< 前回 次回 >>>



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のダメージ。」と出力してください。

<<< 前回 次回 >>>