[FIXED] How can I access subclass attribute through passing its superclass as parameter of a method

Issue

I am having trouble with a method that accepts two classes Pokemon as its parameters because it could be whatever pokemon battling, but if I try to catch the name of the subclass, such as Totodile, the superclass’s name attribute is printed

in Pokemon.java:

abstract  public class Pokemon {
        private String name="POKEMON";
   
    
        public String getName() {
            return name;
        }
        
    }

in Totodile.java :

public class Totodile extends Pokemon {
    String name = "Totodile";
}

in Geodude.java :

public class Totodile extends Pokemon {
    String name = "Geodude";
}

in Battle.java :

public class Battle {
   public void initiateBattle(Pokemon pokemon1,Pokemon pokemon2){
        System.out.println(pokemon1.getName()+ " is battling against " + pokemon2.getName());  
    }

}

in App.java:

public class App {

  public static void main(String[] args) throws Exception {
    Geodude geodude = new Geodude();
    Totodile totodile = new Totodile();
    Battle battle = new Battle();
    battle.initiateBattle(totodile, geodude);
  }
}

The output is "POKEMON is battling against POKEMON"

, but how could I get "Totodile is battling against Geodude"

Solution

You can’t "override" a field. When you write:

class Foo {
  String name;
}

you are always declaring a new field. If your superclass also has a field with the same name, okay. Now your Totodile class has 2 fields, both named name, one of which has value POKEMON, the other has value Totodile.

Which one do you get when you write myPokemon.name? Depends on the compile-time type of myPokemon (the variable, not the object it is pointing at!) – and given that it is Pokemon in your code, you get the Pokemon version of the name field.

This notion that you have 2 fields with identical names is called ‘shadowing’.

Shadowing is a mistake; do not do it unless you really, really know what you are doing.

The solution is therefore quite simple: Don’t define a field named name in the Totodile class. Instead, set the value of the name field (which you inherited, so the Totodile class has that name field already, no need to make a second field with the same name and confuse things):

class Totodile {
  { name = "Totodile"; }
}

This somewhat weird syntax creates an instance initializer, as you need to stuff your code somewhere (you can’t just start putting statements straight into a class, you need to brace em up). This is a tad odd, the more usual design is instead something like this:

abstract class Pokemon {
  private final String name;

  public Pokemon(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }
}

class Totodile extends Pokemon {
  public Totodile() {
    super("Totodile");
  }
}

This is what most java programmers do:

  • It forces subclasses of Pokemon to actually set the name properly. That default ‘POKEMON’ value is weird and no longer needed in the above strategy.
  • It avoids exotic code constructs to make it work. Constructors and abstract classes tend to be a lot more familiar to your average java coder than instance initializers.
  • Adds final in the appropriate places.

Answered By – rzwitserloot

Answer Checked By – Candace Johnson (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published