Java wildcard super T w strumieniach - jak to działa

0

Witam
załóżmy że mamy Liste Double

List<Double> list = Arrays.asList(4.3, 2.7, 5.6);

I teraz mam pytanie odnośnie wildcardów. Wiemy że w strumieniach jak mamy filter to używamy Predicate<? super T>. Moje pytanie brzmi - czemu to właścwie działa.
Np.

list.stream().filter(i -> i > 3.0).forEach(i -> System.out.println(i));

Z tego co wiem zapis: ? super T oznacza że dany obiekt jest albo klasy T albo jest klasa T rozszerza klase tego obiektu - może być to np. Object albo Number. Dlaczego właściwie kompilator sie nie rzuzic że coś nie halo, bo przeciez nie można wywołać Object.compareTo(3) :)
Wzywam @jarekr000000 @Shalom i @Koziołek

0

W interfejsie Predicate przecież nie ma metody compareTo tylko test. Predykatem dla Object albo Number możesz przetestować Double'a, więc filter pozwala na wrzucenie takiego predykatu.

Zabawa z super i extends w generykach ma związek z kowariancją i kontrawariancją.

0

@Wibowit: no w sumie racje, to był taki troche skrót myślowy. Jest test, ale ten test teoretycznie można wywołac na instacji Object a przecież Object nie implementuje compareTo. Super to ograniczenie z góry nie z dołu, stąd tego nie rozumiem. Pojęcia Covariance i Covariant tez nie są mi obce :) (chociaz brzmią tak skomplikowanie że mam problemy z ich zapamiętaniem :D )

1

Nie można wywołać "podając" Object jako instancję, albowiem twoja lista ma obiekty typu T. I inne nie wpadną nigdy jako argumenty metody test (Predykatu). Bo lista dysponuje tylko takimi (T ).
Natomiast jeśli zapodasz Predicate<Object> to ten predykat nadal bedzie działał dobrze z twoją listą elementów typu T. Bo T jest również typu Object. Ot i cała magia kontrwariancji.

0

@jarekr000000: no własnie tak żem czuł że chodzi o to że jak mamy streamy to nie ma możliwości wywołania tego na innym typie niż to ograniczenie z góry czyli T :)
Ale wolałem sie upewnić że mój tok rozumowania jest prawidłowy ;)

1

Gdyby filter przyjmował Predicate<T> to nie mógłbyś wykorzystać Predicate<Object> na strumieniu np Stringów. Strumień stringów wymagałby tylko i wyłącznie Predicate<String>. Dzięki temu, że filter przyjmuje Predicate<? super T> to możesz użyć Predicate<Object> na wszystkich typach strumieni. To ? super T zamiast zwykłego T daje ci taką możliwość - nie ogranicza możliwości strumienia, tylko je rozszerza.

Ma to w końcu sens dla ciebie?

Podobnie w klasie java.util.Collections mamy metodę:

public static <T extends Comparable<? super T>> void sort(List<T> list)

Dzięki takiej sygnaturze może mieć np LIstę obiektów typu X, gdzie X roszerza Y i implementuje Comparable<Y>. Porównywacz obiektów typu Y poradzi sobie z obiektami typu X - a odwrotnie nie.

0

@Wibowit: oczywiście masz racje. Tylko mi chodziło o to czemu kompilator nie ma pretensji i moge wywołac metody z klasy ktora jest górnym ograniczeniem Nie mniej jednak @jarekr000000 utwierdził mnie w przekonaniu że wynika to z faktu że jesli mamy Stream<T> to kompilator będzie wiedział że na pewno predicate będzie wywołany na T a nie jego rodzicu :)

0

Możesz to rozumieć tak, że jeśli generyczny typ jest postaci ? extends X to znaczy, że metoda ma być poprawna dla każdego typu, który jest podklasą X, bądź jest tożsamy X. Analogcznie dla ? super X - wtedy metoda ma być poprawna dla każdego typu, który jest nadklasą X, bądź jest tożsamy X.

W przypadku filter mamy Predicate<? super T>. Oznacza to, że dla każdego typu będącego nadklasą T, bądź tożsamego T metoda ma być poprawna. Chyba nietrudno wydedukować, że filter na Stream<T> zadziała na Predicate<T>, Predicate<Object> czy Predicate<U> dla każdego typu U będącego nadklasą T.

0

Postaram się wyjaśnić sprawę łopatologicznie. Pytajnik oznacza inne rzeczy w zależności od której strony go używamy, tzn czy wrzucamy jakiś obiekt do metody jako parametru z pytajnikiem czy może korzystamy z takiego parametru w metodzie. Konkretnie to jeśli mamy Stream<T> i metodę filter(Predicate<? super T> predicate) to:

  • w miejscu podawania argumentu do takiej metody możemy podać Predicate<U> dla dowolnego U będącego albo nadklasą T albo tożsame T
  • w miejscu korzystania z argumentu typu Predicate<? super T> nasz kod musi działać dla każdego możliwego U (z punktu poprzedniego) jednocześnie. Jeśli interfejs Predicate<T> ma metodę test(T t) to w miejsce t możemy podać argument typu T lub podklasę T. Tak samo jest jeśli mamy parametr typu Predicate<? super T>, bo argument typu T lub będący podklasą T pasuje (jako argumentu metody test) dla każdego typu, który spełnia ograniczenie (tak jak typ U z poprzedniego punktu)

Zauważ, że parametry metody są kontrawariantne, a rezultaty metod są kowariantne. C# i Scala wariancję oznaczają w miejscu definicji klasy:
https://msdn.microsoft.com/pl-pl/library/bb549151(v=vs.110).aspx (słówka in i out przy typach generycznych)
https://www.scala-lang.org/api/current/scala/Function1.html (plusik i minusik przy typach generycznych)
W Javie wariancję oznacza się w miejscu użycia klasy (co jest zwykle mniej czytelne) poprzez te ograniczenia ? super T czy ? extends T.

Chyba jednak poległem w tłumaczeniach, a jest już późno. Polecam poczytanie konkretnych artykułów o wariancji. Strona na Wiki którą wcześniej podałem jest raczej dobrym punktem startowym.

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