简体   繁体   中英

Java referencing final variable before is has been initialized

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:

  • only Monster cares about a max value, so only it should know about this concept
  • all Creatures must have a protection > 0
  • don't defer range checking to a separate method
  • the upper and lower bound checking doesn't need to be in the same place
  • use the Decorator Pattern to solve the problem

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.

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