Wyślij zapytanie Dołącz do Sii

Jedną z nowości wprowadzonych w języku Java w wersji 10 jest mechanizm określany jako Local Variable Type Inference, w skrócie var. Pozwala on na wnioskowanie typu zmiennej lokalnej na podstawie kontekstu w jakim została użyta.

Wcześniejsze wersje języka Java wymagały deklaracji typu dla zmiennych lokalnych. Nowsze wersje określają typ zmiennej lokalnej w trakcie kompilacji na podstawie typu, którym są one inicjowane.

Wykorzystanie var w deklaracji zmiennej lokalnej

Użycie var w deklaracji zmiennej lokalnej oznacza, że programista zostawia ustalenie typu kompilatorowi, który sprawdzając kontekst użycia zmiennej, wnioskuje dla niej typ.

Poniżej przykład pokazuje proste użycie var.

proste użycie var

W przykładzie tym należy zwrócić uwagę na kilka istotnych kwestii:

  1. Typ zadeklarowany przez kompilator to ArrayList<String>, a nie List<String>.
  2. Charakterystyczne dla tego typu inicjalizacji zmiennej jest podanie typu dla elementów przechowywanych w ArrayList <String> w celu precyzyjnego ustalenia typu przechowywanego w liście. Brak tego typu powoduje przyjęcie Object jako typu elementów listy.

Przykład poniżej przedstawia opisywaną w punkcie drugim sytuację z punktu widzenia kodu źródłowego oraz wygenerowanego przez kompilator kodu bajtowego w przypadku, kiedy zdefiniujemy konkretny typ w liście lub gdy go nie podamy.

kod
kod

Wcześniejsze wersje var

Mechanizm wnioskowania typów używany przez var nie jest nowością w języku Java. Jego pierwsze użycie miało miejsce już w wersji 5 tego języka. Poniżej przykład działania mechanizmu wnioskowania w Java 5 oraz wygenerowany kod bajtowy.

kod
kod

W kolejnych wersjach języka rozszerzono zakres użycia mechanizmu wnioskowania o notację „diamentową” <> oraz obsługę parametrów w wyrażeniu lambda. Programista nie musi definiować typów parametrów dla takiego wyrażenia. W wersji 10 wprowadzono var, umożliwiając programiście tworzenie zmiennych lokalnych bez deklaracji typu.

Jaki jest cel var?

Podstawowym celem var jest zwiększenie czytelności kodu i zwiększenie efektywności programistów. Poniżej kilka przykładów użycia var.

kod

Zastosowanie var zostało ograniczone przez twórców języka Java do zmiennych lokalnych zdefiniowanych w metodach.

Poprawne i błędne zastosowanie var

Poniżej przykład niewłaściwego użycia var na poziomie instancji klasy.

kod

W Java var to wciąż konkretny typ, tylko jest on wnioskowany przez kompilator na etapie kompilacji i pozostaje niezmienny w trakcie wykonywania programu.

Poniżej kolejny przykład niewłaściwego użycia var.

kod

Jak możemy zauważyć, powyższy kod nie skompiluje się, ponieważ programista próbuje przypisać do zmiennej typu int wartość, która jest łańcuchem znaków. W tym przypadku mamy więc do czynienia z niekompatybilnymi typami.

Dotychczas używaliśmy deklaracji zmiennej var w jednej linii, jednak poprawna jest również deklaracja w wielu liniach.

kod

Należy zwrócić uwagę na to, iż deklaracja i inicjalizacja muszą się odbyć w jednej instrukcji. Kod poniżej jest niepoprawny.

kod

Poniższy przykład również się nie skompiluje.

kod

Wnioskowanie typu z var

Przykład var i null

Wnioskowanie typu var polega na zdefiniowaniu ograniczeń dla danego typu i ich odpowiedniej interpretacji w oparciu o zasady opisane w specyfikacji języka Java. Istnieją jednak przykłady, gdzie wnioskowanie typów jest bardziej złożone lub niemożliwe. Rozważmy przykład z null.

kod

W tym przypadku wnioskowanie typu jest niemożliwe, ponieważ kompilator na podstawie wyrażenia nie jest w stanie określić ograniczeń, które pozwoliłyby mu wykryć typ. W tym przypadku jedyne możliwe rozwiązanie to jawne zdefiniowanie typu w postaci rzutowania.

kod

Oczywiście taki zapis jest nieczytelny i nie powinien być używany.

Przykład var i wyrażenie lambda

Użycie mechanizmu var z wyrażeniami lambda również może powodować problemy z wnioskowaniem typu. Poniżej niekompilujący się przykład z wyrażeniem lambda.

kod

Przykład var i nowej klasy anonimowej

Jeszcze innym ciekawym przypadkiem działania mechanizmu var jest poniższy przykład.

kod

