Тайна кастинга Java - Class.cast против оператора каста - PullRequest
0 голосов
/ 09 мая 2019

На основании этого примера oracle doc Я пытаюсь создать собственный пример, чтобы протестировать его.Конечно ссылаться на пример работы, как предложено.Но когда я пытаюсь использовать метод Class.cast, у меня возникает проблема:

Мой код:

CL3 расширяет CL2 расширяет CL1 расширяет CL0 расширяет Base.

    public class CL3 extends CL2 {
        int x = 3;
        public int getX() { return x; }
    }

    public class CL2 extends CL1 {
        int x = 2;
        public int getX() { return x; }
    }

    public class CL1 extends CL0 {
        int x = 1;
        public int getX() { return x; }
    }

    public class CL0 extends Base {
        int x = 0;
        public int getX() { return x; }

        public String getPath() {
            System.out.println("before obj    : " + getClass().cast(this));
            System.out.println("before class  : " + getClass());
            System.out.println("before x      : " + getClass().cast(this).x);
            System.out.println("before getX() : " + getClass().cast(this).getX());
            System.out.println();
            return getClazzPath(getClass());
        }
    }

    public abstract class Base {
        int x = -1;
        abstract int getX();

        @SuppressWarnings("unchecked")
        public <T extends CL0> String getClazzPath(Class<T> clazz) {
            System.out.println("clazz       : " + clazz);
            System.out.println("cast        : " + clazz.cast(this));
            System.out.println("cast.x      : " + clazz.cast(this).x);
            System.out.println("cast.getX() : " + clazz.cast(this).getX());
            System.out.println("#");
            return clazz.cast(this).x + (clazz == CL0.class ? "" : "/" + getClazzPath((Class<T>) clazz.getSuperclass()));
        }
    }

икод основной функции:

    public static void main(String[] args) {
        CL3 cl3 = new CL3();

        System.out.println("cl3.getX()=" + cl3.getX());
        System.out.println("((CL2)cl3).getX()=" + ((CL2) cl3).getX());
        System.out.println("((CL1)cl3).getX()=" + ((CL1) cl3).getX());
        System.out.println("((CL0)cl3).getX()=" + ((CL0) cl3).getX());
        System.out.println("((IdentyfiedScope)cl3).getX()=" + ((Base) cl3).getX());

        System.out.println();
        System.out.println("cl3.x=" + cl3.x);
        System.out.println("((CL2)cl3).x=" + ((CL2) cl3).x);
        System.out.println("((CL1)cl3).x=" + ((CL1) cl3).x);
        System.out.println("((CL0)cl3).x=" + ((CL0) cl3).x);
        System.out.println("((IdentyfiedScope)cl3).x=" + ((Base) cl3).x);

        System.out.println();
        System.out.println(cl3.getPath());
    }

вывод:

cl3.getX()=3
((CL2)cl3).getX()=3
((CL1)cl3).getX()=3
((CL0)cl3).getX()=3
((IdentyfiedScope)cl3).getX()=3

cl3.x=3
((CL2)cl3).x=2
((CL1)cl3).x=1
((CL0)cl3).x=0
((IdentyfiedScope)cl3).x=-1

before obj    : test.code.hierarchy.read.CL3@70dea4e
before class  : class test.code.hierarchy.read.CL3
before x      : 0
before getX() : 3

clazz       : class test.code.hierarchy.read.CL3
cast        : test.code.hierarchy.read.CL3@70dea4e
cast.x      : 0
cast.getX() : 3
#
clazz       : class test.code.hierarchy.read.CL2
cast        : test.code.hierarchy.read.CL3@70dea4e
cast.x      : 0
cast.getX() : 3
#
clazz       : class test.code.hierarchy.read.CL1
cast        : test.code.hierarchy.read.CL3@70dea4e
cast.x      : 0
cast.getX() : 3
#
clazz       : class test.code.hierarchy.read.CL0
cast        : test.code.hierarchy.read.CL3@70dea4e
cast.x      : 0
cast.getX() : 3
#
0/0/0/0

И вопрос - почему, когда мы используем Class.cast (вызывается методом getPath ().Метод getClazzPath ()) у нас результаты, отличные от результатов оператора приведения, когда к полю x обращаются напрямую? Как мы видим, выходные данные метода getClazzPath ('clazz:') возвращают надлежащие типы CL3 -> CL2 -> CL1 -> CL0но x всегда ссылается на 0.

Я обнаружил, что это связано с типом T в методе getClazzPath - но я не знаю, как объяснить это правильно.

Если здесь есть какой-либо эксперт, который можетобъясните, почему в моем случае поведение оператора cast и Class.cast отличается?

Ответы [ 2 ]

2 голосов
/ 09 мая 2019

Тип приведения не меняет тип объекта.Это только изменяет тип времени компиляции ссылки на него, с проверкой во время выполнения относительно действительности, когда это необходимо.

Так как приведение не меняет тип объекта, оно никогда не изменяет результат вызовапереопределяемого метода, такого как ваш getX(), который всегда будет вызывать самый конкретный метод.Аналогично, при добавлении объекта к String приведение не имеет никакого эффекта, так как результатом будет вызов переопределяемого метода toString().

Когда дело доходит до полей или private методов,тип ссылки во время компиляции может влиять на доступ к полю или методу, однако, поскольку знания о типе ограничены для параметра типа универсального метода, вы не можете ожидать воздействия только для типа.вызывающий абонент знает.

Поэтому, когда вы замените

