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

РЕДАКТИРОВАТЬ: Начиная с Java 8, статические методы теперь разрешены в интерфейсах.

Вот пример:

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

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

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

IXMLizable.newInstanceFromXML(e);

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

РЕДАКТИРОВАТЬ: Я думаю, что я ищу ответ, который глубже, чем "потому что это так, как Java".

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

РЕДАКТИРОВАТЬ: Проблема с моим дизайном в том, что я пытаюсь использовать интерфейсы для обеспечения соблюдения правил кодирования.

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

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

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

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

Ответы [ 24 ]

3 голосов
/ 15 июня 2016

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

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

2 голосов
/ 20 августа 2013
Why can't I define a static method in a Java interface?

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

2 голосов
/ 04 февраля 2009

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

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

2 голосов
/ 04 февраля 2009

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

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

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.

1 голос
/ 14 февраля 2011

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

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

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

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

0 голосов
/ 04 февраля 2009

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

MyImplClass.myMethod()

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

myInstance.myMethod()

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

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

MyImplClass.myMethod()

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

0 голосов
/ 19 сентября 2015

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

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

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

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

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

.. бла-бла

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

0 голосов
/ 20 мая 2015

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

У меня есть несколько определений enum, в которых я определил поля "id" и "displayName", а также вспомогательные методы, оценивающие значения по разным причинам. Реализация интерфейса позволяет мне гарантировать, что методы getter находятся на месте, но не статические вспомогательные методы. Будучи перечислением, на самом деле нет чистого способа разгрузить вспомогательные методы в унаследованный абстрактный класс или что-то подобное, поэтому методы должны быть определены в самом перечислении. Кроме того, поскольку это enum, вы никогда не сможете фактически передать его как экземплярный объект и трактовать его как тип интерфейса, но мне нравится возможность наличия статических вспомогательных методов через интерфейс. поддерживается в 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);
    }
}

Определение общей утилиты enum:

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, " , ")));
    }
}
0 голосов
/ 13 февраля 2009

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

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.
  }

}
0 голосов
/ 03 октября 2013

Что-то, что может быть реализовано, - это статический интерфейс (вместо статического метода в интерфейсе). Все классы, реализующие данный статический интерфейс, должны реализовывать соответствующие статические методы. Вы можете получить статический интерфейс SI из любого класса Classzz, используя

SI si = clazz.getStatic(SI.class); // null if clazz doesn't implement SI
// alternatively if the class is known at compile time
SI si = Someclass.static.SI; // either compiler errror or not null

тогда вы можете позвонить si.method(params). Это было бы полезно (например, для шаблона проектирования фабрики), потому что вы можете получить (или проверить реализацию) реализацию статических методов SI из неизвестного класса времени компиляции! Динамическая диспетчеризация необходима, и вы можете переопределить статические методы (если не финальные) класса, расширив его (при вызове через статический интерфейс). Очевидно, что эти методы могут обращаться только к статическим переменным своего класса.

...