- PR -

interface のメソッドは、なぜ protected にできないのでしょうか?

投稿者投稿内容
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-07-10 11:39
unibon です。こんにちわ。

以下、わりと初歩的な質問だと思いますが、
interface のメソッドは、なぜ protected が指定できないのでしょうか。

たとえば java.awt.event.MouseListener は
コード:
public interface MouseListener extends EventListener {
    public void mouseClicked(MouseEvent e);
    ...
}


となっていますが、これを implements したクラスをつぎのように作ったとします。
--- MouseTester.java ---
コード:
import java.awt.event.*;

public class MouseTester implements MouseListener {

    protected void mouseClicked(MouseEvent e) {
    }
}


これをコンパイルすると、つぎのようなコンパイルエラーになります。

MouseTester.java:5: MouseTester の mouseClicked(java.awt.event.MouseEvent) は java.awt.event.MouseListener の mouseClicked(java.awt.event.MouseEvent) を実装できません。スーパークラスでの定義より弱いアクセス特権 (public) を割り当てようとしました。

かといって java.awt.event.MouseListener を
コード:
public interface MouseListener extends EventListener {
    protected void mouseClicked(MouseEvent e);
    ...
}


のようにすることもできません。
これは Java の言語の仕様なので、
たとえ MouseListener の作者であっても protected にできません。

私としては自分で作ったクラス MouseTester のメソッドは、
コールバックのメソッドなので、protected にしたいのです。
しかし interface の段階で protected が指定できないので、それが尾を引いてしまい、
その interface を implements した class のメソッドも protected にできません。
なぜなのでしょうか。
お犬様
ベテラン
会議室デビュー日: 2003/01/26
投稿数: 67
投稿日時: 2003-07-10 11:57
今回の件に関して言えば protected にしてしまうと MouseListener に
イベントを通知する側が不便だからではないでしょうか?
interface はあくまでも公開されるインターフェースのみを定義する、
というポリシーなのだと思います。

interface に対する不満としては 他にも interface に public static な
メソッドを書けても良いじゃないか、とかいう話を聞いた事があります。
Shane
大ベテラン
会議室デビュー日: 2003/06/06
投稿数: 132
お住まい・勤務地: Vancouver, BC
投稿日時: 2003-07-10 12:12
interface の意義はメソッドの外部への、、なんと言うか、提供? なので、
public にしかなりえないと思います。
なので protected なメソッドを interface に定義する意味はないでしょう。
ibara
常連さん
会議室デビュー日: 2002/11/15
投稿数: 26
投稿日時: 2003-07-10 12:58
protectedにしたらイベントの発生元からmouseClicked()が呼べない気がするのですが・・・。
R-55
常連さん
会議室デビュー日: 2003/03/13
投稿数: 29
投稿日時: 2003-07-10 13:11
結局interfaceとは外部との関係だけを定義するもので
何らかの機能を持たせるのならば抽象クラスを使う。

という解釈でいいのでしょうか?
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-07-10 13:49
unibon です。こんにちわ。
みなさまありがとうございます。

すみません。例をあげる際に、
呼ばれるほうばかり考えていて、呼ぶほうを考えていませんでした。
新たに別の例を書きます。

まずつぎの 2 つのクラス ClassA と Class B から構成されるサンプルを考えます。
なお、protected のアクセス範囲を考慮してパッケージを故意に分けてますのでご注意ください。
--- packageA/ClassA.java ---
コード:
package packageA;

public class ClassA {

    public static interface InterfaceX {
        public void foo(int value); // protected にしたい。
    }

    private InterfaceX x;

    public void AddX(InterfaceX aX) {
        x = aX;
    }

    public void bar(int value) {
        x.foo(value);
    }
}



--- packageB/ClassB.java ---
コード:
package packageB;

import packageA.*;

public class ClassB {

    public static class ClassY implements ClassA.InterfaceX {
        public void foo(int value) { // protected にしたい。
            System.out.println("value = " + value);
        }
    }

    public static void main(String[] args) {
        ClassA.InterfaceX x = new ClassY();
        x.foo(123); // ここで呼べてしまうが、呼べないようにしたい。
        ClassA a = new ClassA();
        a.AddX(x);
        a.bar(456);
    }
}


これだと、クラス ClassA の内部のインターフェース InterfaceX のメソッド foo は、
protected にできてもよさそうですが、
でも protected にするとこのスレッドの最初に述べたように、
コンパイルエラーになってしまいます。
かといって protected にすれば、クラス ClassB のメソッド main の中から
メソッド foo が呼べてしまいますが、これは避けたいです。


