この記事では、当社 の新人エンジニア研修の参考にJava8を解説します。
前回はカプセル化について解説しました。
今回は抽象クラスについて解説します。
抽象クラスはインスタンス化できないクラスです。
しかし、巧妙な役割が与えられたクラスです。
今回は少し寄り道をしながらその存在意義について解説します。
1.Integerクラス
例によって、JavaのAPIから、今回はIntegerというクラスを見てみましょう。
Integer
クラスは、プリミティブ型int
の値をオブジェクトにラップします。Integer
型のオブジェクトには、型がint
の単一フィールドが含まれます。さらにこのクラスは、
int
をString
に、String
をint
に変換する各種メソッドや、int
の処理時に役立つ定数およびメソッドも提供します。
このような解説があります。
ラップするというのはwrap、すなわち、サランラップと同じように包み込むという意味です。
基本型を参照型に(包み込んで)変換するという意味です。
なぜ、基本型を参照型(オブジェクト)として扱う必要があるのでしょうか?
それは、オブジェクトにはいろいろと便利なメソッドがあるからです。
以下のサンプルプログラムを見てください。
1 2 3 4 5 6 7 8 9 |
public class Example38 { public static void main(String[] args) { String num1 = "1024"; String num2 = "512"; System.out.println(Integer.parseInt(num1) + Integer.parseInt(num2)); } } |
<結果>
1536 |
文字列の”1024″と”512″を足し算しています。
文字列のままではできない相談ですが、上記では出来ています。
Integer.parseInt(num1)
のところでIntegerクラスのparseIntメソッドを使っています。
parseという英語の意味は”解析する”です。
intとして解析する、つまり整数として扱うというメソッドなわけです。
文字列を整数として扱うことがなぜ必要なのでしょうか?
一例ですが、皆さんがWebアプリケーションを作るときに必要です。
Webページのフォームに入力した数値は文字列扱いなので、それを数値として扱いたいときに使います。
いまから覚えておいてください。
こういうと、そもそもなぜ、基本型があるのかという鋭い疑問を持たれる方もいらっしゃいます。
基本型が存在する一番の理由は、パフォーマンスです。
参照型はヒープ領域にインスタンスを作るのでパフォーマンスが悪いのです。
現代であればそれほど問題にならないかもしれませんが、Javaが企画されたのは1990年代ですので、マシンパワーが非力だった訳ですね。
ちなみに、近年iOSなどで人気のswiftという言語にはプリミティブ型はなく、すべてがオブジェクトです。
さらにIntegerクラスを使うと次のようなことが可能です。
1 2 3 4 5 6 7 |
public class Example38_2 { public static void main(String[] args) { System.out.println(Integer.MAX_VALUE); System.out.println(Integer.MIN_VALUE); } } |
<結果>
2147483647 -2147483648 |
int型の範囲を覚えなくてよい、と嬉しくなりますね。
※ただし、エンジニアのたしなみとして±約21億ということは覚えましょう。
なぜ、Integerクラスを抽象クラスの説明で持ち出したかといいますと、理由はその親クラスのNumberクラスにありました。
クラスInteger
- java.lang.Object
- java.lang.Number
- java.lang.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クラスの親クラスなのでした。
抽象クラスとは、親クラスであるということだけが存在意義でインスタンス化できないクラスです。
ですから、以下のサンプルプログラムはエラーが表示されます。
1 2 3 4 5 6 7 |
public class Example38_3 { public static void main(String[] args) { Number n = new Number(); System.out.println(n); } } |
<結果の抜粋>
java.lang.Numberはabstractです。インスタンスを生成することはできません |
では、抽象クラスにはどのような意義があるのでしょうか?
抽象クラスには抽象メソッドを1つ以上定義しなければなりません。
そして抽象クラスを継承した子クラスは抽象メソッドをオーバーライドしなければならないという決まりがあります。
つまり、ある抽象クラスの子クラスには必ず、親クラスの抽象メソッドの実装があることが保証されるのです。
例えば、Numberには抽象メソッドintValueがあります。
整数値を返すメソッドです。
つまり、IntegerクラスにもDoubleクラスにも(Byte, Float, Shortにも)このintValueがオーバーライドされていることが保証されているのです。
以下のサンプルプログラムを見てください。
1 2 3 4 5 6 7 8 9 |
public class Example38_3 { public static void main(String[] args) { Integer i = new Integer(128); Double d = new Double("3.14"); //※ System.out.println(i.intValue()); System.out.println(d.intValue()); } } |
※ちなみに、コンストラクタに渡す値は数値でも文字列でも構いません。(オーバーロードされている)
<結果>
128 3 |
このように確かにintValueメソッドがあります。
子クラスに親クラスの抽象メソッドのオーバーライドを義務付けるというのが抽象クラスの役割です。
では、もしも、抽象クラスという存在がなかったら同じことは可能でしょうか?
もちろん気を付けてオーバーライドすれば可能かもしれません。
しかし、人はうっかりしやすく、忘れやすいものです。
そのため、抽象クラスの仕組みを採用したのです。
この抽象クラスの仕組みを皆さんも使うことができます。
ただ、このお話は実務についてもすぐに活用するレベルのお話ではないので参考までに聞いてください。
3.オリジナルの抽象クラスを作る
ここでは、あくまでサンプルとして簡単な抽象クラスを作成してみます。
会社員クラスという抽象クラスを用意してworkという抽象メソッドを定義しました。
workメソッドの実装は、会社員クラスの子クラスとして、社長クラスとマネージャークラスを用意して定義しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
abstract class AbstractOfficeWorker { abstract void work(); } class President extends OfficeWorker { @Override //※ void work() { System.out.println("社長の仕事をします。"); } } class Maneger extends AbstractOfficeWorker { @Override //※ void work() { System.out.println("マネジメントの仕事をします。"); } } public class Example38_4 { public static void main(String[] args) { AbstractOfficeWorker[] office1 = {new President(), new Maneger()}; for (AbstractOfficeWorker officeWorker : office1) { officeWorker.work(); } } } |
※IDEを使っていると「@Override注釈の追加」などというアドバイスが表示されると思います。@Overrideは正しくオーバーライドされていない時に、コンパイラがエラーを出してくれるという便利なannotation(注釈)です。ちなみに、アノテーションは今後も出てきます。@で始まるものがあればアノテーションだと思ってください。
<結果>
社長の仕事をします。 マネジメントの仕事をします。 |
抽象クラスを使うことで、このようなことができます。
抽象クラスの考え方自体は以前から存在したのですが、実用化したJavaの開発者には頭が下がります。
なお、今回クラス名の先頭に「Abstract」をつけているのは一目で抽象クラスであるということが分かるようにという配慮です。
4.オートボクシングとアンボクシング
今回、ラッパークラスのお話が出てきましたので、ついでにオートボクシングとアンボクシングのお話もしておきます。
どういうお話かといいますと、基本型とラッパーの間の変換に手間はいらないというお話です。
次のサンプルコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Example38_5 { public static void main(String[] args) { double e = 2.71; Double num = e; //Autoboxing System.out.println(num); double e2 = num; //Unboxing System.out.println(e2); } } |
この6行目のように基本型からラッパーに代入できることをオートボクシング(autoboxing)といいます。
逆に、ラッパーから基本形に代入できることをアンボクシング(unboxing:箱から出す)といいます。
ちょうど、購入した電化製品を箱から出すことも英語でunboxingといいます。
実は、ラッパーが真価を発揮するのは後ほど学ぶcollec
したがって今はまず、覚えましょう。
5.NumberFormatException
抽象クラスとは直接関係がありませんが、今回、Integer.parseIntのお話が出てきましたので、ついでにそれにまつわる例外の話もしておきたいと思います。
NumberFormatExceptionといいます。
Number:数値のformat:フォーマットの例外ということで文字通りですね。
以下のサンプルプログラムを見てください。
1 2 3 4 5 6 |
public class Example38_6 { public static void main(String[] args) { System.out.println(Integer.parseInt("2byte")); } } |
<結果>
Exception in thread “main” java.lang.NumberFormatException: For input string: “2byte” (以下略) |
ここまで理解できたら練習問題をやってみましょう。
今回は抽象クラスについて見てきました。
抽象クラスを使うことで、子クラスに親クラスの抽象メソッドのオーバーライドを義務付けることができました。
より、堅牢なクラス体系を作り出すことができるということが理解できたのではないでしょうか?
次回のテーマはインターフェースです。
インターフェースもインスタンス化できないクラス(のようなもの)です。
なぜ、インターフェースが必要なのでしょうか?
抽象クラスで十分ではないでしょうか?
その理由のヒントは、多重継承できないJavaの仕組みにあります。
推理しながら次回を楽しみに待っていてください。
JavaSE8の解説に戻る