4

I'm new to Java. I've had following classes:

public abstract class Beverage {
    String description = "Unknown beverage";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

and:

public class DarkRoast extends Beverage {
    String description = "Dark roast";

    @Override
    public double cost() {
        return 0.99;
    }
}

When I construct a new DarkRoast object:

Beverage beverage2 = new DarkRoast();

I expect it to have desctiption equal to "Dark roast":

assertEquals("Dark roast", beverage2.getDescription());

But actually it's "Unknown beverage". I know I should implement DarkRoast constructor that sets description, but I don't know why, I don't know how it works internally. Shouldn't the subclass field overwrite superclass field?

ducin
  • 25,621
  • 41
  • 157
  • 256
  • 1
    Look into field hiding. Fields are not polymorphic. – Sotirios Delimanolis Mar 02 '14 at 18:25
  • 4
    You're created a *new and different* `description` in your subclass; this shadows the one in the parent. – Brian Roach Mar 02 '14 at 18:26
  • You should make your `description` field `final` and create a constructor in this abstract class which defined it; then in subclasses, create a no-arg constructor which calls `super(description)`. – fge Mar 02 '14 at 18:32
  • Also, see my answer as to why this happens, and how to circumvent the "problem" (which is really a design problem to start with...) – fge Mar 02 '14 at 18:41

6 Answers6

4

Shouldn't the subclass field overwrite superclass field?

No - the subclass field is actually an entirely new field. Since you mean to assign to the superclass field, you need to create a constructor like this:

public DarkRoast()
{
    description = "Dark roast";
}

(This is unlike the method cost() - technically when you override a non-abstract method you still 'sort of' have the old method as well)

OSborn
  • 895
  • 4
  • 10
  • Does it mean that DarkRoast object has references to both description Strings - the superclas one and actual class one? Sounds strange... isn't it only in Java? – ducin Mar 02 '14 at 18:31
  • 1
    @tkoomzaaskz No. `DarkRoast` can only see the `description` declared in `DarkRoast` because it shadows the field declared in the superclass. It (`DarkRoast`) *does* have a reference to the superclass, of course, through which it could access the one from there ... but generally that's not something you'd do. – Brian Roach Mar 02 '14 at 18:33
  • Yes, although access isn't straightforward. This is generally how object oriented languages work - subclasses get whatever attributes and operations they define, plus their superclasses. C# and C++ do roughly the same thing. – OSborn Mar 02 '14 at 18:33
  • @BrianRoach I didn't get clear answer. Does DarkRoast have (direct or indirect) references to both description fields? As far as I can understand you, I think yes, but I'm not sure. – ducin Mar 02 '14 at 18:40
  • 1
    It has both fields, but DarkRoast isn't able to directly access the superclass field. – OSborn Mar 02 '14 at 18:43
  • @tkoomzaaskz It has an indirect reference via the direct reference to the superclass; `super.description` – Brian Roach Mar 02 '14 at 18:44
0

Because the called getDescription method is the method of the class Beverage (DarkRoast doesn't have its own), so the method getDescription access to his description property, whose value is "Unknown beverage".

Ashot Karakhanyan
  • 2,804
  • 3
  • 23
  • 28
0

You do not override the .getDescription() method in subclasses; as a result, the method called is the one in Beverage, and the method in Beverage only knows about the description declared (and initialized, in this case) in Beverage itself.

You could have it display the value in all subclasses by using instead:

return this.description;

However, there is a much better way to solve this problem; and that is:

public abstract class Beverage
{
    private final String description;

    public abstract Beverage(final String description)
    {
        this.description = description;
    }

    public final String getDescription()
    {
        return description;
    }
    // etc
}

// Subclass
public class DarkRoast
    extends Beverage
{
    public DarkRoast()
    {
        super("Dark Roast");
    }
}

You should note that it makes no sense at all to describe an "Unknown beverage" to start with. Beverage being abstract, it cannot be instantiated! The solution proposed here basically forbids anyone from creating a Beverage without a description, so it's a win-win ultimately.

fge
  • 119,121
  • 33
  • 254
  • 329
  • The thing I still can't understand (despite reading all answers here) is why is Java inheritance designed such way. It seems logical to me that a subclass overwrites a field, if it's named the same. What is the reason for creating two separate description objects? – ducin Mar 02 '14 at 19:01
  • Your subclasses do not override the `.getDescription()` method. And since, in this method, you say to return `description`, for lack of a better qualifier for this field, it is `Beverage`'s `description` which will be taken into account. If, on the other hand, you specify `this.description`, then the JVM will resolve `description` from the _class of the instance object_ down to the lower level. You had better avoid that in the first place, and my solution is one sure way to avoid that ;) – fge Mar 02 '14 at 19:10
  • Sorry, I misread your question. "What is the reason for creating two separate description objects?" <-- no idea; I suppose it has its uses, though I cannot think of any use where this doesn't lead to confusion... Which is why I prefer to avoid such conflicts in the first place by using the "pattern" above. – fge Mar 02 '14 at 19:13
0

If the subclass must always provide a description, then you could make getDescription abstract, which would force the subclasses to implement and return a value. Just another technique.

Mark Giaconia
  • 3,844
  • 5
  • 20
  • 42
0

It is allowed for class to hide the members. When you declare a member for field in class and declare in the child class some with the same name you will hide it but not override.

If you want to provide unique name for each class that implement your abstract class you should declare an abstract method

public abstract String getDescription();

Or you can pass the value to the member.

 BaseClass(String description) {
   this.description = description;
 }

then in child class you can use the super to access the constructor

 ChildClass(String description) {
   super(description);
 }

The key word super allows you to access directly the members of parent class.

So in case of your class you could be able to do something like this

  private void test() {
     System.out.println(super.description); //This will refer to parent class
     System.out.println(this.description);  //This will refer to child class
  }

So when you declare a field you create always a new reference point.

0

The problem is there are 2 attributes called description, one in the super class 'Beverage' and one in the subclass 'DarkRoast', and since you are using Beverage as the the reference type you get the description attribute/instance variable of the reference class and that of the subclass is shadowed(not accessible at least to my humble knowledge in Java).

You don't need to define the attribute again in the subclass because it's already there, so basically if you remove the attribute description from the DarkRoast class you will find that it will print unknown description as well, because it is already there.

To make things more clear try this code to see how there are 2 different description instance variables.

public class DarkRoast extends Beverage {
    String description = "Dark roast";

    public String getDescription() {
        return description;
    }

    public String getSuperDescription() {
        return super.description;
    }

    @Override
    public double cost() {
        return 0.99;
    }
    public static void main(String[] args) {
    DarkRoast b = new DarkRoast();
    System.out.println(b.getDescription());
    System.out.println(b.getSuperDescription());
}
}

To see the shadowing try to do the following:

public static void main(String[] args) {
 Beverage b = new DarkRoast();
     DarkRoast b2 = new DarkRoast();
 System.out.println(b.description);
 System.out.println(b2.description);
}
Thresh
  • 470
  • 6
  • 18