Предположим, у меня есть базовый класс Fruit
public class Fruit {
String name;
public Fruit(String name) {
this.name = name
}
}
И что у меня есть класс Apple()
, который расширяется Fruit()
, и я даю этому классу два конструктора.
public class Apple extends Fruit {
// constructor one
public Apple() {
this("Apple");
}
// constructor two
public Apple(String name) {
super(name);
}
}
Это позволяет мне звонить как new Apple()
, так и new Apple("Red Apple")
Вместо того, чтобы добавлять constructor one
к каждому производному классу Fruit
, я бы предпочел, чтобы Fruit
выглядел следующим образом:
public class Fruit {
String name;
// Automatically assign the classname from the derived class
public Fruit() {
this(this.getClass().getSimpleName());
}
public Fruit(String name) {
this.name = name
}
}
И тогда я мог бы удалить constructor one
из Apple
, так как он был бы унаследован от Fruit
В идеале это позволило бы мне вызвать new Apple()
и автоматически установить Apple.name
на Apple
К сожалению, this(this.getClass().getSimpleName());
выдает ошибку
Java не может ссылаться на «это» или «супер» при явном вызове конструктора
Я использую аналогичный шаблон на других языках без каких-либо проблем. Как назначить имя производного класса в качестве параметра в конструкторе базового класса? Возможно ли что-то подобное в Java?
Вы всегда можете реализовать метод getName()
так, как вам нравится. (И убедитесь в своем коде, что никто не обращается к переменной-члену name
напрямую). Но в любом случае, если вы хотите, чтобы конструктор new Apple()
был доступен рядом с new Apple(String name)
, вам нужно реализовать оба, а также Fruit()
и Fruit(String)
.
Но, конечно, есть и другие, более простые способы:
public class Constructoriz0r {
public static void main(final String... args) {
{
final Apple a = new Apple();
System.out.println("a1 name: " + a.getName());
}
{
final Apple a = new Apple().setName("iThink");
System.out.println("a2 name: " + a.getName());
}
{
final Banana b = new Banana();
System.out.println("b1 name: " + b.getName());
}
{
final Banana b = new Banana().setName("YouDo");
System.out.println("b2 name: " + b.getName());
}
}
static public class Fruit<T> {
private String mName = null;
public T setName(final String pName) {
mName = pName;
@SuppressWarnings("unchecked") final T ret = (T) this;
return ret;
}
public String getName() {
if (mName == null) mName = getClass().getSimpleName();
return mName;
}
}
static public class Apple extends Fruit<Apple> { /* apple stuff */ }
static public class Banana extends Fruit<Banana> { /* banana stuff */ }
}
И тогда я мог бы удалить первый конструктор из Apple, поскольку он будет унаследован от Fruit.
Нет, ты бы не стал. Java работает не так. Тривиальный пример:
class Parent {
private final String name;
public Parent() {
this("Jane");
}
public Parent(String name) {
this.name = name;
}
}
class Example extends Parent {
public Example(String name) {
super(name);
}
static void test() {
new Example(); // compiler error
}
}
В Java конструкторы не наследуются. Методы наследуются — если вы class Y extends X
где X содержит какой-то метод, то Y также автоматически содержит этот метод (фактически, вы не можете «удалить его», в лучшем случае вы можете его переопределить). Но конструкторы просто этого не делают. Y может вызвать их, используя синтаксис super
. На этом все и заканчивается.
Но это может сбить с толку! Есть две несвязанные функции языка Java, которые делают его похожим на наследование конструкторов, но это не так:
Если у вашего класса вообще нет конструктора, то Java предположит, что вы хотели написать public YourClassName() {}
- например, Java будет действовать так, как если бы у вас был общедоступный конструктор без аргументов, который ничего не делает.
Все конструкторы обязаны вызывать либо this
, либо super
, они не могут этого не делать. Если вы этого не сделаете, компилятор предположит, что вы намеревались начать свой конструктор с super()
. Это правило применимо и к предыдущему пункту.
Итак, учитывая:
class Example {
Example() {
System.out.println("In example constructor");
}
}
class Child extends Example {
}
class Main {
public static void main(String[] args) {
new Child(); // prints 'In example constructor'
}
}
Но не заблуждайтесь, это не имеет ничего общего с тем, что Child «наследует» конструктор. Это не так. Конструктор не определен, поэтому Java играет в игру воображения и действует так, как будто вы пишете public Child() { super(); }
.
Таким образом, ваш вопрос теперь почти полностью снят.
Но, в качестве аргумента:
Вы просто не можете. Не так, как работает Java. Однако обычно вместо этого приходится работать с дозорными. Поля должны быть private
, а не приватными или защищенными. Это дает нам полный контроль над ним. Если вы хотите, чтобы другие имели доступ к «полю», создайте геттер. Потому что ты это контролируешь.
Теперь, когда оно является частным, мы можем постановить, что если строка имеет определенное значение, это означает просто наследовать имя. Таким образом:
public class Fruit {
private final String name;
public Fruit() {
this.name = null;
}
public Fruit(String name) {
if (name == null) throw new NullPointerException("name");
this.name = name;
}
public String getName() {
return this.name != null ? name : this.getClass().getName();
}
}
Здесь везде, где важно имя, мы проверяем поле: если оно равно нулю, мы возвращаем имя класса. В противном случае мы возвращаем поле.
Если в Fruit(String)
можно заменить на this.name = Objects.requireNonNull(name, "name");
Я бы просто
this.name = this.getClass().getSimpleName();
но это не решило бы. И тогда я мог бы удалить первый конструктор. Вы все равно не сможете вызватьnew Apple()
(если бы это было ваше намерение). Конструктор без параметров не предоставляется, если вы явно указали какой-либо другой конструктор.