I have this superclass Creature
and its subclass Monster
. Now I have this problem of a final variable being referenced without it being initialized.
public class Creature {
private int protection;
public Creature(int protection) {
setProtection(protection);
}
public void setProtection(int p) {
if(!canHaveAsProtection(p))
throw new Exception();
this.protection = p;
}
public boolean canHaveAsProtection(int p) {
return p>0;
}
}
and the subclass:
public class Monster extends Creature {
private final int maxProtection;
public Monster(int protection) {
super(protection);
this.maxProtection = protection;
}
@Override
public boolean canHaveAsProtection(int p) {
return p>0 && p<maxProtection
}
}
As you can see, when I initialize a new Monster
, it will call the constructor of Creature
with super(protection)
. In the constructor of Creature
, the method canHaveAsProtection(p)
is called, which by dynamic binding takes the overwritten one in Monster
. However, this overwritten version uses the final variable maxProtection
which hasn't been initialized yet... How can I solve this?
Some points:
Putting this all together, your code should look like this:
public class Creature {
private int protection;
protected Creature() {
}
public Creature(int protection) {
setProtection(protection);
}
public void setProtection(int p) {
if (p < 0)
throw new IllegalArgumentException();
this.protection = p;
}
}
public class Monster extends Creature {
private final int maxProtection;
private Monster(int protection) {
this.maxProtection = protection;
setProtection(protection);
}
@Override
public void setProtection(int p) {
if (protection > maxProtection)
throw new IllegalArgumentException();
super.setProtection(p);;
}
public static Monster create(int protection) {
Monster monster = new Monster(protection);
monster.validate();
return monster;
}
}
You haven't shown what the validate()
method dies, but if it's only needed for protection checking, I would delete it and the static factory method and make the constructor of Monster public.
Monster
does not extend Creature
in the code you posted.
If it does, I see no reason for Monster
to have a final variable. Creature
ought to have the final variable, and Monster
should simply access it. If there needs to be a max value validation for protection, it seems to me that all Creature
instances ought to have it.
Push it up from the Monster
to Creature
and you're fine. You should have two arguments in the Creature
constructor: protection
and maxProtection
. Throw an IllegalArgumentException
if maxProtection < 0
or protection
falls outside the range 0..maxProtection
.
It's to do with your initialisation chain.
The child class must be fully I initialised before the parent can be initialised.
Currently, it looks like this...
Monster->Creature->Creature#setProtection->Monster#canHaveAsProtection ...
where maxProtextion hasn't been initialised, because the Creature constrcutor hasn't returned.
A solution would be to defer the initialisation some how, perhaps in a init method that the constructors can call
It is really a bad practice to call public methods in constructor, especially when you expect to subclass it.
Your class should be solid and initialize it's state independently. I think you should set protection
variable explicitly in constructor:
public Creature(int protection) {
this.protection = protection;
}
If you really want to validate your parameters in constructor then extract common functionality to private methods:
public Creature(int protection) {
Assert.isTrue(isProtectionValid(p));
this.protection = protection;
}
private static boolean isProtectionValid(int p) {
return p > 0;
}
public boolean canHaveAsProtection(int p) {
return isProtectionValid(p);
}
For the reasons mentioned in the other answers, do:
public Creature(int protection) {
this.protection = protection;
}
public Monster(int protection) {
super(protection + 1);
this.maxProtection = protection;
}
That correction + 1
seems to be your intended logic because of the < maxProtection
.
How about you override the set protection en set the max first en than call on super set protection.
@override
public void setProtection(int p) {
this.maxProtection = p;
super.setProtection(p);
}
You shouldn't call public methods in a constructor. Instead you could change constructor modifier to protected/private and use factory methods in which validation is called:
public class Creature {
private int protection;
protected Creature(int protection) {
this.protection = protection;
}
public void setProtection(int protection) {
if (!canHaveAsProtection(protection))
throw new IllegalArgumentException();
this.protection = protection;
}
public boolean canHaveAsProtection(int protection) {
return protection > 0;
}
protected void validate() {
if (!canHaveAsProtection(this.protection))
throw new IllegalArgumentException();
}
public static Creature create(int protection) {
Creature creature = new Creature(protection);
creature.validate();
return creature;
}
}
And Monster
:
public class Monster extends Creature {
private final int maxProtection;
private Monster(int protection) {
super(protection);
this.maxProtection = protection;
}
@Override
public boolean canHaveAsProtection(int p) {
// I changed '<' to '<=' because '<' wouldn't work anyway
return p > 0 && p <= maxProtection;
}
public static Monster create(int protection) {
Monster monster = new Monster(protection);
monster.validate();
return monster;
}
}
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.