インスタンス

クラスは設計図、設計図を元に実体を作って使います。そこで、クラスを元に実体を作る作業をインスタンス化と呼び、出来上がった実体がインスタンスです。」と多くの教材は説明します。

ではなぜ、そうしなければいけないのでしょう
そうすると何が有難いのでしょう。

設計図を元に物を作ると、出来上がるのは全て同じものです。
設計図は寸分違わない物を作るための指示書です。
この様なプログラムの再利用も必要でしょう。関数やサブルーチンなどは何度呼んでも同じ動きをします。ですから、設計図のようなプログラムの再利用はオブジェクト指向プログラミング以前のプログラミング言語でも可能でした

ソフトウェアの利用現場を考えてみましょう。
以前のプログラム言語ではできなかったプログラムの再利用とは、元のプログラムを利用して少しずつ違ったプログラムを瞬時に作ることです。関数やサブルーチンなど以前からの仕組みで出来なかったことは、まさにこの事です。「クラスは設計図」という説明では説明が付きません。

このコースではclassは同じ種類の集合を分類したものと説明しました。つまり、概念、コンセプトです。概念から物を作ると、作るたびに概念の解釈が少しづつ変わり、似てはいても違う物が出来るはずです。
つまり、classを元に実体を作るときも、似てはいても少し違う実体を作りたいのです。この仕組みがインスタンス化です。

例えば、クラスの説明ページで示した、チケット販売システムを思い出してください。
A席チケット販売システムも、B 席チケット販売システム、S席チケット販売システムだって基本的にはほとんど変わりません。チケットの価格と販売される席の数が違うだけでしょう。つまり、属性は多少違っても、振舞いはほぼ同じです。

これを関数で作ろうとすれば、ほとんど同じ関数を三つ作ることになり、何かを修正しようとすれば三つの関数それぞれに同じ修正を加えなければいけません。これがclassとinstanceならばclassを直すだけで全てのinstanceが同じように変わります。ましてclassを再コンパイルするだけで再リンクは不要ですから、ソフトウェアを使用しながら機能の変更が可能です。(テストは事前に十分行わなければいけませんが)

以上が、面倒な手間に見えるインスタンス化の有難さです。

次は、このインスタンス化を行うためのコンストラクタです。

書式

修飾子 コンストラクタ名 (引数リスト ) {
   処理
}

三つの条件

コンストラクタ名はクラス名と同一
戻り値型は記述しない
newと一緒にしか使えない(インスタンス生成時以外は呼び出しができない)

※クラス名がメソッド名と同じでも戻り値型を記述している場合は、通常のメソッドとして解釈されます。
※インスタンス生成時の記述で引数リストが対応していないと、コンパイルエラーが発生します。

独自にコンストラクタを定義すると、デフォルトコンストラクタは作られません。にも拘わらす、引数なしのコンストラクタ(デフォルトコンストラクタと同一)を呼ぶとコンパイルエラーになります。

多少違ったインスタンスを作るために、コンストラクタのオーバーロードを使います。また、共通部分の記述を簡略化するためオーバーロードしたコンストラクタから、ほかのコンストラクタを呼び出すためにthis(引数リスト);が使えます。なお、スーパークラスのコンストラクタを呼び出す場合にはsuper(引数リスト);を使います。

クラスが継承関係にある場合、スーパークラス分のインスタンスから生成しなければいけません。そこで、サブクラスのコンストラクタの先頭でスーパークラスのコンストラクタを呼出す必要があります。もし、プログラマーが明示的にスーパークラスのコンストラクタを呼出さないと、コンパイラは引数なしのスーパークラスのコンストラクタを呼出すコードを自動的に追加します。もし、スーパークラスに(独自のコンストラクタを記述したため)引数なしのコンストラクタが存在しないとコンパイルエラーが発生します。
※何かの処理の後でスーパークラスのコンストラクタを呼出すとコンパイルエラーになります。

