interface методы - Почему я не могу определить статический метод в интерфейсе Java?




умолчанию создание (21)

Вот пример:

public interface IXMLizable<T>
{
  static T newInstanceFromXML(Element e);
  Element toXMLElement();
}

Конечно, это не сработает. Но почему нет?

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

IXMLizable.newInstanceFromXML(e);

В этом случае, я думаю, он должен просто вызвать пустой метод (т.е. {}). Все подклассы будут вынуждены реализовать статический метод, поэтому при вызове статического метода все будет в порядке. Так почему же это невозможно?

EDIT: Я думаю, что я ищу ответ, который глубже, чем «потому что это так, как Java».

Существует ли конкретная технологическая причина, по которой статические методы не могут быть перезаписаны? Вот почему разработчики Java решили сделать методы экземпляра переопределяемыми, но не статическими методами?

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

То есть цель интерфейса двоякая:

  1. Я хочу, чтобы интерфейс IXMLizable позволял мне преобразовывать классы, которые реализуют его в XML-элементы (используя полиморфизм, отлично работает).

  2. Если кто-то хочет создать новый экземпляр класса, который реализует интерфейс IXMLizable, они всегда будут знать, что будет создан staticInstanceFromXML (Element e) статический конструктор.

Есть ли другой способ обеспечить это, кроме как просто комментировать интерфейс?

EDIT: Начиная с Java 8, в интерфейсах теперь разрешены статические методы.


Answers

Вы не можете определять статические методы в интерфейсе, потому что статические методы относятся к классу не к экземпляру класса, а интерфейсы не относятся к классам. Подробнее читайте здесь.

Однако, если вы хотите, вы можете это сделать:

public class A {
  public static void methodX() {
  }
}

public class B extends A {
  public static void methodX() {
  }
}

В этом случае у вас есть два класса с двумя разными статическими методами, называемыми methodX ().


Java 8 позволяет использовать методы статического интерфейса

С Java 8 интерфейсы могут иметь статические методы. Они также могут иметь конкретные методы экземпляра, но не поля экземпляров.

Здесь есть два вопроса:

  1. Почему в старые добрые времена интерфейсы не могли содержать статические методы?
  2. Почему статические методы не могут быть переопределены?

Статические методы в интерфейсах

Не было сильной технической причины, почему интерфейсы не могли иметь статические методы в предыдущих версиях. Это хорошо подведено плакатом дублирующего вопроса. Методы статического интерфейса были первоначально рассмотрены как небольшое изменение языка, а затем было официальное предложение о добавлении их в Java 7, но позже оно было отменено из-за непредвиденных осложнений.

Наконец, в Java 8 были введены методы статического интерфейса, а также методы переопределения экземпляров с реализацией по умолчанию. Тем не менее, у них все еще нет полей экземпляров. Эти функции являются частью поддержки выражения лямбда, и вы можете больше узнать о них в части H JSR 335.

Переопределение статических методов

Ответ на второй вопрос немного сложнее.

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

Немного о том, как работают методы экземпляров, необходимо понять, что здесь происходит. Я уверен, что фактическая реализация совсем иная, но позвольте мне объяснить свое понятие отправки методов, какие модели наблюдали поведение точно.

Представьте, что каждый класс имеет хеш-таблицу, которая отображает сигнатуры методов (имя и типы параметров) на фактический кусок кода для реализации метода. Когда виртуальная машина пытается вызвать метод в экземпляре, он запрашивает объект для своего класса и просматривает запрошенную подпись в таблице класса. Если тело метода найдено, оно вызывается. В противном случае получается родительский класс класса, и поиск повторяется там. Это продолжается до тех пор, пока метод не будет найден, или нет родительских классов, что приводит к NoSuchMethodError .

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

Теперь предположим, что мы пропустим экземпляр объекта и просто начнем с подкласса. Резолюция может выполняться, как указано выше, и дает вам своего рода «переопределяемый» статический метод. Однако разрешение может произойти во время компиляции, поскольку компилятор начинается с известного класса, а не ждет, пока среда выполнения не запросит объект неопределенного типа для своего класса. Нет смысла «переопределять» статический метод, так как всегда можно указать класс, содержащий нужную версию.

