S-JIS[2008-06-15/2021-03-21] 変更履歴

Javaのクラスの定義

Javaでは、全ての変数・メソッド(関数)はクラス内に書く。

概念的には「クラス」一種類だけあればいい(のかもしれない)が、利便性の為に、インターフェース等の“クラスに似たもの”がコーディングできるよう用意されている。

なお、Javaではクラスを書くことを「宣言する(declare)」と言うらしいが、自分はC言語から流れてきた人間なので「定義する」と言う。


“クラスとインターフェース”

ソース上では、クラスやそれに類するもの(具体的には以下のもの)を定義することが出来る。[/2008-09-13]

この“クラスに似たもの”をまとめて正式に何と呼ぶのか知らないので、ここでは便宜上「クラス類」と呼ぶことにする。
Javaの言語仕様上は、クラスとインターフェースしか無い。enumは特殊なクラス、@interfaceは特殊なインターフェース(「@」と「interface」の間にスペースや改行を入れても文法上は問題ない!)という扱い。
この場合、言語仕様上は、classは「普通のクラス(NormalClass)」、interfaceは「普通のインターフェース(NormalInterface)」と書かれている)

なぜ「クラス類」などと言うまとめ方をしたいかと言うと、Javaでは、ソースファイルにはpublicな「クラス類」は1つしか書けないから。
publicの付いたクラス類がソースファイル内に1つも無くてもよい。
publicでないクラス類はソースファイル内に複数書くことが出来る。
(複数書く際、そのクラスを使用する順序と書く順序は無関係。使いたいクラスが前で定義されていも後で定義されていてもよい(C言語やC++では、必ず使う前に宣言されている必要がある))

そして、publicな「クラス類」クラス名ソースファイルの名前一致させないといけないから。
(publicでないクラス類はソースファイル名と一致させなくて良い…というか、複数書くときには一致させることは不可能^^;
(なお、OracleにJavaを格納する場合等は これらの規則の適用を受けない)

Example.java:

public class Example {
	〜
}

あるいは

Example.java:

public interface Example {
	〜
}

あるいは

Example.java:

public enum Example {
	〜
}

あるいは

Example.java:

public @interface Example {
	〜
}

なお、クラス類は、先頭を大文字にした単語をつないで命名することが多い。(CamelCase)
例: Class、Example、ExampleClass

命名に関する慣例

※Java14で「record」というクラス名のクラスを作ろうとすると警告が出るようになった。(レコード定義で使用する為)[2020-03-21]
 ただ、普通はクラス名は先頭を大文字にするので、これに引っかかることは無いだろう。


また、クラス類パッケージに属することが出来る。

example/example1/Example.java:

package example.example1;

public class Example {
	〜
}

クラスとインターフェースのアクセス範囲

クラス類では、種類を表すキーワード(class・interface等)の前にアクセス範囲を表す修飾子を付けることが出来る。

修飾子 説明 備考
public publicを付けたクラス類は、他のクラスから使用可能。
他クラスのソース上でそのクラス名を使ってコーディングすることが出来る)
publicを付けるのは、そのソースファイル内に1つのみ可能。
publicを付けた場合は、名前がソースファイル名と一致している必要がある。
なし 何も付けていないクラス類は、同一パッケージ内のクラスから使用可能。 「パッケージプライベート」と言う。
private 内部クラスでのみ指定可能。
privateを付けたクラス類は、外側クラスでのみ使用可能。
 
protected 内部クラスでのみ指定可能。
protectedを付けたクラス類は、外側クラスおよび外側クラスを継承したクラス内で使用可能。
 

クラス

クラスの定義(宣言)方法

クラスは以下の様な文法で定義する。

public〕 〔abstract|final〕 class クラス名 〔extends 親クラス名〕 〔implements インターフェース名,…〕 {
	クラス本文
}

abstractを付けると抽象クラスになる。
finalを付けるとそれ以上継承することが出来なくなる。

extendsを付けることにより、どのクラスから派生したのか(どのクラスを継承したのか)を示す。
(Javaでは「継承」という言葉を使うが、自分はC++の「派生」という言葉もよく使う。「C extends S」=「CはSを継承している」=「CはSから派生している」)
どのクラスからも派生しないように宣言されたクラス(extendsを省略した場合)は、暗黙にObjectクラスから派生(extends)したことになる。
つまり、全てのクラスはObjectクラスを継承していることになる。
Javaでは、複数のクラスから直接派生させること(多重継承)は出来ない。