また、サブクラスでコンストラクタがオーバーロードされていて、明示的にスーパークラスのコンストラクタを呼び出した後にthis();を使ってオーバーロードした別のコンストラクタを呼出すと、別のコンストラクタ側でもスーパークラスのコンストラクタを呼ぶので、スーパークラスのコンストラクタが2回呼出されることになります。このような事態を避けるため、スーパークラスのコンストラクタを呼び出した後にthis();を記述するとコンパイルエラーが発生します。
※スーパークラスのコンストラクタをインスタンス化の間に二回以上呼ぶとコンパイルエラーになります。

アクセス修飾子
コンストラクタには全てのアクセス修飾子が指定できます

public
どのクラスからでも対象のクラスをインスタンス化できる。
protected
継承関係にあるサブクラスや同一パッケージ内のクラスだけが対象のクラスをインスタンス化できる。
package default
同一パッケージ内のクラスだけが対象のクラスをインスタンス化できる。
private
非公開のコンストラクタを定義します。
非公開のコンストラクタの用途は、あるアプリケーション内でインスタンスが1つしかないことを保証したり、コンストラクタをオーバーロードして複数定義し、公開するコンストラクタと非公開にするコンストラクタに分ける(公開コンストラクタの中で非公開コンストラクタを呼ぶ)ためにも使います。

クラスに戻る

クラス及びインスタンスの初期化

似ているけれども、同一ではない個々のインスタンスを作りだすのが初期化処理です。
初期化はコンストラクタで行うのが一般的ですが、初期化はそれだけではありません

段階的に行われる初期化

1.
クラスロード時にスタティックイニシャライザが実行され、クラス変数(static変数)の初期化が行われる。
2.
クラスのフィールドで宣言した変数を既定値で初期化(初期値を代入)する。
3.
オブジェクトの生成時にインスタンスイニシャライザが実行される。
4.
次にコンストラクタが実行され、引数などによる初期化処理が行われる。

スタティックイニシャライザ
クラスのロード時、クラス変数を初期化するために使用します。
クラス変数はインスタンス化しなくても存在し、使える変数ですから初期化が必要です。通常は初期値の代入で行われますが大量の要素を初期化しなければいけないような場合には有効です。
スタティックイニシャライザはクラスのどこにでも、いくつでも記述できますが、ロード時に実行されるのでクラスの先頭から順方向で参照が行われることに注意して、適切な位置に記述する必要があります。

書式

static{
  初期化処理
}

参考例

public class StaticInitializeSample {
	public static void main(String[] args) {

		System.out.print("static arrayの値は[");
		for(int num: StaticInitialize.array) {
			System.out.print( num );
		}
		System.out.println( "]です。");
	}
}

class StaticInitialize {
	static int[] array = new int[10];
	
	//スタティックイニシャライザ
	static{
		for (int i=0; i<array.length; i++) {
			array[i] = i;
		}
	}
}

実行結果

>java StaticInitializeSample
static arrayの値は[0123456789]です。

>

なお、JVMがスタティックイニシャライザを処理中に何らかのトラブルが発生(例えばNullPointerException)した場合、そのことを通知するクラスが存在しないため、JVMはExceptionlnlnitializerErrorを発生させ、プログラムを強制終了させます。

インスタンスイニシャライザ(初期化ブロック)
インスタンス生成時にインスタンス変数(インスタンスごとに作成される変数)を初期化します。インスタンス変数は、通常コンストラクタで初期化しますが、オバーロードした複数のコンストラクタで共通する処理がある時にはインスタンスイニシャライザで一括して初期化すればプログラムの可読性が向上します。
なお、インスタンスイニシャライザはコンストラクタより先に実行されます。

書式

{ }に囲まれたブロックで、クラスブロック直下にフィールドやメソッド、コンストラクタと
同列の扱いで記述します。

参考例