Конструктор "интерфейсы"

Вот немного больше материала, чтобы рассмотреть недавнее редактирование вопроса.

Похоже, вы хотите эффективно IXMLizable конструктор-подобный метод для каждой реализации IXMLizable . Забудьте о попытке навязать это с помощью интерфейса на минуту и ​​притворитесь, что у вас есть классы, соответствующие этому требованию. Как бы вы его использовали?

class Foo implements IXMLizable<Foo> {
  public static Foo newInstanceFromXML(Element e) { ... }
}

Foo obj = Foo.newInstanceFromXML(e);

Поскольку вы должны явно называть конкретный тип Foo при «конструировании» нового объекта, компилятор может убедиться, что он действительно имеет необходимый заводский метод. И если это не так, ну и что? Если я могу реализовать IXMLizable которому не хватает «конструктора», и я создаю экземпляр и IXMLizable его вашему коду, он является IXMLizable со всем необходимым интерфейсом.

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


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

Но вы можете поместить классы, содержащие статические методы, внутри интерфейсов. Вы могли бы попробовать это!

public interface Test {
    static class Inner {
        public static Object get() {
            return 0;
        }
    }
}

Я думаю, что java не имеет статических методов интерфейса, потому что они вам не нужны. Вы можете думать, что делаете, но ... Как бы вы их использовали? Если вы хотите их называть

MyImplClass.myMethod()

то вам не нужно объявлять его в интерфейсе. Если вы хотите их называть

myInstance.myMethod()

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

Интерфейсы позволяют определить контракт между экземпляром класса, реализующим интерфейс и код вызова. И java помогает вам быть уверенным, что этот контракт не нарушен, поэтому вы можете положиться на него и не волноваться, какой класс реализует этот контракт, достаточно «того, кто подписал контракт». В случае статических интерфейсов ваш код

MyImplClass.myMethod()

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


Интерфейсы связаны с полиморфизмом, который по своей сути привязан к экземплярам объекта, а не к классам. Поэтому статичность не имеет смысла в контексте интерфейса.


Почему я не могу определить статический метод в интерфейсе Java?

На самом деле вы можете в Java 8.

Согласно Java- docs.oracle/staticMethod :

Статический метод - это метод, который связан с классом, в котором он определен, а не с каким-либо объектом. Каждый экземпляр класса делится своими статическими методами

В Java 8 интерфейс может иметь методы по умолчанию и статические методы . Это облегчает нам организацию вспомогательных методов в наших библиотеках. Мы можем хранить статические методы, специфичные для интерфейса в одном и том же интерфейсе, а не в отдельном классе.

Пример метода по умолчанию:

list.sort(ordering);

вместо

Collections.sort(list, ordering);

Пример статического метода (из самого docs.oracle/staticMethod ):

public interface TimeClient {
    // ...
    static public ZoneId getZoneId (String zoneString) {
        try {
            return ZoneId.of(zoneString);
        } catch (DateTimeException e) {
            System.err.println("Invalid time zone: " + zoneString +
                "; using default time zone instead.");
            return ZoneId.systemDefault();
        }
    }

    default public ZonedDateTime getZonedDateTime(String zoneString) {
        return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
    }    
}

Предположим, вы могли это сделать; рассмотрим этот пример:

interface Iface {
  public static void thisIsTheMethod();
}

class A implements Iface {

  public static void thisIsTheMethod(){
    system.out.print("I'm class A");
  }

}

class B extends Class A {

  public static void thisIsTheMethod(){
    System.out.print("I'm class B");
  } 
}

SomeClass {

  void doStuff(Iface face) {
    IFace.thisIsTheMethod();
    // now what would/could/should happen here.
  }

}

Предположим, что в интерфейсах допускались статические методы: * Они заставили бы все реализующие классы объявить этот метод. * Интерфейсы обычно использовались бы через объекты, поэтому единственными эффективными методами для них были бы нестатические. * Любой класс, который знает определенный интерфейс, может ссылаться на свои статические методы. Следовательно, статический метод реализации класса будет вызываться под ним, но класс invoker не знает, что. Как это знать? У этого нет никакого инстанцирования, чтобы догадаться, что!

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

