Przesyłanie zmiennych w Javie

84

Zastanawiam się, czy ktoś mógłby mi powiedzieć, jak działa casting? Rozumiem, kiedy powinienem to zrobić, ale tak naprawdę nie wiem, jak to działa. Częściowo rozumiem prymitywne typy danych, ale jeśli chodzi o rzutowanie obiektów, nie rozumiem, jak to działa.

Jak można nagle rzutować obiekt typu Object na, powiedzmy, MyType(tylko przykład), a następnie pobrać wszystkie metody?

user626912
źródło
Sugerowana lektura: Dziedziczenie
Francesco Menzani

Odpowiedzi:

182

Rzutowanie w Javie nie jest magią, to ty mówisz kompilatorowi, że obiekt typu A jest w rzeczywistości bardziej specyficznym typem B, a tym samym uzyskujesz dostęp do wszystkich metod na B, których nie miałbyś w innym przypadku. Podczas rzucania nie wykonujesz żadnej magii ani konwersji, po prostu mówisz kompilatorowi „zaufaj mi, wiem co robię i mogę zagwarantować, że ten obiekt w tej linii jest w rzeczywistości rzutem <Wstaw rzut wpisz tutaj>. ” Na przykład:

Object o = "str";
String str = (String)o;

Powyższe jest w porządku, nie magiczne i wszystko w porządku. Obiekt przechowywany w o jest w rzeczywistości łańcuchem, dzięki czemu możemy bez problemu rzutować na łańcuch.

Może to się nie udać na dwa sposoby. Po pierwsze, jeśli rzucasz między dwoma typami w zupełnie różnych hierarchiach dziedziczenia, kompilator będzie wiedział, że jesteś głupi i cię powstrzyma:

String o = "str";
Integer str = (Integer)o; //Compilation fails here

Po drugie, jeśli są w tej samej hierarchii, ale nadal są nieprawidłowym rzutowaniem, ClassCastExceptionzostanie wyrzucony w czasie wykonywania:

Number o = new Integer(5);
Double n = (Double)o; //ClassCastException thrown here

Zasadniczo oznacza to, że naruszyłeś zaufanie kompilatora. Powiedziałeś mu, że możesz zagwarantować, że obiekt jest określonego typu, a tak nie jest.

Dlaczego potrzebujesz odlewania? Cóż, na początek potrzebujesz go tylko wtedy, gdy przechodzisz od bardziej ogólnego typu do bardziej szczegółowego. Na przykład Integerdziedziczy z Number, więc jeśli chcesz zapisać Integerjako a, Numberto jest w porządku (ponieważ wszystkie liczby całkowite są liczbami) .Jeśli jednak chcesz pójść w drugą stronę, potrzebujesz rzutu - nie wszystkie liczby są liczbami całkowitymi (również As Integer mamy Double, Float, Byte, Long, itd.), a nawet jeśli jest tylko jedna podklasa w projekcie lub JDK, ktoś może łatwo tworzyć inny i rozprowadzać, że tak nie masz gwarancji, nawet jeśli uważasz, że jest to jedno, oczywistym wyborem !

Jeśli chodzi o użycie do przesyłania, nadal widzisz potrzebę tego w niektórych bibliotekach. Przed Java-5 był używany intensywnie w kolekcjach i różnych innych klasach, ponieważ wszystkie kolekcje pracowały nad dodawaniem obiektów, a następnie rzutowaniem wyniku, który otrzymałeś z kolekcji. Jednak wraz z pojawieniem się generyków wiele użycia do rzutowania zniknęło - zostało zastąpione przez generyczne, które zapewniają znacznie bezpieczniejszą alternatywę, bez potencjału ClassCastExceptions (w rzeczywistości, jeśli używasz generyków w czysty sposób i kompiluje się bez ostrzeżeń, masz gwarancję, że nigdy nie otrzymasz wyjątku ClassCastException.)