Kod przedstawiony powyżej działa i zwraca wyniki: class App3:$1, Hello, 7. W tym przypadku kompilator na podstawie kontekstu i mechanizmu wnioskowania typu utworzył nową klasę anonimową (class App3:$1). W kodzie bajtowym wygenerowanym przez kompilator wygląda to następująco.

kod

Var i inicjalizacja tablic

Należy również zwrócić uwagę na użycie var z inicjalizacją tablic. Poniżej przykład poprawnego zastosowania.

kod

Przykłady niepoprawnego użycia var z tablicami.

kod

Kiedy nie możemy użyć var?

Nie możemy używać var jako parametru dla metody oraz jako wartości zwracanej z metody.

kod

Podobnie, nie możemy stosować var w try-catch.

kod

Również nie inicjujemy var za pomocą method reference.

kod

Zalety i wady zastosowania var

Jak wynika z powyższych przykładów twórcy języka Java musieli nałożyć wiele ograniczeń na obsługę var. Implementacja tego mechanizmu nie jest prosta i może zaskoczyć programistę, który nie jest świadomy czyhających na niego pułapek.

Nieumiejętne wykorzystanie var może również doprowadzić do sytuacji, gdy nasz kod będzie bardzo trudny do zrozumienia, szczególnie gdy nie będziemy używać zaawansowanego środowiska IDE. Z drugiej strony var pozwala uprościć kodowanie i powoduje, iż staje się ono bardziej zwięzłe.

Dobre praktyki używania var

Poniżej znajduje się lista zasad i dobrych praktyk, jakie powinny być zastosowane w trakcie używania var. Lista ta pochodzi z dokumentu Local Variable Type Inference Style Guidelines, którego autorem jest Stuart W. Marks. Dokument powstał w ramach projektu Amber. Jego celem było dostarczenie między innymi obsługi var w języku Java.

  • Czytanie kodu jest ważniejsze niż pisanie kodu.

Zgodnie z tą zasadą programiści przez dużą część czasu czytają kod, a jego zrozumienie jest kluczowe do implementacji nowych funkcjonalności lub poprawiania istniejących. Pisząc kod, programista jest odpowiedzialny za użycie konstrukcji językowych w taki sposób, by były one zrozumiałe dla innych programistów. W skrócie w myśl tej zasady zawsze musimy pamiętać, że piszemy kod dla innych, a nie dla siebie. Używając var, powinniśmy szczególnie zwrócić uwagę na to, czy w przyszłości ktoś będzie mógł w sposób przejrzysty odczytać nasze intencje i czy napisany kod np. nie jest zbyt długi lub użyte nazwy zmiennych są dostatecznie opisowe i przekazują intencje programisty w taki sposób, by inny programista mógł łatwo zorientować się, jakie jest ich przeznaczenie.

  • Kod powinien być łatwy do zrozumienia.

To założenie wiąże się z pierwszym. Programista, który czyta nasz kod, powinien być w stanie bardzo szybko go zrozumieć, a użycie var nie powinno mu w tym przeszkadzać. W idealnym przypadku programiście powinien wystarczyć fragment kodu do zrozumienia jego działania. Jeżeli proces zrozumienia kontekstu użycia zmiennej lokalnej zadeklarowanej jako var wymaga dłuższej analizy i poruszania się po dużej ilości kodu, należy zastanowić się, czy użycie var jest prawidłowe.

  • Czytelność kodu nie powinna zależeć od używanego IDE.

Jest to jedno z krytycznych założeń związanych z var, ponieważ często jako programiści zbytnio polegamy na narzędziach i stajemy się wręcz ich „zakładnikami”, zakładając, że inni też będą ich używać lub używają w takim samym stopniu jak my. Programując w określonym IDE, pewne rozwiązania stają się dla nas tak ewidentne, że uważamy, iż są one częścią języka programowania. Z tego powodu zawsze, kiedy piszemy kod, powinniśmy założyć, że będzie on czytany za pomocą zwykłego edytora tekstowego. W takim wypadku nazwa zmiennej var staje się bardzo ważna, ponieważ musi ona zawierać informacje nie tylko o przeznaczeniu zmiennej, ale również o jej typie.

  • Zastosowanie typów jawnych zależy od programisty.

Chociaż typy jawne mogą być bardzo pomocne, czasami nie są ważne, a innym razem po prostu przeszkadzają. Niekiedy użycie jawnego typu powoduje, iż kod zmniejsza swoją czytelność. Czasami lepiej jest zastosować bardziej opisową nazwą zmiennej niż używać deklaracji typu z jej nazwą, która często już wskazuje na użyty typ. I to założenie jest kluczowe dla wprowadzenia var w języku Java. Jawne definicje typów często powodują rozproszenie programisty i powodują, iż nie może on skupić się na biznesowym aspekcie, jaki jest realizowany przez określony kod.

  • Wybierz nazwy zmiennych, które dostarczają przydatnych informacji.