Если мы хотим, чтобы это расширение было распространено на статические методы, мы должны иметь возможность прежде всего реализовать класс реализации, а затем передать ссылку на вызывающий класс. Это может использовать класс через статические методы в интерфейсе. Но в чем разница между этой ссылкой и объектом? Нам просто нужен объект, представляющий класс. Теперь объект представляет старый класс и может реализовать новый интерфейс, включая старые статические методы - теперь они нестатические.

Метаклассы служат для этой цели. Вы можете попробовать класс класса Java. Но проблема в том, что Java недостаточно гибкая для этого. Вы не можете объявить метод в объекте класса интерфейса.

Это мета-проблема - когда вам нужно делать задницу

..blah blah

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


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


Интерфейсы просто предоставляют список вещей, которые предоставит класс, а не фактическая реализация этих вещей, что является вашим статическим элементом.

Если вы хотите статику, используйте абстрактный класс и наследуйте его, в противном случае удалите статическую.

Надеюсь, это поможет!


  • «Есть ли особая причина, почему статические методы нельзя переопределить».

Позвольте мне переформулировать этот вопрос для вас, заполнив определения.

  • «Есть ли определенная причина, по которой методы, разрешенные во время компиляции, не могут быть разрешены во время выполнения».

Если вы хотите вызвать метод без экземпляра, но, зная класс, как я могу его разрешить на основе экземпляра, которого у меня нет.


С появлением Java 8 теперь можно писать стандартные и статические методы в интерфейсе. docs.oracle/staticMethod

Например:

public interface Arithmetic {

    public int add(int a, int b);

    public static int multiply(int a, int b) {
        return a * b;
    }
}
public class ArithmaticImplementation implements Arithmetic {

    @Override
    public int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) {
        int result = Arithmetic.multiply(2, 3);
        System.out.println(result);
    }
}

Результат : 6

СОВЕТ. Вызов метода статического интерфейса не требует применения какого-либо класса. Несомненно, это происходит потому, что для статических методов на интерфейсах применяются те же правила для статических методов в суперклассах.


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

С generics, они имеют использование - с или без реализации по умолчанию. Очевидно, что нужно было бы переопределить и так далее. Тем не менее, я предполагаю, что такое использование было не очень OO (так как другие ответы указывают на тупо) и, следовательно, не считаются заслуживающими внимания усилий, которые им потребуются для реализации.


Комментирование EDIT: As of Java 8, static methods are now allowed in interfaces.

Это правильно, статические методы, поскольку Java 8 разрешены в интерфейсах, но ваш пример все равно не будет работать. Вы не можете просто определить статический метод: вы должны его реализовать или вы получите ошибку компиляции.


Чтобы решить эту проблему: ошибка: отсутствует тело метода или объявить абстрактный static void main (String [] args);

interface I
{
    int x=20;
    void getValue();
    static void main(String[] args){};//Put curly braces 
}
class InterDemo implements I
{
    public void getValue()
    {
    System.out.println(x);
    }
    public static void main(String[] args)
    {
    InterDemo i=new InterDemo();
    i.getValue();   
    }

}

выход: 20

Теперь мы можем использовать статический метод в интерфейсе


Хотя я понимаю, что Java 8 разрешает эту проблему, мне показалось, что я буду перекликаться со сценарием, в котором я сейчас работаю (заблокирован в использовании Java 7), где было бы полезно указать статические методы в интерфейсе.

У меня есть несколько определений перечислений, где я определил поля «id» и «displayName» вместе со вспомогательными методами, оценивая значения по различным причинам. Внедрение интерфейса позволяет мне гарантировать, что методы геттера находятся на месте, но не статические вспомогательные методы. Являясь переименованием, действительно нет чистого способа разгрузить вспомогательные методы в унаследованный абстрактный класс или что-то подобное, поэтому методы должны быть определены в самом перечислении. Кроме того, поскольку это переименование, вы никогда не сможете передавать его как объект instanced и рассматривать его как тип интерфейса, но возможность требовать существования статических вспомогательных методов через интерфейс - это то, что мне нравится он поддерживается в Java 8.

Вот код, иллюстрирующий мою мысль.

