Java Коварианты - PullRequest
       13

Java Коварианты

14 голосов
/ 01 января 2009
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 при компиляции и запуске. Он использует ковариантный возврат для переопределенного метода.

Почему он печатает 5 вместо 6, поскольку он выполняет переопределенный метод getObj в классе SubCovariantTest.

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

Ответы [ 7 ]

15 голосов
/ 01 января 2009

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

11 голосов
/ 01 января 2009

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

Кажется, это ошеломляет, потому что методы всегда переопределяются в Java (по сравнению с C ++ и C #, в которых их нет). Обычно вы не сталкиваетесь с этой проблемой, потому что соглашение 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's context.
10 голосов
/ 01 января 2009

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

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

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

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

9 голосов
/ 01 января 2009

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

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

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

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

2 голосов
/ 03 марта 2012
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;
    }
}

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

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

1 голос
/ 01 января 2009

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

Тип возврата несовместим с 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 объясняет это поведение.

0 голосов
/ 02 января 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...