ITエンジニアのプレイングマネージャー化応援サイト

12.抽象クラス

この記事では、弊社の新人エンジニア研修の参考にJava8を解説します。

前回はカプセル化について解説しました。

今回は抽象クラスについて解説します。

抽象クラスはその名前が示すようにインスタンス化できないクラスです。

しかし、巧妙な役割が与えられたクラスです。

今回は少し寄り道をしながらその存在意義について解説します。

 

1.Integerクラス

例によって、Javaの標準APIから、今回はInteger(標準API)というクラスを見てみましょう。

Integerクラスは、プリミティブ型intの値をオブジェクトにラップします。Integer型のオブジェクトには、型がintの単一フィールドが含まれます。

さらにこのクラスは、intStringに、Stringintに変換する各種メソッドや、intの処理時に役立つ定数およびメソッドも提供します。

このような解説があります。

ラップするというのはwrap、すなわち、サランラップと同じように包み込むという意味です。

このようなクラスをラッパークラスと呼びます。

つまり、プリミティブ型を参照型に(包み込んで)変換するのがラッパークラスの役割です

なぜ、プリミティブ型を参照型(オブジェクト)として扱う必要があるのでしょうか?

それは、オブジェクトにはいろいろと便利なメンバがあるからです。

以下のサンプルプログラムを見てください。

<結果>

1536

文字列の”1024″と”512″を足し算しています。

文字列のままではできない相談ですが、上記では出来ています。

Integer.parseInt(num1)

のところでIntegerクラスのparseIntメソッドを使っていますね。

parseという英語の意味は”解析する”です。

intとして解析する、つまり整数として扱うというメソッドなわけです。

 

文字列を整数として扱えると何が嬉しいのでしょうか?

実は、データーがファイルやネットワーク、キーボード入力からプログラムに入ってくる場合、大抵は文字列の形式なのですね。

一例ですが、皆さんがWebアプリケーションを作るときに必要です。

Webページのフォームに入力した数値は文字列扱いなので、それを数値として扱いたいときに使います。

Webアプリケーションで使いますので、いまから覚えておいてください。

 

こういうと、そもそもなぜ、プリミティブ型があるのかという鋭い疑問を持たれる方もいらっしゃいます。

プリミティブ型が存在する一番の理由は、パフォーマンスです。

Integerクラスなどの参照型はヒープ領域にインスタンスを作るのでパフォーマンスが悪いのです。

現代であればそれほど問題にならないかもしれませんが、Javaが企画されたのは1990年代ですので、マシンパワーが非力だった訳ですね。

ちなみに、この原稿で何度か登場したKotlinや近年iOSなどで人気のswiftという言語にはプリミティブ型はなく、すべてがオブジェクトです。

さらにIntegerクラスを使うと次のようなことが可能です。

<結果>

2147483647
-2147483648

int型の範囲を覚えなくてよい、と嬉しくなりますね。
※ただし、エンジニアのたしなみとして±約21億ということは覚えましょう。

なぜ、Integerクラスを抽象クラスの説明で持ち出したかといいますと、理由はそのスーパークラスのNumberクラスにありました。

クラスInteger

APIはこのようになっていたと思いますので、java.lang.Numberをクリックしてみてください。

そうすると、

public abstract class Number

と定義されているのが見えます。

このabstract というキーワードが抽象クラスを意味しています。

 

2.Numberクラス

ところで、”Number”つまりは、”数”のインスタンスとはどのようなものでしょうか?

“1”も”3.14”も数といえば数ですが、それぞれ、IntegerとDoubleという、よりふさわしいクラスがJavaには存在します。

ナンバークラスは実在するか?

ナンバークラスは実在するか?

Integerは一種のNumberである。

Doubleは一種のNumberである。

これは、どこかで聞いたようなお話ですね。

サブクラスは一種のスーパークラスである 。

ということで、NumberクラスはIntegerクラスやDoubleクラスのスーパクラスなのでした。

抽象クラスとは、スーパークラスであるということだけが存在意義でインスタンス化できないクラスです。

