Kolekcje - Guava vs Java

0

Konwencja czy też używanie kolekcji w Javie sprowadza się do tego, że po obu stronach trzeba użyć "ostrego" nawiasu i zadeklarować typ kolekcji.

Map<SomeImmutableType, SomeType> someMap = new HashMap<SomeImmutableType, SomeType>();

Natomiast używając biblioteki Guava, możemy uprościć sobie takie tworzenie, które ogranicza się do deklaracji typów kolekcji tylko z lewej strony.

Map<SomeImmutableType, SomeType> someMap = Maps.newHashMap();

I tu pojawia mi się pytanie. Jak to jest możliwe. Skoro Maps jest klasą statyczną i nie ma podanych deklaracji, że ona wie jakiego typu ma zwrócić mapę?

Tylko nie bijcie :)

1

Inferencja typów w kompilatorze Javy.

0
szarotka napisał(a):

po obu stronach trzeba użyć "ostrego" nawiasu i zadeklarować typ kolekcji.

Map<SomeImmutableType, SomeType> someMap = new HashMap<SomeImmutableType, SomeType>(); 

Nic nie trzeba, można zrobić tak:

Map<SomeImmutableType, SomeType> someMap = new HashMap(); 

W takim razie czemu wymieniają to jako jedną z zalet Guavy - łatwe tworzenie kolekcji?

Inferencja? Możesz coś więcej powiedzieć jak to działa ewentualnie podrzucić coś sensownego do poczytania?

0

Dokładnie, chodzi o inferencję typów. Szczegóły np tutaj: https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html

0
Wibowit napisał(a):

Dokładnie, chodzi o inferencję typów. Szczegóły np tutaj: https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html

Dziękuję.

@Wibowit, a czy w powyższym przykładzie (3 posty wyżej) nie ma błędu i nie powinno być przypadkiem tak:

Map<String, List<String>> myMap = new HashMap**<>**();
1

To co pokazała @szarotka to generalnie błędne użycie generyków (aczkolwiek nie spowoduje błędu kompilacji i nawet można z tym żyć). Zawsze należy używać nawiasów ostrych przy wywoływaniu konstruktora. Od Javy 7 można użyć diamond operator, który pokazałeś - skraca on składnię, bo nie trzeba powtarzać identycznej parametryzacji.

0

@Wibowit można z tym żyć? Jak to?

Tak właśnie zbadałem jeszcze bardziej temat i widzę, czemu w Guave np. Lists.newArrayList() nie ma <>,
bo ona zwraca wlaśnie new ArrayList<>();

@GwtCompatible(serializable = true)
public static <E> ArrayList<E> newArrayList() {
    return new ArrayList<E>();
}
 

Zastanawia mnie jedna rzecz. To pierwsze <E>.

Skoro mamy oznaczone, że lista będzie jakiegoś typu ArrayList<E> i zwracany jest też z <E> to na cholerę jeszcze po static to dajemy?

0

static na metodzie oznacza tyle, że nie musisz mieć instancji danej klasy aby wywołać tą metodę.

0

@NoZi nie o to pytałem. Przeczytaj raz jeszcze :)

1

Skoro mamy oznaczone, że lista będzie jakiegoś typu ArrayList<e> i zwracany jest też z <e> to na cholerę jeszcze po static to dajemy?

Dajemy <E> bo to jest metoda generyczna, więc przed nazwą funkcji stoją dwa elementy - jeden to generyczne typy użyte w sygnaturze funkcji, drugi to typ zwracany. Oprócz metod generycznych są klasy generyczne i tam generyczne typy podajesz po nazwie klasy (klasa nie ma typu zwracanego, więc nie ma elementu opisującego tenże typ).

Podany typ generyczny nie musi też występować w typie zwracanym. Może być np metoda generyczna typu:

<T extends Number> void showNumber(T number) { ... }

@Wibowit można z tym żyć? Jak to?

Można żyć, bo taki zapis generuje jedynie ostrzeżenie kompilatora. W Javie jest wymazywanie typów (ang. type erasure). Oznacza to, że po kompilacji typy generyczne są wymazywane i istnieje tylko jedna instancja każdej klasy (zamiast różnych instancji dla różnych parametryzacji). Z drugiej strony kompilator automatycznie wrzuca odpowiednie rzutowania, więc przy błędnym użyciu generyków dostaniemy ClassCastException. Klasa typu class X<E> po wymazywaniu typów będzie wyglądać tak: class X. Czyli np new HashMap<K, V> będzie zamienione na new HashMap, z tym, że w odpowiednich miejscach w kodzie będą wstawione rzutowania (na etapie kompilacji). Wymazywanie można zrobić ręcznie przypisując surowy typ do typu sparametryzowanego. Wygeneruje to jednak ostrzeżenie kompilatora - przy dodaniu odpowiedniej flagi (Xlint:unchecked czy jakoś tak) kompilator zacznie więcej mówić nt sprawy. Przy braku unchecked warnings w naszym kodzie możemy być w zasadzie pewni, że nie dostaniemy ClassCastException w naszym kodzie podczas używania generyków. Jeśli jednak zastosujemy błędne konstrukcje to kompilator już takiej gwarancji nie może dać. Z tego względu warto unikać unchecked warnings, by nie narażać się niepotrzebnie na strzał w stopę.

W pewnym sensie, jeżeli pomniemy parametryzację klasy generycznej to jest to tożsame z wstawieniem Object jako typów. Jednak tylko w pewnym sensie, bo kompilator nie sprawdza kowariancji, inwariancji, etc (uporządkowania typów: https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) )

