Подтвердить что ты не робот

Явный тип литья пример в java

Я столкнулся с этим примером на http://www.javabeginner.com/learn-java/java-object-typecasting, а в той части, где он говорит о явном типе, есть один пример, который меня смущает.

Пример:

class Vehicle {

    String name;
    Vehicle() {
        name = "Vehicle";
    }
}

class HeavyVehicle extends Vehicle {

    HeavyVehicle() {
        name = "HeavyVehicle";
    }
}

class Truck extends HeavyVehicle {

    Truck() {
        name = "Truck";
    }
}

class LightVehicle extends Vehicle {

    LightVehicle() {
        name = "LightVehicle";
    }
}

public class InstanceOfExample {

    static boolean result;
    static HeavyVehicle hV = new HeavyVehicle();
    static Truck T = new Truck();
    static HeavyVehicle hv2 = null;
    public static void main(String[] args) {
        result = hV instanceof HeavyVehicle;
        System.out.print("hV is an HeavyVehicle: " + result + "\n");
        result = T instanceof HeavyVehicle;
        System.out.print("T is an HeavyVehicle: " + result + "\n");
        result = hV instanceof Truck;
        System.out.print("hV is a Truck: " + result + "\n");
        result = hv2 instanceof HeavyVehicle;
        System.out.print("hv2 is an HeavyVehicle: " + result + "\n");
        hV = T; //Sucessful Cast form child to parent
        T = (Truck) hV; //Sucessful Explicit Cast form parent to child
    }
}

В последней строке, где T присваивается ссылка hV и typecast as (Truck), почему она говорит в комментарии, что это успешный явный ролик от родителя к ребенку? Поскольку я понимаю, что кастинг (неявный или явный) будет изменять только объявленный тип объекта, а не фактический тип (который никогда не должен изменяться, если вы фактически не назначаете новый экземпляр класса этой ссылке на объект). Если hv уже был назначен экземпляр класса HeavyVehicle, который является суперклассом класса Truck, то как это поле может быть введено в конкретный подкласс под названием Truck, который простирается от класса HeavyVehicle?

Я понимаю, что кастинг служит для ограничения доступа к определенным методам объекта (экземпляра класса). Поэтому вы не можете отличать объект как более конкретный класс, который имеет больше методов, чем объект, которому присвоен класс. Это означает, что объект может быть передан только как суперкласс или тот же класс, что и класс, из которого он был фактически создан. Это правильно или я здесь не прав? Я все еще участвую, поэтому я не уверен, что это правильный способ взглянуть на вещи.

Я также понимаю, что это должен быть пример downcasting, но я не уверен, как это работает, если у фактического типа нет методов класса, к которому этот объект опускается. Является ли явное кастинг каким-то образом изменяет фактический тип объекта (а не только объявленный тип), так что этот объект больше не является экземпляром класса HeavyVehicle, а теперь становится экземпляром класса Truck?

4b9b3361

Ответ 1

Ссылка на объект и типы

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

Когда мы создаем объект в Java, мы объявляем его истинную природу, которая никогда не изменится. Но любой объект Java может иметь несколько типов. Некоторые из этих типов, очевидно, предоставляются благодаря иерархии классов, другие не так очевидны (т.е. Дженерики, массивы).

В частности, для ссылочных типов иерархия классов диктует правила подтипирования. Например, в вашем примере все грузовики - это тяжелые транспортные средства, а все тяжелые транспортные средства - это транспортные средства. Таким образом, эта иерархия отношений - означает, что грузовик имеет несколько совместимых типов.

Когда мы создаем Truck, мы определяем "ссылку", чтобы получить к нему доступ. Эта ссылка должна иметь один из этих совместимых типов.

Truck t = new Truck(); //or
HeavyVehicle hv = new Truck(); //or
Vehicle h = new Truck() //or
Object o = new Truck();

Итак, ключевым моментом здесь является осознание того, что ссылка на объект не является самим объектом. Природа создаваемого объекта никогда не изменится. Но мы можем использовать разные типы совместимых ссылок для получения доступа к объекту. Это одна из особенностей полиморфизма. К тому же объекту можно получить доступ через ссылки разных "совместимых" типов.

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

Ускоренное преобразование или расширение ссылки

Теперь, имея ссылку типа Truck, мы можем легко заключить, что он всегда совместим со ссылкой типа Vehicle, потому что все Грузовики являются Транспортными средствами. Таким образом, мы могли бы оптимизировать ссылку, не используя явное выражение.

Truck t = new Truck();
Vehicle v = t;

