it-swarm.com.ru

Наследование Java-конструктора

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

public class Super {

  public Super(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC){
    this.serviceA = serviceA;
    //etc
  } 

}

Позже, когда вы унаследуете от Super, Java будет жаловаться, что не определен конструктор по умолчанию. Решение очевидно что-то вроде:

public class Son extends Super{

  public Son(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC){
    super(serviceA,serviceB,serviceC);
  }

}

Этот код повторяется, а не DRY и бесполезен (ИМХО) ... так что снова возникает вопрос:

Почему Java не поддерживает наследование конструктора? Есть ли какая-то польза в том, что ты не разрешаешь это наследство?

169
Pablo Fernandez

Предположим, что конструкторы были унаследованы ... тогда, поскольку каждый класс в конечном итоге наследуется от Object, every класс получит конструктор без параметров. Это плохая идея. Что именно вы ожидаете:

FileInputStream stream = new FileInputStream();

сделать?

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

188
Jon Skeet

Когда вы наследуете от Супер, вот что на самом деле происходит:

public class Son extends Super{

  // If you dont declare a constructor of any type, adefault one will appear.
  public Son(){
    // If you dont call any other constructor in the first line a call to super() will be placed instead.
    super();
  }

}

Так вот почему, потому что вы должны вызывать свой уникальный конструктор, так как «Super» не имеет значения по умолчанию.

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

27
David Santamaria

Потому что создание вашего объекта подкласса может быть сделано не так, как ваш суперкласс. Возможно, вы не захотите, чтобы клиенты подкласса могли вызывать определенные конструкторы, доступные в суперклассе. 

Глупый пример:

class Super {
    protected final Number value;
    public Super(Number value){
        this.value = value;
    }
}

class Sub {
    public Sub(){ super(Integer.valueOf(0)); }
    void doSomeStuff(){
        // We know this.value is an Integer, so it's safe to cast.
        doSomethingWithAnInteger((Integer)this.value);
    }
}

// Client code:
Sub s = new Sub(Long.valueOf(666L)): // Devilish invocation of Super constructor!
s.doSomeStuff(); // throws ClassCastException

Или даже проще:

class Super {
    private final String msg;
    Super(String msg){
        if (msg == null) throw new NullPointerException();
        this.msg = msg;
    }
}
class Sub {
    private final String detail;
    Sub(String msg, String detail){
        super(msg);
        if (detail == null) throw new NullPointerException();
        this.detail = detail;
    }
    void print(){
        // detail is never null, so this method won't fail
        System.out.println(detail.concat(": ").concat(msg));
    }
}
// Client code:
Sub s = new Sub("message"); // Calling Super constructor - detail is never initialized!
s.print(); // throws NullPointerException

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

11
gustafc

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

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

5
Andrzej Doyle

Ответ Дэвида правильный. Я хотел бы добавить, что вы можете получить знак от Бога, что ваш дизайн испорчен, и что «Сын» не должен быть подклассом «Супер», но вместо этого «Супер» имеет некоторые детали реализации, которые лучше всего выражены имея функциональность, которую предоставляет Сон, как своего рода стратегия.

Правка: ответ Джона Скита является удивительным.

2
Jonathan Feinberg

Конструкторы не являются полиморфными.
Имея дело с уже созданными классами, вы можете иметь дело с объявленным типом объекта или любым его подклассами. Вот для чего полезно наследование.
Конструктор всегда вызывается для определенного типа, например, new String(). Гипотетические подклассы не играют никакой роли в этом.

2
Rik

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

0
David R Tribble

По сути, вы наследуете конструкторы в том смысле, что вы можете просто вызвать super, если и когда это уместно, просто это может привести к ошибкам по причинам, упомянутым другими, если это произошло по умолчанию. Компилятор не может предполагать, когда он уместен, а когда нет. 

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

0
clearlight