Как назначить имя производного класса в качестве параметра в конструкторе базового класса?

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

Я бы просто this.name = this.getClass().getSimpleName(); но это не решило бы. И тогда я мог бы удалить первый конструктор. Вы все равно не сможете вызвать new Apple() (если бы это было ваше намерение). Конструктор без параметров не предоставляется, если вы явно указали какой-либо другой конструктор.

Sotirios Delimanolis 05.09.2024 01:58
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
1
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы всегда можете реализовать метод 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, которые делают его похожим на наследование конструкторов, но это не так:

  1. Если у вашего класса вообще нет конструктора, то Java предположит, что вы хотели написать public YourClassName() {} - например, Java будет действовать так, как если бы у вас был общедоступный конструктор без аргументов, который ничего не делает.

  2. Все конструкторы обязаны вызывать либо 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");

Mark Rotteveel 05.09.2024 12:09

Другие вопросы по теме