カウンターアプリを作る

JavaFXでWindowを表示する

  • JavaFXのアプリケーションはjavafx.application.Applicationを継承して作る。
  • ちなみにjavafx.application.Applicationは抽象クラス(アブストラクトクラス)。
  • startメソッドをオーバーライドして、そこで各種設定(部品配置等)を行う。startメソッドはJavaFXにおけるコンストラクタのような役割。
  • アプリケーションの起動は「Application.launch(アプリケーションのクラス名.class);」で行う。

ボタンなどを配置する

イベントハンドラー(オブザーバー)

  • ボタンクラスはイベント(ボタンがクリックされた等)が発生した時に、何か処理をすること(させること)ができる。
  • 「イベントが発生した時の処理」を担当するクラスを「イベントハンドラー(event handler)」という。
  • もっと詳しく知りたい人へ: Observerパターン
  • (JavaFxの場合)ButtonのイベントハンドラーはEventHandlerインターフェースを実装する。
  • ボタンに関するイベントが発生すると、ボタンに登録したイベントハンドラーのhandleメソッドが実行される。

ExampleHandlerをきちんと動作させる(カウントボタンでラベルに表示されるカウンターを増やし、リセットボタンで0にする)には次の2つができるようにすればいいが、煩雑なコードになる。

  • labelのsetTextメソッドを呼べるようにする
  • countButtonとresetButtonのどちらからイベントが発生したか区別できるようにする

カウンターの機能をうまく実現するには、CounterAppがイベントを処理するようにするのが簡潔になる。

JavaFXについて知る

インターフェース(復習)

  • クラスと同様で、型を定義できる。
  • クラスと違って、単体では使えない(インスタンスを作ることができない)。クラスに実装(implements)する。
  • インターフェースで記述できるメソッドは抽象メソッド(本体がない)。したがってクラスでそのメソッドをオーバーライドして実装しなければ(本体を書かなければ)ならない。

例えると:

  • クラス: 設計図(どのような機能があるのか、その機能をどのような処理で実現するのかが書いてある)。
  • インスタンス: 製品・生産物・実体(設計図に基づいて作られるもの)。
  • インターフェース: 規格(設計の時点で持つべき機能が定義されている。どのように実現するのかは書いていない)。

設計図が違っても、同じ規格に準拠していれば、それらの設計図から作られた実体は同じように扱うことができる(その規格の範囲内では)。

例えばスマホ内蔵のバッテリーは特に標準規格がないので、あるスマホバッテリーを他のスマホで使うことはできない。
乾電池は形・サイズ・電圧の規格(単一、単二等)が合っていれば(インターフェースが合っていれば)、メーカや電池系(マンガン、アルカリ等)が違っても(処理の実装が違っても)入れ替えて使うことができる(注: 厳密には、電池系についても規格があるのでマンガンとアルカリで入れ替えてはダメな場合等もある)。

インターフェース

キャッシュレジスターをプログラムする。

レジで会計する(税込の合計金額を計算してレシートを印刷する)ときに必要なのは、商品の値段と商品名だ。だから、Bookクラスに「値段を取得するメソッド」と「商品名を取得するメソッド」があればOK。

では、Bookで表現できないものをレジで会計するには、どのようにすればいいのか?

  • Book以外も受け付けるようにレジを改良する(クラスごとに保存、計算するプログラムが必要になる)。
  • Bookより上位のクラス(例えば商品クラス)を定義し、それを継承するように書き直す(Java言語は単一継承のためうまく実現できない場合も)。
  • getPriceメソッドをなんとかして呼ぶ(リフレクションを使う)。ちなみに、ダック・タイピングをサポートする言語ならば簡単だが、Java言語はそうでは無い。
  • 商品を表すインターフェースを定義し、レジで扱いたいクラスはそれを実装(implements)する。
  • etc…

インターフェースとは

クラスに特定のメンバー(メソッドとフィールド、主にメソッド)が存在することを保証する仕組み。クラスと同様に型として扱うことができる。

  • インターフェースには「クラスが持っていてほしいメンバー」を定義する。ただしメソッドの本体(実際に実行される部分)は無い(抽象メソッドという)。
  • インターフェースをimplementsしたクラスは、そのインターフェースに定義されているメンバー全体を実装する(メソッドの本体も書く)。

まずはBookだけを計算できるレジ

商品を表すProduct

  • クラスと同様にメンバー(フィールド、メソッド)を持つ。
  • ただし、メソッドは本体がない(抽象メソッド)。

Productを実装(implements)

Product対応レジ

getterとsetter

