Почему не абстрактные поля?

102

Почему классы Java не могут иметь абстрактные поля, как у них могут быть абстрактные методы?

Например: у меня есть два класса, которые расширяют один и тот же абстрактный базовый класс. Каждый из этих двух классов имеет идентичный метод, за исключением константы String, которая является сообщением об ошибке внутри них. Если бы поля могли быть абстрактными, я мог бы сделать эту константу абстрактной и перенести метод в базовый класс. Вместо этого мне нужно создать абстрактный метод, вызываемый getErrMsg()в этом случае, который возвращает String, переопределить этот метод в двух производных классах, а затем я могу вызвать метод (который теперь вызывает абстрактный метод).

Почему я не мог просто сделать поле абстрактным для начала? Может ли Java быть разработана для этого?

Пол Райнерс
источник
3
Похоже, вы могли бы обойти всю эту проблему, сделав поле непостоянным и просто передав значение через конструктор, получив в результате 2 экземпляра одного класса, а не 2 класса.
Nate
5
Делая поля абстрактными в суперклассе, вы определяете, что это поле должно быть у каждого подкласса, поэтому оно ничем не отличается от неабстрактного поля.
Питер Лоури
@peter, я не уверен, что понимаю твою точку зрения. если в абстрактном классе была указана неабстрактная константа, то ее значение также остается постоянным для всех подклассов. если бы он был абстрактным, то его значение должно было бы быть реализовано / предоставлено каждым подклассом. так что это было бы совсем не то же самое.
liltitus27
1
@ liltitus27 Я думаю, что 3,5 года назад моя точка зрения заключалась в том, что наличие абстрактных полей не сильно изменится, кроме как разрушить всю идею отделения пользователя интерфейса от реализации.
Питер Лоури
Это было бы полезно , потому что это может позволить пользовательские поля аннотаций в классе ребенка
гэг

Ответы:

104

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

abstract class Base {

    final String errMsg;

    Base(String msg) {
        errMsg = msg;
    }

    abstract String doSomething();
}

class Sub extends Base {

    Sub() {
        super("Sub message");
    }

    String doSomething() {

        return errMsg + " from something";
    }
}

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

rsp
источник
У меня нет редактора, чтобы попробовать это здесь, но это было то, что я тоже собирался опубликовать ...
Тим
6
"компилятор выдаст предупреждение". Фактически, дочерний конструктор пытается использовать несуществующий конструктор noargs, и это ошибка компиляции (а не предупреждение).
Stephen C
И добавление к комментарию @Stephen C фразы «например, когда абстрактный метод не реализован» также является ошибкой, а не предупреждением.
Лоуренс Гонсалвес
4
Хороший ответ - у меня это сработало. Я знаю, что прошло много времени с тех пор, как он был опубликован, но думаю, что его Baseнужно объявить abstract.
Стив Чемберс,
2
Мне по-прежнему не нравится этот подход (хотя это единственный выход), потому что он добавляет дополнительный аргумент в конструктор. В сетевом программировании мне нравится создавать типы сообщений, в которых каждое новое сообщение реализует абстрактный тип, но должно иметь уникальный обозначенный символ, например «A» для AcceptMessage и «L» для LoginMessage. Это гарантирует, что пользовательский протокол сообщений сможет передать этот отличительный символ на провод. Они неявны для класса, поэтому лучше всего использовать их как поля, а не как что-то переданное в конструктор.
Адам Хьюз
7

Я не вижу в этом смысла. Вы можете переместить функцию в абстрактный класс и просто переопределить какое-то защищенное поле. Я не знаю, работает ли это с константами, но эффект тот же:

public abstract class Abstract {
    protected String errorMsg = "";

    public String getErrMsg() {
        return this.errorMsg;
    }
}

public class Foo extends Abstract {
    public Foo() {
       this.errorMsg = "Foo";
    }

}

public class Bar extends Abstract {
    public Bar() {
       this.errorMsg = "Bar";
    }
}

Итак, ваша точка зрения заключается в том, что вы хотите обеспечить реализацию / переопределение / что-то еще errorMsgв подклассах? Я думал, вы просто хотите иметь метод в базовом классе и тогда не знали, как обращаться с полем.

