Конструктор в интерфейсе?

149

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

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

Например, рассмотрим следующий класс сообщений:

public class MyMessage {

   public MyMessage(String receiver) {
      this.receiver = receiver;
   }

   private String receiver;

   public void send() {
      //some implementation for sending the mssage to the receiver
   }
}

Если определить интерфейс для этого класса, чтобы у меня было больше классов, реализующих интерфейс сообщений, я могу определить только метод send, а не конструктор. Итак, как я могу гарантировать, что каждая реализация этого класса действительно имеет набор получателей? Если я использую такой метод, setReceiver(String receiver)я не могу быть уверен, что этот метод действительно вызывается. В конструкторе я смог это обеспечить.

Даниэль Куллманн
источник
2
Вы говорите: «В конструкторе я мог убедиться, что [в каждой реализации этого класса действительно есть набор получателей]». Но нет, вы не могли этого сделать. При условии, что можно было определить такой конструктор, параметр был бы сильным указанием для ваших разработчиков - но они могли бы просто проигнорировать его, если захотят.
Жюльен Силланд
3
@mattb Хмм, это другой язык.
Йесеннес

Ответы:

130

Взяв некоторые из вещей, которые вы описали:

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

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

... эти требования как раз и предназначены для абстрактных классов .

Мэтт Б
источник
но учтите, что описанный в моем ответе сценарий использования @Sebi (вызов перегруженных методов из родительских конструкторов) - плохая идея.
rsp
44
Мэтт, это действительно так, но абстрактные классы страдают от ограничения единственного наследования, которое заставляет людей искать другие способы определения иерархий.
CPerkins
6
Это правда и может решить непосредственную проблему Себи. Но одна из причин использования интерфейсов в Java заключается в том, что вы не можете иметь множественное наследование. В случае, когда я не могу сделать свою «вещь» абстрактным классом, потому что мне нужно наследовать от чего-то другого, проблема остается. Не то чтобы я претендовал на решение.
Джей
7
@Perkins, хотя это правда, я не предполагаю, что простое использование абстрактного класса решит сценарий использования Sebi. Во всяком случае, лучше всего объявить Messageинтерфейс, который определяет send()метод, и если Sebi желает предоставить «базовый» класс для реализаций Messageинтерфейса, то предоставьте AbstractMessageтакже и. Абстрактные классы не должны занимать место интерфейсов, никогда не пытался это предложить.
Мэтт б
2
Понял, Мэтт. Я не спорил с тобой, более подчеркивая, что это не полная замена того, что хочет опера.
CPerkins
76

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

Или в коде:

interface Named { Named(String name); }
interface HasList { HasList(List list); }

class A implements Named, HasList {

  /** implements Named constructor.
   * This constructor should not be used from outside, 
   * because List parameter is missing
   */
  public A(String name)  { 
    ...
  }

  /** implements HasList constructor.
   * This constructor should not be used from outside, 
   * because String parameter is missing
   */
  public A(List list) {
    ...
  }

  /** This is the constructor that we would actually 
   * need to satisfy both interfaces at the same time
   */ 
  public A(String name, List list) {
    this(name);
    // the next line is illegal; you can only call one other super constructor
    this(list); 
  }
}
Даниэль Куллманн
источник
1
Не мог бы язык сделать это, допустив такие вещи, какclass A implements Named, HashList { A(){HashList(new list()); Named("name");} }
Мако
1
Самое полезное значение для «конструктора в интерфейсе», если это разрешено, было бы, если new Set<Fnord>()бы можно было интерпретировать как «Дайте мне то, что я могу использовать как Set<Fnord>»; если автор Set<T>намерен HashSet<T>стать готовой реализацией для вещей, в которых нет особой необходимости в чем-то еще, интерфейс, который затем new Set<Fnord>()может быть определен, может считаться синонимом new HashSet<Fnord>(). Для класса, реализующего несколько интерфейсов, не возникнет никаких проблем, поскольку new InterfaceName()он просто создаст класс, обозначенный интерфейсом .
суперкат
Встречный аргумент: ваш A(String,List)конструктор может быть назначенным конструктором A(String)и A(List)может быть вторичным, который его вызывает. Ваш код не контрпример, просто плохой.
Бен Легжеро
Почему вы вызываете все конструкторы в реализации ?! Да, если он реализует больше интерфейсов с ctor, один со строкой, а другой с целым, ему нужны два ctor - но любой из них или может быть использован. Если это не применимо, класс просто не реализует оба интерфейса. Ну и что!? (есть и другие причины отсутствия ctor в интерфейсах).
Кай
@kai Нет, он должен вызывать оба конструктора интерфейса при создании экземпляра. Другими словами: в моем примере экземпляр имеет как имя, так и список, поэтому каждый экземпляр должен создавать экземпляр как имени, так и списка.
Даниэль Куллманн
13

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

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

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

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

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

RSP
источник
6

Обходной путь, который вы можете попробовать, - определить getInstance()метод в вашем интерфейсе, чтобы разработчик знал, какие параметры необходимо обработать. Он не такой прочный, как абстрактный класс, но он обеспечивает большую гибкость, чем интерфейс.

Однако этот обходной путь требует, чтобы вы использовали getInstance()экземпляр всех объектов этого интерфейса.

Например

public interface Module {
    Module getInstance(Receiver receiver);
}
Lappro
источник
5

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

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

Сатьяджит Гами
источник
3

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

interface IMyMessage(){
    @NonNull String getReceiver();
}
  • это не нарушит инкапсуляцию
  • всем, кто использует ваш интерфейс, он сообщит, что Receiverобъект должен быть каким-то образом передан классу (либо конструктором, либо установщиком)
Денис Василенко
источник
2

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

Ишай
источник
1

Посмотрите этот вопрос для почему (взято из комментариев).

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

JSB ձոգչ
источник
1

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

Асиф Али
источник
0

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

private interface Listener {
    void onComplete(databaseError, databaseReference);
}

public abstract class MyCompletionListener implements Listener{
    String id;
    String name;
    public MyCompletionListener(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

private void removeUserPresenceOnCurrentItem() {
    mFirebase.removeValue(child("some_key"), new MyCompletionListener(UUID.randomUUID().toString(), "removeUserPresenceOnCurrentItem") {
        @Override
        public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {

        }
    });
    }
}

@Override
public void removeValue(DatabaseReference ref, final MyCompletionListener var1) {
    CompletionListener cListener = new CompletionListener() {
                @Override
                public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                    if (var1 != null){
                        System.out.println("Im back and my id is: " var1.is + " and my name is: " var1.name);
                        var1.onComplete(databaseError, databaseReference);
                    }
                }
            };
    ref.removeValue(cListener);
}
ExocetKid
источник
Как вы можете использовать privateмодификатор доступа с interface?
Рудра
0

Обычно конструкторы предназначены для инициализации нестатических членов определенного класса относительно объекта.

Для интерфейса не создается объект, поскольку есть только объявленные методы, но не определенные методы. Почему мы не можем создать объект для объявленных методов? Создание объекта - это не что иное, как выделение некоторой памяти (в динамической памяти) для нестатических элементов.

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

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

вывод:

без создания объекта нет возможности инициализировать нестатические члены с помощью конструктора. Именно поэтому конструктор не допускается внутри интерфейса (так как конструктор не используется внутри интерфейса).

Сай Кумар
источник