To jest jeden z artykułów w ramach darmowego kursu programowania w Javie. Proszę zapoznaj się z pozostałymi częściami, mogą one być pomocne w zrozumieniu materiału z tego artykułu.
Rzutowanie
Zaczniemy od rzutowania (ang. cast). Jak już wiesz kompilator Javy ma pewną wiedzę na temat tego, jaki rodzaj obiektu kryje się pod daną referencją w trakcie kompilacji. Wie to z typu zmiennej do której przypisany jest dany obiekt. Jednak możliwa jest sytuacja kiedy pod referencją typu X przypisany jest obiekt typu Y jak w przykładzie poniżej:
public void differentTypes() {
Object objectInstance = new Object();
Object stringInstance = "string";
}
Obie referencje są typu Object
, jednak druga z nich przechowuje zmienną typu String
. Jest to w 100% poprawny kod. Jednak jeśli na zmiennej stringInstance
chciałbyś wywołać metodę, którą implementuje klasa String
a nie ma jej w klasie Object
skończy się to błędem kompilacji:
Error:(15, 23) java: cannot find symbol
symbol: method length()
location: variable stringInstance of type java.lang.Object
Jak zatem wywołać taką metodę? Przecież jesteśmy pewni, że pod zmienną stringInstance
kryje się obiekt typu String
. Tutaj z pomocą przychodzi rzutowanie. Java pozwala rzutować typ A na typ B używając wyrażenia rzutowania, możesz je zobaczyć w przykładzie poniżej:
public void differentTypes() {
Object objectInstance = new Object();
Object stringInstance = "string";
String realString = (String) stringInstance;
realString.length();
}
Oczywiście nie każde rzutowanie jest poprawne. W przykładzie poniżej możesz zobaczyć błędne rzutowanie z typu Object na typ String
. Tego typu operacje kończą się wyjątkiem w trakcie wykonania programu:
public class RuntimeType {
public static void main(String[] args) {
Object[] someMysteriousObjects = new Object[] {"1234", new Object()};
String castedString = (String) someMysteriousObjects[0];
String classCastException = (String) someMysteriousObjects[1];
}
}
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
at pl.samouczekprogramisty.kursjava.RuntimeType.main(RuntimeType.java:8)
Wyjątek mówi tyle, że obiektu typu java.lang.Object
nie możemy rzutować do typu java.lang.String
. Jakie rzutowanie jest w takim razie dozwolone? Możemy rzutować wyłącznie na typ, który znajduje się hierarchii dziedziczenia danego obiektu. Z tego właśnie powodu rzutowanie String
na Object
jest dopuszczalne ale odwrotna operacja kończy się błędem.
W jednym z kolejnych artykułów przeczytasz o typach generycznych, które pomagają rozwiązać część sytuacji, kiedy rzutowanie jest potrzebne. W codziennym programowaniu radziłbym unikać tego typu operacji. Z pewnością istnieje inny sposób napisania programu, który pozwoli na uniknięcie rzutowania.
Pobierz opracowania zadań z rozmów kwalifikacyjnych
Przygotowałem rozwiązania kilku zadań algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.
Konwersja
Rzutowanie to specyficzny przypadek konwersji, jest to konwersja jawna, wymuszona przez programistę. W trakcie niektórych operacji może dochodzić do automatycznej konwersji, konwersji niejawnej. Konwersja niejawna może wystąpić na przykład podczas wywołania metod czy operacji arytmetycznych. Poniżej przykład konwersji automatycznej, która zachodzi w trakcie wywołania metody:
public class WideningConversion {
public static void main(String[] args) {
WideningConversion conversion = new WideningConversion();
int intVariable = Integer.MAX_VALUE;
long longVariable = Long.MAX_VALUE;
conversion.methodLongArgument(longVariable);
conversion.methodLongArgument(intVariable); // extending conversion
}
public void methodLongArgument(long argument) {
System.out.println(argument);
}
}
Dochodzi do konwersji ponieważ metoda methodLongArgument
przyjmuje parametr typu long
, a jedno z wywołań przyjmuje zmienną typu int
. Jest to tak zwana konwersja rozszerzająca. Może być wykonana niejawnie ponieważ podczas takiej konwersji nie zachodzi ryzyko utracenia informacji (o tym dalej). Kompilator robi to automatycznie za programistę. W przykładzie powyżej zmienna intVariable
została automatycznie rozszerzona do typu long
. Nie utraciliśmy żadnych informacji ponieważ typ long
zawsze może pomieścić liczby które przechowuje int
. W przykładzie używam zmiennych statycznych MAX_VALUE
, które są typu int
lub long
i trzymają największą liczbę możliwą do przechowywania przez dany typ.
Konwersja w odwrotną stronę wymaga już jawnego rzutowania. Taka konwersja może prowadzić do utraty informacji. Proszę spójrz na przykład:
public class NarrowingConversion {
public static void main(String[] args) {
NarrowingConversion conversion = new NarrowingConversion();
int intVariable = Integer.MAX_VALUE;
long longVariable = Long.MAX_VALUE;
long longVariableWithoutLoosingInformation = Integer.MAX_VALUE; // automatic conversion from int to long
conversion.methodIntArgument(intVariable);
conversion.methodIntArgument((int) longVariable);
conversion.methodIntArgument((int) longVariableWithoutLoosingInformation);
}
public void methodIntArgument(int argument) {
System.out.println(argument);
}
}
Jak myślisz co zostanie wypisane na konsoli po uruchomieniu tego programu?
2147483647
-1
2147483647
Dziwne prawda? :) Środkowa linijka to nic innego jak właśnie “utrata informacji”, która może zajść w trakcie jawnej konwersji1. Ostatnia linijka pokazuje, że nie każda konwersja z long
do int
prowadzi do utraty informacji.
Konwersja typów zmiennoprzecinkowych do całkowitoliczbowych
Innym przykładem konwersji w której dochodzi do utraty informacji jest konwersja z typów zmiennoprzecinkowych do typów całkowitoliczbowych:
int intValue = (int) 123.123F;
long longValue = (long) 456.456;
W obu przypadkach tracimy informację o ułamku, zostaje wyłącznie część całkowitoliczbowa.
Automatyczna konwersja podczas przypisania
Podobnie rzecz się ma w przypadku przypisania wartości zmiennej, tutaj także dochodzi do automatycznej konwersji. Poniższy przykład pokazuje kilka możliwych przypadków:
public class AssignmentConversion {
public static void main(String[] args) {
long longValue = 123;
int intValue = (short) 123;
float floatValue = 12.12F;
double doubleValue = floatValue;
System.out.println(longValue);
System.out.println(intValue);
System.out.println(floatValue);
System.out.println(doubleValue);
}
}
Pierwsza linijka metody main
to niejawna konwersja z typu int
na long
(literały całkowitoliczbowe domyślnie są typu int
), kolejna zawiera jawne rzutowanie 123 na typ short
, które następnie konwertowane jest niejawnie z powrotem na typ int
. Ostatni przykład to konwersja z typu float
do double
.
Automatyczna konwersja podczas operacji arytmetycznych
Podczas operacji arytmetycznych także może dochodzić do niejawnej konwersji. Zgodnie ze specyfikacją języka Java możliwa jest konwersja (zachodzi pierwszy pasujący warunek):
- rozszerzająca do typu
double
jeśli którykolwiek z elementów operacji arytmetycznej jest typudouble
, - rozszerzająca do typu
float
jeśli którykolwiek z elementów operacji jest typufloat
, - rozszerzająca to typu
long
jeśli którykolwiek z elementów operacji jest typulong
, - rozszerzająca do typu
int
.
Wszystkie cztery przypadki pokazane są w przykładzie poniżej:
public class ArithmeticConversion {
public static void main(String[] args) {
short shortValue = 1;
int intValue = 1;
long longValue = 2;
float floatValue = 3.1F;
double doubleValue = 4.1;
System.out.println(intValue + doubleValue);
System.out.println(intValue + floatValue);
System.out.println(intValue + longValue);
System.out.println(shortValue + shortValue);
}
}
Tutaj drobna dygresja, operator dzielenia (/) wykonuje w języku Java dzielenie całkowitoliczbowe jeśli dzielna i dzielnik są całkowitoliczbowe. Jeśli chcemy otrzymać typ zmiennoprzecinkowy co najmniej jeden z elementów musi być typu zmiennoprzecinkowego:
5 / 2 = 2
5.0 / 2 = 2.5
5 / 2.0 = 2.5
6 / 2 = 3
6.0 / 2 = 3.0
Boxing i unboxing
Jak wiesz w języku Java występują zarówno typy proste jak i obiekty reprezentujące liczby np. int
i Integer
. Kompilator Java jest w stanie dokonać konwersji pomiędzy odpowiadającymi sobie typami prostymi i obiektami automatycznie. Proszę spójrz na przykład poniżej
int primitiveInt = Integer.valueOf(123);
long primitiveLong = Long.valueOf(123L);
float primitiveFloat = Float.valueOf(123.123F);
double primitiveDouble = Double.valueOf(123.123);
boolean primitiveBoolean = Boolean.valueOf(true);
Mamy tu do czynienia z tak zwanym “unboxing‘iem”, czyli automatycznym odpakowywaniem obiektu do odpowiadającego mu typu prostego.
Integer objectInteger = 123;
Long objectLong = 123L;
Float objectFloat = 123.123F;
Double objectDouble = 123.123;
Boolean objectBoolean = true;
Przykłady powyżej pokazują z kolei “boxing”, czyli automatyczne tworzenie instancji obiektów na podstawie typów prostych.
Podczas boxingu/unboxingu może dość do rzucenia różnych wyjątków, na przykład gdy obiekt przypisywany do typu prostego jest nullem lub gdy do typu prostego próbujemy przypisać inny obiekt (np. Long
do int
).
Konwersja do typu String
Konwersja do typu String
jest specyficznym rodzajem konwersji automatycznej. Jest ona specyficzna ponieważ bazuje na metodzie toString
, która może być przedefiniowana przez programistę. Konwersja do typu String
zachodzi przy operatorze dodawania + jeśli któryś z dodawanych elementów jest typu String
.
Typy proste także są automatycznie konwertowane do typu String
, odbywa się to dwuetapowo, na początku zachodzi boxing następne obiekt konwertowany jest do typu String
(wywoływana jest metoda toString
).
String x = "123" + new Object();
String y = new Object() + "123";
String z = 1 + "123";
Zadania
- Napisz program przyjmujący od użytkownika liczbę całkowitą i wyświetl wynik mnożenia tej liczby oraz stałej pi (
Math.PI
). Wyświetl wynik w postaci liczby całkowitej i liczby zmiennoprzecinkowej. - Napisz program pobierający od użytkownika dwie liczby całkowite. Wyświetl wynik ich dzielenia wraz z częścią ułamkową.
- Napisz program, który skończy się wyjątkiem spowodowanym błędem podczas boxingu/unboxingu.
- Jak myślisz co otrzymasz przypisując zmienną typu
char
do zmiennej typuint
? Znajdziesz ten numer w tabeli ASCII?
Przygotowałem też dla Ciebie zestaw przykładowych rozwiązań zadań, analizując je także możesz się czegoś nauczyć.
Dodatkowe materiały do nauki
- https://docs.oracle.com/javase/specs/jls/se12/html/jls-15.html#jls-15.16 – “cast expression”,
- https://docs.oracle.com/javase/specs/jls/se12/html/jls-5.html – rzutowanie i konwersje,
- https://github.com/SamouczekProgramisty/KursJava/tree/master/11_konwersja_rzutowanie – kod źródłowy przykładów użytych w artykule.
Podsumowanie
Właśnie dowiedziałeś się o kilku kolejnych zakamarkach języka Java. Mam nadzieję, że Ci się podobało. Jak zwykle na koniec mam do Ciebie prośbę, podziel się artykułem ze swoimi znajomymi, zleży mi na dotarciu do jak największego grona czytelników. Jeśli nie chcesz pominąć żadnego postu polub Samouczka na Facebook’u. Do następnego razu! :)
-
Wartość -1 wynika ze sposobu zapisywania liczb w Javie. Wiesz już o binarnym zapisie, tutaj wykorzystywana jest jego specyficzna odmiana – uzupełnienia do dwóch, jeżeli jesteś zainteresowany szczegółami daj znać w komentarzu, skrobnę o tym artykuł :). ↩
Pobierz opracowania zadań z rozmów kwalifikacyjnych
Przygotowałem rozwiązania kilku zadań algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.
Zostaw komentarz