一方、これと対比するために、
つぎの 2 つのクラス ClassC と Class D から構成される別のサンプルを考えます。
ここでは interface ではなく abstract class を使っていますが、
これだと目論見どおりのことができます。
--- packageC/ClassC.java ---
コード:
package packageC;

public class ClassC {

    abstract public static class AbstractX {
        abstract protected void foo(int value); // protected にできる。
    }

    private AbstractX x;

    public void AddX(AbstractX aX) {
        x = aX;
    }

    public void bar(int value) {
        x.foo(value);
    }
}



--- packageD/ClassD.java ---
コード:
package packageD;

import packageC.*;

public class ClassD {

    public static class ClassY extends ClassC.AbstractX {
        protected void foo(int value) { // protected にできる。
            System.out.println("value = " + value);
        }
    }

    public static void main(String[] args) {
        ClassC.AbstractX x = new ClassY();
        // x.foo(123); // ここで呼ぼうとするとコンパイルエラーになるので嬉しい。
        ClassC c = new ClassC();
        c.AddX(x);
        c.bar(456);
    }
}


しかし、これだと interface ではないので、多重継承ができない Java では応用が効きません。
できれば interface でもこれと同じことがやりたいです。
yamasa
ベテラン
会議室デビュー日: 2003/02/15
投稿数: 80
投稿日時: 2003-07-10 16:16
うーん、以下のようにするとClassDのmainメソッドからでも
ClassYのfooメソッドを呼ぶことができますよね。
コード:
ClassC.AbstractX x = new ClassY();
// x.foo(123); // この呼び出しはコンパイルエラーになる。
((ClassY)x).foo(123); // この呼び出しはコンパイルエラーにならない。


そもそも、同じトップレベルクラスの中で宣言されたメソッドは、
たとえネストしたクラス内にあるものだとしても、
privateやprotectedなどのアクセス制限を無視して呼び出すことができる
というのがJava言語の基本的なポリシーです。
# もちろんローカルクラスや匿名クラスの場合は
# 宣言のスコープも影響してきますが…

それよりもまず、実装クラスであるClassYの宣言を
コード:
private static class ClassY implements ClassA.InterfaceX {

private static class ClassY extends ClassC.AbstractX {


のように変えるべきです。
このようにしても、相変わらずClassB, ClassDのmainメソッドから
fooメソッドを呼び出すことはできてしまいますが、
ClassB以外のクラスから
コード:
ClassA.InterfaceX x = new ClassB.ClassY();
x.foo(123);


などとすることはできなくなります。

カプセル化という観点からすると、こちらのほうがより重要だと思います。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-07-11 09:42
unibon です。こんにちわ。

引用:

yamasaさんの書き込み (2003-07-10 16:16) より:
うーん、以下のようにするとClassDのmainメソッドからでも
ClassYのfooメソッドを呼ぶことができますよね。


ご指摘ありがとうございます。そしてすみません。これも私の例が悪かったと思います。
ご指摘のように呼べてしまえるのは、protected なメソッドに共通の仕様であり、
この例に限らずとも起こりうることだと思います。
(たとえば、
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=3963&forum=12
のスレッドの私の2番目の投稿のような感じ。)
これはこれで protected なメソッドはこのような場面で呼べるべきか呼べないべきか、
という議論はできるのかもしれませんが、
今回の私の最初の疑問ではそれよりも以前の段階であり、
メソッドを protected にできるかできないかを問題にしています。
#横道に逸れたくないというわけではありませんが。

引用:

yamasaさんの書き込み (2003-07-10 16:16) より:
それよりもまず、実装クラスであるClassYの宣言を

のように変えるべきです。
このようにしても、相変わらずClassB, ClassDのmainメソッドから
fooメソッドを呼び出すことはできてしまいますが、
ClassB以外のクラスから

などとすることはできなくなります。

カプセル化という観点からすると、こちらのほうがより重要だと思います。


ClassY を public にするか private にするかは、
ClassY を使う側の都合で決めれば、どちらでも良いと思います。
すなわち内部クラスである ClassY を囲んでいる、
(外側の) ClassB や ClassD の作りで決めることです。

以下、ご指摘されたことと重なる部分がありますが、確認のためにサンプルを追加しました。
たとえば、packageC/ClassC.java や packageD/ClassD.java は前回のまま一切変えずに(ClassY を public のままにして)、
つぎのような ClassE を追加してこの main メソッドを実行すると、
目論見どおりコンパイルエラーになります。

--- packageE/ClassE.java ---
コード:
package packageE;

import packageD.*;

public class ClassE {

    public static void main(String[] args) {
        ClassD.ClassY x2 = new ClassD.ClassY();
        x2.foo(123); // 別パッケージなので protected でも呼べないので、コンパイルエラーになる。
    }
}


スキルアップ/キャリアアップ(JOB@IT)