※abstractという単語には抽象絵画といった意味もあります。絵をご覧いただくと、その意味も分かっていただけるのではないでしょうか?

 

ですから、以下のサンプルプログラムはエラーが表示されます。

<結果>

(中略)java.lang.Numberはabstractです。インスタンスを生成することはできません

 

では、抽象クラスにはどのような意義があるのでしょうか?

抽象クラスには抽象メソッドを1つ以上定義しなければなりません。

そして抽象クラスを継承したサブクラスは抽象メソッドをオーバーライドしなければならないという決まりがあります。

つまり、ある抽象クラスのサブクラスには必ず、スーパクラスの抽象メソッドの実装があることが保証されるのです。

例えば、クラスNumberには以下のような抽象メソッドintValueがあります。

整数値を返すメソッドです。

つまり、IntegerクラスにもDoubleクラスにも(Byte, Float, Shortにも)このintValueメソッドがオーバーライドされていることが保証されているのです。

以下のサンプルプログラムを見てください。

ちなみに、コンストラクタに渡す値は数値でも文字列でも構いません。(オーバーロードされている)

<結果>

128
3

このように確かにintValueメソッドがあります。

 

サブクラスにスーパークラスの抽象メソッドのオーバーライドを義務付けるというのが抽象クラスの役割です。

では、もしも、抽象クラスという存在がなかったら同じことは可能でしょうか?

もちろん気を付けてオーバーライドすれば可能かもしれません。

しかし、人はうっかりしやすく、忘れやすいものです。

そのため、抽象クラスの仕組みを採用したのです。

この抽象クラスの仕組みを皆さんも使うことができます。

 

3.オリジナルの抽象クラスを作る

ここでは、あくまでサンプルとして簡単な抽象クラスを作成してみます。

例えば、ゲームを例にとって考えます。
 
オンラインカジノを作るとします。
 
カジノには通常のプレイヤー(NormalPlayer1)とVIPプレイヤー(VipPlayer1)がいるとします。
 
どちらも人間ですので、抽象クラスAbstractHuman1を作ることにします。
 

抽象クラスの先頭にAbstractをつけて分かりやすくしています。

 
プレイする(play)というメソッドを抽象メソッドとしてAbstractHuman1クラスに定義するとサブクラスではオーバーライドしなくてはなりません。
 

IDEを使っていると「@Override注釈の追加」などというアドバイスが表示されると思います。

@Overrideは正しくオーバーライドされていない時に、コンパイラがエラーを出してくれるという便利なannotation(注釈)でした

以下のテストコードで検証してみます。

<結果>

通常会員用の画面が表示される
VIP会員用の画面が表示される

抽象クラスを使うことで、このようにオーバーライドを義務付けることができます。

 

4.スーパークラスのメンバ呼び出し

superキーワードを使ってサブクラスでスーパークラスのメンバを呼び出すことができます。
 
先程のゲームの例で考えます。
 
通常のプレイヤー(NormalPlayer2)とVIPプレイヤー(VipPlayer2)がいて、共通のスーパークラスに人間(AbstractHuman2)クラスがあるものとします。
 
また、3つのクラス全てに名前というフィールドがあるものとします。
 

ただし、前回のカプセル化の考え方にのっとり、名前フィールドはプライベート宣言されており、コンストラクタで初期化されるものとしました。

この場合でもサブクラスのコンストラクタ内でスーパークラスのコンストラクタを呼んでフィールドを初期化できます。
 
super(実引数列);
 
という書き方をします。
 

コンストラクタでは、名前フィールドをセットして、その名前を使って挨拶をするだけです。

VIPプレイヤーも同様ですが、挨拶表現が洗練されています。
 

以下のサンプルコードでテストしてみます。

<結果>

Hey! I’m yamazaki
How do you do, I’m kokubun
このことは何が嬉しいのでしょうか?
 
この短いコードではメリットが感じられないかもしれません。
 
