В Java, почему я не могу объявить окончательный член (без его инициализации) в родительском классе и установить его значение в подклассе? Как я могу обойти это?

В программе Java у меня есть несколько подклассов, наследуемых от родителя (который является абстрактным). Я хотел сказать, что каждый дочерний элемент должен иметь элемент, который устанавливается только один раз (что я планировал сделать из конструктора). Мой план состоял в том, чтобы закодировать s.th. так:

public abstract class Parent {
    protected final String birthmark;
}

public class Child extends Parent {
    public Child(String s) {
        this.birthmark = s;
    }
}

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

Итак, каков способ Java для этого? Что мне не хватает?


person Hanno Fietz    schedule 19.03.2009    source источник
comment
Не прямой ответ, но в полях конечного экземпляра формата файла класса можно задать только тот же файл класса, но не обязательно в конструкторе. IIRC.   -  person Tom Hawtin - tackline    schedule 19.03.2009
comment
Связанный вопрос: stackoverflow.com/questions/14383276/   -  person user905686    schedule 03.06.2013


Ответы (8)


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

public abstract class Parent {
    protected final String birthmark;
    protected Parent(String s) {
        birthmark = s;
    }
}

public class Child extends Parent {
    public Child(String s) {
        super(s);
        ...
    }
}
person Adam Rosenfield    schedule 19.03.2009
comment
Если бы я пропустил объявление дочернего конструктора (и в этом случае, вероятно, оставил родительский ctor общедоступным), я мог бы сделать «новый дочерний элемент (установить это)» и получить то, что я хочу, верно? - person Hanno Fietz; 19.03.2009

Передайте его родительскому конструктору:

public abstract class Parent {
    private final String birthmark;
    public Parent(String s) {
        birthmark = s;
    }
}

public class Child extends Parent {
    public Child(String s) {
        super(s);
    }
}
person McDowell    schedule 19.03.2009
comment
В моем случае я не могу пройти, так как объект требует ссылки на «это», какой-нибудь совет? - person Aquarius Power; 16.01.2016

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

person Paul Tomblin    schedule 19.03.2009
comment
вызов абстрактного метода получения в конструкторе может быть потенциально опасным, однако хорошим правилом является никогда не вызывать переопределяемый метод, прямо или косвенно, из конструктора. - person TofuBeer; 20.03.2009
comment
@TofuBeer есть пример, насколько это может быть опасно? этот ответ отлично подойдет для меня здесь! РЕДАКТИРОВАТЬ ммм - person Aquarius Power; 16.01.2016
comment
Если вы вызываете переопределенный метод в конструкторе, и этот метод обращается к переменной из дочернего класса, тогда эта переменная еще не будет инициализирована, поскольку конструктор дочернего класса еще не создан. Это опасно, потому что это может работать сейчас, но если кто-то позже изменит геттер, чтобы сделать это, он выйдет из строя или получит неправильное значение. - person TofuBeer; 16.01.2016

Я бы сделал это так:

public abstract class Parent 
{
    protected final String birthmark;

    protected Parent(final String mark)
    {
        // only if this makes sense.
        if(mark == null)
        {
            throw new IllegalArgumentException("mark cannot be null");
        }

        birthmark = mark;
    }
}

public class Child 
    extends Parent 
{
    public Child(final String s) 
    {
        super(s);
    }
}

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

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

person TofuBeer    schedule 19.03.2009
comment
Есть ли смысл помечать параметры и в объявлении функции как final? Компилятор позволил мне обойтись без него. - person Hanno Fietz; 19.03.2009
comment
Я делаю это по привычке... мешает вам сделать param = instanceVariable. Если горячая точка когда-нибудь сможет лучше оптимизировать конечные переменные (я не думаю, что сейчас), то мой код тоже будет волшебным образом быстрее :-) - person TofuBeer; 19.03.2009

Почему бы не делегировать инициализацию методу. Затем переопределите метод в родительском классе.

public class Parent {
   public final Object x = getValueOfX();
   public Object getValueOfX() {
      return y;
   }
}
public class Child {
  @Override
  public Object getValueOfX() {
     // whatever ...
  }
}

Это должно разрешить пользовательскую инициализацию.

person ng.    schedule 19.03.2009
comment
Эта идиома - действительно плохая идея... если пользователь делает getValueofX() зависимым от любого поля класса Child, будет NullPointerException, так как Child еще не создан. Это может сработать, если вы будете очень осторожны с документами, но глупые/злонамеренные пользователи могут сломать его в мгновение ока. - person Art Doler; 19.03.2009
comment
@Chamelaeon: Это такой глупый комментарий, что злонамеренный или глупый пользователь будет писать код для вашего проекта? Злоумышленник или глупый пользователь мог написать System.exit(0)? - person ng.; 20.03.2009

Да, конечные элементы должны быть назначены в классе, в котором они объявлены. Вам нужно добавить конструктор с аргументом String в Parent.

person Maurice Perry    schedule 19.03.2009

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

person Jorn    schedule 19.03.2009

Вы, вероятно, захотите иметь конструктор Parent(String bloodmark), чтобы вы могли гарантировать, что в вашем родительском классе всегда будет инициализирован final. Затем вы можете вызвать super(родимое пятно) из конструктора Child().

person Cody Casterline    schedule 19.03.2009