getter/setterとは

  • getterはフィールドを取得(ゲット)するためのメソッド。メソッド名は「getフィールド名」とする。フィールド名の最初を大文字に変える。対応するフィールドをreturnする。 
  • setterはフィールドに設定(セット)するためにメソッド。メソッド名は「setフィールド名」とする。フィールド名の最初を大文字に変える。引数をフィールドにセットする。

getter/setterでフィールドにアクセスする利点(例)

  • フィールドをprivateとしてgetterだけ用意すれば読み出し専用のフィールドとなる。
  • セッターでセットする値をチェックすることができる。
  • etc…

Bookの改良

Bookのpriceを後から書き換えられないようにしよう。

継承: Objectクラス

(Objectクラスを除く)全てのクラスはObjectクラスを継承している

  • extendsを書かない場合は、自動的にObjectクラスを継承する(「extends Object」と同じ)。
  • Objectのメソッドをオーバーライドして動作を変えることができる。

UMLによる継承の関係の図示

Diagram

toStringメソッド

System.out.printlnで表示しようしたり、文字列(Stringクラスのインスタンス)を+演算子で繋げようとすると、ObjectクラスにあるtoStringメソッドが呼ばれて、そのインスタンスを表す文字列型が取り出される。

例えば、

とすると、

lesson11.d00000.Comic@5e481248

等と表示される。これは、ObjectのtoStringが

