javainheritance

How do I assign the derived class name as a param within the base class constructor?


Let's assume I have a base class Fruit

public class Fruit {

  String name;

  public Fruit(String name) {
    this.name = name
  }
}

And that I have class Apple() which extends Fruit() and I give that class two constructors.

public class Apple extends Fruit {
    
    // constructor one
    public Apple() {
      this("Apple");
    }

    // constructor two
    public Apple(String name) {
        super(name);
    }
}

This lets me call new Apple() as well as new Apple("Red Apple")

Rather than having to add constructor one to each derived class of Fruit, I would prefer that Fruit resemble the following:

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

And then I could remove constructor one from Apple as it would be inheritted from Fruit

Ideally, this would let me call new Apple() and automatically set Apple.name to Apple

Unfortunately, this(this.getClass().getSimpleName()); throws an error of

java cannot refer to 'this' nor 'super' while explicitly invoking a constructor

I use a similar pattern in other languages without any issues. How do I assign the derived class name as a param within the base class constructor? Is something like this even possible in Java?


Solution

  • And then I could remove constructor one from Apple as it would be inheritted from Fruit

    No, you wouldn't. That's not how java works. Trivial example:

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

    In java, constructors do not inherit. Methods inherit - if you class Y extends X where X contains some method, then Y also contains that method, automatically (in fact, you can't 'remove it', at best, you can override it). But constructors simply do not do that. Y can call them, using super syntax. That's where it ends.

    But, this might be confusing! There are 2 unrelated java language features that make it look like constructors inherit, but they don't:

    1. If your class does not have a constructor at all, then java will assume you meant to write public YourClassName() {} - as in, java will act as if you have a public no-args do-nothing constructor.

    2. All constructors are required to invoke either this or super, they can't not. If you fail to do it, the compiler assumes you intended to start your constructor off with super(). This rule applies to the previous bullet, too.

    So, given:

    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'
      }
    }
    

    But, make no mistake, this has nothing to do with Child 'inheriting' a constructor. It does not. There is no constructor defined so java plays a game of make believe and acts as if you write public Child() { super(); }.

    Your question is therefore almost entirely obviated now.

    But, for argument's sake:

    You simply can't. Not how java works. However, the solution is generally instead to work with sentinels. Fields should be private, not package private or protected. That gives us full control over it. If you want others to access the 'field', make a getter. Because you control that.

    Now that it is private, we can decree that if the string is a certain value, that means to just inherit the name. Thus:

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

    Here, everywhere name is relevant, we check the field: if it is null, we return the class name. Otherwise we return the field.