2013-11-20 3 views
31

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

Пример:

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 присваивается ссылка кч и напечатанный материал как (грузовик), почему не говорится в комментарии, что это успешный Явная Cast от родителей к ребенку ? Поскольку я понимаю, что кастинг (неявный или явный) изменит только объявленный тип объекта, а не фактический тип (который никогда не должен изменяться, если вы фактически не назначаете экземпляр нового класса для справки об этом объекте). Если hv уже был назначен экземпляр класса HeavyVehicle, который является суперклассом класса Truck, как же тогда это поле может быть введено в конкретный подкласс под названием Truck, который простирается от класса HeavyVehicle?

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

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

+0

Мы знаем, что высоковольтный грузовик (или суперкласса Грузовика), потому что неявное литье одной линии над обработанным (и это также очевидно из проверки деклараций). Вы всегда можете явно передать реальный тип объекта из ссылки на суперкласс. –

+0

(Кастинг от класса к его суперклассу, чтобы преднамеренно ограничить доступные методы, редко делается. На самом деле, я никогда не вспоминаю об этом. Я полагаю, что это может быть рекомендовано некоторыми «лучшими практиками» гуру, но эти люди редко имеют какое-либо отношение к реальной жизни.) –

+1

'hV = T;' => hV указывает на грузовик сейчас. – njzk2

ответ

56

Ссылка против объекта против Типов

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

Когда мы создаем объект в 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; 

Это также называется widening reference conversion, в основном потому, что, как вы идете в иерархии типов, тип получает более общий характер.

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

понижающее приведение или сужающие преобразования ссылки

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

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

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

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

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

Sedan s = (Sedan) v; 

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

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

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

double d = 5 + 6.0; 

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

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

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.

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

+7

+1 Это действительно помогло мне понять, «когда» я должен использовать явное литье. –

+0

Ссылка на объект фактически содержит адрес объекта в куче. если вы: грузовик t = новый грузовик(); System.out.print (т); он напечатает что-то вроде @ 6bc7c054, которое является расположением t в куче. – niclaslindgren

5

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

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

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

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


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

2

Когда вы делаете бросок от объекта Truck к HeavyVehicle вроде этого:

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)

1

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

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

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

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

Но во время выполнения JVM не может оправдать литье и бросает ClassCastException «HeavyVehicle нельзя отбрасывать на Truck».

2

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

  1. Нч изначально относится к объекту типа HeavyVehicle (давайте назовем этот объект h1):

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

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

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

T уже называют t1, так что это заявление ничего не изменилось.

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

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

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

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

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

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

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

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

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

0

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

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's 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 

     } 

} 
Смежные вопросы