public class InstanceInitializeSample {
	public static void main(String[] args) {

		StaticInitialize si1 = new StaticInitialize();
		StaticInitialize si2 = new StaticInitialize("引数一つの");

		System.out.println("インスタンスsi1は" + si1.constracterType + si1.postFix);
		System.out.println("インスタンスsi2は" + si2.constracterType + si2.postFix);
	}
}

class StaticInitialize {
	String postFix;
	String constracterType;
	
	//インスタンスイニシャライザ
	{
		postFix = "コンストラクタを使いました。";
	}
		
	StaticInitialize() {
		this.constracterType = "デフォルト";
	}

	StaticInitialize(String type) {
		this.constracterType = type;
	}
}

実行結果

>java InstanceInitializeSample
インスタンスsi1はデフォルトコンストラクタを使いました。
インスタンスsi2は引数一つのコンストラクタを使いました。

>

クラスに戻る

フィールド

フィールドはクラスの属性を定義します。
属性自体は変数の集まりです。

どの様なプログラムも結局のところデータと処理を記述したものです。
自由気ままにデータを処理していては、良いプログラにはなりません。そこで、「構造化プログラミング」で処理についたは、こうするべきだという手本が示されました。しかし、データについては型は規定しても、使われ方が多岐にわたるのでオブジェクト指向プログラミング以前の言語では規定はなく、プログラマ任せでした。

フィールドの書式はいくつかの変数宣言(同時に初期値を設定しても良い)をクラスの先頭で記述します。何もオブジェクト指向プログラミング以前の言語における変数宣言と変わりません

Javaのデータに対する規定はどこにあるのでしょうか。それは、
フィールドのスコープ(有効範囲)と変数へのアクセス手法が明確
フィールド内の変数に対し、static修飾子を付けたスタティック変数と、修飾子を付けないインスタンス変数の使い分けが可能
ことです。これにより、使われ方が多岐にわたるデータを変数という形で扱うことが出来るようになっています。

フィールドのスコープ
勿論、フィールドが属すクラス内です。
クラス内のメソッドのローカル変数の名前とフィールド内の変数の名前が重複した場合には、ローカル変数が優先されます。そこで、フィールド内の変数をメソッド内でアクセスしたい場合は、その変数名の前にthis.を書き加えます。thisはJVMが用意してくれる変数(プログラマが宣言する必要がない)の1つで、インスタンス自身への参照が入っています。このため、「this. 変数名」と記述することで、ローカル変数ではなく、フィールド内の変数を明示する事になります。
また、メソッドが仮引数としてフィールド内の変数と同じ名前を持つ場合も同じです。仮引数名の前には何もつけず変数名だけを記述します。
なおスコープの違いから、スーパークラスとサブクラスのフィールド内に同じ名前の変数があっても名前の衝突は起きません。しかし、サブクラスからスーパークラスのフィールドにアクセスするときに名前の混乱が起きないようサブクラス内のフィールドへのアクセスにはthis.を付け、スーパークラス内のフィールドへのアクセスにはsuper.を付ければ、スコープを越えたアクセスも可能です。

static なフィールドヘのアクセス
Java では、プログラムの実行中に必要なクラスを読み込んで(ダイナミックリンクして)実行します。
クラスを読込むと、staticで修飾されたフィールド(内の変数)やメソッドは、「static領域」に配置されます。それ以外の部分は、「ヒープ領域」に配置されます。インスタンスが生成されるときには、ヒーフ領域にあるクラス定義に従ってインスタンスが生成されます。
このため、staticなフィールドはそのフィールドを持つクラスのインスタンスを作らなくても、既に存在していて使うことが出来ます。また、static なフィールドはインスタンス内には存在しないので、いくつか作られたインスタンスからstaticフィールドをアクセスすると、全てstatic領域内の同じフィールドをアクセスすることになります。
なお、static なフィールドにアクセスするには「クラス名.フィールド名」と記述するか、インスタンスを生成し「インスタンス変数名.フィールド名」(コンパイル時に「クラス名.フィールド名」に置き換えられる)でアクセスします。