Он также называется расширением ссылочного преобразования, в основном потому, что, когда вы поднимаетесь в иерархии типов, тип становится более общим.

Вы можете использовать явный приведение здесь, если хотите, но это было бы необязательно. Мы видим, что фактический объект, на который ссылаются t и v, тот же. Он всегда будет Truck.

Downcasting или сужение ссылочного преобразования

Теперь, имея ссылку типа Vechicle, мы не можем "безопасно" заключить, что на самом деле ссылается на Truck. В конце концов, он может также ссылаться на другую форму автомобиля. Например,

Vehicle v = new Sedan(); //a light vehicle

Если вы находите ссылку v где-то в своем коде, не зная, к какому конкретному объекту она ссылается, вы не можете "безопасно" аргументировать, указывает ли она на Truck или на Sedan или любой другой вид транспортное средство.

Компилятор хорошо знает, что он не может дать никаких гарантий относительно истинной природы ссылающегося объекта. Но программист, читая код, может быть уверен в том, что он/она делает. Как и в случае выше, вы можете ясно видеть, что Vehicle v ссылается на Sedan.

В этих случаях мы можем сделать снимок. Мы называем это так, потому что мы идем вниз по иерархии типов. Мы также называем это сужением ссылочного преобразования. Мы могли бы сказать

Sedan s = (Sedan) v;

Это всегда требует явного приведения, потому что компилятор не может быть уверен, что это безопасно, и почему это похоже на запрос программиста: "Вы уверены в том, что делаете?". Если вы лжете компилятору, вы получите ClassCastException во время выполнения, когда этот код будет выполнен.

Другие типы правил подтипирования

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

double d = 5 + 6.0;

В этом случае выражение, состоящее из двух разных типов, integer и double, увеличивает или увеличивает число целых чисел до double перед вычислением выражения, что приводит к двойному значению.

Вы также можете выполнить примитивное повышение и понижение. Как и в

int a = 10;
double b = a; //upcasting
int c = (int) b; //downcasting

В этих случаях явный приведение требуется, когда информация может быть потеряна.

Некоторые правила подтипирования могут быть не столь очевидными, как в случае массивов. Например, все ссылочные массивы являются подтипами Object[], но примитивные массивы не являются.

И в случае дженериков, особенно с использованием подстановочных знаков, таких как super и extends, все становится еще сложнее. Как в

List<Integer> a = new ArrayList<>();
List<? extends Number> b = a;

List<Object> c = new ArrayList<>(); 
List<? super Number> d = c;

Если тип b является подтипом типа a. И тип d является подтипом типа c.

А также бокс и распаковка подчиняются некоторым правилам кастинга (опять же, это тоже некоторая форма принуждения, на мой взгляд).

Ответ 2

У тебя все получилось. Вы можете успешно применить объект только к его классу, некоторым его родительским классам или к некоторому интерфейсу, который он или его родители реализуют. Если вы передали его некоторым родительским классам или интерфейсам, вы можете вернуть их к исходному типу.

В противном случае (хотя вы можете использовать его в исходном коде), это приведет к исключению ClassCastException во время выполнения.

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

Если вы хотите получить полный доступ, вы можете отбросить их (например, автомобиль на грузовик)


В этом примере я уверен, что последний оператор недействителен, а комментарий просто неверен.

Ответ 3

Когда вы делаете бросок с объекта Truck на тяжелый транспорт, например:

Truck truck = new Truck()
HeavyVehicle hv = truck;

Объект по-прежнему является грузовиком, но у вас есть доступ только к методам и полям heavyVehicle с использованием ссылки HeavyVehicle. Если вы снова опуститесь в грузовик, вы можете снова использовать все методы и поля грузовика.

Truck truck = new Truck()
HeavyVehicle hv = truck;
Truck anotherTruckReference = (Truck) hv; // Explicit Cast is needed here

Если фактический объект, который вы сбрасываете вниз, не является грузовиком, ClassCastException будет бросаться, как в следующем примере:

HeavyVehicle hv = new HeavyVehicle();
Truck tr = (Truck) hv;  // This code compiles but will throw a ClasscastException

Исключение выбрано потому, что фактический объект не относится к правильному классу, его объект суперкласса (HeavyVehicle)

Ответ 4

Последняя строка кода компилируется и выполняется успешно без каких-либо исключений. То, что он делает, совершенно законно.

  • hV первоначально относится к объекту типа HeavyVehicle (позвольте этому объекту h1):

    static HeavyVehicle hV = new HeavyVehicle(); // hV now refers to h1.
    
  • Позже мы делаем hV ссылкой на другой объект типа Truck (позвольте назвать этот объект t1):

    hV = T; // hV now refers to t1.
    
  • Наконец, мы делаем T ссылкой на t1.

    T = (Truck) hV; // T now refers to t1.
    