@SuppressWarnings("unchecked")
public <T extends CL0> String getClazzPath(Class<T> clazz) {
    System.out.println("clazz       : " + clazz);
    System.out.println("cast        : " + clazz.cast(this));
    System.out.println("cast.x      : " + clazz.cast(this).x);
    System.out.println("cast.getX() : " + clazz.cast(this).getX());
    System.out.println("#");
    return clazz.cast(this).x + (clazz == CL0.class ? "" : "/" + getClazzPath((Class<T>) clazz.getSuperclass()));
}

на

@SuppressWarnings("unchecked")
public <T extends CL0> String getClazzPath(Class<T> clazz) {
    System.out.println("clazz       : " + clazz);
    System.out.println("cast        : " + (T)this);
    System.out.println("cast.x      : " + ((T)this).x);
    System.out.println("cast.getX() : " + ((T)this).getX());
    System.out.println("#");
    return clazz.cast(this).x + (clazz == CL0.class ? "" : "/" + getClazzPath((Class<T>) clazz.getSuperclass()));
}

, результат не изменится.Нет разницы между обычным типом приведений (T)… и clazz.cast(…), когда clazz является Class<T>, оба имеют эффект изменения типа времени компиляции ссылки на T ¹ .

Но что такое T?Метод не знает, все, что он знает, это то, что он может быть назначен на CL0, благодаря объявлению <T extends CL0>, и, следовательно, позволяет получить доступ к членам CL0, включая поле CL0.x.

Вы можете считать ошибкой проектирования языка, чтобы разрешить доступ к не перезаписываемым элементам, таким как поля CL0, через ссылку типа T, хотя T может быть подклассом, объявляющим свое собственное поле с тем же именем,Фактически, для private членов компилятор генерирует ошибку при обращении к ним по ссылке типа T, даже когда private члены CL0 доступны.


Комудалее продемонстрируйте, что нет разницы между приведением обычного типа и Clazz.cast, вы можете изменить

System.out.println("cl3.getX()=" + cl3.getX());
System.out.println("((CL2)cl3).getX()=" + ((CL2) cl3).getX());
System.out.println("((CL1)cl3).getX()=" + ((CL1) cl3).getX());
System.out.println("((CL0)cl3).getX()=" + ((CL0) cl3).getX());
System.out.println("((IdentyfiedScope)cl3).getX()=" + ((Base) cl3).getX());

System.out.println();
System.out.println("cl3.x=" + cl3.x);
System.out.println("((CL2)cl3).x=" + ((CL2) cl3).x);
System.out.println("((CL1)cl3).x=" + ((CL1) cl3).x);
System.out.println("((CL0)cl3).x=" + ((CL0) cl3).x);
System.out.println("((IdentyfiedScope)cl3).x=" + ((Base) cl3).x);

вашего main метода на

System.out.println("cl3.getX()=" + cl3.getX());
System.out.println("((CL2)cl3).getX()=" + CL2.class.cast(cl3).getX());
System.out.println("((CL1)cl3).getX()=" + CL1.class.cast(cl3).getX());
System.out.println("((CL0)cl3).getX()=" + CL0.class.cast(cl3).getX());
System.out.println("((IdentyfiedScope)cl3).getX()=" + Base.class.cast(cl3).getX());

System.out.println();
System.out.println("cl3.x=" + cl3.x);
System.out.println("((CL2)cl3).x=" + CL2.class.cast(cl3).x);
System.out.println("((CL1)cl3).x=" + CL1.class.cast(cl3).x);
System.out.println("((CL0)cl3).x=" + CL0.class.cast(cl3).x);
System.out.println("((IdentyfiedScope)cl3).x=" + Base.class.cast(cl3).x);

, и результат также будеттот же самый.Нет никакой разницы между этими подходами, все, что имеет значение, это то, что в одном месте вы применяете конкретные типы CL3, CL2, CL1, CL0 или Base, в другом,вы приводите к параметру типа T.


¹ Есть разница, что Class.cast будет проверять действительность во время выполнения, в отличие от непроверенного приведения, но так каквсе они действительны в этом примере, результат не меняется, и мы можем сосредоточиться на влиянии на выбор участника.

0 голосов
/ 10 мая 2019

Спасибо, Хольгер, за объяснения.Сделайте историю короче.Я не могу основывать в своем примере тот факт, что поля не являются полиморфными , потому что ссылки на поля разрешаются во время компиляции .

В методе main код:

System.out.println("cl3.x=" + cl3.x);
System.out.println("((CL2)cl3).x=" + ((CL2) cl3).x);
System.out.println("((CL1)cl3).x=" + ((CL1) cl3).x);
System.out.println("((CL0)cl3).x=" + ((CL0) cl3).x);
System.out.println("((IdentyfiedScope)cl3).x=" + ((Base) cl3).x);

сгенерировать вывод:

cl3.x=3
((CL2)cl3).x=2
((CL1)cl3).x=1
((CL0)cl3).x=0
((IdentyfiedScope)cl3).x=-1

, потому что во время компиляции, например, '((CL1) cl3) .x)' меняется на 'CL1.x'.

Вывод консоли из метода:

public <T extends CL0> String getClazzPath(Class<T> clazz) {
    System.out.println("clazz       : " + clazz);
    System.out.println("cast.x      : " + clazz.cast(this).x);
}

всегда будет печататься для «cast.x: 0», потому что во время компиляции компилятор всегда разрешал его в «CL0.x».В этом месте не имеет значения, о каком сообщении сообщается во время выполнения, потому что во время компиляции всегда будет использоваться CL0.x.Это логично, потому что мы не можем гарантировать, что у потомков CL0 есть поле x.

clazz       : class test.code.hierarchy.read.CL3
cast.x      : 0

clazz       : class test.code.hierarchy.read.CL2
cast.x      : 0
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...