と定義されているから(例えば、JDK6のソースコードhttp://hg.openjdk.java.net/jdk6/jdk6/jdk/file/48e891c51be5/src/share/classes/java/lang/Object.java#l235)。

オーバーライドして、インスタンスの情報を見やすく表示するようにしてみよう。Bookクラスに

を加えることで、

の表示が変わる(Bookを継承しているComicの場合も)。

継承: スーパークラスとサブクラス

書籍を表すBookクラスを継承して、漫画本(巻という概念がある)を表すクラスを作ろう。

ソースコード

mainメソッドで、それぞれ使ってみよう。

実行結果:

Javaの絵本, アンク, 1580円
ドラえもん, 藤子不二雄, 500円

Comicのコンストラクタ

  • 継承したクラス(サブクラス)のコンストラクタでは、スーパークラスのコンストラクタを最初に呼ばなければならない。
  • スーパークラスのコンストラクタはsuper(引数)で呼ぶことができる。
  • スーパークラスに引数なしのコンストラクタ(デフォルトコンストラクタを含む)がある場合、「super();」という記述を省略することができる(つまり書かなくても呼ばれる)。

オーバーライド

  • スーパークラスと同じメソッドを定義するとそのメソッドを上書きすることができる。
  • スーパークラスのメソッドは super.メソッド名(引数)で呼び出すことができる。
  • @Overrideはなくてもいいが、つけておくとオーバーライドできているかチェックしてくれる(アノテーション)。

実行結果:

Javaの絵本, アンク, 1580円
ドラえもん, 藤子不二雄, 500円, 1巻

合計金額を計算する(1)

代入記号の意味

プリミティブ型(基本型)は「箱」のイメージ。代入によって、値がコピーされて箱に格納される。

Assignment1

クラス型(参照型)は「タグ」のイメージ。
代入によって、同じインスタンスに結びつく。

Assignment2

もっと色々知りたい人のために:

  • タグが1つも付いていないインスタンスはどうなるのか?→GC(ガベージコレクション、ゴミ集め)によってメモリから消えて無くなる。[教科書P.82]
  • Null Pointer(ヌルポ)とは、タグの先にインスタンスがない状態のこと。
  • クラス型のインスタンスをコピーするのはどうするのか?→自分で実装する。大抵はcloneメソッドをオーバーライドする。
  • インスタンスをコピーするコードを書いたけど、思った通りにコピーされてない。→シャローコピー(shallow copy)とディープコピー(deep copy)について調べよう。

サブクラスのインスタンスをスーパークラスのインスタンスとして扱う

Comicクラスのインスタンスは、Bookクラスのインスタンスとして扱うことができる。なぜならComicクラスはBookクラスを受け継いでいるから(継承しているから)。

多態性[教科書P.108]

  • comic2で呼び出すときは、Comicとして扱うので、volumeフィールドにアクセスできる。
  • book2で呼び出すときは、Bookとして扱うので、volumeフィールドにアクセスできない。
  • book2.getInfo()は上書きした方(ComicのgetInfo)が呼び出される(Bookとして扱う場合であっても、実体はComicクラスのインスタンスだから)。

合計金額を計算する(2)

ArrayListに数値を入れる

キーボードから入力した整数の和と積を計算しよう!(ArrayListにintを入れる)

ポイント

  • ArrayListにはプリミティブ型は入れられないが、ラッパークラス(プリミティブ型に対応したクラス型)を使うことで入れることができる。(教科書P.144)
  • 文字列から整数に変換するにはInteger.parseIntメソッドを使う。(教科書P.145)
  • 「quit」以外の文字列を入力すると、例外(エラー)が発生してプログラムがそこで終了する。例外が発生してもきちんと動くプログラム(例えばエラーメッセージを出して再入力させる等)にするには、例外処理を行うようにする。(教科書P.122)

キーボードから文字列を入力

キーボードから文字列を入力しよう。

ポイント:

  • Scannerクラス(教科書P.176)
  • 文字列を比較する(教科書P.53)

リスト

ArrayListとは(リストとは)

ArrayListは「順序付きのデータコンテナ」を表すクラスで、配列のように複数の変数(インスタンス)を入れておくことができる。

配列との違い:

  • プリミティブ型の変数は入れられない(ただしラッパークラスを使えばいいので支障はない)
  • 入れられる個数が変えられる。というか、追加できる(個数が増えていく)
  • 要素を操作する便利なメソッドがある
  • etc…

利用例

  • インスタンス化は、ArrayList<中に入れる型> インスタンス名 = new ArrayList<中に入れる型>();とする。
  • 要素の数は指定しない。最初は0。追加すれば勝手に増えていく。
  • 追加するにはaddメソッドを使う。
  • sizeメソッドで要素の数が取得できる。
  • 取り出す時はgetメソッドを使う。引数は0から始まる順番を指定する。
  • 全部消すにはclearメソッドを使う。

配列

C言語の配列

C言語では、データをまとめて扱う方法(の1つ)として、「配列」があった。例えば、int型の変数を5つ扱う場合には、

と宣言することによって、a[0]、a[1]、a[2]、a[3]、a[4]という5つの変数が使える。

Java言語の配列

Java言語でも配列が使えるが、C言語とは違うところがある。

宣言とインスタンス化

Java言語では、「int型の配列」は「「int型の配列」という型」があると考えた方が良い。だからというわけでもないが、C言語のように変数名の後ろにかっこがくるのではなく、「int[]」を型として扱うように書く。そして「インスタンス化」が必要。インスタンス化はnew演算子を用いる。

注意して欲しいのは、プリミティブ型の配列の場合、インスタンス化時に各要素は勝手に初期化されてしまう、ということ。例えば、

は、bが初期化(値の代入)がされていないのでエラーだが、

はエラーにならない。aの各要素は0で初期化されている。もちろん、

はaの宣言だけでインスタンス化がされていないので、何もできない。次のように宣言とインスタンス化を分けるのはOK。

要素へのアクセス

C言語と同様に添え字でアクセスできる。

添え字に変数を使うこともできるので、for文を使ってアクセスすることもできる。例えば、全ての値を表示するには次のようにする。

ここで、a.lengthに注目しよう。これは「配列aの要素数」を表している。

実は、配列というのは、クラスのインスタンスのようなもので、lengthというフィールドを持っているように扱える(ただし読み出し専用)。配列の要素も、「添え字でアクセスできる特殊なフィールド」と考えて良い。

つまり、

のイメージはこんな感じ。

クラス型(参照型)の配列

参照型の配列も同様だが、各要素はそれぞれインスタンス化する必要がある。
例えば、lesson05で作成したBookクラスの配列を作ってみよう。

これは、18行目でエラーになる。bookArrayの要素bookArray[0]がインスタンス化されていない(つまりbookArray[0]という入れ物が空っぽ)だからだ。これが有名な「ヌルポ」である。

だから、各要素をインスタンス化(あるいはインスタンスを代入)してやらなければならない。17〜19行目を削除して、次を追加しよう。

これで、先ほどのループも実行できる(28〜30行目を追加)。

もちろん(条件が合えば)for文を使って各要素のインスタンス化をしても良い。

mainメソッドの引数

mainメソッドの引数はString[]である。これはコマンドラインから実行するときに指定することができる。つまり、プログラムの実行時に(コンパイル時ではなく)値を渡すことができる!!

その他のトピックス

他にも、初期化や多次元配列に関して知っておいた方がいいことがあるが、本講義では取り扱わない。実用の観点からは配列よりもコレクションフレームワークを使うことの方が多く、その説明に時間を使いたいからである。ただし、処理二よっては配列を使った方が都合が良い場合もあるので、ぜひ調べてみて欲しい。