Чому не абстрактні поля?


100

Чому класи Java не можуть мати абстрактні поля, як вони можуть мати абстрактні методи?

Наприклад: У мене є два класи, які розширюють один і той самий абстрактний базовий клас. Кожен із цих двох класів має ідентичний метод, за винятком рядкової константи, яка, як трапляється, є повідомленням про помилку. Якби поля могли бути абстрактними, я міг би зробити цю константу абстрактною і перетягнути метод в базовий клас. Натомість я маю створити абстрактний метод, викликаний getErrMsg()у цьому випадку, який повертає String, замінює цей метод у двох похідних класах, і тоді я можу витягнути метод (який тепер викликає абстрактний метод).

Чому я не міг просто зробити поле абстрактним для початку? Чи могла Java бути розроблена, щоб дозволити це?


3
Схоже, ви могли обійти всю цю проблему, зробивши поле непостійним і просто подавши значення через конструктор, закінчивши 2 екземплярами одного класу, а не 2 класами.
Нейт

5
Роблячи поля абстрактними в суперкласі, ви конкретно визначили, що кожен підклас повинен мати це поле, тож воно нічим не відрізняється від абстрактного поля.
Peter Lawrey

@peter, я не впевнений, що дотримуюся вашої думки. якщо в абстрактному класі вказана не абстрактна константа, то це значення також є константою у всіх підкласах. якби воно було абстрактним, тоді його значення повинно було б бути реалізоване / подане кожним підкласом. отже, це було б зовсім не однаково.
liltitus27,

1
@ liltitus27 Я думаю, що 3,5 роки тому я говорив про те, що наявність абстрактних полів не сильно змінилося б, за винятком того, що порушило б всю ідею відокремлення користувача інтерфейсу від реалізації.
Пітер Лорі

Це було б корисно, оскільки це може дозволити власні анотації полів у дочірньому класі
geg,

Відповіді:


103

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

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";
    }
}

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


У мене немає редактора, щоб спробувати тут, але це те, що я збирався також опублікувати ..
Тім,

6
"компілятор дасть попередження". Насправді, конструктор Child намагався б використовувати неіснуючий конструктор noargs, і це помилка компіляції (а не попередження).
Stephen C

І додавання до коментаря @Stephen C, "наприклад, коли абстрактний метод не реалізований" - це також помилка, а не попередження.
Лоуренс Гонсалвес

4
Гарна відповідь - це спрацювало для мене. Я знаю, що з моменту його публікації минуло багато часу, але думаю, що це Baseпотрібно оголосити abstract.
Steve Chambers

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
Ну, я міг би це зробити, звичайно. І я про це думав. Але чому я повинен встановлювати значення в базовому класі, коли точно знаю, що збираюся перевизначити це значення в кожному похідному класі? Ваш аргумент також може бути використаний, щоб стверджувати, що дозволяти абстрактні методи також не має значення.
Paul Reiners

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 (..) пахне недоліком дизайну.
whiskeysierra

У наші дні ми в основному завжди використовуємо Spring DI, щоб виконати подібні речі; але до того, як цей фреймворк став відомим і популярним, ми визначали класи реалізації у властивостях або файлах XML, а потім використовували Class.forName () для їх завантаження.
Drew Wills

Я можу надати вам варіант їх використання. Відсутність «абстрактних полів» майже неможливо оголосити поле універсального типу в абстрактному загальному класі: @autowired T fieldName. Якщо Tвизначається як щось подібне T extends AbstractController, стирання типу призводить до того, що поле генерується як тип AbstractControllerі яке не може бути автоматично підключено, оскільки воно не є конкретним і не унікальним. Я загалом погоджуюсь, що від них мало користі, і тому вони не належать до мови. З чим я б не погодився, це те, що від них НЕМАЄ користі.
DaBlick

0

Інший варіант полягає у визначенні поля як загальнодоступного (final, якщо хочете) у базовому класі, а потім ініціалізації цього поля в конструкторі базового класу, залежно від того, який підклас використовується зараз. Це трохи тіньово, оскільки вводить кругову залежність. Але, принаймні, це не залежність, яка коли-небудь може змінитися - тобто підклас буде або існувати, або не існувати, але методи або поля підкласу не можуть впливати на значення 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;
    }
  }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.