アルゴリズムとプログラミング (Algorithms and Programming) 第10回:インスタンス変数、クラス変数 インスタンス変数とクラス変数 インスタンスメソッドとクラスメソッド ローカル変数とスコープ 変数の寿命、ガーベージコレクション 講義資料等: http://www.pe.titech.ac.jp/~watanabe/lecture/ap/index-j.html
インスタンス変数 インスタンス変数 これまで出てきた変数は、インスタンスごとにメモリ領域が個別に割り当てられ、別々に読み書きができた class Point { // 点クラス double x; // x座標 double y; // y座標 } これまで出てきた変数は、インスタンスごとにメモリ領域が個別に割り当てられ、別々に読み書きができた Point p1 = new Point(); Point p2 = new Point(); インスタンス変数 p1とp2はクラスは同じだが、別のインスタンスであり、それぞれ個別に変数double x, double yを持つ Point: p1 Point: p2 x: 1.5 x: 0.2 y: 2.0 y: 0.0 お互い無関係に 読み書き可能
クラス変数 クラス変数 同じクラスのどのインスタンスからでも、共通の1つの変数(=メモリ領域)を読み書きしたい場合がある クラス全体で p1からクラス変数の値を 変更すると、p2から見ても 値が変更されている!! Point: p1 Point: p2 x: 1.5 x: 0.2 y: 2.0 y: 0.0 count : count : 実体は1つ クラス全体で 共有する変数 count : 2.0 クラス変数
クラス変数の宣言 static修飾子 クラス変数へのアクセス方法: オブジェクト変数名.クラス変数名 クラス名.クラス変数名 class Point { // 点クラス double x; // x座標 double y; // y座標 public static int count; } static修飾子 クラス変数へのアクセス方法: オブジェクト変数名.クラス変数名 クラス名.クラス変数名
クラス変数の宣言と利用の例 class Point { // 点クラス double x; // x座標 double y; // y座標 //クラス変数の定義と初期化(static修飾子の有無で出力結果を比較せよ) public static int count = 0; Point( double a, double b ){ x = a; y = b; count++; } class SampleAP0801 { public static void main(String[] args){ // インスタンスが生成される前からクラス変数は存在する! System.out.println("count=" + Point.count); Point p1 = new Point(1.0,2.0); System.out.println("count=" + p1.count); Point p2 = new Point(3.0,4.0); System.out.println("count=" + p2.count); コンストラクタ count=0 count=1 count=2
クラスメソッド クラス単位で共有されるメソッド インスタンス変数にアクセス不可 インスタンスメソッドの呼び出し不可
クラスメソッドは、クラス内で定義されているのでクラスのメンバーといえるが、実体はインスタンスの外に存在する オブジェクト(実体) インスタンス1 インスタンス変数 インスタンスメソッド クラス定義 インスタンス2 クラス変数 クラスメソッド static static インスタンス3 アクセス クラスで共有 static static
public static int count = 0; public static void printValue(){ class Point { // 点クラス double x; // x座標 double y; // y座標 //クラス変数、クラスメソッドの定義 public static int count = 0; public static void printValue(){ System.out.println( "value:" + count ); } public static void printValue(int i){ System.out.println( "value:" + i ); class SampleAP0802 { public static void main(String[] args){ // インスタンスが生成される前からクラス変数、クラスメソッドは存在する! Point.count = 5 ; Point.printValue(); Point.printValue(6); Point o = new Point(); o.printValue(7); インスタンス変数 クラス変数 クラスメソッド クラスメソッド クラスメソッドの中からインスタンス変数であるx,yを参照することはできない! value:5 value:6 value:7
main()はクラスメソッドだった! main()メソッドが実行される約束になっている class ユーザ定義 { public static void main(String[] args){ 処理; } main()メソッドが実行される約束になっている
どこで宣言された変数か? 変数の3つの分類 宣言される場所 インスタンス変数 クラス変数 ローカル変数 クラス内 メソッド内
ローカル変数が見える 範囲(スコープ)と寿命 中カッコ{}で囲まれたブロックがスコープの目印 class SampleAP0803 { public static void main(String[] args){ int x = 0; for( int i = 1; i <= 10; i++) { x = x + i; } System.out.println("total = " + x ); 変数iの値をこのforブロックの外から参照することはできない 変数xの値はforブロック内からでも参照可能
変数の寿命 使い終わった変数は破棄され、使用していたメモリが解放される (ガベージコレクション) ローカル変数の寿命:ブロックの最初にメモリが確保され、ブロックの終わりで破棄される インスタンス変数の寿命:newで生成されてから、最後に参照された後、Java VM が判断したタイミング クラス変数の寿命:プログラムの開始から終了まで
バッティングしても有効 ローカル変数 class X { static int i = 100; int j = 200; SampleAP0804.java class X { static int i = 100; int j = 200; void printValue(){ System.out.println("i= " + i ); System.out.println("j= " + j ); int i = 123; int j = 456; } クラス変数 インスタンス変数 ローカル変数
変数名がバッティングした場合は ローカル変数が優先される class SampleAP0804 { SampleAP0804.java続き class SampleAP0804 { public static void main(String[] args){ X o = new X(); o.printValue(); } 実行結果 i= 100 j= 200 i= 123 j= 456 ローカル変数 クラス変数・インスタンス変数 変数名がバッティングした場合は ローカル変数が優先される バッティングしないように名前を付けるのが望ましい
値を変更しない変数(定数)の宣言 final修飾子 class X { static final int MAX = 100; final static int MIN = -100; final int DEFAULT = 10; } class SampleAP0805 { public static void main(String[] args){ System.out.println("value= " + X.MAX); System.out.println("value= " + X.MIN); X o = new X(); System.out.println( "value= " + o.DEFAULT); 値を変更しないのなら、インスタンスごとにこの定数のためにメモリを確保するのは無駄といえる→普通はクラス変数にする(static)
まとめ インスタンス変数とクラス変数 インスタンスメソッドとクラスメソッド ローカル変数とスコープ 変数の寿命、ガーベージコレクション final修飾子
アルゴリズムとプログラミング (Algorithms and Programming) 第11回:継承の実装 継承の実装: extends キーワード コンストラクタ 継承とメソッドのオーバーライド,可視性 抽象クラス (abstractキーワード) 講義資料等: http://www.pe.titech.ac.jp/~watanabe/lecture/ap/index-j.html
継承の実装 Vehicle(乗り物) Plane サブクラスは、スーパークラスの全てのフィールドとメソッドを含んでいる。 - speed: double # changeSpeed(s:double) # getSpeed(): double サブクラスは、スーパークラスの全てのフィールドとメソッドを含んでいる。 (実際にアクセスできるかは、アクセス修飾子に従う) サブクラス Plane - altitude: double # takeOff () # land ()
継承の実装:extendsキーワード Vehicle(乗り物) Plane スーパークラス - speed: double # changeSpeed(s:double) # getSpeed(): double class Plane extends Vehicle { private double altitude; protected void takeOff() { 処理(スーパークラスのメソッドも呼べる); } protected void land() { 処理; サブクラス Plane - altitude: double # takeOff () # land ()
スーパークラスへのアクセス Vehicle(乗り物) Plane スーパークラス - speed: double # changeSpeed(s:double) # getSpeed(): double public static void main(略) { Plane o = new Plane(); o.changeSpeed(0.0); for(int i=0;i<=300;i+=50){ o.changeSpeed(i); o.takeOff(); } System.out.println("離陸!"); サブクラス Plane - altitude: double # takeOff () # land ()
全てのクラスのスーパークラス Objectクラス extendsキーワードが無い全てのクラスは、自動的にObjectクラスのサブクラスとなる。従って、全てのクラスはObjectクラスのフィールドとメソッドを暗黙的に継承している。 Vehicle(乗り物) - speed: double # changeSpeed(s:double) # getSpeed(): double Plane - altitude: double # takeOff () # land ()
継承とコンストラクタ サブクラスのコンストラクタを呼び出すと.. まずスーパクラスのコンストラクタが実行される (何も指定しなければデフォルトコンストラクタが呼ばれる.引数は無し) 次にサブクラスのコンストラクタが実行される
サブクラスのコンストラクタ(例) ここにsuper();が省略 されていると考える class X { int x; X() { SampleAP0901.java class X { int x; X() { x = 100; } class Y extends X { int y; Y() { y = 200; ここにsuper();が省略 されていると考える
サンプルプログラム(続き) x= 100 y= 200 class SampleAP0901 { SampleAP0901.java続き class SampleAP0901 { public static void main(String[] args) { Y o = new Y(); System.out.println("x= " + o.x ); System.out.println("y= " + o.y ); } 実行結果 x= 100 y= 200
親のコンストラクタを指定する場合 Superキーワード (thisキーワードとの併用は不可) SampleAP0902.java class X { int x; X(int a) { x = a; } class Y extends X { int y; Y(int a, int b) { super(a); y = b; コンストラクタでsuperを使うときは 最初の行でしか使えない!
SampleAP0902.java続き class SampleAP0902 { public static void main(String[] args) { Y o = new Y( 1, 2 ); System.out.println("x= " + o.x ); System.out.println("y= " + o.y ); } 実行結果 x= 1 y= 2
暗黙の親コンストラクタ呼び出し ここにsuper();が省略 されていると考える class X { int x; X() { SampleAP0901.java(再) class X { int x; X() { x = 100; } class Y extends X { int y; Y() { y = 200; ここにsuper();が省略 されていると考える
継承を禁止するfinalキーワード サブクラスの宣言を禁止! コンパイルエラー! final宣言したクラスは、extendsで final class X { int x; } class Y extends X { int y; サブクラスの宣言を禁止! コンパイルエラー! final宣言したクラスは、extendsで サブクラスを作ることが禁止される
メソッドのオーバーライド オーバーライド(override) オーバーロード (overload) 親クラスと子クラスのそれぞれに、 メソッド名、引数、戻り値が全て同一で内容が異なるメソッドを定義すること 既出 混同注意! オーバーロード (overload) ひとつのクラス内に、メソッド名が同じで、引数の型及び数が異なるメソッドを定義すること
オーバーライドの目的 オーバーライドの必要性 Vehicle(エンジン付き乗り物) もともと、サブクラスにはスーパークラスのフィールドとメソッドが全て自動的に引き継がれている。 - speed: double # getSpeed(): double # startEngine () しかし、引き継がれているメソッドの処理内容を、サブクラスで再定義(変更)したい場合がある 。 Plane - altitude: double # takeOff () # startEngine () オーバーライドの必要性
オーバーライドと可視性 子クラスでオーバーライドするメソッドの可視性は、親クラスのメソッドの可視性により制限される public オーバーライドされるスーパークラスのメソッドに指定されている修飾子 オーバーライドするサブクラスのメソッド public public指定が必要 protected protectedかpublicが必要 private アクセス不可につき、オーバーライド不可 無指定 private指定は不可 親クラスでprotectedが指定されているメソッドを子クラスでオーバーライドするにはprotectedかpublic指定が必要
親・子クラスにおける オブジェクト参照型変数の互換性 Javaのきまり: 型の違いに厳しいJavaですが.. 子クラスのインスタンスを指すオブジェクト参照型変数を、親クラスの参照変数に代入することができる。 代入後も、インスタンスの出身が子クラスであることを忘れずに記憶しておく仕組みになっている。 1つのオブジェクトが、あたかも複数の型を持つかのように見せることができる。
オブジェクト指向3原則:ポリモーフィズム (再) (メソッドのオーバーライドと参照型変数の 互換性によって実現される) 乗り物 ジェット機クラスのインスタンスを、乗り物オブジェクトとして扱うことができる。 飛行機 自動車 ジェット機クラスのインスタンスに対する操作を、乗り物に対する操作として記述できる ジェット機 プロペラ機 このような記述の仕方にどんなメリットがあるの? ジェット機は飛行機でもあり、乗り物でもある
操作名は同じだが、実際の操作内容はそれぞれ異なる ジェット機 プロペラ機 乗り物 #動力を始動する() 飛行機 自動車 #動力を始動する() #動力を始動する() 操作名は同じだが、実際の操作内容はそれぞれ異なる ジェット機 プロペラ機 #動力を始動する() #動力を始動する() メソッドのオーバーライド
ジェット機クラスのインスタンスを生成する :ジェット機 new ジェット機(); このインスタンスを乗り物オブジェクトと見なす (乗り物オブジェクト変数 v1 に格納する) 乗り物 v1 = new ジェット機(); 乗り物クラス ジェット機クラスのインスタンスへの参照 動力を始動する()メッセージを送る この記法は、ジェット機に限らず、プロペラ機にも自動車にも、将来的に追加される乗り物にもそのまま変更なく使える。 v1.動力を始動する() 乗り物オブジェクトの操作が呼び出されるように記述されているが、自動的にジェット機オブジェクトに対する操作が呼び出される 上位概念(スーパークラス)オブジェクトに対する操作として記述しておくと、サブクラスの細かい修正や追加などに対して強いプログラムになる(保守性が高い)
ジェット機クラスのインスタンスを生成する :ジェット機 new ジェット機(); このインスタンスをジェット機オブジェクト変数 j1 に格納する ジェット機 j1 = new ジェット機(); ジェット機クラス ジェット機クラスのインスタンスへの参照 動力を始動する()メッセージを送る j1.動力を始動する() この記法では、プロペラ機や自動車に変更したり、さらには将来的に追加される乗り物に対しては、そのままでは使えない。プログラムの記述を変更する必要がある!
オブジェクト指向プログラミングの有効性! ポリモーフィズムの意味と効果 ポリモーフィズム(polymorphism)の意味: 1つのオブジェクトが複数の型を持つ ← 継承により実現 ポリモーフィズムの活用の仕方: 上位概念(スーパークラス)への操作としてプログラムを記述する その効果: サブクラスの細かい修正や追加などに対して強い(保守性が高い)プログラムになる オブジェクト指向プログラミングの有効性!
参照型変数への代入 (例) class X { // スーパー(親)クラス int x; } SampleAP1001.java class X { // スーパー(親)クラス int x; } class Y extends X { // サブ(子)クラス int y; class SampleAP1001 { public static void main(String[] args){ X o = new Y();//サブクラスYのインスタンスを親クラスX } //の参照型変数へ代入できる!
メソッドのオーバーライド(例) class Shape { //2次元図形の面積を表現するクラス SampleAP1002.java class Shape { //2次元図形の面積を表現するクラス double a; //一般に、面積を計算するには、縦×横や底辺×高さ/2など、 double b; //2つの辺の長さを与える必要があると考え、変数を2つ用意する Shape(double x, double y) { a = x; b = y; } double getArea() { //ここでは具体的な定義を与えないことにする。 return 0.0; //具体的な定義はサブクラスで与える。
オーバーライド オーバーライド class Triangle extends Shape { //三角形 SampleAP1002.java続き class Triangle extends Shape { //三角形 Triangle(double x, double y); super(x,y); // x,yとしては底辺と高さを与える } double getArea() { return ( a * b / 2 ); class Rectangle extends Shape { //四角形 Rectangle (double x, double y){ super(x,y); //x,yとしては各辺の長さを与える return ( a * b ); オーバーライド オーバーライド
public static void main(String[] args) { SampleAP1002.java続き class SampleAP1002 { public static void main(String[] args) { Shape o1 = new Shape( 10.0, 10.0 ); Shape o2 = new Triangle( 10.0, 10.0 ); Shape o3 = new Rectangle( 10.0, 10.0 ); System.out.println("o1の面積は" + o1.getArea() ); System.out.println("o2の面積は" + o2.getArea() ); System.out.println("o3の面積は" + o3.getArea() ); }
引数に与えた数値はo1~o2まで全て同じだが、それぞれオーバーライドされたgetArea()メソッドが、異なる処理を実行している。 実行結果 o1の面積は0.0 o2の面積は50.0 o3の面積は100.0 引数に与えた数値はo1~o2まで全て同じだが、それぞれオーバーライドされたgetArea()メソッドが、異なる処理を実行している。
抽象クラス: 抽象メソッドを持つためオブジェクト生成できないクラス 抽象クラス: 抽象メソッドを持つためオブジェクト生成できないクラス 乗り物 クラス名をイタリックにすると 抽象クラス -スピード #スピードを変える() #スピードを表示する() 乗り物クラスのインスタンスは 存在しないことが保証される 飛行機 飛行機クラスでは、依然として スピードを表示する()操作は必須 であり、乗り物クラスは使われる。 -高度 #離陸する() #着陸する() #高度を表示する()
抽象メソッド: abstractキーワード 抽象クラスの実装 親クラスではメソッド名だけ定義して、子クラスにおいて、オーバーライドにより具体的な処理内容を記述する 抽象メソッド: abstractキーワード 具体的な処理内容が与えられていないため、インスタンスの作成は不可
抽象クラスの使用例 abstract class Shape { //抽象クラス double a; double b; SampleAP1003.java abstract class Shape { //抽象クラス double a; double b; Shape(double x, double y) { a = x; b = y; } //抽象メソッドとして定義(この方が自然) abstract double getArea(); 抽象メソッドが1つでもあるとそのクラスは抽象クラス →サブクラスで具体的な処理内容を与えて使用する 抽象クラスだからといって、全てが抽象クラスである必要は無い
class Triangle extends Shape { //三角形 Triangle(double x, double y); SampleAP1003.java続き この部分はSampleAP1002.javaと同じ class Triangle extends Shape { //三角形 Triangle(double x, double y); super(x,y); // x,yとしては底辺と高さを与える } double getArea() { return ( a * b / 2 ); class Rectangle extends Shape { //四角形 Rectangle (double x, double y){ super(x,y); //x,yとしては各辺の長さを与える return ( a * b );
抽象クラスのインスタンス化を削除 class SampleAP1003 { SampleAP1003.java続き class SampleAP1003 { public static void main(String[] args) { Shape o2 = new Triangle( 10.0, 10.0 ); Shape o3 = new Rectangle( 10.0, 10.0 ); System.out.println("o2の面積は" + o2.getArea() ); System.out.println("o3の面積は" + o3.getArea() ); } 抽象クラスのインスタンス化を削除
まとめ 継承の実装: extends キーワード 継承とコンストラクタ: superキーワード 継承の禁止: finalキーワード 継承とメソッドのオーバーライド 抽象メソッド、抽象クラス より保守性の高いプログラムの実現 世の中で既に作成され公開されている各種のJavaクラスライブラリはこのような考え方で作成されている。適当なクラスを継承してカスタマイズすることにより、極めて簡単に自分独自の問題を解決することができる。