Hej. Byłem ostatnio na rozmowie rekrutacyjnej i myślę, że warto podzielić się tu moim doświadczeniem. Na początku zaznaczę, że była to rozmowa na stanowisko Java Developera – stricte Java – nie padały pytania o frameworki. Rekruterzy skupili się na pytaniach wymuszających logiczne myślenie (no kto by się spodziewał) i dopytywali się o wiedzę odnośnie core’owych elementów Javy – co, jak i dlaczego. Nawiasem mówiąc bardzo podobał mi się taki styl prowadzenia rekrutacji.
Jeszcze słowo odnośnie nietechnicznych aspektów. Rozmowa była prowadzona przez 2 developerów (tech leadów) i trwała około 2 godzin. Zaoferowali mi całkiem fajną ofertę ale niestety nie godzili się na pracę zdalną w 100% więc się nie dogadaliśmy.
Z tej rozmowy na bloga chcę przelać jedno z ciekawszych pytań jakie padło i jedno zadanie jakie musiałem wykonać robiąc live-coding (btw. poległem na tym zadaniu bardzo bardzo…). Pytanie opiszę w tym artykule a zadanie pojawi się za dwa tygodnie w sobotę.
Ok… o co chodzi z tym pytaniem? Prymitywy-Wrappery-Obiekty 🤷♂️❓. Miałem przedstawiony następujący kawałek kodu
1 2 3 4 5 6 7 8 9 10 11 | public class Decrementer { private void decrement(double x) { x = x - 1.0; } public static void main(String[] args) { double x = 12.3; Decrementer decrementer = new Decrementer(); decrementer.decrement(x); System.out.println(x); } } |
I dostałem pytanie: Co zostanie wypisane na ekranie?
- a) 12.3
- b) 0
- c) 11.3
Pytanie raczej z gatunków proste – btw. zanim przeczytasz dalej, sam na nie odpowiedz (albo napisz w komentarzu, żeby dać mi satysfakcję że nie piszę do ściany). Szybka analiza kodu i wiedza, że Java działa pass-by-value daje nam oczywiście odpowiedź a) 12.3.
A teraz pytanie co się stanie jak kod zmienimy w następujący sposób? (zmiana prymitywów na Double)
1 2 3 4 5 6 7 8 9 10 11 | public class Decrementer { private void decrement(Double x) { x = x - 1.0; } public static void main(String[] args) { Double x = 12.3; Decrementer decrementer = new Decrementer(); decrementer.decrement(x); System.out.println(x); } } |
Java działa pass-by-value ale jeżeli przesyłamy obiekt i modyfikujemy coś w nim (najczęściej przez settery) to stan obiektu powinien się zmienić nawet po wyściu z metody, tak? To takie trochę pytanie retoryczne bo odpowiedź brzmi TAK. Więc co teraz? Jaka będzie odpowiedź? …. <chwilka na namysł>. Ponownie prawidłową odpowiedzią jest a) 12.3. Dlaczego? Jeżeli nie wiesz (ja się na tym wywaliłem, bo nie wiedziałem co tak naprawdę robi wrapper double’a) to spójrz na linię 3. Nie używamy tu żadnego setter’a do zmiany stanu obiektu, używamy tylko znaku przypisania =. Ba… więcej. Zajrzyjmy do klasy Double i zobaczmy jakie metody są tu dostępne.
Widzisz jakieś metody do zmiany stanu wewnętrznej zmiennej (value)? Raczej nie i nie szukaj bo nie znajdziesz. Wszystkie wrappery zostały celowo zaprojektowane tak aby nie dało się zmienić ich stanu wewnętrznego. Dlaczego? Taką decyzję projektową podjęły osoby, które to implementowały – nie ma na to jasnej odpowiedzi. Jednym z plusów takiego rozwiązania jest na pewno to, że możemy działać bezpiecznie z wieloma wątkami dzięki temu, że klasa jest niemutowalna. Więc co tak naprawdę się stało, że mimo wszystko przypisanie do zmiennej x, która była typu Double zadziałało wewnątrz metody i że po wyjściu z tej metody mieliśmy tę samą wartość co poprzednio? <Dzięki Cepewka> Zadziałał tu wcześniej wspominany mechanizm pass-by-value i bardzo polecam prześledzić sobie dokładnie tą odpowiedź na stackoverflow, która fajnie obrazkowo pokazuje dlaczego to działa właśnie w ten sposób. I nie ma to znaczenia czy prześlemy tu obiekt wrapujący czy jakikolwiek inny. Autoboxing i Unboxing, czyli automatyczna konwersja, której dokonuje kompilator Javy dzięki czemu my – developerzy – mamy czystszy kod. Dla bardziej dociekliwych – oto jak wygląda byte code metody decrement
1 2 3 4 5 6 7 | 0 aload_1 1 invokevirtual #2 <java/lang/Double.doubleValue> 4 dconst_1 5 dsub 6 invokestatic #3 <java/lang/Double.valueOf> 9 astore_1 10 return |
To co nas interesuje to linie 1 (unboxing) i linia 6 (autoboxing). Jak widzisz pomimo tego, że wysyłaliśmy Double’a to jednak kompilator pod spodem robi trochę niewidocznej roboty… Nasza wartość została na początku skonwertowana do typu prymitywnego (java/lang/Double.doubleValue), następnie została wykonana operacja odejmowania i z powrotem dostajemy klasę wrapującą (java/lang/Double.valueOf). Myślę, że warto wiedzieć takie rzeczy… prędziej lub później (w moim przypadku później :D) :).
Jeżeli chodzi o pytania z rozmowy odnośnie tego zagadnienia to właściwie na tym się skończyło. Po tym jak poległem z brakiem mojej wiedzy o klasach wrapujących, myślę, że rekruterzy nie do końca chcieli ciągnąć ten wagonik. Jednak wydaje mi się, że mogłoby tu się pojawić pytanie, co zrobić, żeby móc zmodyfikować stan tego obiektu. I można tu udzielić dwóch odpowiedzi. Pierwsza to oczywiście zwracać wartość z metody i przypisywać ją do nowej zmiennej. Druga opcja to dodanie pola do klasy Decrement przechowującego wartość i modyfikowanie tego pola w ramach metody decrement a następnie odwoływanie się do niego w metodzie wypisującej wynik na ekran (tylko trzeba pamiętać, że to już nie jest thread-safe). O coś takiego
1 2 3 4 5 6 7 8 9 10 11 12 | public class Decrementer { Double x; private void decrement(Double x) { this.x = x - 1.0; } public static void main(String[] args) { Double x = 12.3; Decrementer decrementer = new Decrementer(); decrementer.decrement(x); System.out.println(decrementer.x); } } |
To na tyle w tym tygodniu :). Dzięki za przeczytanie i standardowo zapraszam za tydzień o 12:00 (sobota), gdzie pojawi się wpis o najbardziej przydatnych (moim zdaniem) skrótach klawiszowych do IntelliJ.
[…] Dziś przedstawię Ci temat o którym wspominałem 2 tygodnie temu tutaj – czyli zadanie praktyczne jakie dostałem na rozmowie […]
link nie dziala…
To nie ma nic wspólnego z boxingiem lub unboxingiem, jakbyś zrobił analogiczne zadanie na stringach, to byłby taki sam efekt: https://ideone.com/G7C1Jg
Tu chodzi o to, że w Javie typy referencyjne są przekazywane przez wartość referencji (co wielu niepoprawnie nazywa call by reference, a co poprawnie nazywa się call by share).
Dzięki. Poprawiłem wpis.