Jawny przykład odlewania typu w Javie

Natknąłem się na ten przykład na http://www.javabeginner.com/learn-java/java-object-typecasting a w części, w której mówi się o wyraźnym odlewaniu typu, jest jeden przykład, który mnie myli.

Przykład:

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

W ostatniej linijce, gdzie T jest przypisany do referencji hV i typecast jako (Truck), dlaczego w komentarzu jest napisane, że jest to udana wyraźna Obsada z rodzica na dziecko? Jak rozumiem casting (implicit lub explicit) będzie Zmień tylko deklarowany typ obiektu, a nie rzeczywisty typ (który nigdy nie powinien się zmieniać, chyba że faktycznie przypisujesz nową instancję klasy do referencji pola tego obiektu). Jeśli hv została już przypisana instancja klasy HeavyVehicle, która jest super klasą klasy Truck, w jaki sposób to pole może być przypisane do bardziej specyficznej podklasy o nazwie Truck, która rozciąga się od klasy HeavyVehicle?

Rozumiem, że casting służy ograniczeniu dostępu do pewnych metod obiektu (instancji klasy). Dlatego nie można oddać obiektu jako bardziej specyficznej klasy, która ma więcej metod niż rzeczywista przypisana Klasa obiektu. Oznacza to, że obiekt może być rzucany tylko jako klasa nadrzędna lub ta sama klasa co klasa, z której został utworzony. Czy to prawda, czy się mylę? Wciąż się uczę, więc nie jestem pewien, czy jest to poprawny sposób patrzenia na rzeczy.

Rozumiem również, że powinien to być przykład downcasting, ale nie jestem pewien, jak to faktycznie działa, jeśli rzeczywisty typ nie ma metod klasy, do której ten obiekt jest downcasting. Czy jawne odlewanie zmienia w jakiś sposób rzeczywisty typ obiektu (nie tylko deklarowany Typ), tak że ten obiekt nie jest już instancją klasy HeavyVehicle, ale teraz staje się instancją klasy Truck?

Author: Edwin Dalorzo, 2013-11-20

6 answers

Reference vs Object vs Types

Kluczem jest dla mnie zrozumienie różnicy między obiektem a jego odniesieniami, lub innymi słowy, różnica między obiektem a jego typami.

Kiedy tworzymy obiekt w Javie, deklarujemy jego prawdziwą naturę, która nigdy się nie zmieni (np. new Truck()). Jednak każdy obiekt w Javie może mieć wiele typów. niektóre z tych typów są oczywiście podane przez hierarchię klas, inne nie są tak oczywiste (tj. generyki, tablice).

W szczególności dla typów referencyjnych, hierarchia klas dyktuje reguły podtypowania. Na przykład w twoim przykładzie wszystkie ciężarówki są ciężkimi pojazdami, A wszystkie ciężkie pojazdy są pojazdamiS. dlatego ta hierarchia relacji is-a nakazuje, że ciężarówka ma wiele kompatybilnych typów.

Kiedy tworzymy Truck, definiujemy "odniesienie", aby uzyskać do niego dostęp. To odniesienie musi mieć jeden z tych zgodnych typy.

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

Kluczowym punktem jest więc uświadomienie sobie, że odniesienie do obiektu nie jest samym obiektem . Natura tworzonego obiektu nigdy się nie zmieni. Ale możemy użyć różnych rodzajów zgodnych odniesień, aby uzyskać dostęp do obiektu. Jest to jedna z cech polimorfizmu. Dostęp do tego samego obiektu można uzyskać poprzez odwołania różnych "kompatybilnych" typów.

Kiedy wykonujemy dowolny rodzaj odlewu, po prostu Zakładamy, że charakter tej zgodności między różnymi rodzajami odniesień.

Upcasting lub rozszerzenie konwersji odniesienia

Teraz, mając odniesienie typu Truck, możemy łatwo wywnioskować, że zawsze jest ono zgodne z odniesieniem typu Vehicle, ponieważ wszystkie ciężarówki są pojazdami . W związku z tym, możemy upcast odniesienie, bez użycia wyraźnej obsady.

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

Nazywa się to również rozszerzającą konwersją odniesienia , W zasadzie dlatego, że w miarę w hierarchii typów Typ staje się bardziej ogólny.