Jest to praktyka, która dotyczy całości powstającego kodu, jednak w przypadku użycia var jest ona szczególnie ważna. Programista usuwając informacje o typie, powinien również zastanowić się nad nazwą zmiennej, która w tym kontekście powinna zawierać informacje pozwalające na łatwe zidentyfikowanie roli i typu danej zmiennej. Poniżej przykład przedstawiający jak ważna jest nazwa zmiennej zdefiniowanej z pomocą var.

kod

Widzimy w nim jak trudne może być zidentyfikowanie typu zmiennej przez programistę, jeżeli nie ma on dodatkowych informacji. Czasami dodawanie informacji o typie do nazwy zmiennej jest nadmiarowe i wystarczy nadać tylko opisową nazwę.

Ryc. 23 - Local Variable Type Inference (var) w Java 10
  • Zminimalizuj zasięg zmiennych lokalnych.

Programując, powinniśmy unikać metod zawierających duże ilości linijek kodu, ale również skutecznie ograniczać zasięg zmiennych lokalnych. Poniżej przykład metody zawierającej dużo kodu.

kod

Jak możemy zauważyć, w chwili, gdy nasza metoda zawiera dużo kodu i definicja typu jest dość „daleko” od miejsca jej użycia, programista musi wykonać dodatkowe kroki, by dokładnie zdefiniować, z jakim typem ma do czynienia.  Z tego powodu zasięg zmiennych var powinien być jak najmniejszy.

Najlepiej, kiedy programista widzi deklaracje i użycie na jednym ekranie. Jeżeli nasza metoda zawiera dużo kodu, to lepiej rozważyć jej refaktor na mniejsze fragmenty (metody), zamiast unikać używania zmiennych var.

  • Rozważ użycie var, gdy inicjator dostarcza czytelnikowi wystarczających informacji.

Zmienne lokalne są często inicjowane za pomocą konstruktorów. Nazwa konstruowanej klasy jest zwykle powtarzana jako typ jawny po lewej stronie. Jeśli nazwa typu jest długa, użycie var zapewnia zwięzłość bez utraty informacji.

kod

Poprawnym jest również użycie var w przypadkach, gdy inicjator jest wywołaniem metody, takiej jak statyczna metoda fabryczna zamiast konstruktora, a jej nazwa zawiera wystarczającą ilość informacji o typie.

kod

W takich przypadkach nazwy metod silnie implikują określony typ zwracany, który pozwala programiście na szybkie określenie typu zmiennej.

  • Używaj var do rozdzielenia powiązanych lub zagnieżdżonych wyrażeń.

Poniżej znajduje się fragment kodu, który operuje na liście zawierającej łańcuchy znaków i faktycznie składa się z dwóch strumieni wykonywanych jeden po drugim.

kod

Jak możemy zauważyć, aktualna implementacja jest nieczytelna i nieuważny programista mógłby założyć, iż ma do czynienia z jednym strumieniem. W takim przypadku najlepiej użyć var i rozbić jeden strumień na mniejsze. Poniżej wynik takiego podziału.

kod

Dodając do tego odpowiednie nazewnictwo, otrzymujemy czytelny i łatwy do zrozumienia kod, który jednoznacznie wskazuje na intencje programisty. Jak możemy zauważyć na tym przykładzie, użycie var może się wiązać nie tylko z usunięciem informacji o typie, ale także z refaktoringiem kodu.

  • Nie zwracaj uwagi na interfejsy w zmiennych lokalnych.

Jak mogliśmy się przekonać we wcześniejszych przykładach mechanizm wnioskowania typów w języku Java nadaje zmiennym var bezpośredni typ.

kod

Możemy zauważyć, iż Java nie rozwija typu do jego interfejsu (abstrakcji) – w tym przykładzie do List<String> – a raczej deklaruje zmienną jako typ wynikający bezpośrednio z przypisania ArrayList<String>. W skrócie możemy napisać, iż Java wiąże kod z implementacją, a nie z abstrakcją.

Takie stwierdzenie może kłócić się z dobrymi praktykami mówiącymi, iż powinniśmy wiązać kod za pomocą abstrakcji, by uzyskać większą elastyczność. Jednak jest to prawdziwie założenie dla typów pól, typów parametrów, metod i typów zwracanych przez metody. W przypadku użycia lokalnego w metodach ma to mniejsze znaczenie.

  • Zachowaj ostrożność podczas używania var z notacją „diamentową” <> lub typami generycznymi.

