Javaに関する様々な情報をご紹介します。

Javaに関する様々な情報をご紹介します。
評価

0

初心者です。継承とprivate

以下の3つのプログラムの実行結果がなぜそうなるのかわかりません。

プログラムA
public class Parent {
    private void disp(){
        System.out.println("Parent class");
    }
    public static void main(String[] args) {
        Parent p=new Child();
        p.disp();
    }
}

public class Child extends Parent {
    void disp(){
        System.out.println("Child class");
    }    
}

プログラムB
public class Parent {
    private void disp(){
        System.out.println("Parent class");
    }
}

public class Child extends Parent {
    void disp(){
        System.out.println("Child class");
    }    
}

public class Exec {
    public static void main(String[] args) {
        Parent p=new Child();
        p.disp();
    }
}

プログラムC
public class Parent {
    private void disp(){
        System.out.println("Parent class");
    }
    public static void main(String[] args) {
        Child p=new Child();
        p.disp();
    }
}

public class Child extends Parent {}

実行結果
プログラムA : "Parent Class"
プログラムB : コンパイルエラー。
              Parentクラスのp.disp();で、[メソッドdisp()は型Parentで不可視です]
プログラムC : コンパイルエラー。
       Parentクラスのp.disp();で、[メソッドdisp()は型Parentで不可視です]


プログラムAについて、Parent p=new Child();としたあとにp.disp();とpが自身のdisp()を参照できる意味が分かりません。
Child型のオブジェクトのメンバは、Childクラスのdisp()だけではないのですか?
また、実行できたとしても実行結果が"Parent class"になっていますが"Child class"とオーバーライドされないのはなぜですか?
代わりにParent p=new Parent();としたら実行結果が"Parent class"となることは理解できるのですが。

初心者で新しい概念が次々出てくるので頭の中で整理ができていない状態です。
最終的にプログラムA,B,Cがなぜその結果になるのかを理解したいです。

回答よろしくお願いします。

11

回答

95753

閲覧

11件の回答

評価

0

感覚的な説明をしてみます。

プログラムA:
  Parent.disp()はprivateなので派生クラスに継承され
ないParentだけの「秘密」のメソッドです。ということ
でChild.disp()はParent.disp()をオーバーライドして
いるのではなく、Childクラスがたまたま定義した別物
のメソッドなんです。

  Parent p;
  ...
  p.disp();

 そんな訳で上記がParentクラス内にあった場合は自身
のprivateメソッドの呼び出しを意図しているものとみ
なします。

プログラムB:
  ExecクラスはParentクラスではないクラスなので、
Parentだけの秘密のメソッドは呼び出せません。

プログラムC:
  
 これは確かにParentクラス内でdisp()を呼び出そうと
しています。しかしpの型はChildです。なので、Child
クラスとして他者に公開しているメソッドを呼び出すこ
とを意図しています。Childクラスとして外部に公開し
ているクラスはありません。確かにsuperclassでdispは
定義されてますが、これは派生クラスにはないものだと
考えてください。「Childクラスで利用可能なdispなん
てメソッドはないです」というメッセージでもいいくら
いなんですが、コンパイラさんは「ひょっとしてこのプ
ログラマーはソースをhackして知ってちゃだめな
Parent.disp()を呼び出そうとしているのかもしれな
い。いやいやダメですよ」みたいな感じのメッセージを
出しているだけです。

おかしな動きをしているように思えるかもしれないのは
Parentがわざわざ自分の派生クラスを使って何かしよう
としていることなんです。基本的にParentクラスの実装
者は自分の派生クラスとしてどんなクラスが定義されて
いるのかということは意識せず「Parentクラスとはこう
いうクラスです」ということだけ考えて実装すべきなの
です。よって子供クラスを派生クラスの中で参照するこ
とが普通ではないことなのです。


以下のような例を考えてみると多少自然に感じないでし
ょか。
boo()は、自身の秘密のメソッドdisp()を使って何か秘
密のことをします。どんな派生クラスを定義しても、
booは必ず自分自身のクラスにあるdisp()を呼び出した
いんです。

class Parent {
  private void disp() {...}

  Parent() {
    ...
  }

  protected void boo() {
    disp();
  }
  
}

/* どこかしらない別のソースで誰か他人が実装したク
ラス */
class Child extends Parent {
  Child() {
    super();
    boo();
  }

  void disp() { ... }
}

  
なんか適当な説明になってしまってますがいかがでしょ
うか。

評価

0

回答ありがとうございます。
いろいろ考えた結果、継承の概念がよくわかってないのだと思いました。

たとえばプログラムAですが、
Parent p=new Child();とします。private void disp()は継承されませんよね。
それはつまり、作成したオブジェクトにprivate void disp()メンバは存在しないということではないのですか?
だからpがParent型でもp.disp()はメンバが存在しないのだからコンパイルエラーにならないとおかしいと思ってしまいます。