T уже упоминается в t1, поэтому это утверждение ничего не изменило.

Если hv уже был назначен экземпляр класса HeavyVehicle, который является суперклассом класса Truck, как же тогда это поле может быть введено в более конкретный подкласс под названием Truck, который простирается от класса HeavyVehicle?

К моменту достижения последней строки hV больше не относится к экземпляру HeavyVehicle. Это относится к экземпляру Truck. Выдача экземпляра Truck для печати на грузовике не проблема.

Это означает, что объект может быть передан только как суперкласс или тот же класс, что и класс, из которого он был фактически создан. Это правильно или я здесь не прав?

В принципе, да, но не путайте сам объект с переменной, которая ссылается на объект. См. Ниже.

Является ли явное кастинг каким-то образом изменяет фактический тип объекта (а не только объявленный тип), так что этот объект больше не является экземпляром класса HeavyVehicle, а теперь становится экземпляром класса Truck?

Нет. Объект, однажды созданный, никогда не сможет изменить его тип. Он не может стать экземпляром другого класса.

Чтобы повторить, в последней строке ничего не изменилось. T относится к t1 перед этой линией, и после этого она относится к t1.

Итак, почему явный бросок (Truck) необходим в последней строке? Мы в основном помогаем просто помогать компилятору.

Мы знаем, что к этому моменту hV ссылается на объект типа Truck, поэтому можно назначить этот объект типа Truck переменной T. Но компилятор недостаточно умен, чтобы это знать. Компилятор хочет, чтобы наша уверенность в том, что когда он доберется до этой линии и попытается выполнить задание, он найдет экземпляр Truck, ожидающий его.

Ответ 5

Вышеприведенный код будет компилироваться и работать нормально. Теперь измените код выше и добавьте следующую строку System.out.println(T.name);

Это позволит убедиться, что вы не используете объект T после того, как объект hV downcasting как Грузовик.

В настоящее время в вашем коде вы не используете T после downcast, поэтому все в порядке и работает.

Это связано с тем, что, явно передавая hV как Truck, complier жалуется на то, что этот программист как литой объект, и знает, какой объект был отброшен на что.

Но во время выполнения JVM не может оправдать кастинг и бросает ClassCastException: "HeavyVehicle не может быть добавлен в Truck".

Ответ 6

Чтобы лучше проиллюстрировать некоторые моменты, приведенные выше, я модифицировал данный код и добавлял к нему дополнительные коды с встроенными комментариями (включая фактические выходы) следующим образом:

class Vehicle {

        String name;
        Vehicle() {
                name = "Vehicle";
        }
}

class HeavyVehicle extends Vehicle {

        HeavyVehicle() {
                name = "HeavyVehicle";
        }
}

class Truck extends HeavyVehicle {

        Truck() {
                name = "Truck";
        }
}

class LightVehicle extends Vehicle {

        LightVehicle() {
                name = "LightVehicle";
        }
}

public class InstanceOfExample {

        static boolean result;
        static HeavyVehicle hV = new HeavyVehicle();
        static Truck T = new Truck();
        static HeavyVehicle hv2 = null;
        public static void main(String[] args) {

                result = hV instanceof HeavyVehicle;
                System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true

                result = T instanceof HeavyVehicle;
                System.out.print("T is a HeavyVehicle: " + result + "\n"); // true
//      But the following is in error.              
//      T = hV; // error - HeavyVehicle cannot be converted to Truck because all hV are not trucks.                               

                result = hV instanceof Truck;
                System.out.print("hV is a Truck: " + result + "\n"); // false               

                hV = T; // Sucessful Cast form child to parent.
                result = hV instanceof Truck; // This only means that hV now points to a Truck object.                            
                System.out.print("hV is a Truck: " + result + "\n");    // true         

                T = (Truck) hV; // Sucessful Explicit Cast form parent to child. Now T points to both HeavyVehicle and Truck. 
                                // And also hV points to both Truck and HeavyVehicle. Check the following codes and results.
                result = hV instanceof Truck;                             
                System.out.print("hV is a Truck: " + result + "\n");    // true 

                result = hV instanceof HeavyVehicle;
                System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true             

                result = hV instanceof HeavyVehicle;
                System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true 

                result = hv2 instanceof HeavyVehicle;               
                System.out.print("hv2 is a HeavyVehicle: " + result + "\n"); // false

        }

}