finalメソッドについて
スーパークラスをもとにしたサブクラスでは、スーパークラス側にfinalをつけられたメソッドを呼び出すことはできますが、オーバーライドすることはできません。
finalをつけるメソッドは、スーパークラスにて、システムの根幹に関わるメソッドに対してつけます。
書き換えてしまってシステムが誤動作させたくないメソッドについて使用します。
スーパークラスをもとにしたサブクラスでは、スーパークラス側にfinalをつけられたメソッドを呼び出すことはできますが、オーバーライドすることはできません。
finalをつけるメソッドは、スーパークラスにて、システムの根幹に関わるメソッドに対してつけます。
書き換えてしまってシステムが誤動作させたくないメソッドについて使用します。
多様性について勉強します。
正直、なにがなんだかよくわかりませんが、オブジェクト指向ではよく登場するようです。
言葉の意味では、以下のような要約になります。
オブジェクト指向での多様性とは、サブクラスのインスタンスを、スーパークラスのインスタンスのように使うことといいます。
例えばこれまで書いてきた以下のようなテストコードがあるとすると、サブクラスのインスタンスを作った後に、そのインスタンスをもとに、スーパークラスの型の変数に代入することができます。
TestClassChild tcc = new TestClassChild(); TestClassParent tcp = tcc;
上記のtcpから、スーパークラスのフィールドやメソッドが呼び出すことができます。(tccも通常どおり使うことができる)
このような使い方を多様性(ポリモルフィズム)と呼ぶようです。
このあたりは実践で使ってみないとピンとこないかもしれません。
public class SuperClassTest3{ public static void main(String[] args) { //オブジェクトのインスンタンスを生成する TestClassChild tcc = new TestClassChild(); System.out.println(tcc.TestA); System.out.println(tcc.TestB); //tcc.PrintTextC(); //tcc.PrintTextP(); } } class TestClassChild extends TestClassParent { TestClassChild() { //引数なしコンストラクタ用 //スーパークラスの引数なしコンストラクタを呼ぶ super(); } TestClassChild(int TestAA, int TestBB) { //スーパークラスの引数つきコンストラクタを呼ぶ super(TestAA, TestBB); } void PrintTextC() { System.out.print("PrintTextC !! \n"); } void PrintTextP() { System.out.print("PrintTextP オーバーライドテスト !! \n"); } } class TestClassParent { int TestA = 10; int TestB = 20; TestClassParent(int a, int b) { //やっていることは「setTest」メソッドを同じ TestA = a; TestB = b; System.out.print("引数つきコンストラクタ !! \n"); } TestClassParent() { //引数なしコンストラクタ用 System.out.print("コンストラクタ !! \n"); } void PrintTextP() { System.out.print("PrintTextP !! \n"); } void PrintParentTest() { System.out.print("PrintParentTest !! \n"); } }
JKD1.5以降では、オーバーライドされているメソッドかどうかという区別をする為に
@Override
という記述をメソッドの上に書く様式が出てきました。
コンパイル時にこのメソッドは親クラスのメソッドをオーバーライドしているものかどうか、をチェックする動作になり、オーバーライド漏れを防ぐ場面で有効な手段となります。
(2015.05.09追記)
この書き方はアノテーションといい、Override以外にも使用方法は多岐にわたる
親クラス内のメソッドをオーバーライドする条件として、以下の点を考えます。
・メソッド名が同じか
・引数列の型が同じか
上記の条件がそろった時にはじめて、子クラス内のメソッドでオーバーライドが成立します。
この条件(メソッド名、引数列の型)のことをメソッドのシグニチャとよび、signatureと書きます。
HAS-A関係について勉強します。
オブジェクト指向では、おもに包含と呼ぶこともあるようです。
正直よくわかりませんので、調べてみました。
HAS-A関係を簡単に表すと
犬は頭、胴体、を含んでいる。 胴体は首、シッポ、ヘソ、足を含んでいる 足は、指、肉球(?)を含んでいる
という関係になり、犬というものを分割して捕らえていく考え方になります。
オブジェクト指向に、このHAS-A関係、の考え方がどのように関係してくるのか、じっくりと勉強したいと思います。
道は長い
オブジェクト指向では、「AはBの一種である」ということがいえる関係のことをIS-A関係という。
例えば、サブクラスはスーパークラスの一種である。というように、次のような形で表します。
人間は哺乳類の一種である。 哺乳類は動物の一種である。 鳥は鳥類の一種である。 鳥類は動物の一種である。
というような例をIS-A関係と考えます。
前回の例では、継承したスーパークラスを何気に使っていましたが、スーパークラス側で初期化(コンストラクタ)が必要な設計になっている場合、どのように実装したらいいのかが気になります。
javaの言語仕様としては、コンストラクタの呼び出しは自動的に呼ばれる仕様になっているようです。
このときに呼ばれるのは「引数なし」コンストラクタとなるようです。
この自動的に呼び込まれるコンストラクタを自分の明示により呼び出す仕組みが「super()」という呼び出し方です。
以下に例を書きます。
public class SuperClassTest3{ public static void main(String[] args) { //オブジェクトのインスンタンスを生成する TestClassChild tcc = new TestClassChild(); System.out.println(tcc.TestA); System.out.println(tcc.TestB); //tcc.PrintTextC(); //tcc.PrintTextP(); } } class TestClassChild extends TestClassParent { TestClassChild() { //引数なしコンストラクタ用 //スーパークラスの引数なしコンストラクタを呼ぶ super(); } TestClassChild(int TestAA, int TestBB) { //スーパークラスの引数つきコンストラクタを呼ぶ super(TestAA, TestBB); } void PrintTextC() { System.out.print("PrintTextC !! \n"); } void PrintTextP() { System.out.print("PrintTextP オーバーライドテスト !! \n"); } } class TestClassParent { int TestA = 10; int TestB = 20; TestClassParent(int a, int b) { //やっていることは「setTest」メソッドを同じ TestA = a; TestB = b; System.out.print("引数つきコンストラクタ !! \n"); } TestClassParent() { //引数なしコンストラクタ用 System.out.print("コンストラクタ !! \n"); } void PrintTextP() { System.out.print("PrintTextP !! \n"); } }
実行した結果は以下のようになります。
コンストラクタ !! 10 20
結果として「コンストラクタ !! 」と表示されているのは
TestClassChild tcc = new TestClassChild();
でTestClassChildのインスタンスが生成された際に、子クラス(TestClassChild)の引数なしコンストラクタ「TestClassChild()」が反応し
「super();」が呼ばれた結果
親クラスの引数なしコンストラクタ(TestClassParent())が呼ばれて
「System.out.print(“コンストラクタ !! \n”);」が実行されたから
になります。
少しややこしいけど、書きなれて体得していくしかないかと思います。
先ほど書いた実験コードに、フィールドが継承されるかどうかの検証をしてみます。
以下のようなコードを書いてみました。
public class SuperClassTest3{ public static void main(String[] args) { //オブジェクトのインスンタンスを生成する TestClassChild tcc = new TestClassChild(); System.out.println(tcc.TestA); System.out.println(tcc.TestB); //tcc.PrintTextC(); //tcc.PrintTextP(); } } class TestClassChild extends TestClassParent { TestClassChild() { //引数なしコンストラクタ用 } void PrintTextC() { System.out.print("PrintTextC !! \n"); } void PrintTextP() { System.out.print("PrintTextP オーバーライドテスト !! \n"); } } class TestClassParent { int TestA = 10; int TestB = 20; TestClassParent() { //引数なしコンストラクタ用 } void PrintTextP() { System.out.print("PrintTextP !! \n"); } }
結果は下記のようになり、親クラスのTestA、TestBというフィールドの値を参照することができています。
10 20
■注意■
フィールドとメソッドは継承元(親クラス)のものが参照できますが、コンストラクタは継承されず、使うことができません。
先ほど作成したテストコードを改造して、親クラスをextendsした子クラス内で親クラスと同名のメソッドを記述してオーバーライドしてみます。
書いたコードは以下のとおり
public class SuperClassTest2{ public static void main(String[] args) { //オブジェクトのインスンタンスを生成する TestClassChild tcc = new TestClassChild(); tcc.PrintTextC(); tcc.PrintTextP(); } } class TestClassChild extends TestClassParent { TestClassChild() { //引数なしコンストラクタ用 } void PrintTextC() { System.out.print("PrintTextC !! \n"); } void PrintTextP() { System.out.print("PrintTextP オーバーライドテスト !! \n"); } } class TestClassParent { TestClassParent() { //引数なしコンストラクタ用 } void PrintTextP() { System.out.print("PrintTextP !! \n"); } }
子クラス(TestClassChild)内に「PrintTextP」というメソッドを書いて、親クラスのメソッドをオーバーライドしています。
これを実行すると以下のような結果になります。
PrintTextC !! PrintTextP オーバーライドテスト !!
もともと親クラスにあったメソッドが子クラス内に書いたメソッドに置き換えられて実行されている。
スーパークラスから拡張したクラスを作ってみます。
下記のようなテストコードを書いて実行してみます。
public class SuperClassTest{ public static void main(String[] args) { //オブジェクトのインスンタンスを生成する TestClassChild tcc = new TestClassChild(); tcc.PrintTextC(); tcc.PrintTextP(); } } class TestClassChild extends TestClassParent { TestClassChild() { //引数なしコンストラクタ用 } void PrintTextC() { System.out.print("PrintTextC !! \n"); } void PrintTextP() { System.out.print("PrintTextP !! \n"); } } class TestClassParent { TestClassParent() { //引数なしコンストラクタ用 } void PrintTextP() { System.out.print("PrintTextP !! \n"); } }
メインの処理の「SuperClassTest」のクラスの中から「TestClassChild」のクラスのインスタンスを生成して、そのTestClassChild内のメソッドを呼びました。
結果は
PrintTextC !!
という表示がされ、TestClassChild内のメソッドを呼べていることがわかります。
次にに、TestClassChildクラスが継承している「TestClassParent」クラスのメソッドも、同じTestClassChildインスタンス(ここではtccという名前)から呼んでみました。
すると結果は
PrintTextP !!
と表示され、継承元の「PrintTextP」メソッドが正しく動作していることがわかります。
今回の例はシンプルな継承の例ですが、一つの親クラスから、多数の子クラスを派生させることも可能なので、後ほどつめて実験してみようと思います。
スーパークラスについて勉強します。
続きは後日、、
2014.10.09追記
スーパークラスという前に、まずは継承という概念について。
javaやc++等のオブジェクト指向言語で、作成したクラスをもとに、その機能を引き継ぎつつ、違うクラスを作成することができます。
この概念を継承といいます。
元のクラスを継承してできた新しいクラスのことを、サブクラスと呼び、元のクラスのことをスーパークラスといいます。
スーパークラスは原則として1つのみです。
スーパークラスは1個のみですが、サブクラスは1個のみという制約はなく、いくつでも作り出すことができます。
サブクラスは多段に拡張していくことができます。
javaはクラスが階層的に積み重なるように作ることができ、この構造をクラス階層という。
public、protected、privateは、変数やクラスを、どの範囲から参照可能かを決める修飾子です。(他にも色々ありますが、もっとも良く使う修飾子です)
public 自ファイルおよび他ファイル、全てのクラスから参照可能 protected 他ファイルの他クラス以外、全てのクラスから参照可能 private 自ファイルの自クラスのみ参照可能 指定なし 自ファイル内の自クラス、サブクラス、他クラスから参照可能
ちょっとわかりづらいですが、上記のような関係性があり、それらの動きをよく考えつつプログラムを組みます。
クラス内のフィールドやメソッドに対して、修飾子をつけて区別します。
■final
変更不可能な値やクラスを指し示します
主に、「定数」のような使い方をします。
また、finalで指定した変数には、プログラムの途中で値を代入することはできません。
■abstract
抽象クラスや抽象メソッドであることを示します
メソッド本体がないメソッドのことを言います。
メソッドにはつけられるけど、フィールドにはつけられない。
■static
クラスフィールドやクラスメソッドであることを示します
■synchronized
synchronizedメソッド
■native
JAVA言語以外で書かれたメソッドであることを示します
、、、と列挙してみましたが、まだよくわかっていないです。
クラス内にあるメソッドについても、フィールドと同じようにクラスメソッドと呼ぶような宣言の仕方があります。
クラスフィールドと同じように「static」という修飾子をつけて表します。
例として、下記の「countUpTestD」というメソッドをクラスフィールドにしています。
class TestClassSub { //フィールドの初期化 int TestA = 10; int TestB = 30; static int TestC = 20; int TestD = 40; TestClassSub(int a, int b) { //やっていることは「setTest」メソッドを同じ TestA = a; TestB = b; } TestClassSub() { //引数なしコンストラクタ用 } void setTest(int a, int b){ TestA = a; TestB = b; } int sumAB(){ return TestA + TestB; } int countUpTestC(){ return TestC++; } void countUpTestC2(){ TestC++; } static int countUpTestD(){ return TestD++; } }
クラスメソッドは、別名「staticメソッド」という場合もある。
staticをつけずに宣言したメソッドは「インスタンスメソッド」や「staticではない(通常の)メソッド」などと呼び、クラスメソッドと区別をします。
クラスメソッドは特定のインスタンスに関連していな為、インスタンスが生成されていない状態でも呼び出すことができます。
他のクラスから呼び出すときには次のように書きます。
クラス名.メソッド名(引数)
例えば、以下のクラスをnewしてインスタンスを生成する際、インスタンスをnewした回数を取り扱いたい場合は、クラスの中にクラスフィールドという、全インスタンスに共通の情報保存場所が必要になります。
この保存場所をクラスフィールド(またはクラス変数、スタティックフィールド)と呼び、クラス内に宣言することができます。
これまでに作ってきたクラス
class TestClassSub { //フィールドの初期化 int TestA = 10; int TestB = 30; TestClassSub(int a, int b) { //やっていることは「setTest」メソッドを同じ TestA = a; TestB = b; } TestClassSub() { //引数なしコンストラクタ用 } void setTest(int a, int b){ TestA = a; TestB = b; } int sumAB(){ return TestA + TestB; } }
上記のクラスに対し、クラスフィールドを加えたクラス
class TestClassSub { //フィールドの初期化 int TestA = 10; int TestB = 30; static int TestC = 20; TestClassSub(int a, int b) { //やっていることは「setTest」メソッドを同じ TestA = a; TestB = b; } TestClassSub() { //引数なしコンストラクタ用 } void setTest(int a, int b){ TestA = a; TestB = b; } int sumAB(){ return TestA + TestB; } }
上記のTestClassSubクラスを使う場合、一度、クラスのインスタンスを生成し、クラスフィールドを加算するメソッドを何度か呼んでみます。
public class TestClass5 { public static void main(String[] args) { int retA; System.out.print("hello\n"); //引数なしコンストラクタ TestClassSub tc = new TestClassSub(); retA = tc.countUpTestC(); System.out.print("retA -> " + retA + "\n"); retA = tc.countUpTestC(); System.out.print("retA -> " + retA + "\n"); retA = tc.countUpTestC(); System.out.print("retA -> " + retA + "\n"); retA = tc.countUpTestC(); System.out.print("retA -> " + retA + "\n"); retA = tc.countUpTestC(); System.out.print("retA -> " + retA + "\n"); } } class TestClassSub { //フィールドの初期化 int TestA = 10; int TestB = 30; static int TestC = 20; TestClassSub(int a, int b) { //やっていることは「setTest」メソッドを同じ TestA = a; TestB = b; } TestClassSub() { //引数なしコンストラクタ用 } void setTest(int a, int b){ TestA = a; TestB = b; } int sumAB(){ return TestA + TestB; } int countUpTestC(){ return TestC++; } }
すると、実行した結果は次のようになります。
hello retA -> 20 retA -> 21 retA -> 22 retA -> 23 retA -> 24
前例のプログラムで、次のように、クラスからインスタンスを生成しました。
//引数なしコンストラクタ TestClassSub tc = new TestClassSub();
この時、tcが確保されている領域をスタック(stack)と呼びます。
また、TestClassSubのインスタンスが確保されている領域をヒープ(heap)と呼びます。
フィールドの初期化を行うには、コンストラクタの内部でもよく、コンストラクタの外でもOK。
前回に投稿したTestClassSubクラスのフィールドを初期化するには、次のように書いてもOK。
class TestClassSub { int TestA = 10; int TestB = 30; TestClassSub(int a, int b) { //やっていることは「setTest」メソッドを同じ TestA = a; TestB = b; } TestClassSub() { //引数なしコンストラクタ用 } void setTest(int a, int b){ TestA = a; TestB = b; } int getAB(){ return TestA + TestB; } }
初期化されていないフィールドの値は、その型に応じて初期化される。
変数の値については、未定義になる。
具体的には、値は次のようになる。
Boolean型 false;
整数型 0
浮動小数点型 0.0
参照型 null
前回に書いたクラスで、コンストラクタを呼び出した時は以下のように書きました。
TestClass tc = new TestClass(100, 200);
これはコンストラクタを呼ぶと同時に引数、100、200をつけています。
このコンストラクタの呼び方を変更して、引数をつけないで呼び出す方法もやってみます。
具体的には下記のように記述します。
TestClass tc = new TestClass();
また、ひとつのクラスの中にはコンストラクタが複数あってもエラーにはならないです。
全体として下記のように記述してみました。
詳しい動作検証は、また後ほどやります。
public class TestClass2 { public static void main(String[] args) { int retA; System.out.print("hello\n"); //これだとエラーになる //TestClassSub tc = new TestClassSub(); //引数をつけるとエラーにならない(コンストラクタで使う為?) TestClassSub tc = new TestClassSub(100, 200); retA = tc.getAB(); System.out.print("retA -> " + retA); } } class TestClassSub { int TestA; int TestB; TestClassSub(int a, int b) { //やっていることは「setTest」メソッドを同じ TestA = a; TestB = b; } TestClassSub() { //引数なしコンストラクタ用 } void setTest(int a, int b){ TestA = a; TestB = b; } int getAB(){ return TestA + TestB; } }
実行した結果は次のようになります
hello retA -> 0
次のようなプログラムを作り、コンパイル後、実行しました。
public class TestClass { public static void main(String[] args) { int retA; System.out.print("hello\n"); //これだとエラーになる //TestClassSub tc = new TestClassSub(); //引数をつけるとエラーにならない(コンストラクタで使う為?) TestClassSub tc = new TestClassSub(100, 200); retA = tc.getAB(); System.out.print("retA -> " + retA); } } class TestClassSub { int TestA; int TestB; TestClassSub(int a, int b) { //やっていることは「setTest」メソッドを同じ TestA = a; TestB = b; } void setTest(int a, int b){ TestA = a; TestB = b; } int getAB(){ return TestA + TestB; } }
一番最初に、TestClassSubのインスタンスを作ろうとした時に、下記のようなエラーが表示されました。
d:\data\java>javac TestClass.java TestClass.java:7: シンボルを見つけられません。 シンボル: コンストラクタ TestClassSub() 場所 : TestClassSub の クラス TestClassSub tc = new TestClassSub(); ^ エラー 1 個
少し悩んでいたところ、TestClassSubのコンストラクタは引数を書いていたので、インスタンスを作る際にも引数がないとエラーになることに気づき、引数をつけると無事にコンパイルがとおりました。
インスタンスを作り、「getAB」というメソッドを試したまでですが、無事に下記のように表示されました。
d:\data\java>java TestClass hello retA -> 300
まずはエラーにならずにサクっと動作させるようにして進みたいと思います。
エラーでつまずくと、少し後ろ向きになってしまいますが、そうゆう場合には、一旦できているところまでを見直しつつ、なるべく前向きに勉強していきます。
道は長い
先ほど作成した下記のクラス
class TestClass { int TestA; int TestB; void setTest(int a, int b){ TestA = a; TestB = b; } int getAB(){ return TestA + TestB; } }
に対して、コンストラクタを追加します。
class TestClass { int TestA; int TestB; TestClass(int a, int b) { //やっていることは「setTest」メソッドを同じ TestA = a; TestB = b; } void setTest(int a, int b){ TestA = a; TestB = b; } int getAB(){ return TestA + TestB; } }
コンストラクタを呼び出すのは、以下のように書きます。
TestClass tc = new TestClass(100, 200);
さきほど作成したTestClass内にあるメソッドを呼び出して使って見ます。
TestClass tc = new TestClass(); tc.setTest(100, 200);
上記は「setTest」というメソッドを呼んで値を渡しています。
その後処理はメソッドに基づいて計算されます。
新しいインスタンスを作成した後は、フィールドに値を代入してみます。
TestClass tc = new TestClass(); tc.TestA = 1000; tc.TestB = 2000;
TestAやTestBという変数名は意味としてわかりづらいですが、まずは感触をつかむ為にこのように書いてみます。
先ほど作ったTestClassを使うためには、インスタンスを作る必要があります。
具体的には次のように書きます。
new TestClass();
newで宣言するだけなら、なにも計算ができないので、次のようにしてTestClass型として変数に代入します。
TestClass tc = new TestClass();
これまでに何度も出てきている「フィールド」について勉強します。
フィールドは情報を保存する変数のようなものです。
例えば、下記のように書きます。
class TestClass { int TestA; int TestB; }
TestAとTestBはTestClassのフィールドと呼びます。
続いてメソッドについて、考えます。
メソッドは関数のような意味で、宣言します。
class TestClass { int TestA; int TestB; void setTest(int a, int b){ TestA = a; TestB = b; } int getAB(){ return TestA + TestB; } }
抽象的なインタフェースを具体的に実装するクラスを宣言することもできます。
class TestClass implements TestAAA{ }
TestAAAインタフェースを実装したTestClassというクラスを宣言する。という意味になります。
が、まだよくわかっていません。そもそもインタフェースとはなにか、これから掘り下げてみたいと思います。
既に存在しているクラスをもとにして、拡張するクラスを宣言することもできます。
class TestClass extends Thread{ }
上記の場合は、Threadクラスを拡張してTestClassを宣言しています。
javaに標準搭載されているクラスの他に自分でクラスを作る場合は、次のように書きます。
class TestClass{ }
クラスの名前は大文字で始めるのが慣習となっているようです。
うむむ。
クラスの宣言と同時にフィールドの宣言、メソッドの宣言も、書くと次のようになります。
class TestClass{ フィールドの宣言 メソッドの宣言 }
「フィールド」は、そのクラスにおける変数と考え、
「メソッドは、関数、と考えられます。
クラスには様々な種類があります。
例えば
文字列を表す「String」クラス
ファイルを表す「File」クラス
ファイルを読み込む「FileReader」クラス
ファイルに書き込む「FileWrite]クラス
Javaシステム全体で扱う「System」クラス
javaで開発を行う際には、もともと搭載されているクラスにどんなものがあるのかを一旦把握した上で開発するのがよいと思います。実際になにかを開発して、手足のように使いこなすまでは、しらばく勉強を続けたいと思います。
オブジェクト指向を勉強するにあたり、クラスとインスタンスの考え方からやっていきます。
インスタンスはクラスから生成される。という原則をもとに考えると、文字列もまたStringクラスのインスタンス。と考えることができます。
プログラム内で「”Hellow”」と記述した場合は、Stringクラスのインスタンスが生成されたものと同じことになります。
そのクラスの性質としては、「文字列を表す」「文字列の長さを取得できる」といった文字列に関係する操作(関数)ができるようになります。
「インスタンス」とは、上記の「”Hellow”」のように、「特定のもの」を表すと考えることができます。
全てのインスタンスは、クラスというものに属しています。
「インスタンスは具体的な特定のもの」といえます。また、「同じクラスのインスタンスは共通の性質を持つ」といえます。
「クラスとオブジェクト」の時に書いた以下のコード
class TestClass { String test_a; int test_b; }
の部分を詳しくみると、「String test_a;」という部分があります。
この「String」の部分は型、「test_a」の部分はフィールドと呼びます。(クラス内にある関数はメソッドと呼びます)
オブジェクト指向のプログラムを行うときは、インスタンスを生成してフィールド内の(変数の)値を参照したりすることが多くあるので、用語として覚えておきます。
また、クラスの宣言を確認するときは、必ず「フィールド」と「メソッド」をチェックしてプログラムを行います。