この考えの間違いを正してほしいです。

評価

0

private void disp() ももちろん継承されます。
ただ private なので宣言しているクラス外からの利用が
出来ないのです。
その事が「不可視」と表現されています。

評価

0

継承のこと嘘をいってしまいました。混乱させてしまい
申し訳ないです。

仙人さんがコメントされているとおりです。

一応元のコメントの間違い訂正いたします。
------------------------------------
プログラムA:
  Parent.disp()はprivateなので派生クラスに継承され
ないParentだけの「秘密」のメソッドです。
===>間違い
  Parent.disp()はprivateなので派生クラスに継承され
るけど、その存在が隠された「秘密」のメソッドです。

-------------------------------------
確かにsuperclassでdispは定義されてますが、これは派
生クラスにはないものだと考えてください。「Childク
ラスで利用可能なdispなんてメソッドはないです」とい
うメッセージ...
===>間違い
確かにsuperclassでdispは定義されてますが、これは派
生クラスでは存在が隠されたものだと考えてください。
「Childクラスから直接利用可能なdispメソッドはない
です」というメッセージ...
---------------------------------------------

評価

10

>たとえばプログラムAですが、
>Parent p=new Child();とします。private void disp()は継承されませんよね。
>それはつまり、作成したオブジェクトにprivate void disp()メンバは
>存在しないということではないのですか?
>だからpがParent型でもp.disp()はメンバが存在しないのだから
>コンパイルエラーにならないとおかしいと思ってしまいます。

コンパイラはコンパイル時の型(変数の場合は宣言された際の型)から
スーパークラスに向かってメソッドの検索を行います。
実際にどのクラスのインスタンスとして生成されたかは考慮されません。
プログラムAのmainの場合、pはParent型でdispが定義されており、mainメソッドは
Parentクラス内部に定義されているので、privateであるdispにアクセス可能であり、
コンパイルエラーにはなりません。

例えばプログラムAに下のTestを加えた場合はコンパイルエラーになります。
public class Test {
    public static void main(String[] args) {
        Parent p=new Child();
        p.disp();
    }
}

>プログラムAについて、Parent p=new Child();としたあとにp.disp();とpが
>自身のdisp()を参照できる意味が分かりません。
>Child型のオブジェクトのメンバは、Childクラスのdisp()だけではないのですか?
>また、実行できたとしても実行結果が"Parent class"になっていますが
>"Child class"とオーバーライドされないのはなぜですか?
>代わりにParent p=new Parent();としたら実行結果が"Parent class"となることは
>理解できるのですが。

Parent#dispはprivateなのでChildには継承されません。
またChildでParent#dispと同じシグネチャのdispを定義しても
オーバーライドにはなりません。
実行時のVMはコンパイラとは違い、実行時の型(インスタンスとして生成したクラス)
からスーパークラスに向かってメソッドの検索を行います。
プログラムAのmainの場合、pの実行時の型はChildなのでChildクラスが
Parent#dispをオーバーライドしていないかを調べます、同じ名前のメソッドが
ありますがオーバーライドではないので、親クラスのParentを検索して
見つかったdispを実行する事になります。

java言語規定のメソッドの継承やオーバーライドに関する部分です。
http://www.y-adagio.com/public/standards/tr_javalang2/classes.doc.html#228745
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.8
(日本語訳は第2版で古く、継承部分の定義はjava7の方とは違っています)





評価

0

質問者です。回答ありがとうございます。

自分は今まで、「継承されない」というのは、例えばプログラムAだと
new Child()で生成されたオブジェクトにはprivate void disp()メンバは完全に存在しないということだと思っていました。だから、変数の型がParentだろうがそのメンバを参照することはできないと。

でも、そうではないのですね。
「継承されない」というのは、
Parent p=new Child(); p.disp();としたときは、private void disp()は「継承されない」から、Childクラスからはそれが見えない。よってオーバーライドされずにprivate void disp()を実行する。

要は、生成したオブジェクトの参照を変数に代入し、その変数の型によって「継承」が問題になってくる。

今のところはこういう認識で合っているでしょうか?
もしこの認識でいいのならプログラムA,B,C全て理解できたことになるのですが。


評価

0

>Parent p=new Child(); p.disp();としたときは、private void disp()は
>「継承されない」から、Childクラスからはそれが見えない。
>よってオーバーライドされずにprivate void disp()を実行する。

privateが継承されないのも、不可視なのも、オーバーライドできないのも、
一言でいえば仕様だからなのですが、上のように動作すると理解しても
問題ないと思います。

前回の回答で
>実行時のVMはコンパイラとは違い、実行時の型(インスタンスとして生成したクラス)
>からスーパークラスに向かってメソッドの検索を行います。
>プログラムAのmainの場合、pの実行時の型はChildなのでChildクラスが
>Parent#dispをオーバーライドしていないかを調べます、同じ名前のメソッドが
>ありますがオーバーライドではないので、親クラスのParentを検索して
>見つかったdispを実行する事になります。
と書きましたがこれは私が勝手にこのように理解していただけでまちがいでした。
申し訳ありませんでした。

