[???] /
[Java FAQ] / [S010]
S010: コンストラクタ - constructor
[S010 Q-01]
コンストラクタの記述がないクラスのインスタンスが生成できるのはなぜですか?
[S010 A-01]
デフォルトコンストラクタが暗黙的に定義されるからです。
クラスの定義にコンストラクタを一つも記述しなかった場合、
コンパイラによって自動的に引数なしのコンストラクタが生成されます。
この自動生成されるコンストラクタをデフォルトコンストラクタと呼びます。
デフォルトコンストラクタでは、スーパークラスの引数なしコンストラクタの
呼び出しのみを行ないます。
そのため、スーパークラスに引数なしコンストラクタ(デフォルトコンストラクタも
含まれます)がない場合には、コンパイルエラーとなってしまいます。
[S010 Q-02]
スーパークラスで定義されたコンストラクタと引数が同じなんですが、
サブクラスで必ずコンストラクタを記述しないといけないのですか?
[S010 A-02]
はい、必ず記述しなくてはなりません。
メソッドやフィールドと異なり、コンストラクタは継承されません。
したがってコンストラクタは、明示的に記述しなければなりません。
例えば、
class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
class LazyPerson extends Person {
}
と、クラスが定義されてあったときに、
Person lazyPerson1 = new LazyPerson("なまけもの");
とすると、適切なコンストラクタが見つからずコンパイルエラーになります。
LazyPersonクラスにコンストラクタを追加する必要があります。
public LazyPerson(String name) {
super(name);
}
参考記事 [JavaHouse-Brewers:1697] [JavaHouse-Brewers:3805]
[S010 Q-03]
スーパークラスのコンストラクタ Foo() がないとコンパイルエラーになるのですが?
[S010 A-03]
実はどこかでFoo()が呼び出されているからです。
コンストラクタの最初の文が、自クラスの他のコンストラクタか、
スーパークラスのコンストラクタを明示的に呼び出していない場合、暗黙の
うちにスーパークラスの引数なしコンストラクタを呼び出しているとみなさ
れます。つまり、明示的なコンストラクタ呼び出しが書かれていない場合に、
super(); が書かれているのと同等に扱われるのです。
この理由で、例えば以下のコードはコンパイルエラーとなります。
class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
class LazyPerson extends Person {}
以下のいずれかの方法でこの問題を解決することができます。
・サブクラスから呼び出せる引数なしのコンストラクタをスーパークラスに
宣言する
public Person() {
this("ななしのごんべい")
}
・サブクラスの別のコンストラクタを呼び出す
public LazyPerson() {
this("なまけもの");
}
public LazyPerson(String name) {
super(name);
}
・スーパクラスの別のコンストクタを呼び出す
public LazyPerson() {
super("なまけもの");
}
[S010 Q-04]
インスタンス生成を制限したいのですが、どのようにしたら良いですか?
[S010 A-04]
コンストラクタのアクセス制限と、staticメソッドの組み合わせで行ないます。
例えば、ただ一つのインスタンスのみを生成する場合や、同一内容のインスタン
スを使いまわす場合などに、インスタンス生成を制限することがあります。
例1:
class Foo {
private static Foo instance = new Foo();
private Foo() {}
public static Foo getInstance() {
return instance;
}
}
例2:
class Foo {
private static java.util.Dictionary table = new java.util.Hashtable();
private String name;
private Foo(String name) {
this.name = name;
}
static Foo getInstance(String name) {
if (table.get(name) != null) {
return (Foo)table.get(name);
} else {
Foo foo = new Foo(name);
table.put(name, foo);
return foo;
}
}
}
参考記事 [JavaHouse-Brewers:16551]
[S010 Q-05]
コンストラクタが private になっているのですが?
[S010 A-05]
そのクラスの外部からインスタンスを直接生成させないようにするためです。
インスタンス生成を制限する動機については、
S010-04「インスタンス生成を制限したいのですが、どのようにしたら良いですか?」
を参照してください。
参考記事 [JavaHouse-Brewers:16551] [JavaHouse-Brewers:1368]
[S010 Q-06]
サブクラスのコンストラクタからスーパークラスのコンストラクタを
呼び出す時には他のメソッドと違い、なぜコンストラクタの最初で
呼び出さないといけないのですか?
[S010 A-06]
文法上許されていないためです。
仮に、スーパークラスのコンストラクタを、サブクラスのコンストラクタの
最初ではない場所で呼び出すことができたとすると、
スーパークラスで初期化を行っているインスタンス変数が初期化されないままと
なってしまいます。
その時点で、スーパークラスのインスタンス変数の状態に依存したメソッドを実行すると、
スーパークラスのコンストラクタが実行されてない、すなわちスーパークラスの
インスタンス変数が初期化されていないために、意図しない値を受け取ることになりかねません。
そのため、スーパークラスのコンストラクタを先頭に置かなければならない、
と、文法上、規定されています。
なお、サブクラスのコンストラクタ内で、スーパークラスのコンストラクタを
一切記述しない場合には、サブクラスのコンストラクタの先頭でスーパークラスの
引数無しのコンストラクタを実行するようなバイトコードをjavac コンパイラが
自動的に生成します。
[S010 Q-07]
コンストラクタは継承されますか?
[S010 A-07]
いいえ。継承されません。
したがって、スーパークラスのコンストラクタを利用可能にするには、
スーパークラスのコンストラクタを呼び出すコンストラクタを作成しなければな
りません。ただし、引数なしのコンストラクタに限り、暗黙的に呼ばれます。
例えば、
abstract class Person {
private String name;
public Person() {
this("ななしのごんべい");
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
abstract public boolean isLazy();
}
class LazyPerson extends Person {
public boolean isLazy() {
return true;
}
}
とクラスが定義されてあったときに、
Person lazyPerson1 = new LazyPerson("那魔毛藻野");
とすると適切なコンストラクタが見つからずコンパイルエラーになります。新しく
public LazyPerson(String name){
super(name);
}
とコンストラクタを追加する必要があります。
ただし、引数なしのコンストラクタに限り暗黙的に呼ばれるため、この場合、コンストラ
クタを追加せずに
Person lazyPerson2 = new LazyPerson();
は可能です。
[S010 Q-08]
インスタンス変数の初期化子とコンストラクタの実行順序はどうなっていますか?
[S010 A-08]
インスタンス変数の初期化子は、スーパークラスのコンストラクタの呼び出しの直後に実
行されます。
したがって、
class Foo extends Bar {
int a = 1;
int b;
Foo() {
b = 2;
}
}
のようなインスタンス変数の初期化子は、以下と同じように扱われます。
class Foo extends Bar {
int a;
int b;
Foo() {
super();
a = 1;
b = 2;
}
}
[S010 Q-09]
インスタンス変数が初期化されていないみたいなのですが?
[S010 A-09]
スーパークラスのコンストラクタがサブクラスのコンストラクタより先に呼び出されるた
め、スーパークラスのコンストラクタ内でサブクラスのメソッドを呼び出した場合、サブ
クラスのフィールドは初期化されていません。
その例として次のようなものが挙げられます。
abstract class Person {
public Person(){
showIntroduction();
}
abstract public void showIntroduction();
}
class LazyPerson extends Person {
private String intromsg;
public LazyPerson(String intromsg) {
this.intromsg = intromsg;
}
public void showIntroduction() {
System.out.println(intromsg);
}
}
この場合、
Person lazyPerson = new LazyPerson("I'm lazy.");
としても、 intromsg が初期化されておらず、
null
と表示され、
I'm lazy.
とは表示されません。
[S010 Q-10]
コンストラクタ内で this を参照するとエラーになってしまいます。どうしてですか?
[S010 A-10]
スーパークラスのコンストラクタの呼び出しが終了された後に参照可能になります。
それまではインスタンス変数にアクセスしたり、インスタンスメソッドを呼び出すことは
できません。しかし、コンストラクタ内で 2 度コンストラクタを呼び出せないことになって
いますので、スーパークラスのコンストラクタの呼び出し終了後でも例えば this(); と
することはできません。これに関しては [Q7] を参照して下さい。
例として、以下はコンパイルエラーとなります。
class Bar {
private int i;
public Bar(int i) {
this.i = i;
}
}
class Foo extends Bar {
private int j;
public Foo() {
super(this.j);
// ^^^^^^ ここが問題
}
}
[S010 Q-11]
コンストラクタ本体でコンストラクタを 2 度以上呼び出すことはできないのですか?
[S010 A-11]
できません。最初の文のみにおいて別のコンストラクタを呼び出すことができます。
そのため、以下はコンパイルエラーとなります。
class Person {
private String name;
private int salary;
public Person(String name) {
this.name = name;
}
public Person(int salary) {
this.salary = salary;
}
public Person(String name, int salary) {
this(name);
this(salary);
}
}
この場合の解決方法として以下のようなものがあります。
class Person {
private String name;
private int salary;
public Person(String name) {
this(name, 0);
}
public Person(int salary) {
this("ななし", salary);
}
public Person(String name, int salary) {
this.name = name;
this.salary = salary;
}
}
contributor: morphylu
コメントの送り先:Java FAQ BBS