Reputation: 35
Summary: Is it possible to get constructors with different arguments to create objects of different subclasses in one method?
I have a (non-abstract) class with a subclass extending it. The subclass has one additional field. Both are immutable:
class Superclass{
final Type type; // Type is an enum; It has a final double decayFactor for each type.
final int amount;
Superclass(Type type, int amount){//simple constructor initializing final fields
...
}
//decay method talked about below is implemented here
}
class Subclass extends Superclass{
final boolean additionalProperty;
Subclass(Type type, int amount, boolean additionalProperty){
super(type, amount);
this.additionalProperty = additionalProperty;
}
}
Now I would like to have a method which returns an Object of the same class (Subclass
or Superclass
) with an updated amount.
This method should decrease amount
by a certain percentage defined by the enum Type
.
This simple method could look like that(implemted in Superclass
):
public Superclass decay(){
return new Superclass(type, amount * type.getDecayFactor())
}
Now my problem is that if I have a Subclass
and call this method would return a Superclass
in one method. I would lose the information stored in additionalProperty
.
So I thought about other ways to solve this. The most simple would be to overwrite decay()
in Subclass
. However, I found that I cannot force a subclass to override this method. So if later other subclasses are added, the same information loss can happen if they don't implement it.
So I tried to solve this by using generics to get the constructor of the class:
public <T extends Superclass> Superclass decay() throws ...[Many exceptions]{
int newAmount = type.getDecayFactor() * amount;
Constructor<? extends Superclass> constructor = this.getClass().getConstructor(Type.class, int.class);
return constructor.newInstance(type, amount);
}
I tried, but failed to use the generic T
instead of this.getClass()
.
However, this also does not work. I still would have to overwrite it as Subclass(Type, int) does not exist.
My thinking is that if I could somehow get all the fields of the class, I could call the (only) constructor of subclasses OR superclass and pass all the values it needs, only modifing amount. I tried to get a matching Constructor by using this.getClass().getFields()
, but it says: ... getConstructor() ... is not applicable for argument Fields[].(I would need the type of fields?)
My question: Is this possible at all? And is this an elegant or favoured implementation? Or should you better state that subclasses have to override decay(), which imho is more simple and readable? Are generics needed here at all?(I slowly begin to think not...)
Additional info: I first had both Superclass and Subclass as abstract and for each Type
enum an own subclass extending those(Superclass
or Subclass
). But then I would have to write very similiar code for each of those classes. So I put the information those contained into an enum with final fields.(Could easily have been 100 classes representing similiar things)
The decay()-method is meant to be used like this to "modify" the immutable Superclass
/Subclass
:
Superclass someObject = newSuperclass/Subclass
...
someObject = someObject.decay();
Thank you very much for your answers.
Upvotes: 2
Views: 288
Reputation: 1957
I believe that overriding the method should work just fine. You can change the return type of decay()
long as it is a sub-type of Superclass
.
You can also isolate calculating decay logic in a separate method to avoid redudant code.
class Superclass {
final double rate;
final int amount;
Superclass(double rate, int amount) {
this.rate = rate;
this.amount = amount;
}
public Superclass decay() {
return new Superclass(this.rate, this.calcuateDecay());
}
protected int calcuateDecay() {
return (int) (this.amount * this.rate);
}
}
class Subclass extends Superclass {
final boolean additionalProperty;
Subclass(double rate, int amount, boolean additionalProperty) {
super(rate, amount);
this.additionalProperty = additionalProperty;
}
@Override
public Subclass decay() {
return new Subclass(this.rate, this.calcuateDecay(), this.additionalProperty);
}
}
See it working here.
Overriding here is optional. To further reinforce type safety, generic interface can be defined as follows
interface <T extends Superclass> Decayable<T> {
T decay();
}
Superclass
can then implement Decayable<Superclass>
and Subclass
can implement Decayable<Subclass>
. See it in action here.
Upvotes: 1