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;
}

Вышеприведенный код печатает 5 при компиляции и запуске. Он использует ковариантное возвращение для метода over-ridden.

Почему он печатает 5 вместо 6, так как он выполняет over-ridden метод getObj в классе SubCovariantTest.

Может кто-то проливает свет на это. Спасибо.

Ответ 1

Это потому, что в переменных-членах Java не переопределяются, они теневые (в отличие от методов). И A, и B имеют переменную x. Поскольку c объявлен как тип CovarientTest, возврат getObj() неявно является A, а не B, поэтому вы получаете A x, а не B x.

Ответ 2

Java не переопределяет поля (ака. атрибуты или переменные-члены). Вместо этого они тень над друг друга. Если вы запускаете программу через отладчик, вы найдете две переменные x в любом объекте, имеющем тип B.

Вот объяснение того, что происходит. Сначала программа извлекает то, что неявно относится к типу A, а затем вызывает x, который предполагается из A. Несмотря на то, что это явно подтип, в вашем примере объект типа B создается через SubCovariantTest, он все же предполагает, что вы возвращаете что-то в getObj(), который неявно вводится A. Так как Java не может переопределять поля, тест будет вызовите A.x, а не 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

Похоже, что getboggling gotcha, потому что методы всегда переопределяются в Java (по сравнению с С++ и С#, в которых они не являются). Обычно вы не сталкиваетесь с этой проблемой, потому что в коде Java-кода вам не нужно напрямую обращаться к полям. Вместо этого убедитесь, что поля всегда доступны через методы доступа, т.е. 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;
    }
}

Код для получения этой работы следующий:

c.getObj().getX();
// Will now call getX() in B and return the x that is defined in B context.

Ответ 3

Замените свои A и B выше:

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

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

Это, вероятно, ответит на ваш вопрос о том, что не так: -)

Ответ 4

В объекте есть два поля с именем x: один из класса A и один из класса B, который скрывает одно в A. Поле x, на которое ссылается, является единицей в A, из-за объявления c.

На практике это не проблема, потому что это очень плохой стиль для

  • скрыть поле в подклассе,

  • доступ к полю напрямую, а не через метод.

Ответ 5

package ch2;

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

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

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

class A
{
    int x = 5;

    int getX()
    {
        return x;
    }
}

class B extends A
{
    int x = 6;

    int getX()
    {
        return x;
    }
}

Простой... Полиморфизм применим только для функций. Не переменные.

Переменные будут разрешены во время компиляции.

Ответ 6

Я смотрел на ваш код и имел некоторые проблемы с его компиляцией. Получение ошибки

Тип возврата несовместим с CovariantTest.getObj()

Я сделал небольшое изменение.

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();
    }

 }

Придерживайте точку останова в линии системы и посмотрите на переменную. Он содержит два члена x, один из которых имеет значение 5 и один - 6.

Ответ на Starblue объясняет это поведение.

Ответ 7

c набирается как CovariantTest во время компиляции, и поэтому вызов c.getObj() привязан к методу CovariantTest.getObj() во время компиляции (и который не может быть изменен во время выполнения).

Кроме того, x существует как в A, так и в B (он затенен, а не переопределен). Поскольку вызываемый метод - это CovariantTest.getObj(), и этот метод работает с A, то получаемый x равен A.x, хотя фактический объект имеет тип B.