クラスに戻る

ラムダ式

数学の世界では関数を表現する式にギリシャ文字のラムダ(lambda):λを使う慣習があったそうですが、この名前に由来するラムダ計算という計算体型が1930年代に考案され、1958年に誕生した関数型言語のLISPで関数の定義法として採用され、多くの言語で関数を他の関数の引数として与える操作が許されています。

こういった背景のラムダを冠した表記法:ラムダ式がJava8で導入されました。
ラムダ式を使えば、実装が必要なメソッドを1つだけ持つインターフェース(関数型インタフェース)の実装が、従来の無名クラスを使うよりも簡潔に記述できます

書式

( 実装するメソッドの引数 ) -> { 処理 }

利用例
インターフェース

interface IntFunc {
	public int func(int x);
}

無名クラスによる実装

public class NamelessClass {
      public static void main( String args[] ) {
		IntFunc iNLF = new IntFunc(){
			public int func(int x){
				return x+x;
			}
		};
		System.out.println("NamelessFunc return = " + iNLF.func(2));
	}
}

実行結果

>java NamelessClass
NamelessFunc return = 4

>

ラムダ式による実装

public class LambdaClass {
      public static void main( String args[] ) {
		IntFunc iLF = (int x) -> {return x+x;};
		System.out.println("LambdaFunc return = " + iLF.func(2));
	}
}

無名クラスでは5行の記述が、ラムダ式では僅か1行で記述できます。

実行結果

>java LambdaClass
LambdaFunc return = 4

>

ラムダ式の基本書式は、引数には型を明示し、処理は{ }で囲みます。

(int x, int y) -> {
	return x + y;
};

しかし、条件によりラムダ式はさらに簡略記述できます。

引数の型定義は省略可能(コンパイラが推測)

(x, y) -> {
	return x + y;
};

処理が1行なら、{ }もreturnも不要

[javat gutter=”false”]
(x, y) -> x + y;
[/java]

引数が1つなら、引数の( )も不要

x -> x * 2;

ただし、引数が無い場合は、( )は必要

() -> 1;

また、Java8では、使用頻度の高い関数型インターフェースがjava.util.functionの中で提供されています。

Supplier系
引数なしで値を返す
Supplier s = () -> “input”;
System.out.println(s.get());
Consumer系
値を受け取るが、返却しない(処理を行う)
SupplierConsumer c = (str) -> System.out.println(str);
c.accept(“output”);
Predicate系
値を受け取って論理値を返す
Predicate p = (str) -> str == “yes”;
System.out.println(p.test(“no”));
Function系
値を受け取って、何らかの値を返す
ToIntFunction f = s -> s.length();
System.out.println(f.applyAsInt(“abc”));
上記全て、引数を2つ受け取る場合はインターフェース名の先頭に「Bi」を付ける
BiFunction lambda5 = (str, num) -> str + num;

さらに、受取るデータ型によりInt、Long、Doubleを名前の先頭に付けたインターフェースがありますし、返すデータ型によりToInt、ToLong、ToDoubleを名前の先頭に付けたインターフェースがあります。

Javaではメソッドを関数と考えられない事もありませんが、メソッドはあくまでクラスの振舞い、機能なのでクラスを意識せずに使うことが出来ません。これに対しラムダ式はインターフェースで規定されたメソッドを、クラスを意識した実装を記述しないで必要な処理だけ記述し、直接使う方法とも考えられるでしょう。

クラスに戻る

内部クラスと無名クラス

クラス内に宣言されたクラスを内部クラス(インナークラス)といいます。また、内部クラスにはクラス名を宣言しない無名クラス(匿名クラス)もあります。

あるクラスのメンバ変数やメソッドに強く依存するクラスは、そのクラスの中に内部クラスとして記述する方が使いやすくなります。

クラス内にインナークラスを定義したクラス

public class ClassInClass {
	private String classInClassMsg = "インナークラスの外のprivateメンバ変数にアクセスしました。";

