6

I want to call an abstract method, generateId(), in the constructor of an abstract super class, where this abstract method depends on some fields of the respective subclasses. Consider the following pieces of code for clarity:

Abstract class: SuperClass

public abstract class SuperClass {
   protected String id;

   public SuperClass() {
        generateId();
   }

   protected abstract void generateId();
}

Subclass: Sub1

public class Sub1 extends SuperClass {
   private SomeType fieldSub1;

   public Sub1(SomeType fieldSub1) {
      this.fieldSub1 = fieldSub1;
      super();
   }

   protected void generateId() {
      // Some operations that use fieldSub1
   }
}

Subclass: Sub2

public class Sub2 extends SuperClass {
   private SomeOtherType fieldSub2;

   public Sub2(SomeOtherType fieldSub2) {
      this.fieldSub2 = fieldSub2;
      super();
   }

   protected void generateId() {
      // Some operations that use fieldSub2
   }
}

However, the subclass constructors won't work because the super(); must be the first statement in a constructor.

OTOH, if I make super(); the first statement in the constructors of the subclasses, then I won't be able to call generateId() in SuperClass. Because generateId() uses fields in subclasses, where these fields must be initialized before being used.

The only way to "solve" this problem appears to me: Remove the call to generateId() in the superclass. Place a call to generateId() at the end of constructors of each subclass. But this results in code duplication.

So is there any way to solve this problem without duplicating my code? (That is, without calling generateId() at the end of constructors of each subclass?)