implementsを付けることにより、どのインターフェースを実装(インプリメント)するのかを示す。

クラス本文には、フィールド(クラス内の共通変数)メソッド(関数)初期化子(イニシャライザー)を定義する。

クラスの使用方法
他言語のクラス定義方法


フィールドの定義方法

クラス内の変数はフィールドと呼ぶ。C++風に「メンバー変数」と呼ぶ方が分かり易いかも。
(フィールドは“クラスのメンバーである変数”を指すので、「メンバーフィールド」とか「フィールド変数」とは言わない)

static修飾子の付いたフィールドをクラスフィールドと呼ぶ(クラスメンバーだから)。staticフィールドと言う方が通りがいいかも。
付いていないフィールドをインスタンスフィールドと呼ぶ(インスタンスメンバーだから)。[2009-03-28]
フィールドへのアクセス方法

class クラス名 {

	〔修飾子 変数名;
	〔修飾子 変数名 = 初期値;
}

初期値を指定しなかった場合、に応じたデフォルト値がセットされる。

フィールドの初期化順序はJava言語仕様で決まっており、初期化されていない変数を別のフィールドの初期値として使うことは出来ない。
フィールドの初期化順序
 

継承した親クラスのフィールドと同名のフィールドを定義することも出来る。[2008-08-10]
フィールドはオーバーライドされないので、同名フィールドであっても別の変数となる。

祖先(?)フィールドの指定方法


メソッドの定義方法

メソッドとは、C++風に言えば「メンバー関数」のこと。

static修飾子の付いたメソッドをクラスメソッドと呼ぶ(クラスメンバーだから)。staticメソッドと言う方が通りがいいかも。
付いていないメソッドをインスタンスメソッドと呼ぶ(インスタンスメンバーだから)。[2009-03-28]
メソッドの呼び出し方法

class クラス名 {

	〔修飾子戻り型 メソッド名(引数,…) 〔throws 例外,…〕 {
		メソッド本体
	}
}

JDK1.5から、可変長引数も定義できるようになった。

メソッドから同一クラス内の別のメソッドを呼び出せるが、そのメソッドは前に書かれていても後に書かれていてもよい。
同一クラス内のフィールドを使用する場合も、そのフィールドはメソッドの前に書かれていても後に書かれていてもよい。
(C言語やC++では、使用したいメソッド(関数)やフィールド(変数)は必ず使用箇所より前で宣言されている必要がある)
 