	private class InnerClass {
		private String innerClassMsg = "インナークラスの中のprivateメンバ変数にアクセスしました。";
		
   		void innerSpeak() {
   			System.out.println("インナークラスのメソッドがメッセージを出します。");
			System.out.println(classInClassMsg);
			System.out.println(innerClassMsg);
   		}
	}
	
	void classInClassSpeak() {
		System.out.println("クラス内でインナークラスのインスタンスを作り、そのメソッドを呼びます。");
		InnerClass inC = new InnerClass();
		inC.innerSpeak();
	}
}

インナークラスを定義したクラスのインスタンスを使ってインナークラスを使う処理

public class ClassInClassSample {
	public static void main( String args[] ) {
		ClassInClass cic = new ClassInClass();
		cic.classInClassSpeak();
	}
}

実行結果

>java ClassInClassSample
クラス内でインナークラスのインスタンスを作り、そのメソッドを呼びます。
インナークラスのメソッドがメッセージを出します。
インナークラスの外のprivateメンバ変数にアクセスしました。
インナークラスの中のprivateメンバ変数にアクセスしました。

>

内部クラスからは同じクラス内のprivate修飾子の付いたメンバ変数、メソッドにもアクセスできます。

内部クラスはメソッド内にも作ることができます。その場合、その内部クラスはメソッド内でのみ機能します。

メソッド内にインナークラスを定義したクラス

public class ClassInMethod {
	private String mainClassMsg = "インナークラスの外のprivateメンバ変数にアクセスしました。";

	
	void classInMethodSpeak() {
		
		class InnerClass {
			private String innerClassMsg = "インナークラスの中のprivateメンバ変数にアクセスしました。";
		
   			void innerSpeak() {
   				System.out.println("インナークラスのメソッドがメッセージを出します。");
				System.out.println(mainClassMsg);
				System.out.println(innerClassMsg);
   			}
		}
		System.out.println("メソッド内でインナークラスのインスタンスを作り、そのメソッドを呼びます。");
		InnerClass inC = new InnerClass();
		inC.innerSpeak();
	}
}

インナークラスを定義したクラスのインスタンスを使ってインナークラスを使う処理

public class ClassInMethodSample {
	public static void main( String args[] ) {
		ClassInMethod cim = new ClassInMethod();
		cim.classInMethodSpeak();
	}
}

実行結果

>java ClassInMethodSample
メソッド内でインナークラスのインスタンスを作り、そのメソッドを呼びます。
インナークラスのメソッドがメッセージを出します。
インナークラスの外のprivateメンバ変数にアクセスしました。
インナークラスの中のprivateメンバ変数にアクセスしました。

>

メソッド内で内部クラスを作る場合、内部クラスに名前を与えないクラスを無名クラスと呼びます。
無名クラスは、宣言から、実装、インスタンス化までをまとめて一文で記述できますが、その記述が通常のクラスの記述と異なるため注意が要ります。
名前が無いため継承が出来ませんから一代限りのシンプルな抽象クラスの実現やインターフェースの実装に使います

書式

new 抽象クラス名( ) { 無名クラス };

new インタフェース名( ) { 無名クラス };

インターフェースを無名クラスで実装するところを見てみましょう。

インターフェース

interface Conversation{		
	public void greeting();
}

インターフェースを実装し、インスタンス化して、インスタンスを利用する処理
7行目の}の後に;が必要なことに注意してください。

public class NamelessClass {
	  public static void main( String args[] ) {
		  Conversation introduction=new Conversation(){					//インタフェースを無名クラスで実装し、即インスタンス化。
			  public void greeting(){
				  System.out.println("私は無名クラスで実装されました。");
			  }
		  };
		  introduction.greeting();
	  }
}

実行結果

>java NamelessClass
私は無名クラスで実装されました。

>

参考に、インターフェースを通常のクラスで実装し、そのクラスをインスタンス化して利用する場合を見てみましょう。

