Na jednej z rozmów kwalifikacyjnych otrzymałem ciekawe pytania dotyczące działania mechanizmów dostępnych w Javie. Jedno z nich naprawdę zapadło mi dobrze w pamięci. Brzmiało ono następująco: “Czy null może być kluczem w HashMap oraz ConcurrentHashMap?”. Przyznam szczerze, że nie umiałem odpowiedzieć na to pytanie, bo nigdy nie przyszło mi do głowy, aby coś takiego spróbować zrobić. Jednak jak przystało na programistę żyłka ciekawości nie dała mi spokoju. Postanowiłem to sprawdzić, a przy okazji podzielić się z Wami odpowiedzią na to pytanie.
Oficjalna dokumentacja Javy
HashMap
Wchodząc na stronę oficjalnej dokumentacji Javy znajdziemy wpis dotyczący HashMap. Już pierwszy akapit jest dla nas wartościowy w temacie dzisiejszego artykułu.
Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.
Dostajemy informację, że HashMap pozwala na przechowywanie kluczy oraz wartości w postaci nulla. Jak dodatkowo informują twórcy ta implementacja jest bardzo podobna do rozwiązania Hashtable z wyjątkiem tego, że nie jest synchronizowana oraz zezwala właśnie na nulle.
ConcurrentHashMap
W przypadku ConcurrentHashMap musimy się bardziej wgryźć w to co jest napisane w dokumentacji. Dopiero po pewnym czasie lektury dowiadujemy się, że w przypadku tego rozwiązania trzymanie null jako klucza nie jest dozwolone (także wartość w mapie nie może być null).
Like Hashtable but unlike HashMap, this class does not allow null to be used as a key or value.
Od razu nasuwa się pytanie - skąd takie ograniczenia? Dlaczego jedna implementacja mapy pozwala na trzymanie nulla, a druga nie? Odpowiedź znajdziemy na niezawodnym StackOverflow.
Źródło wiedzy - StackOverflow
Użytkownik Bruno udzielił precyzyjnej odpowiedzi posługując się słowami samego Doug Lea.
The main reason that nulls aren’t allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can’t be accommodated. The main one is that if map.get(key) returns null, you can’t detect whether the key explicitly maps to null vs the key isn’t mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls.
Wychodzi na to, że używając map.get(key)
i otrzymując null do końca nie wiemy czy to wynika z tego, że wartością dla danego klucza jest null czy po prostu brakuje tego klucza w mapie. To jednak dalej nie tłumaczy faktu, dlaczego w HashMap jest możliwe umieszczanie null. Dopiero ostatnie zdanie daje nam pogląd na całą sytuację. W niewspółbieżnej implementacji możemy wywołać map.contains(key)
i sprawdzić czy faktycznie mapa zawiera taki klucz czy nie. Natomiast dla rozwiązań wielowątkowych użycie contains
, a potem get
nie jest atomowe, więc stan mapy może się zmienić pomiędzy tymi wywołaniami. Z tego powodu twórca ConcurrentHashMap zabronił wstawiania null jako wartości.
No dobra, ale co z kluczem mapy?
W wątku na StackOverflow, który przytoczyłem właśnie w ten sposób uzasadniali brak możliwości umieszczania null jako klucza oraz wartości w ConcurrentHashMap. Jeśli chodzi o wartość to ma to jak największy sens, ale dla klucza już nie do końca. Postanowiłem jeszcze bardziej zgłębić temat i oto co znalazłem. Jedną z tez jest to, że ConcurrentHashMap miał za zadanie wiernie odtworzyć implementację Hashtable, co w takich słowach opisano w dokumentacji.
This class is fully interoperable with Hashtable in programs that rely on its thread safety but not on its synchronization details.
Dlaczego jednak nie można wrzucić klucza null do Hashtable? Użytkownika Gautam Kumar uzasadnił to tak, że ówcześni inżynierowie Javy chcieli zniechęcić programistów do tego zabiegu bądź też nie zdawali sobie sprawy z jego przydatności. Natomiast użytkownik DaoWen tłumaczy, że ta restrykcja została wprowadzona ze względów wydajnościowych. Z kolei Khalid Nouh skomentował, że skoro dana implementacja mapy jest synchronizowana to nie może trzymać null jako klucza, bo nie można się blokować na braku wartości. Oczywiście jeszcze inni podają uzasadnienie takie, że w kodzie po prostu jest sprawdzenie czy klucz jest null. Jednak to nie tłumaczy intencji za tym stojącej.
Podsumowanie
Dzięki rozmowom kwalifikacyjnym możemy nie tylko sprawdzić swój aktualny poziom wiedzy, ale również uświadomić sobie jak wiele jeszcze nie wiemy. Ja programuję już naprawdę sporo w Javie, a nadal potrafię być zaskoczony swoją niewiedzą w niektórych tematach. Mam nadzieję, że ten wpis nie tylko przedstawił Ci ciekawostkę na temat mechanizmów Javy, ale również zachęci Cię do weryfikowania swoich umiejętności poprzez aplikowanie na stanowiska dostępne na rynku pracy.