親クラスと同シグニチャー(メソッド名が同じで引数の種類が全て同じ)のメソッドを定義することが出来る。static以外のメソッドでは、これをオーバーライド(override)と言う。
(ただし、戻り値の型を変える事は(共変以外は)出来ない 。また、throwsの例外の種類を変える事も出来ない(減らす事は可能))
(staticなメソッドでも同じ状態のメソッドを作ることは出来るが、オーバーライドはされない

staticでないメソッドは、全てC++で言うところのvirtual扱い(仮想関数)になる。
すなわち、オーバーライドされたメソッドがあれば、サブクラスのメソッドが呼ばれる。→仕組み

abstractを付けると抽象メソッドになる。(メソッド本体は書かない/書けない)
抽象メソッドとは、いわば呼び出し方だけを決めておく仕組み。
メソッドの本体は実装せず、継承したサブクラスでオーバーライドして本体を実装するもの。
抽象メソッドを書く場合はクラスの宣言も抽象クラスとする必要がある。

finalを付けると、そのメソッドはサブクラスでオーバーライドできなくなる。
 

メソッド名が同じで、引数の種類が異なるメソッドを定義することが出来る。これをオーバーロード(overload)と言う。[2008-08-10]
この場合は戻り値の型が異なってもよいが、オーバーロードは呼び出し側の利便性の為に用意するものなので、プログラマーが混乱するような戻り型にはしてはいけない。
複数のオーバーロードがあると、メソッド呼び出し側は引数の型が最も適合したメソッドを呼び出す。(適合する候補が複数あって解決できない・曖昧な状態だとコンパイルエラーになる)
新しいオーバーロードを後から作っても、それまで元のメソッドを呼んでいたクラスでは、コンパイルし直さない限り 新しいメソッドを呼ぶことは無い


レシーバーパラメーター

JDK1.8から、メソッドの先頭の引数にthisが指定できるようになった。[2020-06-24]
これを明示的なレシーバーパラメーター(explicit receiver parameters)(コンパイル時のエラーメッセージ的には「明示的なthisパラメーター」)と呼ぶ。

これは、自分自身のメソッド呼び出しに対してアノテーションを付けたい場合に使用する。

レシーバーパラメーターが入っているメソッドを呼び出すときは、この引数に値をセットする必要は無い。

// 例
class Main {
  void method(@Annotation Main this) {
    〜
  }

  public static void main(String[] args) {
    new Main().method(); // 呼び出すときは実引数不要
  }
}

内部クラスのコンストラクターのレシーバーパラメーター
ラムダ式の引数var(アノテーションを付けるための機能という意味で同目的)


コンストラクターの定義方法

コンストラクターは、newでインスタンスを生成したときに初期化の為に呼ばれるメソッド。
コンストラクターの中でフィールドの初期化などを行う。
(もちろん、厳密にはメソッドとは異なる。文法上、フィールドとメソッドはクラスの「メンバー」だが、コンストラクターはメンバーではない)

戻りの型を書かない、クラス名と同名のメソッドがコンストラクターになる。

class クラス名 {
	〔修飾子〕 クラス名(引数,…) 〔throws 例外,…〕 {
		コンストラクター本体
	}
}

JDK1.5から、可変長引数も定義できるようになった。

引数の無いコンストラクターはデフォルトコンストラクターと呼ばれる。

コンストラクターを1つも定義しないと、暗黙に(publicな)デフォルトコンストラクターが定義される。

引数の種類を変えて複数のコンストラクターを定義することが出来る。
その場合は、引数無しのコンストラクターを定義したい場合は明示的にコーディングする必要がある。
(コンストラクターが1つでも定義されていたら、デフォルトコンストラクターは生成されない)

コンストラクター本体の先頭では、別のコンストラクターを呼び出すことが出来る。→コンストラクターのコーディング [2008-08-24]


デストラクター

C++と異なりJavaにはデストラクターは存在しないが、finalize()というメソッド(ファイナライザー)がそれに近い。[2003-07-06]

しかしfinalize()が呼ばれるのは(スコープを抜けた時やdeleteされた時でなく)GCが実行される時なので、呼ばれるタイミングが全然違う。
また、デストラクターは必ず呼ばれるが、finalize()は必ず呼ばれるとは限らない。 (JavaVMの終了時にはGCは実行されないので、finalize()は呼ばれずに終わる)

したがってC++でデストラクターにメモリー解放などの処理を実装することは多々あるが、Javaでfinalize()を実装することは滅多にない。


メンバー(フィールド・メソッド)に対する修飾子

修飾子 説明
public FM クラスの外からも使用可能
private FM クラスの内部のみ使用可能
protected FM クラス内部と派生クラスと同一パッケージ内のみ使用可能 [/2007-02-21]
なし FM 同一パッケージ内で使用可能パッケージプライベート[/2007-02-21]
static FM 静的なメンバー
staticを付けたメンバーは、クラスメンバーと呼ばれることがある。
なし FM 動的なメンバー
staticを付けないメンバーは、インスタンスメンバーと呼ばれることがある。
(クラスをインスタンス化(newで生成)しないと、インスタンスメンバーにはアクセスできない)
abstract M 抽象メソッド(C++のvirtualに相当)
メソッドに付けると、純粋仮想関数(C++では「=0」を付けた宣言)になる。 その場合、classにもabstractを付けなければならない。
final FM それ以上変更しない
変数に付けて初期値を与えると、定数として使える(C++のconstに相当)。初期値を設定しないと、一度は代入できるが、二度目は駄目。
メソッドに付けると、それ以上オーバーライドできなくなる
クラスに付けると、それ以上派生できなくなる
const   (C言語や)C++において、定数や“関数の副作用が無い”事を表す [2008-04-08]
Javaではこの機能は無いが、constは予約語になっている。(使うとコンパイルエラーになる)

synchronized修飾子


アクセス制御

メンバー(フィールドメソッド)に付けられた修飾子によって、そのメンバーにアクセス(読み書き・呼び出し)可能かどうかを制御する。[2007-02-21]
「どの修飾子だと どの範囲からアクセスできるか」は、以下のようになっている。

アクセス対象との関係 アクセス対象のメンバーの修飾子
パッケージ 派生関係 インスタンス public protected package private
同一 自分自身 this(自分)
別インスタンス
継承(サブクラス) this(自分) ×
別インスタンス ×
無関係 別インスタンス ×
継承(サブクラス) this(自分) × ×
別インスタンス × × ×
無関係 別インスタンス × × ×

たしかC++では、別インスタンスなら同一クラスであってもpublic(とfriend)以外はアクセスできなかったと思う。…けど勘違いの公算が大(汗)
また、Javaのprotectedは「継承先のサブクラスからアクセスできる」という説明が多いが、厳密には「同一インスタンス内」という条件が抜けている。

同一パッケージにクラスを作っておけば他のクラスでもprivate以外のメソッドであれば呼び出すことが出来るので、private以外のメソッドはJUnitでテスト対象に出来る。

妙:一見無意味に見えるオーバーライド

メソッドにfinalを付けると、そのメソッドはオーバーライドできなくなるという意味になる。[2008-04-26]
privateは自クラスからしか呼べないので、「private」と「private final」は動作上は同じ(バイトコードではどちらもinvokespecial)。
protectedならサブクラスを呼ばないと分かる分だけ実行効率が良くなるかとも思ったが、「protected」と「protected final」も同じ(invokevirtual)。

自分のサブクラスとして宣言した変数では、自分のprivateメンバーにアクセスできない。[2012-05-20]
自分自身にキャストすればアクセスできる


抽象クラス

抽象クラス(abstract class)は、継承されることを前提(必須)としたクラス。
(→インターフェース(特殊な抽象クラス)

抽象クラスの一部のメソッドは抽象メソッドとして定義する。(抽象メソッドとは、C++でいう純粋仮想関数のこと)
すなわち実体(動作・処理)を書かず、引数と戻り型(と発生しうる例外)だけ定義する。
このため、抽象クラスは直接インスタンス化することは出来ない。(抽象メソッドの動作が記述されていないから。インスタンス化できたとして、抽象メソッドが呼ばれたら どんな処理を行えばいい?)

abstract class 抽象クラス名 {

	〔修飾子abstract 戻り型 抽象メソッド名(引数,…);	←抽象メソッド定義

	〔修飾子戻り型 メソッド名(引数,引数…) {		←抽象クラス内にも通常のメソッドを書ける
		メソッド本体
	}
}

メソッドの具体的な処理(メソッド本体)を書くことを「実装する」と言う。
また、全メソッドを実装するクラスを作ることも「実装する」と言う。

抽象メソッドはサブクラスで実装される(実装する必要がある)。
全てのメソッドが実装されたクラスのことを具象クラス(コンクリートクラス・concrete class)と言う。
具象クラスはnewでインスタンス化することが出来る。

class 具象クラス名 extends 抽象クラス名 {

	〔修飾子戻り型 抽象メソッド名(引数,…) {	←親クラスの抽象メソッドをオーバーライドして
		メソッド本体                        	←メソッド本体を実装する
	}
}

実装したメソッドの戻り値の型は、親クラスで定義されている型と同じにする。
が、JDK1.5以降では、派生クラスを返すように定義できるようになった。[2010-06-27]
共変戻り値型

親クラスでthrowsが宣言されている場合、子クラスではその例外を減らしてもいい(増やすのは駄目)。[2010-06-27]
例外の基礎


内部クラス(メンバークラス・入れ子クラス・エンクロージング型)

クラスの中にクラスを定義することが出来る。[2007-02-07/2008-08-30]
この、ネストした内側のクラスを入れ子クラス(エンクロージング型・ネストしたクラス:nested class)と呼ぶらしい。
(ネストしていないクラス、すなわち一番外側のクラスはトップレベルクラス(top level class)と呼ぶ)

クラスの直下(フィールドやメソッドと同じ位置)に定義したネストクラスは、メンバークラスと言う。
(→クラス直下でないのは局所クラス
ネストしたクラスのうち、staticでないもの内部クラス(インナークラス:inner class)と呼ぶらしい。 外側のクラスはアウタークラス(outer class)。
(内部クラスでは、final定数以外のstaticなメンバーを持つことが出来ない)

public class Root {
	private class Inner {	//内部クラス
	}
}

内部クラスは、外部クラス内のメソッドで普通に「new Inner()」としてインスタンスを作れる。

内部クラスが「public class」のときは、無関係なクラスからでもインスタンス化できる。

	Root r = new Root();
	Root.Inner i = r.new Inner();	

	Root.Inner j = new Root().new Inner();

メンバークラスが「public static class」のときは、インスタンス化の文法が少し変わる。

	Root.Inner s = new Root.Inner();

Eclipsestaticでない内部クラスAに対して「new X.A()」という書き方をすると、「エンクロージング・インスタンスで割り振りを限定する必要があります」というエラーが出る。解決方法は上記の通り、Xのインスタンスxに対して「x.new A()」とするか、Aをstaticクラスにする)

内部クラスをリフレクションを用いてインスタンス化するときは、“暗黙の引数”付きのコンストラクターを使用する。[2007-02-09]
ネストされたクラスかどうかを判断するにはisMemberClass()を使う。[2008-02-10]

内部クラスからは外側クラス内のメンバー(変数・メソッド)にアクセスできるのだが、staticな ネストクラスからは外部クラスのstaticメンバーにしかアクセスできない。
それで良ければstaticなネストクラスにするのがいいだろう(暗黙の引数が不要になるから)。[2008-07-14]
そうでなければstatic無しの内部クラスにする。


無名内部クラス(匿名クラス)
妙:内部クラス名


レシーバーパラメーター

JDK1.8から、メソッドのレシーバーパラメーターと同様に、内部クラスのコンストラクターでもthisが指定できる。[2020-06-24]

// 例
class Main {
  class Inner {

    // コンストラクター
    Inner (@Annotation Main Main.this) {
      〜
    }
  }

  void main() {
    new Inner(); // 呼び出すときは実引数不要
  }
}

もともと内部クラスでは、暗黙の引数として親クラスのインスタンスを受け取るようになっていた。
それを明示的に書けるようになったということだろう。


内部クラスのstaticメンバー

staticでない内部クラス(inner class)には、static(finalでない)フィールドやstaticメソッドを定義することが出来ない。[2021-03-21]
が、Java16から定義可能になった。

class Outer {
  class Inner {
    static final int CONST = 1; // 定数(static finalフィールド)はOK

    static int staticField = 1; // Java15以前はNG

    static void staticMethod() { // Java15以前はNG
    } 
  }
}

Java16で導入されたレコードが、この制限により内部クラスで定義できなかったので、緩和されたらしい。


局所クラス(ローカルクラス:local class)

メソッドの中にクラスを定義(宣言)することも出来る。[2008-06-15]

	void method() {
		class 局所クラス {
		}
		局所クラス obj = new 局所クラス();
	}

この場合も、実態(コンパイルされた結果)としては内部クラスになる。

この局所クラスは、他のメソッドからは使用することは出来ない。

匿名クラス(無名内部クラス)は、局所クラスの特殊形態である。

局所クラスも内部クラスの一種なので、外側クラスのフィールドやメソッドにアクセスできる[2009-04-15]
また、局所クラスを含んでいるメソッドのローカル変数(メソッドの引数を含む)には、その変数がfinal変数であればアクセスできる


staticメソッド内で定義したローカルクラスは、staticなメンバークラスと同様な扱いになる。[2015-06-17]

staticでないメソッド内のクラス staticメソッド内のクラス 備考
class Outer {

  static int s0;
  int d0;

  void outerMethod() {
    class LocalClass {
      public void method() {
        System.out.println(s0);
        System.out.println(d0);
      }
    }
  }
}
class Outer {

  static int s0;
  int d0;

  static void outerMethod() {
    class LocalClass {
      public void method() {
        System.out.println(s0);
//×    System.out.println(d0);
      }
    }
  }
}
staticメソッド内のローカルクラスの場合、
外側クラスのstaticでないメンバーにアクセスすることは出来ない。
Outer$1LocalClass(Outer) Outer$1LocalClass() ローカルクラスのコンストラクター。
(コンストラクターを定義し、リフレクションで確認)
staticでない方は外側クラスのインスタンスを暗黙の引数に取るが、
staticの方はそれが無い。

クラス使用方法へ戻る / Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま