简体   繁体   中英

Java, how to stop inheriting classes from calling a particular abstract class constructor, apart from one of them?

I have an abstract class and several concrete classes which extend it.

The abstract class has two constructors. I want one of the constructors to only be callable in one particular concrete class.

(I do know about the enum pattern for Java state machines, but two levels of subclassing (and immutable POJOs) work better for the problem I'm solving.)

public abstract class SuperState {
    public final long mValue;
    protected SuperState(long value) { mValue = value; }
    protected SuperState(SuperState last) { mValue = last.mValue + 1; } 
    ...
}

public class FirstState extends SuperState {
    public FirstState() { super(0); }
    ...
}

public class SecondState extends SuperState {
    public SecondState(SuperState last) { super(last); }
    ...
}

public class ThirdState extends SuperState {
    public ThirdState(SuperState last) { super(last); }
    ...
}

I want to make it a compile-time (or at least runtime) error for any subclass (apart from FirstState ) to call the SuperState(long value) constructor.

Could I find out the type of the concrete class being constructed in the SuperState constructor, and throw a runtime exception if it's not as expected?

Is there a was of having a "preferred" concrete class for an abstract class, such that it has some form of extra access?

I think you don't know clearly what you're doing. You're saying that SuperState has a constructor which is only callable from one specific implementation. Why? Is that subclass special? Why shouldn't other implementation call that?

If FirstState is so special, maybe you want to have it as an internal class:

public abstract class SuperState {
  public final long mValue;
  private SuperState(long value) { mValue = value; }
  protected SuperState(SuperState last) { mValue = last.mValue + 1; } 
  ...
  public static class FirstState {
    //Can call SuperState(long) from here
  }
}

If this doesn't seem appropriate to you, then probably you should leave both constructors open. If, as it seems to me, you're making a chain-like structure, then you probably don't even want to have FirstState as an accessible class:

public abstract class SuperState {
  public final long mValue;
  private SuperState(long value) { mValue = value; }
  protected SuperState(SuperState last) { mValue = last.mValue + 1; } 
  ...
  private static class FirstState extends SuperState {
    private FirstState() { super(0); }
  }
  public static SuperState getFirstState() { return new FirstState(); }
}

One way I can think of, though I find it ugly :

protected SuperState(long value) 
{
    if (!this.getClass().getName().equals("SomeConcreteClassName"))
        throw new SomeException ();
    mValue = value; 
}

Though Tom's comment about putting the FirstState class in the same package as SuperState and using package private access sounds better.

I think the only way to achieve it is to use analog of "friend". The trick is to have private Value class in the FirstState which is possible to construct only by FirstState . Other classes can see the FirstState.Value class, but cannot instantiate it.

abstract class SuperState {
    public final long mValue;
    protected SuperState(FirstState.Value value) { mValue = value.value; }
    protected SuperState(SuperState last) { mValue = last.mValue + 1; } 
}

class FirstState extends SuperState {
    public static class Value { private Value() {} }
    private static Value value = new Value();

    public FirstState() { super(value); }
}

class SecondState extends SuperState {
    public SecondState(SuperState last) { super(last); }
}

You can do it with default access modifier like this:

package a;

public abstract class SuperState {
    public final long mValue;
    SuperState(long value) { mValue = value; } // constructor has default access modifier
    protected SuperState(SuperState last) { mValue = last.mValue + 1; } 
    ...
}



package a;

public class FirstState extends SuperState {
    public FirstState() { super(0); }
    ...
}



package b;
// is not able to access constructor SuperState(long) ie. calling contructor
// SuperState(long) will result in compile time error
public class SecondState extends SuperState {
    public SecondState(SuperState last) { super(last); }
    ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM