web-dev-qa-db-ja.com

なぜクラスの静的メソッドは継承されますが、インターフェースの静的メソッドは継承されないのですか?

Java静的メソッドはインスタンスメソッドと同じように継承されますが、再宣言すると、親の実装がオーバーライドされるのではなく非表示になるという違いがあります。ただし、これは理にかなっています。ただし、- the Java tutorial は、

インターフェイスの静的メソッドは継承されません。

どうして?通常のメソッドとインターフェイスの静的メソッドの違いは何ですか?

静的メソッドを継承できると私が言っているときの意味を明確にしましょう。

class Animal {
    public static void identify() {
        System.out.println("This is an animal");
    }
}
class Cat extends Animal {}

public static void main(String[] args) {
    Animal.identify();
    Cat.identify(); // This compiles, even though it is not redefined in Cat.
}

しかしながら、

interface Animal {
    public static void identify() {
        System.out.println("This is an animal");
    }
}
class Cat implements Animal {}

public static void main(String[] args) {
    Animal.identify();
    Cat.identify(); // This does not compile, because interface static methods do not inherit. (Why?)
}
49

これが私の推測です。

Catは、CatextendsAnimalの場合、1つのクラスしか拡張できないため、Cat.identifyの意味は1つだけです。 Cat can implement静的な実装を持つことができる複数のインターフェース。したがって、コンパイラーはどちらを選択すればよいかわかりませんか?

しかし、著者が指摘したように、

デフォルトのメソッドでは、Javaにはすでにこの問題があります。 2つのインターフェースがデフォルトのvoidのidentify()を宣言する場合、どちらが使用されますか?これはコンパイルエラーであり、オーバーライドメソッド(これは単にAnimal.super.identify()の場合もあります)を実装する必要があります。したがって、Javaはデフォルトのメソッドのこの問題をすでに解決しています–なぜ静的メソッドではないのですか?

もう一度考えてみると、defaultの実装はCatのvtableの一部であると言えます。 staticではできません。メイン関数は何かにバインドする必要があります。コンパイル時にCat.identifyはコンパイラによってAnimal.identifyに置き換えることができますが、Catが再コンパイルされてもmainを含むクラスが一致しない場合、コードは実際と一致しません。

18
agbinfo

Java 8の前は、staticメソッドをinterfaceで定義できませんでした。これはよく議論されます この質問では 。 Java設計者がおそらくstaticinterfaceメソッドを最初に望まなかった理由について、これについて answer (ユーザー@ JamesA.Rosenによって)を参照します。 :

ここでの遊びにはいくつかの問題があります。 1つ目は、静的メソッドを定義せずに宣言する問題です。これは

public interface Foo {
  public static int bar();
}

そして

public interface Foo {
  public static int bar() {
    ...
  }
}

Javaではどちらも許可されていませんが、2つ目は許可されます。 Espoが言及している理由により、最初のものは不可能です:どの実装クラスが正しい定義であるかわかりません

インターフェースをファーストクラスのオブジェクトとして扱っていれば、Javaは後者を許可できます。 Rubyのモジュールは、Javaのインターフェースとほぼ同等であり、正確に次のことを可能にします。

module Foo
  def self.bar
    ...
  end
end

ただし、Java 8のリリース以降、defaultおよびstaticメソッドをinterface内に実際に追加できます。

これを引用します source ここでたくさん。これが最初の問題です。

Javaのインターフェース言語機能を使用すると、抽象メソッドでインターフェースを宣言し、インターフェースを実装するクラスでそれらのメソッドの実装を提供できます。それぞれのメソッドを実装する必要がありますが、実装するメソッドが多い場合は面倒です。また、インターフェイスを公開した後、ソースとバイナリの互換性を壊さずに新しい抽象メソッドを追加することはできません。

これは解決策でしたJava 8提供されたdefault

Java 8は、デフォルトおよび静的メソッドをサポートするようにインターフェースを進化させることにより、これらの問題に対処します。デフォルトメソッドは、メソッドヘッダーがデフォルトキーワードで始まるインターフェースで定義されたインスタンスメソッドです。また、コード本体も提供します。インターフェイスを実装するすべてのクラスは、インターフェイスのデフォルトメソッドを継承し、それらをオーバーライドできます

そしてstaticの場合:

静的メソッドは、そのクラスから作成されたオブジェクトではなく、それが定義されているクラスに関連付けられているメソッドです。クラスのすべてのインスタンスは、クラスの静的メソッドを共有します。 Java 8では、静的メソッドをデフォルトのメソッドを支援できるインターフェースで定義することもできます。

静的メソッドを含むインターフェースを実装しても、静的メソッドはインターフェースの一部であり、実装クラスの一部ではありません。このため、メソッドの前にクラス名を付けることはできません。代わりに、メソッドの前にインターフェース名を付ける必要があります

例:

interface X
{
   static void foo()
   {
      System.out.println("foo");
   }
}

class Y implements X
{
}

public class Z 
{
   public static void main(String[] args)
   {
      X.foo();
      // Y.foo(); // won't compile
   }
}

Y.foo()はインターフェイスXの静的メンバーであり、クラスYの静的メンバーではないため、式foo()はコンパイルされません。

インターフェースの静的メソッドは、継承されている場合、ダイヤの死をもたらす可能性があります。したがって、適切なインターフェースから静的メソッドを呼び出すことは、同じ名前の静的メソッドを含む複数のインターフェースを実装する可能性のある具象クラスからそれを呼び出すリスクに比べれば十分です。

静的メソッドが異なるのはなぜですか?

静的メソッドは、オブジェクトに関係のない関数です。それらをユーティリティの抽象クラス(Collections.sort()の呼び出しなど)に配置する代わりに、これらの関数(静的メソッド)を適切なインターフェースに移動します。デフォルトのメソッドのように、継承されたオブジェクトにバインドできますが、それは彼らの仕事ではありません。静的メソッドは、クラスのインスタンスとは無関係の機能を提供します。

例:

interface Floatable {

    default void float() {
        // implementation
    }

    static boolean checkIfItCanFloat(Object fl) {
         // some physics here
    } 
}

class Duck implements Floatable { }

つまり、ポイントはDuckが浮動する可能性があるが、オブジェクトが実際に浮動するかどうかをチェックする関数はDuckが実行できるものではないということです。いくつかのユーティリティクラス内に配置するのではなく、Floatableインターフェースに渡すことができるのは無関係な機能です。

3
Styl

いくつかの背景から始めましょう...

Javaは多重継承(複数のクラスを拡張する機能)をサポートしていません。これは、Javaの設計者が先取りすることを選択した)致命的な死のダイヤ(ダイヤの問題とも呼ばれます)が多重継承の傾向にあるためです。

enter image description here

BとCがAから継承したメソッドをオーバーライドする場合、Dはどのメソッドを継承しますか?

インターフェースメソッドはオーバーライドのために縮小されるため、クラスは複数のインターフェースを実装できます。クラスCが同じメソッドを宣言する2つのインターフェースAとBを実装している場合、Cの同じメソッドがいずれかのインターフェース(AまたはB)のクライアントによって呼び出されます。 Java 8でのインターフェースのデフォルトメソッドの導入は、曖昧な場合に実装者にデフォルトをオーバーライドすることを強制することによって可能になりました。デフォルトメソッドはdefensive(実装者によって明示的に他の実装が提供されていない場合に使用されます。ただし、コンパイラーは静的メソッドを強制的にオーバーライドすることができないため(静的メソッドは本質的にオーバーライドできません) )、Javaでのインターフェースの静的メソッドの導入には、1つの制限がありました:インターフェースの静的メソッドは継承されません