Utku
  • 2,025
  • 22
  • 42
  • 2
    What if you made `SomeType` and `SomeOtherType` have a common parent, then pass `ParentType` to the `SuperClass` constructor? – Arc676 Jan 05 '16 at 13:11
  • 1
    @Arc676 I couldn't visualize it. Could you post an answer containing code? – Utku Jan 05 '16 at 13:12
  • 3
    You might want to check this question : http://stackoverflow.com/questions/3404301/whats-wrong-with-overridable-method-calls-in-constructors . Simply put : don't use overridable methods in constructors. – Guillaume Darmont Jan 05 '16 at 13:15
  • @GuillaumeDarmont Then what would be a solution that doesn't duplicate code to solve this problem? – Utku Jan 05 '16 at 13:19
  • @GuillaumeDarmont I have read the answer you linked to but could not find a suggestion to how to solve this problem. It only states why calling an overridable method in the constructor of a superclass might be a bad idea. – Utku Jan 05 '16 at 13:23
  • 1
    Could you explain what your concrete problem is? I think this might be an [XY-Problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Turing85 Jan 05 '16 at 13:25
  • Your client code has to explicitly call the `generateId()` method. It's often wrapped into an `init()` method that does all the stuff we would like to do at the end of the instance creation. If you familiar with Spring, you can see that as an `afterPropertiesSet()`-like method. You can also use the Builder pattern, but it could be a little overkill for simple use cases. – Guillaume Darmont Jan 05 '16 at 13:26
  • @Turing85 The id of the `SuperClass` needs to be generated w.r.t some fields in the subclasses of the `SuperClass`. Hence the id generation is performed in an abstract method named `generateId()`. The id must be generated at the constructor. The rest of the problem is explained in the question. – Utku Jan 05 '16 at 13:29
  • 1
    @Utku why must the id be generated at initialization? Why can you not calculate the id just-in-time when the id is first used? – Turing85 Jan 05 '16 at 13:36
  • I was just about to write the same comment as @Turing85. Calculate it when needed. Make `getId` be an abstract getter. Unless you have some really time consuming process for generating the id, this would be a good example of a computed property. – Michael Welch Jan 05 '16 at 13:43
  • @Turing85 I am not sure how I can do that. I have checked [this](https://en.wikipedia.org/wiki/Lazy_initialization#Java) Wikipedia page but it appears to be something different. Could you provide an answer containing code? – Utku Jan 05 '16 at 13:44
  • @Utku your `getId` method would do basically the same thing that `generateId` does except instead of storing it in a field, it returns the id to the caller. – Michael Welch Jan 05 '16 at 13:45
  • @Utku as Miachael Welch said: write some abstract `getId()` method. This method can calculate the id on the fly or check, whether it has been created before and calculated, if needed (or return the previously calculated id). – Turing85 Jan 05 '16 at 13:46
  • @Turing85 (AT)MichaelWelch Yes, this sounds like the best solution up to now. Probably I will do it that way. Thanks. – Utku Jan 05 '16 at 13:50

3 Answers3

6

As @GuillaumeDarmont pointed out, using overridable methods in constructs is bad practice.

You want to oblige the superclass id to be initialized by subclasses, so change the constructor:

public abstract class SuperClass {
    protected String id;

    public SuperClass(String id) {
        this.id = id;
    }
}

Also, you might want to change generateId() to be a static method, since you cannot reference this before superclass constructor has been called:

public class Sub1 extends SuperClass {
    private SomeType fieldSub1;

    public Sub1(SomeType fieldSub1) {
        super(generateId(fieldSub1));
        this.fieldSub1 = fieldSub1;
    }

    private static String generateId(SomeType fieldSub1) {
        // Some operations that use fieldSub1
    }
}

EDIT: As SuperClass does not know how to calculate the id, but you want to enforce it has an id, one of your options is the solution above. Another option would be:

public abstract class SuperClass {
    private String id;

    public String getId() {
        if (id == null) { id = generateId(); }
        return id;
    }
    protected abstract String generateId();
}


public class Sub1 extends SuperClass {
    private SomeType fieldSub1;

    public Sub1(SomeType fieldSub1) {
        this.fieldSub1 = fieldSub1;
    }

    @Override protected String generateId() {
        // Some operations that use fieldSub1
    }
}

The difference between both solutions is about when the id will be calculated: at the object initialization time, or the first time the id is requested. That is what @Turing85 was discussing.

ericbn
  • 10,163
  • 3
  • 47
  • 55
  • Are you sure that java accepts the last construct that you posted? – plalx Jan 05 '16 at 13:44
  • @ericbn This is a solution but how semantically correct is this? That is: The parameter given to the constructor of the `SuperClass` is actually not an input, but it is something that can be calculated with the available data at hand. So isn't writing it this way makes it look like it is something that needs to be obtained as an input? – Utku Jan 05 '16 at 15:19
0

You can try to use instance or static initialization blocks.

Order of initialization:

  1. All static items in their source code order.
  2. All member variables.
  3. The constructor is called.

For example:

class InitBlocksDemo {

    private String name ;

    InitBlocksDemo(int x) {
        System.out.println("In 1 argument constructor, name = " + this.name);
    }

    InitBlocksDemo() {
        name = "prasad";
        System.out.println("In no argument constructor, name = " + this.name);

    }

    /* First static initialization block */
    static {
        System.out.println("In first static init block ");
    }

    /* First instance initialization block  */
    {
        System.out.println("In first instance init block, name = " + this.name);
    }

    /* Second instance initialization block */
    {
        System.out.println("In second instance init block, name = " + this.name);
    }

    /* Second static initialization block  */
    static {
        System.out.println("In second static int block ");
    }

    public static void main(String args[]) {
        new InitBlocksDemo();
        new InitBlocksDemo();
        new InitBlocksDemo(7);
    }

}

Output:

In first static init block
In second static int block
In first instance init block, name = null
In second instance init block, name = null
In no argument constructor, name = prasad
In first instance init block, name = null
In second instance init block, name = null
In no argument constructor, name = prasad
In first instance init block, name = null
In second instance init block, name = null
In 1 argument constructor, name = null

Source of examples: link.

solomkinmv
  • 1,804
  • 3
  • 19
  • 30
0

You could make SuperClass have a ParentType variable and then cast the type in the subclasses. Sorry about what I said in the comments, the terminology was a bit off. You can't change the type of a superclass's variables in the subclass. But you can cast.

I just realized that you can't pass a super class and cast it to a subclass. You can only go the other way, as "all dogs are animals but not all animals are dogs".

Anyway, I thought of another possible solution. First the variables:

public class ParentType { //stuff }

public class SomeType extends ParentType { //more stuff }

public class SomeOtherType extends ParentType { //even more stuff }

You could use an idea from this post. BTW I'm kind of venturing into the unknown here, I haven't done this before. If someone spots a mistake with this, please comment or edit.

Make your class generic:

public abstract class SuperClass<Type extends ParentType> {
    protected Type field;
}

public class Sub1<SomeType> {
    //now field is of type SomeType
}

public class Sub2<SomeOtherType> {
    //now field is of type SomeOtherType
}
Community
  • 1
  • 1
Arc676
  • 4,445
  • 3
  • 28
  • 44
  • Thanks for the contribution but AFAIK, _in oop semantics_ this solution is not correct right? That is: `SuperClass` is not actually supposed to be a generic class. – Utku Jan 05 '16 at 13:37
  • I'm not particularly sure. As I said, I've never tried this before so it's all theoretical. I figured this couldn't be impossible, and my Google search gave me that link with this solution, so I thought it might be applicable. If I have time I might get around to testing this, but there's always the chance that `SuperClass` has some features that forbid it from being `abstract`. – Arc676 Jan 05 '16 at 13:40