Почему статические методы класса унаследованы, но не статические методы интерфейса?

Я понимаю, что в Java статические методы наследуются так же, как и методы экземпляра, с той разницей, что при их повторном использовании родительские реализации скрываются, а не переопределяются. Хорошо, это имеет смысл. Однако учебник Java отмечает, что

Статические методы в интерфейсах никогда не наследуются.

Почему? Какая разница между обычными и интерфейсными статическими методами?

Позвольте мне пояснить, что я имею в виду, когда говорю, что статические методы могут быть унаследованы:

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?)
}

Ответ 1

Вот моя догадка.

Так как Cat может распространять только один класс, если Cat extends Animal, то Cat.identify имеет только одно значение. Cat может реализовать несколько интерфейсов, каждый из которых может иметь статическую реализацию. Поэтому компилятор не знал, какой из них выбрать?

Однако, как указал автор,

Java уже имеет эту проблему, используя методы по умолчанию. Если два интерфейса объявить default void ident(), какой из них используется? Это компиляция ошибки, и вы должны реализовать метод переопределения (который мог бы просто Animal.super.identify()). Итак, Java уже разрешает это проблема для методов по умолчанию - почему бы не использовать статические методы?

Если бы я снова угадал, я бы сказал, что с default реализация является частью Cat vtable. С static этого не может быть. Основная функция должна быть привязана к чему-то. Во время компиляции Cat.identify может быть заменено на Animal.identify компилятором, но код не будет соответствовать действительности, если Cat был перекомпилирован, но не класс, содержащий основной.

Ответ 2

До Java 8 вы не можете определить методы static в interface. Это обсуждалось в в этом вопросе. Я буду ссылаться на этот ответ (по пользователю @JamesA.Rosen) относительно того, почему разработчикам Java, вероятно, не нужны методы static в interface изначально:

Здесь есть несколько проблем. Во-первых, проблема объявляя статический метод без его определения. В этом разница между

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

и

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

Java тоже не позволяет, но это может позволить второй. Первый невозможно по причинам, о которых упоминает Эспо: вы не знаете, какие класс реализации - это правильное определение.

Java может разрешить последнее, если он обрабатывает интерфейсы как первоклассные объекты. Модули Ruby, которые приблизительно эквивалентные Java-интерфейсам, позволяют именно это:

module Foo
  def self.bar
    ...
  end
end

Однако, начиная с выпуска Java 8, вы можете фактически добавить методы default и static внутри interface.

Я собираюсь много цитировать этот источник. Это первоначальная проблема:

Функция языка интерфейса 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() не будет компилироваться, потому что foo() является статическим членом интерфейса X, а не статический член класса Y.

Ответ 3

Статические методы в интерфейсах могли бы создать алмаз смерти, если бы они были унаследованы. Таким образом, вызов статического метода из соответствующего интерфейса достаточно хорош по сравнению с риском вызова его из конкретного класса, который может реализовывать несколько интерфейсов, содержащих статические методы с тем же именем.

Почему разные статические методы?

Статические методы - это просто функции, не связанные с объектами. Вместо того, чтобы помещать их в абстрактные классы полезности (например, вызов Collections.sort()), мы перемещаем эти функции (статические методы) в соответствующие интерфейсы. Они могут быть связаны с унаследованными объектами, такими как методы по умолчанию, но это не их работа. Статические методы обеспечивают функциональность, не связанную с экземплярами класса.

Пример:

interface Floatable {

    default void float() {
        // implementation
    }

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

class Duck implements Floatable { }

Итак, дело в том, что Duck может плавать, но функция, которая проверяет, действительно ли объект плавает, не является тем, что может сделать Duck. Это нерелевантная функциональность, которую мы могли бы передать нашему интерфейсу Floatable вместо того, чтобы он находился внутри некоторого класса утилиты.

Ответ 4

Давайте начнем с некоторого фона...

Java не поддерживает множественное наследование (возможность расширять более одного класса). Это связано с тем, что множественное наследование склонно к смертельному алмазу смерти (также известному как проблема алмазов), который дизайнеры Java решили исключить.

enter image description here

Если B и C переопределяют метод, унаследованный от A, какой метод наследует D?

Класс может реализовывать несколько интерфейсов, потому что методы интерфейса заключают контракт на переопределение; если класс C реализует два интерфейса A и B, которые объявляют один и тот же метод, то один и тот же метод в C будет вызываться клиентами любого интерфейса (A или B). Введение методов по умолчанию для интерфейсов в Java 8 стало возможным благодаря принудительному выполнению переопределить значение по умолчанию в случае неоднозначности. Это был приемлемый компромисс, так как методы по умолчанию предназначены для защиты (должны использоваться, если никакой другой реализацией явно не предусмотрено реализатором). Однако, поскольку компилятор не может заставить вас переопределить статический метод (статические методы по своей сути не могут быть переопределены), введение статических методов для интерфейсов в Java имело одно ограничение: статические методы интерфейса не наследуются.