Skutki użycia z var z notacją „diamentową” <> już były przedstawione w tym artykule. Jeżeli tworzymy zmienną var i używamy notacji „diamentowej”, automatycznie nie dostarczamy informacji o typie. Założeniem tej notacji jest to, iż informacja o typie jest dostarczona przez lewą stronę przypisania, a nie przez prawą. W przypadku var i notacji „diamentowej” nie mamy tej informacji ani po lewej stronie wyrażenia, ani prawej stronie. W takim przypadku Java rozwiązuje ten typ jako Object. Przykład poniżej prezentuje zachowanie mechanizmu wnioskowania z wykorzystaniem var i notacji „diamentowej”.

kod

Innym ciekawym przykładem jest ten przedstawiony poniżej.

kod

W tym przypadku wnioskowany typ to Object. Wynika to z tego, iż kompilator nie jest w stanie wywnioskować właściwego typu na podstawie kodu.

Poniżej mamy przykład wnioskowania na podstawie argumentu, który został dostarczony do metody, a następnie poprawnie rozwiązany do typu List<BigInteger>.

kod

Kolejną sytuację, jaką możemy przeanalizować, jest ta przedstawiona poniżej.

kod

Jak możemy zauważyć w powyższym przykładzie niedostarczenie informacji o typie, skutkuje tym, iż kompilator języka Java przyjmie typ List<Object> – w momencie, gdy chcemy wymusić typ, musimy go jawnie zadeklarować.

  • Zachowaj ostrożność, używając var z literałami.

Literały mogą być używane jako inicjatory dla var. Użycie var w takim kontekście jest mało użyteczne, ale możliwe. Większość typów, tak jak to zostało przedstawione na przykładzie poniżej, jest rozwiązywana poprawnie.

kod

Należy być jednak bardzo ostrożnym w momencie, kiedy chcemy używać var z literałami całkowitymi. Poniżej przykład, który pokazuje możliwe konsekwencje takiego przypisania.

kod

Literały zmiennoprzecinkowe są w większości jednoznacznie zdefiniowane.

kod

Podsumowanie

Użycie var dla deklaracji zmiennych lokalnych może ulepszyć kod poprzez zwiększenie jego czytelności w postaci bardziej opisowych nazw. Z drugiej strony bezkrytyczne stosowanie var może pogorszyć sytuację.

Używany właściwie var może pomóc ulepszyć kod, czyniąc go krótszym i bardziej przejrzystym, bez uszczerbku dla jego zrozumienia.

Podstawowym kryterium, jakie programista powinien rozważyć przy użyciu var, jest czytelność kodu i jego zrozumienie przez innych programistów.

Użycie var w kodzie istniejącym zawsze powinno być poprzedzone analizą skutków takiej zmiany oraz wykonaniem odpowiedniego refaktoringu. Ważnym elementem użycia var jest odpowiednie zdefiniowanie zmiennej, tak by kompilator miał możliwość wywnioskować właściwy typ.

Ostatni przykład pokazuje różnicę w czytelności kodu z użyciem var i bez.

kod

Literatura

Inspiracją do napisania powyższego artykułu były artykuły przedstawione poniżej – szczególnie artykuł Braiana Goetza.

Celem powyższego artykułu jest zebranie ważnych informacji na temat użycia mechanizmu Local Variable Type Interfacje w jednym miejscu oraz dodanie dodatkowych informacji, których często brakuje w innych artykułach – mianowicie spojrzenie z punktu widzenia wygenerowanego kodu bajtowego, co w mojej ocenie pozwala na lepsze zrozumienie tego mechanizmu.

***

Jeśli interesuje Cię tematyka Java i JavaScript, to zachęcamy do zapoznania się z innymi artykułami naszych ekspertów np. Wyrażenie lambda w Javie oraz WebRTC: what stands behind most video conferencing software? Setting up a simple connection with JavaScript/Kotlin.

Ocena:
Autor
Avatar
Radosław Krzywiecki

Pracuje w Sii z roczną przerwą od 3 lat. Aktualnie jako Software Engineer. W Sii zajmuje się głównie programowaniem w Javie. Czasami zdarza się mu pracować jako Team Leader oraz Architekt. Oprócz Javy interesuje się też tematami DevOps. Programuje również w GO i JavaScript.

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany.

Może Cię również zainteresować

Pokaż więcej postów

Bądź na bieżąco

Zapisz się do naszego newslettera i otrzymuj najświeższe informacje ze świata Sii.

Otrzymaj ofertę

Jeśli chcesz dowiedzieć się więcej na temat oferty Sii, skontaktuj się z nami.

Wyślij zapytanie Wyślij zapytanie

Natalia Competency Center Director

Get an offer

Dołącz do Sii

Znajdź idealną pracę – zapoznaj się z naszą ofertą rekrutacyjną i aplikuj.

APLIKUJ APLIKUJ

Paweł Process Owner

Join Sii

ZATWIERDŹ

This content is available only in one language version.
You will be redirected to home page.

Are you sure you want to leave this page?