Możesz użyć wyraźnej obsady, jeśli chcesz, ale byłoby to niepotrzebne. Widzimy, że rzeczywisty obiekt, do którego odnoszą się t i v, jest taki sam. Jest i zawsze będzie Truck.

Redukcja lub zawężenie konwersji odniesienia

Teraz, mając odniesienie typu Vechicle nie możemy "bezpiecznie" wywnioskować, że faktycznie odnosi się ono do Truck. Wszakże może również odwoływać się do innych forma pojazdu. Na przykład

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

Jeśli znajdziesz v odniesienie gdzieś w swoim kodzie, nie wiedząc, do którego konkretnego obiektu się odnosi, nie możesz "bezpiecznie" argumentować, czy wskazuje na Truck, czy na Sedan lub jakikolwiek inny rodzaj pojazdu.

Kompilator dobrze wie, że nie może dać żadnych gwarancji co do prawdziwej natury przywoływanego obiektu. Ale programista, czytajÄ ... c kod, moĹźe byÄ ‡ pewien tego, co robi. Jak w powyższym przypadku, ty widać wyraźnie, że {[19] } odnosi się do Sedan.

W takich przypadkach możemy zrobić downcast. Nazywamy to w ten sposób, ponieważ schodzimy w dół hierarchii typów. Nazywamy to również zawężającą konwersją odniesienia . Możemy powiedzieć
Sedan s = (Sedan) v;

To zawsze wymaga wyraźnej obsady, ponieważ kompilator nie może być pewien, że jest to bezpieczne i dlatego jest to jak pytanie programisty: "czy jesteś pewien tego, co robisz?". Jeśli skłamiesz kompilatorowi, rzucisz ci ClassCastException w czasie wykonywania tego kodu.

Inne rodzaje reguł podtypu

Istnieją inne zasady podtypowania w Javie. Na przykład istnieje również pojęcie o nazwie promocja liczbowa, które automatycznie wymusza liczby w wyrażeniach. Jak w

double d = 5 + 6.0;

W tym przypadku wyrażenie złożone z dwóch różnych typów, integer i double, upcasts / przymusza liczbę całkowitą do liczby podwójnej przed obliczeniem wyrażenia, co daje podwójną wartość.

Możesz także czy prymitywne upcasting i downcasting. Jak w

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

W takich przypadkach, gdy informacja może zostać utracona, wymagana jest wyraźna Obsada.

Niektóre reguły podtypowania mogą nie być tak oczywiste, jak w przypadku tablic. Na przykład wszystkie tablice referencyjne są podtypami Object[], ale prymitywne tablice nie są.

A w przypadku leków generycznych, szczególnie przy użyciu symboli wieloznacznych, takich jak super i extends, sprawy stają się jeszcze bardziej skomplikowane. Jak w

List<Integer> a = new ArrayList<>();
List<? extends Number> b = a;
        
List<Object> c = new ArrayList<>(); 
List<? super Number> d = c;

Gdzie Typ b jest podtypem typu a. Typ d jest podtypem typu c.

A także boks i unboxing podlegają pewnym zasadom castingu (po raz kolejny jest to również jakaś forma przymusu moim zdaniem).

 64
Author: Edwin Dalorzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-06 15:55:02

Masz rację. Można z powodzeniem wysłać obiekt tylko do jego klasy, niektórych klas nadrzędnych lub do interfejsu, który implementuje lub jego rodzice. Jeśli przypisano go do niektórych klas nadrzędnych lub interfejsów, można go przypisać z powrotem do oryginalnego typu.

W Przeciwnym Razie (chociaż możesz mieć go w źródle), spowoduje to wywołanie klasy runtime ClassCastException.

Casting jest zazwyczaj używany, aby umożliwić przechowywanie różnych rzeczy (tego samego interfejsu lub klasy nadrzędnej, np. wszystkie Twoje samochody) w tym samym polu lub kolekcji tego samego typu (np. Pojazd), dzięki czemu można z nimi pracować w ten sam sposób.

Jeśli chcesz uzyskać pełny dostęp, możesz je oddać z powrotem (np. Pojazd do ciężarówki)


W przykładzie jestem prawie pewien, że ostatnia wypowiedź jest nieprawidłowa, a komentarz jest po prostu błędny.

 5
Author: MightyPork,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-20 13:32:00

Kiedy wykonujesz odlew z przedmiotu ciężarówki do ciężkiego pojazdu w taki sposób:

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