Феликс Клинг
источник
4
Ну, конечно, я мог бы это сделать. И я думал об этом. Но почему мне нужно устанавливать значение в базовом классе, если я точно знаю, что собираюсь переопределить это значение в каждом производном классе? Ваш аргумент также может быть использован, чтобы утверждать, что нет смысла также разрешать абстрактные методы.
Пол Райнерс
1
Таким образом, не каждый подкласс должен переопределять значение. Я также могу просто определить, protected String errorMsg;что каким-то образом заставляет меня устанавливать значение в подклассах. Это похоже на те абстрактные классы, которые фактически реализуют все методы в качестве заполнителей, поэтому разработчикам не нужно реализовывать каждый метод, хотя он им и не нужен.
Феликс Клинг,
7
Говорить protected String errorMsg;ничего не навязывать , что вы установите значение в подклассах.
Лоуренс Гонсалвес,
1
Если значение null, если вы не устанавливаете его, считается «принудительным», то что бы вы назвали неисполнением?
Лоуренс Гонсалвес,
9
@felix, есть разница между принудительным исполнением и контрактами . Весь этот пост сосредоточен вокруг абстрактных классов, которые, в свою очередь, представляют собой контракт, который должен выполняться любым подклассом. поэтому, когда мы говорим об абстрактном поле, мы говорим, что любой подкласс должен реализовывать значение этого поля для каждого контракта. ваш подход не гарантирует никакой ценности и не дает явного указания на то, что ожидается, как в контракте. очень и очень разные.
liltitus27
3

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

Лоуренс Гонсалвес
источник
@Laurence - мне не очевидно, что могли быть включены абстрактные поля. Нечто подобное, вероятно, окажет глубокое и фундаментальное влияние на систему типов и на удобство использования языка. (Точно так же, как множественное наследование порождает глубокие проблемы ... которые могут укусить неосторожного программиста на C ++.)
Стивен К.
0

Читая ваш заголовок, я подумал, что вы имеете в виду абстрактные элементы экземпляра; и я не видел в них особого смысла. Но абстрактные статические члены - это совсем другое дело.

Мне часто хотелось объявить в Java такой метод, как следующий:

public abstract class MyClass {

    public static abstract MyClass createInstance();

    // more stuff...

}

По сути, я хотел бы настоять на том, чтобы конкретные реализации моего родительского класса предоставляли статический фабричный метод с определенной сигнатурой. Это позволило бы мне получить ссылку на конкретный класс Class.forName()и быть уверенным, что я смогу построить его в соответствии с выбранным мной соглашением.

Дрю Уиллс
источник
1
Да ладно ... это, вероятно, означает, что вы слишком много используете отражение !!
Stephen C
Для меня это звучит как странная привычка к программированию. Class.forName (..) пахнет недостатком дизайна.
Whkeysierra
В наши дни мы в основном всегда используем Spring DI для выполнения подобных задач; но до того, как эта структура стала известной и популярной, мы указывали классы реализации в свойствах или файлах XML, а затем использовали Class.forName () для их загрузки.
Дрю Уиллс,
Я могу дать вам пример их использования. Отсутствие «абстрактных полей» почти невозможно объявить поле универсального типа в абстрактном общем классе: @autowired T fieldName. Если Tопределяется как что-то вроде T extends AbstractController, стирание типа приводит к тому, что поле создается как тип, AbstractControllerкоторый не может быть автоматически связан, потому что он не конкретный и не уникальный. Я в целом согласен, что от них мало толку, и поэтому они не принадлежат языку. С чем я бы не согласился, так это с тем, что они НЕ нужны.
DaBlick
0

Другой вариант - определить поле как общедоступное (окончательное, если хотите) в базовом классе, а затем инициализировать это поле в конструкторе базового класса, в зависимости от того, какой подкласс в настоящее время используется. Это немного сомнительно, поскольку вводит циклическую зависимость. Но, по крайней мере, это не зависимость, которая может когда-либо измениться - то есть подкласс будет существовать или не существовать, но методы или поля подкласса не могут влиять на значение field.

public abstract class Base {
  public final int field;
  public Base() {
    if (this instanceof SubClassOne) {
      field = 1;
    } else if (this instanceof SubClassTwo) {
      field = 2;
    } else {
      // assertion, thrown exception, set to -1, whatever you want to do 
      // to trigger an error
      field = -1;
    }
  }
}
ragerdl
источник