しかし、スーパークラスのコンストラクタで複雑な処理をしているとしたら大変便利です。
 
もし、コンストラクタを使った同じ初期化処理が、色々なクラスに書かれていたとしたら、その初期化処理を書き換えるのは大変です。
 
スーパークラスと複数のサブクラスで共用していれば変更は楽になります。
 
 
もし、これが抽象クラスではなく通常のスーパークラスとサブクラスですと困ることもあります。
 
それは、スーパークラスの初期化の内容とサブクラスの初期化の内容が相違するようになった場合です。
 
スーパークラスとサブクラスが密結合してしまうわけです。
 
しかし、抽象クラスはインスタンス化されないクラスなのでそのような密結合の心配がないのです。
 
 

5.オートボクシングとオートアンボクシング

今回、ラッパークラスのお話が出てきましたので、ついでにオートボクシングとオートアンボクシングのお話もしておきます。

どういうお話かといいますと、プリミティブ型とラッパーの間の変換に手間はいらないというお話です。

次のサンプルコードを見てください。

<結果>

2.72
2.72

上記※1のようにプリミティブ型から対応するラッパーに自動変換する(代入できる)ことをオートボクシング(Autoboxing)といいます

逆に、上記※2のようにラッパーから対応するプリミティブ型に自動変換する(代入できる)できることをオートアンボクシング(Auto-Unboxing:箱から出す)といいます

 

オートボクシングとオートアンボクシングのイメージ

オートボクシングとオートアンボクシングのイメージ

ちょうど、購入した電化製品を箱から出すことも英語でUnboxingといいます。

 

実は、ラッパーは後ほど学ぶArrayListクラスなどのコレクションフレームワークに関連して重要なのですが、詳細は後ほど。

したがって今はまず、覚えましょう。

 

6.NumberFormatException

抽象クラスとは直接関係がありませんが、今回、Integer.parseIntのお話が出てきましたので、ついでにそれにまつわる例外の話もしておきたいと思います。

NumberFormatExceptionといいます。

Number(数値の)format(形式)のException(例外)ということで文字通りですね。

以下のサンプルプログラムを見てください。

<結果>

Exception in thread “main” java.lang.NumberFormatException: For input string: “2byte”

(以下略)

これもWebアプリケーション研修でよく見る例外です。

 

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□プリミティブ型を参照型に(包み込んで)変換するのがラッパークラスの役割
 
□ある抽象クラスのサブクラスには必ず、スーパクラスの抽象メソッドの実装があることが保証される
 
□サブクラスにスーパークラスの抽象メソッドのオーバーライドを義務付けるというのが抽象クラスの役割
 
□@Overrideは正しくオーバーライドされていない時に、コンパイラがエラーを出してくれるという便利なannotationである
 
□プリミティブ型から対応するラッパーに自動変換することをオートボクシングと言い、その逆をオードアンボクシングという

 

まとめができたら、アウトプットとして演習問題にチャレンジしましょう。

問題12.抽象クラス

 

今回は抽象クラスについて見てきました。

抽象クラスを使うことで、サブクラスにスーパークラスの抽象メソッドのオーバーライドを義務付けることができました。

より、堅牢なクラス体系を作り出すことができるということが理解できたのではないでしょうか?

次回のテーマはインターフェースです。

インターフェースもインスタンス化できないクラス(のようなもの)です。

なぜ、インターフェースが必要なのでしょうか?

抽象クラスで十分ではないでしょうか?

その理由のヒントは、多重継承できないJavaの仕組みにあります。

推理しながら次回を楽しみに待っていてください。

実は「抽象クラスよりも、インターフェイスを選ぶ」という言葉があるくらい、インターフェースは抽象クラスよりも目にする機会が多いです。

 

【今回の復習Youtube】

041-抽象クラス-抽象クラスの定義

042-抽象クラス-オーバーライドとポリモーフィズム

043-抽象クラス-抽象クラスの配列

 

JavaSE8の解説に戻る

PAGETOP
Copyright © Say Consulting Group, Inc. All Rights Reserved.