Michael Berry
źródło
Dziękuję za wyjaśnienie. Jeśli dobrze to rozumiem, prawdopodobnie nie, kiedy rzucasz obiekt, po prostu mówisz kompilatorowi, że znam obiekt na tym adresie pamięci, wiem, jak odpowiedzieć na te metody itp., Więc nie odmawiaj mi? Czy to jest poprawne?
user626912
1
@ user626912 Trochę - nie myśl o tym jednak w kategoriach adresów pamięci. Nie mówisz tylko kompilatorowi, że dany obiekt jest zgodny z interfejsem i dlatego ma implementacje danych metod (możesz utworzyć zupełnie inny obiekt z tymi samymi metodami i rzutowanie niekoniecznie zadziała). Mówisz kompilatorowi, że obiekt jednego typu jest w rzeczywistości bardziej specyficznym typem, dlatego możesz użyć metod dostępnych dla tego konkretnego obiektu. Poczytaj o polimorfizmie, jeśli jeszcze tego nie zrobiłeś, może pomóc wyjaśnić niektóre rzeczy.
Michael Berry,
Wątpię w twoje stwierdzenie „potrzebujesz go tylko wtedy, gdy przechodzisz od bardziej ogólnego typu do bardziej szczegółowego”. ((Object) gpsLastLoc.getLatitude ()). GetClass (). GetSimpleName () zwróci w czasie wykonywania nazwę „double” i jest to przykład użycia rzutowania z bardziej konkretnego typu na bardziej ogólny.
Owoce
1
@ 林果 皞 Używasz w tym przypadku tylko rzutowania, aby promować prymityw - to nie to samo, co przejście do bardziej ogólnego typu i bardzo dziwnego sposobu robienia rzeczy. Byłby bardziej normalny (lepszy) sposób Double.valueOf(gpsLastLoc.getLatitude()).getClass().getSimpleName(). W obu przypadkach nie powinieneś nigdy potrzebować dynamicznego pobierania klasy prymitywu, ponieważ jeśli getLatitude()zwraca podwójny prymityw, zawsze wiesz, że będzie on promowany do Doubleobiektu.
Michael Berry
Podoba mi się sposób, w jaki to ujęłaś, że kod „naruszył zaufanie kompilatora”. (Ps. Masz literówkę, napisałeś „jesteś” zamiast „masz”.)
Jason L.,
7

Właściwie casting nie zawsze działa. Jeśli obiekt nie jest instanceofklasą, do której go przesyłasz, otrzymasz ClassCastExceptionw czasie wykonywania.

sandrstar
źródło
5

Załóżmy, że chcesz rzutować a Stringna a File(tak, to nie ma żadnego sensu), nie możesz rzutować bezpośrednio, ponieważ Fileklasa nie jest dzieckiem ani rodzicem Stringklasy (i kompilator narzeka).

Ale możesz rzucić Stringna Object, ponieważ a Stringjest Object( Objectjest rodzicem). Następnie możesz rzucić ten obiekt na a File, ponieważ File to Object.

Więc wszystkie twoje operacje są „legalne” z punktu widzenia pisania w czasie kompilacji, ale nie oznacza to, że będą działać w czasie wykonywania!

File f = (File)(Object) "Stupid cast";

Kompilator pozwoli na to, nawet jeśli nie ma to sensu, ale ulegnie awarii w czasie wykonywania z następującym wyjątkiem:

Exception in thread "main" java.lang.ClassCastException:
    java.lang.String cannot be cast to java.io.File
Christophe Roussy
źródło
3

Rzutowanie referencji będzie działać tylko wtedy, gdy jest to instanceoften typ. Nie możesz przesyłać losowych odniesień. Musisz też przeczytać więcej Casting Objects.

na przykład

String string = "String";

Object object = string; // Perfectly fine since String is an Object

String newString = (String)object; // This only works because the `reference` object is pointing to a valid String object.
asgs
źródło
3

Właściwy sposób jest następujący:

Integer i = Integer.class.cast(obj);

Ta metoda cast()jest znacznie bezpieczniejszą alternatywą dla rzutowania w czasie kompilacji.

yegor256
źródło