How to force subclasses to set a variable in java?
Asked Answered
C

3

27

I have a class which defines all of the basic parameters for a given screen. From here every screen in the application is a subclass of this. I need every screen (i.e. subclass) to set the value of a variable in its implementation (namely, each screen must define what level it is in a navigation tree).

Also, ideally, this variable should be final when it is set in the sub classes (I realise this probably isn't possible).

What is the best way to go about this? Is there a way to correctly enforce this type of behaviour in Java?

Cellini answered 22/2, 2013 at 2:16 Comment(0)
C
40

@pst's comment lead to this solution.

This can't be done with a variable. But an abstract class can require that a particular method is implemented: this method could return the applicable value

From declaring an abstract function to set or return the variable, you can force any subclass to implement it correctly.

Next, the function must be called by every single subclass of the outer class. This implies that it must be done somewhere in the outer class. This can be done in the no-argument constructor of the outer class without having to worry about subclasses calling super:

Note: If a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the no-argument constructor of the superclass. If the super class does not have a no-argument constructor, you will get a compile-time error. Object does have such a constructor, so if Object is the only superclass, there is no problem. (Java docs: Super)

Based on that, this solution will hold up and correctly force the variable to be set as long as either:

  1. No other constructor is created in the superclass (hence super can't be used in a subclass to call a different constructor instead)
  2. All other constructors in the superclass still call the default constructor internally

The code:

Superclass:

public abstract class SuperClass {
    // Variable all inner classes must set
    static int myVar = 0;

    public SuperClass() {
        myVar = giveValue();
    }

    public abstract int giveValue();
}

Subclass:

public class SubClass extends SuperClass {

    @Override
    public int giveValue() {
        return 5; // individual value for the subclass
    }

}
Cellini answered 22/2, 2013 at 3:4 Comment(4)
Thanks! Saved my day !!Dithionite
Invoking an overridable method from constructor! Does it invite problem like this? help.semmle.com/wiki/display/JAVA/…Cloe
@Cloe link is not valid anymoreGlendoraglendower
@Ketan's link can be found here now: codeql.github.com/codeql-query-help/java/…Stood
L
13

Rather than enforce that child class instances initialize the fields, you could follow a strategy of composition by having your parent class constructor take a parameter implementing an interface that provides the fields you wish to have initialized.

class Screen {
  final Configuration config;
  Screen(Configuration config) {
    this.config = config;
  }
  // or
  Screen(ConfigFactory configFactory) {
    this.config = configFactory.make();
  }
}

interface ConfigFactory {
  Configuration make();
}

I would caution against requiring a subclass instance initializing the configuration, say using an abstract method implementation. The assignment in the parent class constructor occurs before the subclass instance is initialized, implicitly making proper computation of the configuration static.

If the computation isn't static, you risk null references or NullPointerExceptions by developers following you (or yourself if your memory is less than perfect). Make it easier on your collaborators (and yourself) and make the constraints explicit.

Loblolly answered 22/2, 2013 at 2:23 Comment(1)
Can you add analogous as in accepted answer example in your answer? This seems kind of obscure for me..Wherefrom
P
3

As mentioned by @Ketan in @B T's answer, invoking an overridable method from constructor is not especially a good practice (https://help.semmle.com/wiki/display/JAVA/Non-final+method+invocation+in+constructor)

One way to avoid this problem consists in having an abstract (protected) getter for the field. Hence the superclass doesn't have the field anymore, but it is still accessible in the super class using the getter. Each subclass is forced to declare the field because it must override the abstract getter.

Superclass:

public abstract class SuperClass {

  public SuperClass() {}

  protected abstract int getMyVar();

  public void functionUsingMyVar(){
     int a = 12 + getMyVar();
  }
}

Subclass1:

public class SubClass1 extends SuperClass {

  private int myVar;

  public SubClass1() {
     super();
     myVar = 1;
  }

  @Override
  protected int getMyVar(){
     return myVar;
  }

}

Subclass2:

 public class SubClass2 extends SuperClass {

  private int myVar;

  public SubClass2() {
     super();
     myVar = 1;
  }

  @Override
  protected int getMyVar(){
     return myVar;
  }

}

instead of having for the superclass (where giveValue() is overridable and called in the constructor) :

public abstract class SuperClass {

  private int myVar;

  public SuperClass() {
     myVar = giveValue();
  }

  protected abstract int giveValue();

  public void functionUsingMyVar(){
     int a = 12 + myVar;
  }
}
Palocz answered 15/11, 2019 at 13:54 Comment(1)
There are error in this code for example class is not extending SuperClass.Pedigree

© 2022 - 2024 — McMap. All rights reserved.