В 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-способ для этого? Что мне не хватает?

Ответ 1

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

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);
        ...
    }
}

Ответ 2

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

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

Ответ 3

Другой способ Java-ish сделать это, вероятно, состоит в том, чтобы родительский класс определял абстрактный "getter", и чтобы дети его реализовали. Это не отличный способ сделать это в этом случае, но в некоторых случаях это может быть именно то, что вы хотите.

Ответ 4

Я бы сделал это следующим образом:

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, чтобы показать, что вы также получаете возможность проверять аргументы в одном месте, а не каждый cosntructor.

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

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