Obiekt jest nadal ciężarówką, ale masz dostęp do metod i pól heavyVehicle tylko za pomocą referencji HeavyVehicle. Jeśli ponownie przejdziesz do ciężarówki, możesz ponownie użyć wszystkich metod i pól Ciężarówki.

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

Jeśli rzeczywisty obiekt nie jest ciężarówką, ClassCastException zostanie rzucony jak w poniższym przykładzie:

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

Wyjątek jest wyrzucany, ponieważ obiekt rzeczywisty nie jest poprawną klasą, jest obiektem superklasy (HeavyVehicle)

 2
Author: David SN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-20 12:58:49

Ostatnia linia kodu kompiluje się i działa pomyślnie bez żadnych WYJĄTKÓW. To co robi jest całkowicie legalne.

  1. HV początkowo odnosi się do obiektu typu HeavyVehicle (nazwijmy ten obiekt h1):

    static HeavyVehicle hV = new HeavyVehicle(); // hV now refers to h1.
    
  2. Później sprawiamy, że HV odnosi się do innego obiektu, typu Truck (nazwijmy ten obiekt t1):

    hV = T; // hV now refers to t1.
    
  3. Wreszcie, t odnosi się do t1.

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

T odnosi się już do t1, więc to stwierdzenie się nie zmieniło cokolwiek.

Jeśli hv została już przypisana instancja klasy HeavyVehicle, która jest super klasą klasy Truck, w jaki sposób to pole może być przypisane do bardziej specyficznej podklasy o nazwie Truck, która rozciąga się od klasy HeavyVehicle?

Zanim dotrzemy do ostatniego wiersza, hV nie odnosi się już do instancji HeavyVehicle. Odnosi się do przykładu Ciężarówki. Odlewanie instancji ciężarówki do typu Ciężarówki nie stanowi problemu.

Oznacza to, że obiekt może być tylko cast jako superklasa lub ta sama klasa co klasa, z której została faktycznie utworzona. Czy to prawda, czy się mylę?

Zasadniczo tak, ale nie myl samego obiektu ze zmienną, która odnosi się do obiektu. Patrz poniżej.

Czy jawne odlewanie zmienia w jakiś sposób rzeczywisty typ obiektu (nie tylko deklarowany Typ), tak że ten obiekt nie jest już instancją klasy HeavyVehicle, ale teraz staje się instancją klasy Truck?
Nie. Obiekt, raz stworzony, nigdy nie może zmienić swojego typu. Nie może stać się instancją innej klasy.

Powtarzam, nic się nie zmieniło w ostatniej linijce. T odnosi się do t1 przed tą linią, a następnie odnosi się do t1.

Więc dlaczego w ostatniej linijce jest potrzebna wyraźna Obsada (Ciężarówka)? W zasadzie pomagamy tylko kompilatorowi.

wiemy, że w tym punkcie hV odnosi się do obiektu typu Truck, więc można przypisać ten obiekt typu Truck do zmiennej T. Ale kompilator nie jest wystarczająco mądry, aby to wiedzieć. Kompilator chce naszego zapewnienia, że kiedy dotrze do tej linii i spróbuje wykonać przypisanie, znajdzie instancję Trucka czekającą na nią.

 2
Author: dshiga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-22 21:36:00

Powyższy kod będzie kompilowany i uruchamiany poprawnie. Teraz zmień powyższy kod i dodaj następujący wiersz System. out. println(T.name);

To upewni się, że nie używasz obiektu T po downcastingu obiektu hV jako Ciężarówki.

Obecnie w kodzie nie używasz T po downcast więc wszystko jest w porządku i działa.

Dzieje się tak dlatego, że przez jawne rzucenie hV jako Truck, kompilator skarży się, że programista jako rzucił obiekt i jest świadomy tego, jaki obiekt został oddany do czego.

Ale podczas wykonywania JVM nie jest w stanie uzasadnić rzucania i rzuca ClassCastException "HeavyVehicle nie może być rzucany na ciężarówkę".

 1
Author: Prashant Thakkar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-20 13:06:45

Aby lepiej zilustrować niektóre punkty powyżej, zmodyfikowałem dany kod i dodałem do niego więcej kodów z komentarzami inline (w tym rzeczywistymi wyjściami) w następujący sposób:

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

        }

}
 1
Author: S. W. Chi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-16 19:41:23