tags:

views:

471

answers:

6
+14  Q: 

Java Covariants

public class CovariantTest {
    public A getObj() {
        return new A();
    }

    public static void main(String[] args) {
        CovariantTest c = new SubCovariantTest();
        System.out.println(c.getObj().x);
    }
}

class SubCovariantTest extends CovariantTest {
    public B getObj() {
        return new B();
    }
}

class A {
    int x = 5;
}

class B extends A {
    int x = 6;
}

The above code prints 5 when compiled and run. It uses the covariant return for the over-ridden method.

Why does it prints 5 instead of 6, as it executes the over ridden method getObj in class SubCovariantTest.

Can some one throw some light on this. Thanks.

+9  A: 

Java doesn't override fields (aka. attributes or member variables). Instead they shadow over each other. If you run the program through the debugger, you'll find two x variables in any object that is of type B.

Here is an explanation on what is happening. The program first retrieves something that is implicitly of type A and then call for the x that is assumed to come from A. Even though it is clearly a subtype, in your example an object of type B is created through SubCovariantTest, it still assumes you return something in getObj() that is implicitly typed A. Since Java cannot override fields, the test will call A.x and not B.x.

CovariantTest c = new SubCovariantTest();
// c is assumed the type of CovariantTest as it is
// implicitly declared

System.out.println(c.getObj().x);
// In this method chain the following happens:

// c.getObj() will return object of type B
// BUT will assume it is an A

// c.getObj().x will return the x from A
// since in this context the compiler assumes 
// it is an A and make the call to A.x

It seems like a mindboggling gotcha because methods are always overridden in Java (in comparison to C++ and C# in which they are not). You usually won't run into this problem because Java code convention tells you to never access fields directly. Instead make sure that fields are always accessed through accessor methods, i.e. getters:

class A {
    private int x = 5;

    public int getX() { // <-- This is a typical accessor method
        return x;
    }
}

class B extends A {
    private int x = 6;

    @override
    public int getX() {
        // will be called instead even though B is implied to be A
        // @override is optional because methods in Java are always virtual
        // thus are always overridden
        return x;
    }
}

Code to get this working is the following:

c.getObj().getX();
// Will now call getX() in B and return the x that is defined in B's context.
Spoike
+8  A: 

Replace your A and B above with:

class A {
    public int getX() { return 5; }
}

class B extends A {
    public int getX() { return 6; }
 }

That will probably answer your question about what is wrong ;-)

billybob
+9  A: 

There are two fields named x in the object, one from class A and one from class B, which hides the one in A. The field x referred to is the one in A, because of the declaration of c.

In practice this is not a problem, because it is very bad style to

  • hide a field in a subclass,

  • access a field directly instead of via a method.

starblue
+1  A: 

I was looking at your code and was having some trouble compiling it. Getting error

The return type is incompatible with CovariantTest.getObj()

I made a small change.

class A {
    int x = 5;
}

class B extends A {
    int x = 6;
 }

public class CovariantTest {

    public A getObj() {
        return new A();
    }

    public static void main(String[] args) {
        CovariantTest c = new SubCovariantTest();
        A a = c.getObj();

        System.out.println(c.getObj().x);
    }
}

 class SubCovariantTest extends CovariantTest {
    public A getObj() {
        return new B();
    }

 }

Stick a breakpoint in at the system out line and look at the a variable. It contains two x members, with one set to 5 and one to 6.

Starblue's answer explains this behaviour.

Feet
+14  A: 

This is because in Java member variables don't override, they shadow (unlike methods). Both A and B have a variable x. Since c is declared to be of type CovarientTest, the return of getObj() is implicitly an A, not B, so you get A's x, not B's x.

Software Monkey
A: 

c is typed as CovariantTest at compile time and thus the call to c.getObj() is bound to the method CovariantTest.getObj() at compile time (and that can't be modified at runtime).

Also, x exists in both A and B (it is shadowed, not overridden). Because the method being called is CovariantTest.getObj() and that method works with A, the x being retrieved is A.x even though the actual object is of type B.

Alex Miller