インターフェースは同じなので、インターフェースを実装するクラス

class Talking implements Conversation {
	public void greeting(){
		System.out.println("私は名前のある通常クラスで実装されました。");
	}
}

そして、クラスをインスタンス化して利用する処理

public class NamedClass {
	  public static void main( String args[] ) {
		  Talking introduction=new Talking();
		  introduction.greeting();
	  }
}

実行結果

>java NamedClass
私は名前のある通常クラスで実装されました。

>

使う用途によっては無名クラスは簡潔に処理を記述できますが、用途は限定されるでしょう。自ら作ることは少ないかもしれませんが、一つだけで良いインターフェースの実現、たとえばGUI作成のためActionListenerインターフェースなどの実現などではよく使われます

クラスに戻る

メソッド

メソッドはクラスの振舞い方、所作、何かを行う機能の定義です。
メソッドは引数を受取ったり、処理の結果の値を返すことが出来ます。

引数はメソッドを実行するために必須のデータであり、呼び出し側(実引数)と呼び出される側(仮引数)の引数の数、及び型が一致しないとコンパイルエラーが発生します。
基本型の引数をメソッドに渡すとき、引数の値はコピーされて渡されるため、引数を渡されたメソッド内で値が変更されても、呼び出し元の値は変わりません
一方、オブジェクト型の引数では、呼び出し元から呼び出されたメソッドにオブジェクトの参照値が渡されるので、2つのメソッドが参照するインスタンスは同じ物であり、内容を変更すれば両方のメソッドがその変更を参照することになります。

同じ型の引数は任意個まとめて可変長引数にすることが出来ます。
可変長引数は引数の型の直後にピリオド3つ「…」を付けて宣言し、その後に変数名(配列名)を書きます
可変長引数を持つメソッドは、引数を2つでも、3つでも、いくつでも渡して呼び出すことができます。渡された複数の値は、JVMによって配列に置き換えられますから、**可変長引数の値を使うときには、配列と同じように[ ]を変数名に追加して使います。

void sample(int... num) {
     for (int i =0; i<num. length; i++) {
          System.out.println(num[i]);
     }
}

なお、可変長引数を使う時には、

可変長引数は一組のみ記述できます。
同じ型の引数を任意個まとめられるだけで、異なる型はまとめられません。
可変長引数以外に通常の引数も受け取る必要がある場合、可変長引数は引数の最後に記述します。可変長引数の後に通常の引数を書くとコンパイルエラーになります。

処理の結果の値は戻り値と呼びますが、戻り値を呼び出し元のメソッドに戻すためには、return文を使います。
戻り値型にはint 型を、return文ではdouble型を戻すように記述すると、コンパイルエラーが発生します。
また、戻り値型を宣言しているにもかかわらす、return文を記述しない場合もコンパイルエラーになります。
逆に戻り値を戻さないvoidを宣言しているのにreturn文で値を戻そうとしてもコンパイルエラーになります。

public class ReturnSample {
	public static void main(String[] args) {

		System.out.println("retrun value = " + returnValue() );
		int voidGet = voidReturn();
		
	}
	static int returnValue() {
		return 1.0;
	}
	static void voidReturn() {}
}

コンパイル結果

>javac ReturnSample.java
ReturnSample.java:5: エラー: 不適合な型: voidをintに変換できません:
                int voidGet = voidReturn();
                                        ^
ReturnSample.java:9: エラー: 不適合な型: 精度が失われる可能性があるdoubleからintへの変換
                return 1.0;
                       ^
エラー2個

>

メソッドからクラスのメンバへのアクセス
staticなメンバはインスタンスの有無にかかわらす使えますが、staticではないメンバは、インスタンスがないと使えません。
このため、staticなメソッドはstaticなフィールドやメソッドにはアクセスできますが、staticでないメンバにはアクセスできません。しかし、逆にstaticではないメソッドから、staticなメンバにアクセスすることは可能です。

クラスに戻る