Определение интерфейса:

public interface IGenericEnum <T extends Enum<T>> {
    String getId();
    String getDisplayName();
    //If I was using Java 8 static helper methods would go here
}

Пример одного определения перечисления:

public enum ExecutionModeType implements IGenericEnum<ExecutionModeType> {
    STANDARD ("Standard", "Standard Mode"),
    DEBUG ("Debug", "Debug Mode");

    String id;
    String displayName;

    //Getter methods
    public String getId() {
        return id;
    }

    public String getDisplayName() {
        return displayName;
    }

    //Constructor
    private ExecutionModeType(String id, String displayName) {
        this.id = id;
        this.displayName = displayName;
    }

    //Helper methods - not enforced by Interface
    public static boolean isValidId(String id) {
        return GenericEnumUtility.isValidId(ExecutionModeType.class, id);
    }

    public static String printIdOptions(String delimiter){
        return GenericEnumUtility.printIdOptions(ExecutionModeType.class, delimiter);
    }

    public static String[] getIdArray(){
        return GenericEnumUtility.getIdArray(ExecutionModeType.class);
    }

    public static ExecutionModeType getById(String id) throws NoSuchObjectException {
        return GenericEnumUtility.getById(ExecutionModeType.class, id);
    }
}

Утилита общего назначения перечислений:

public class GenericEnumUtility {
    public static <T extends Enum<T> & IGenericEnum<T>> boolean isValidId(Class<T> enumType, String id) {       
        for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
            if(enumOption.getId().equals(id)) {
                return true;
            }
        }

        return false;
    }

    public static <T extends Enum<T> & IGenericEnum<T>> String printIdOptions(Class<T> enumType, String delimiter){
        String ret = "";
        delimiter = delimiter == null ? " " : delimiter;

        int i = 0;
        for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
            if(i == 0) {
                ret = enumOption.getId();
            } else {
                ret += delimiter + enumOption.getId();
            }           
            i++;
        }

        return ret;
    }

    public static <T extends Enum<T> & IGenericEnum<T>> String[] getIdArray(Class<T> enumType){
        List<String> idValues = new ArrayList<String>();

        for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
            idValues.add(enumOption.getId());
        }

        return idValues.toArray(new String[idValues.size()]);
    }

    @SuppressWarnings("unchecked")
    public static <T extends Enum<T> & IGenericEnum<T>> T getById(Class<T> enumType, String id) throws NoSuchObjectException {
        id = id == null ? "" : id;
        for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
            if(id.equals(enumOption.getId())) {
                return (T)enumOption;
            }
        }

        throw new NoSuchObjectException(String.format("ERROR: \"%s\" is not a valid ID. Valid IDs are: %s.", id, printIdOptions(enumType, " , ")));
    }
}

Поскольку статические методы нельзя переопределять в подклассах, и поэтому они не могут быть абстрактными. И все методы в интерфейсе де-факто абстрактны.


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

Например, я работаю с объектно-реляционным уровнем, который имеет объекты значений, но также имеет команды для управления объектами значений. По разным причинам каждый класс класса значений должен определять некоторые статические методы, позволяющие инфраструктуре находить экземпляр команды. Например, чтобы создать Личность, которую вы сделали бы:

cmd = createCmd(Person.getCreateCmdId());
Person p = cmd.execute();

и загрузить персонаж по ID, который вы бы сделали

cmd = createCmd(Person.getGetCmdId());
cmd.set(ID, id);
Person p = cmd.execute();

Это довольно удобно, однако у него есть свои проблемы; в частности, существование статических методов не может быть реализовано в интерфейсе. Преодолеваемый статический метод в интерфейсе будет именно тем, что нам нужно, если бы он мог работать как-то.

EJB разрешают эту проблему, имея домашний интерфейс; каждый объект знает, как найти его «Дом», а «Дом» содержит «статические» методы. Таким образом, «статические» методы могут быть переопределены по мере необходимости, и вы не загромождаете нормальный (он называется «удаленный») интерфейс с методами, которые не применяются к экземпляру вашего компонента. Просто сделайте нормальный интерфейс, указав метод getHome (). Верните экземпляр объекта Home (который может быть синглом, я полагаю), и вызывающий может выполнять операции, которые влияют на все объекты Person.