javapで逆アセンブルしてみると
Parent p=new Child();
p.disp();
このdispの呼び出し部分でParent#dispを呼び出す命令を出力しますが
dispがprivateなのでオーバーライドを考慮しないinvokespecialという
バイトコードが使われていました。
このinvokespecialは検索を行わずにParent#dispを実行します。

オーバーライドの可能性がある場合にはinvokevirtualが使われ、
この場合には実行時の型からからスーパークラスに向かって
メソッドの検索を行います。
SDKが入っていればで確認できるので試してみてください。
javap -c -p -v Parent.class


>要は、生成したオブジェクトの参照を変数に代入し、
>その変数の型によって「継承」が問題になってくる。

"「継承」が問題になってくる"の部分がよく分かりません。
要は、生成したオブジェクトの参照を変数に代入し、
その変数の型でコンパイラは整合性をチェックする
という意味なら合っていると思います。

継承やオーバーライドや隠蔽などで疑問を感じた場合は
コンパイル時のコンパイラの動作と、classファイルが実行される際の
VMの動作を分けて理解した方が良いと思います。
その気があれば言語仕様やVM仕様または検索などで学習してみてください。

VM仕様(英語)
http://docs.oracle.com/javase/specs/jvms/se7/html/index.html
(古いですが日本語訳の「Java仮想マシン仕様」という書籍も
以前出版されていました。図書館などにはあるかもしれません。)

評価

0

先に間違いコメントしたものです。

>Parent p=new Child(); p.disp();としたときは、
private void disp()は
>「継承されない」から、Childクラスからはそれが見え
ない。

>privateが継承されないのも、不可視なのも、オーバー
ライドできないのも、
>一言でいえば仕様だからなのですが、上のように動作
すると理解しても
>問題ないと思います。


使えるようになればいいという話もありますが、用語は
コミュニケーションする際に混乱するし私のように恥も
かくので正確にとらえるべきであろうと思います。
仙人さんがおっしゃるように派生クラスへはもれなく全
てのメソッド(とかフィールドとか・・・)が継承され
ます。

>要は、生成したオブジェクトの参照を変数に代入し、
>その変数の型によって「継承」が問題になってくる。

継承が問題になるのではなくて「誰が」「どの型」とし
て扱おうとしているかで「何がアクセスできるか」が問
題になってくるのです。「どの型」を扱っているかを決
めるのは呼び出しに際して使っている変数の型になりま
す。

ParentがParnt型を扱う場合はParentのprivateをアクセ
スできる。

ChildがParent型を扱う場合はParentのprivateはアクセ
スできない。

ChildがChild型を扱う場合はChildに継承されている
Parentのprivateはアクセスできない。

だれが扱おうとChild型を扱う場合はChildに継承されて
いるParentのprivateはアクセスできない。例えそれが
Parent自身であっても同じ。

説明がへたくそですみませんがこんな感じです。

評価

0

私も用語は正確であった方がいいと思います。
Javaの仕様では継承されるメソッドにprivateは含まれていません。
フィールドに関してはスーパークラスのprivateなものも
当然オブジェクトごとに値が変わってくるので保持しているでしょうが、
仕様ではそれを継承とは呼んでいません。


Java7の言語仕様です
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.8

8.4.8. Inheritance, Overriding, and Hiding

A class C inherits from its direct superclass and 
direct superinterfaces all abstract and 
non-abstract methods of the superclass and 
superinterfaces that are public, protected, or

 declared with default access in the same package as C, 
and are neither overridden (§8.4.8.1) nor hidden (§8.4.8.2) by a declaration in the class. 

(こちらは古いので表現が違いますがJava言語規定 第2版です)
http://www.y-adagio.com/public/standards/tr_javalang2/classes.doc.html#228745
8.4.6 継承,上書き及び隠ぺい
クラスは,その直接的上位クラス及び直接的上位インタフェースから,
クラス内でコードにアクセス可能であり,クラス内の宣言によって
上書き(8.4.6.1)又は隠ぺい(8.4.6.2)されていない上位クラス及び
上位インタフェースのすべての非privateメソッドを(abstractであってもなくても)継承(inherits)する。

評価

0

ご指摘ありがとうございます。恥の上塗りですね...(^^;

Javaに関しての話なのだから言語仕様をベースに話すべき
でした!
あらためてお詫びします。

評価

0

皆様、回答ありがとうございます。

なんとなく、分かってきました。

仕様とか厳密なことはまだ自分には難しそうなので、とりあえずこのまま勉強を進めていきたいと思います。

質問から6ヶ月以上経過しているので、回答を書き込むことはできません。