0
Krwawy Młoteczek napisał(a):

Skoro mamy oznaczone, że lista będzie jakiegoś typu ArrayList<E> i zwracany jest też z <E> to na cholerę jeszcze po static to dajemy?

Przeczytałem, raz jeszcze. I odpisałem Ci na pytanie. Chyba nie rozumiesz o co pytasz w takim razie.

0

@NoZi w przeciwieństwie do niektórych forumowiczów rozumiem, że czasami nie czyta się ze zrozumieniem. Ba. Sam mam ten problem więc wybaczam :D
@karolinaa kolejna, która nie zrozumiała i wytyka mi tu od podstaw :P

@Wibowit, dziękuję Ci za bardzo obszerny post. W zasadzie wszystko zrozumiałem, ale jeszcze zainteresowałem się - kowariancją, inwariancją.
Zapewne to jest coś czego używam, ale nie jestem świadom.
No, ale najważniejsze, że chcę zgłębić.

0

Tablice w Javie są kowariantne, tzn. że jeśli mamy typ

Integer

który jest pod typem klasy Number

 to znaczy że tablica <code class="java">Integer[]

jest podtypem Number[]


Generyki nie są kowariantne, tzn. że <del>Collection<Integer></del> 
```java
List<Integer>

nie jest podtypem List<Number>

Tak w telegraficznym skrócie.
0

@NoZi dzięki wielkie. Jeszcze o tym poczytam.
@Wibowit jeszcze raz dzięki.

Poza tematem:

Kurde skąd się biorą tacy ludzie. Nie mają nic do powiedzenia w konkretnym temacie i pieprzą trzy po trzy jak @karolinaa z tymi cząsteczkami.
Bierz przykład z @NoZi, bo przyznał się do "błędu" i przeprosił :P

Gdzie się podziała ta kultura. Ah...

0

@Wizzie widzę właśnie
@karolinaa I właśnie o to się rozchodziło. Moje pytanie nie było o static. Tak jak wcześniej @Wibowit poprawił @NoZi
Ale faktycznie nie ma co karmić.

0

@Wibowit a mam taki przykład jeszcze z Guavy:

  public static <T> Optional<T> absent() {
    return Absent.withType();
  }

Wywołanie jej wygląda tak:

Optional.<JakisMojObiekt>absent()

Zatem. Metoda absent pomiędzy static, a Optional ma zadeklarowane, użycie generyka (tak jak wcześniej mi tłumaczyłeś), następnie jest typ zwracany Optional<T> (bo to klasa abstrakcyjna generyczna). I jakim cudem działa to dalej?

Absent.withType();

Nie ma już nigdzie niczego zdefiniowanego. W sensie wewnątrz withType() jest, ale akurat ta linijka powyżej nie ma. Dlaczego nie ma tak:
Skoro tutaj robimy tak:

Optional.<JakisMojObiekt>absent()

To, to:

Absent.withType();

nie wygląda tak:

Absent.<T>withType();

Mam nadzieję, że nie pokręciłem :P

0

Znowu działa inferencja typów. Możesz też zrobić coś takiego:

Optional<String> stringOpt = Optional.absent();

i to zadziała.

0

Właśnie wiem. Ale czemu skoro nie ma "diamond operator"?

No i czy mógłbyś wytłumaczyć to co pisałem w poście powyżej? To, że te oznaczenia generyczne są "gubione" przy wywołaniu kolejnych metod (wchodząc głębiej).

0
Krwawy Młoteczek napisał(a):

Właśnie wiem. Ale czemu skoro nie ma "diamond operator"?

https://en.wikipedia.org/wiki/Type_inference

W największym skrócie - kompilator, na podstawie typu zmiennej, jest w stanie określić z jakim typem powinna być wywołana metoda.

0

@wartek01 a czytałeś temat cały?

@Wibowit też dał mi linka do inferencji, ale ustaliliśmy to, że w Javie trzeba dodać "diamond operator" <>.

https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html
tak jak tu

Map<String, List<String>> myMap = new HashMap<>();

a w przykładzie dwa posty wyżej nie ma <>

0

Ok. Doczytałem.

Odpowiedź:

Target Types

The Java compiler takes advantage of target typing to infer the type parameters of a generic method invocation. The target type of an expression is the data type that the Java compiler expects depending on where the expression appears. Consider the method Collections.emptyList, which is declared as follows:

static <T> List<T> emptyList();

Consider the following assignment statement:

List<String> listOne = Collections.emptyList();

This statement is expecting an instance of List<String>; this data type is the target type. Because the method emptyList returns a value of type List<T>, the compiler infers that the type argument T must be the value String. This works in both Java SE 7 and 8. Alternatively, you could use a type witness and specify the value of T as follows:

List<String> listOne = Collections.<String>emptyList();

However, this is not necessary in this context.
0

W sumie gdyby generyki istniały od początku Javy to pewnie wywołanie konstruktora klasy generycznej bez nawiasów ostrych zachowywałoby się podobnie jak dzisiaj wersja z diamond operator. Sprawa jest taka, że obecnie wywołanie konstruktora klasy generycznej bez podania nawiasów ostrych w ogóle skutkuje utworzeniem raw type. Surowy typ pasuje do wszystkich typów generycznych co podkopuje gwarancje kompilatora co do poprawności generyków.

Więcej o raw types: https://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html

1 użytkowników online, w tym zalogowanych: 0, gości: 1