Интерфейс никогда не может быть разыменован статически, например, ISomething.member . Интерфейс всегда разыменовывается с помощью переменной, которая ссылается на экземпляр подкласса интерфейса. Таким образом, ссылка на интерфейс никогда не может знать, к какому подклассу он относится без экземпляра своего подкласса.

Таким образом, самым близким приближением к статическому методу в интерфейсе будет нестатический метод, который игнорирует «это», т. Е. Не получает доступа к нестационарным членам экземпляра. При абстракции низкого уровня каждый нестатический метод (после поиска в любом vtable) на самом деле является просто функцией с областью класса, которая воспринимает это «как» неявный формальный параметр. См . Одноэлементный объект Scala и совместимость с Java в качестве доказательства этой концепции. И таким образом, каждый статический метод является функцией с областью класса, которая не принимает «этот» параметр. Таким образом, обычно статический метод можно назвать статически, но, как было сказано ранее, интерфейс не имеет реализации (является абстрактным).

Таким образом, чтобы получить самое близкое приближение к статическому методу в интерфейсе, нужно использовать нестатический метод, а затем не обращаться к любому из нестатических членов экземпляра. Не было бы никакой выгоды от производительности любым другим способом, потому что нет способа статически связать (во время компиляции) ISomething.member() . Единственное преимущество, которое я вижу в статическом методе в интерфейсе, заключается в том, что он не будет вводить (т.е. игнорировать) неявное «это» и тем самым запрещать доступ к любому из нестатических членов экземпляра. Это неявно объявляло бы, что функция, которая не имеет доступа к «этому», является неизменной и даже не читается только относительно ее содержащего класса. Но объявление «статического» в интерфейсе ISomething также ISomething бы людей, которые пытались получить к нему доступ с помощью ISomething.member() которые вызывают ошибку компилятора. Я полагаю, что если ошибка компилятора была достаточно объяснительной, было бы лучше, чем пытаться обучать людей использованию нестатического метода для достижения того, что они хотят (по-видимому, главным образом, в методах фабрики), как мы это делаем (и было повторено для 3 Q & A раз на этом сайте), поэтому это, очевидно, проблема, которая не является интуитивной для многих людей. Я должен был подумать об этом некоторое время, чтобы получить правильное понимание.

Способ получить изменяемое статическое поле в интерфейсе - использовать нестатические методы getter и setter в интерфейсе, чтобы получить доступ к этому статическому полю, которое находится в подклассе. Sidenote, по-видимому неизменяемая статика, может быть объявлена ​​в интерфейсе Java со static final .


Об этом уже спрашивали и отвечали, here

Чтобы дублировать мой ответ:

Никогда не бывает смысла объявлять статический метод в интерфейсе. Они не могут быть выполнены обычным вызовом MyInterface.staticMethod (). Если вы вызываете их, указав реализующий класс MyImplementor.staticMethod (), вы должны знать фактический класс, поэтому не имеет значения, содержит ли он его интерфейс или нет.

Что еще более важно, статические методы никогда не переопределяются, и если вы попытаетесь сделать это:

MyInterface var = new MyImplementingClass();
var.staticMethod();

правила для static говорят, что метод, определенный в объявленном типе var, должен быть выполнен. Поскольку это интерфейс, это невозможно.

Причина, по которой вы не можете выполнить «result = MyInterface.staticMethod ()», заключается в том, что ей нужно будет выполнить версию метода, определенного в MyInterface. Но не может быть версии, определенной в MyInterface, потому что это интерфейс. Он не имеет кода по определению.

Хотя вы можете сказать, что это означает «потому что Java делает это именно так», на самом деле решение является логическим следствием других дизайнерских решений, которые также сделаны по очень веской причине.


Нет ничего плохого в статических переменных как таковых. Это просто синтаксис Java, который сломан. Каждый Java-класс фактически определяет две структуры - одноэлементный объект, который инкапсулирует статические переменные и экземпляр. Определение как в одном исходном блоке является чистым злом, так и результатом является код, который трудно читать. Скала сделала это правильно.





java interface static-methods