2013-01-31 10 views
8

Dlaczego metody fabryki narzędzi często używają określonego parametru ogólnego (np. T) zamiast ograniczonego parametru wieloznacznego (np. ? super T)?W guava, dlaczego jest po prostu "T", gdzie "? Super T" byłoby możliwe?

Na przykład, podpis Functions#forPredicate jest:

public static <T> Function<T, Boolean> forPredicate(Predicate<T> predicate) 

Dlaczego nie używać:

public static <T> Function<T, Boolean> forPredicate(Predicate<? super T> predicate) 

co czyniłoby coś jak następuje możliwe?

Predicate<Number> isPositivePredicate = ... 
Function<Integer, Boolean> isPositiveInteger = Functions.forPredicate(isPositivePredicate); 
// above line is compiler error: 
// Type mismatch: cannot convert from Function<Number,Boolean> to Function<Integer,Boolean> 

Czy dlatego oczekuje się konsumenci z Function i Predicate mieć ograniczony niezbędnych parametrów zastępczych, aby to niepotrzebne? Na przykład, ogólne granice na Iterables#find pozwoliłby Predicate<Number> być stosowany na Iterable<Integer>:

public static <T> T find(Iterable<T> iterable, 
         Predicate<? super T> predicate) 

Czy istnieją inne powody?

Odpowiedz

6

Tak, to absolutnie dokładne, że oczekujemy, że konsumenci mają prawo ograniczone parametry wieloznacznych, ale kilka dodatkowych powodów na myśl:

  • W ogóle, nie rozszerzają rodzaje metod generycznych dopóki nie mamy konkretnego powodu. Ta polityka opłacała się kilka razy.
  • Wnioskowanie typu Java nie zawsze musi automatycznie obliczać bardziej zaawansowane generyczne, więc utrzymanie węższych rodzajów generycznych zmniejsza liczbę użytkowników, którzy muszą wyraźnie określić T.
+0

re. po pierwsze, czy to nie tylko popycha konsumenta do rzucenia, co jest gorsze od dwóch zła? Co ciekawe, kiedy mówisz "my", masz na myśli to, że rozwinęłaś się w tej lub tylko społeczności oprogramowania w ogóle. – djechlin

+0

Bardziej konkretnie - w jaki sposób ta polityka została spłacona w praktyce? Obecnie jedyne sytuacje, w których mogę sobie wyobrazić, że to ważne, konsument po prostu ominąłby casting do typu pochodnego. – djechlin

+0

Kiedy mówię "my", mam na myśli to, że jestem w zespole Google Java Core Libraries, który rozwija Guava. Ale konsument nie powinien nigdy rzucać: 'Predicate ' może być zawsze użyty bezpośrednio, gdziekolwiek byś użył 'Predicate '. Nie ma potrzeby rzucania; można je zastosować dokładnie do tych samych wartości. –

2

Na przykład T można zawsze jednoznacznie wywnioskować.

W przykładzie forPredicate[1]() można również jednoznacznie wywnioskować T.

W przykładzie forPredicate[2]() nie ma pewności, co powinno być: T. Jeśli wynik metody jest przypisany do typu docelowego, można określić T z typu celu. W przeciwnym razie jest to małe drapanie głowy:

forPredicate(isPositive).apply(42); // T is a subtype of Number, but which one? 

W java5/6 nie powinno się kompilować. (Dobrze Testowałem go na Java 6 i to nie skompilować, ale to chyba błąd, ponieważ Java 6 zestawia również forPredicate(p).apply("str"))

Java 7 poprawiła się trochę, a nowa reguła stanie dyktować że T=Number. Działa, ale wydaje mu się, że jest to bardziej arbitraż dla niego.


Idealnie nie powinniśmy się martwić o symbole wieloznaczne. Jeśli potrzebuję predykatu dla moich liczb całkowitych, powinienem zadeklarować parametr Predicate<Integer>. Fakt, że argument Predicate<Number> byłby akceptowalny, to inna historia. Zadaniem kompilatora powinno być przekonwertowanie Predicate<Number> na Predicate<Integer> - możemy to zrobić bez przebudowywania istniejącego systemu typów generycznych java, nowa zasada konwersji jest wszystkim, co jest potrzebne.Można również bibliotekę konwersji

Predicate<Number> pn = ...; 
Predicate<Integer> pi = convert(pn); 

Iterable<Integer> iter1 = ...; 
Iterable<Number> iter2 = convert(iter1); 

Wszystkie metody convert() mogą być generowane mechanicznie.


Java 8 sprawia, że ​​wszystko jest łatwiejsze. Nadal nie możemy zrobić

Predicate<Number> pn = n -> n.intValue()>0; 
Predicate<Integer> pi = pn; // error! 

ale możemy zrobić

Predicate<Integer> pi = pn::test; // ok 
// it means  pi = (Integer i)->pn.test(i) 

również

Function<Integer, Boolean> f = pn::test; // ok 

co jest równoważne f = forPredicate[2](pn). W języku Java 8 rzadko będziemy potrzebować forPredicate() itd., Aby przekonwertować między typami funkcjonalnymi.

+0

Nie ma problemu z 'forPredicate (isPositive) .apply (42)' , chociaż — w tym przypadku 'T' jest najbardziej konkretnym typem, jaki może być,' Number'. 'Integer' dziedziczy po' Number', więc wywołanie 'apply' jest w porządku. Jeśli kompiluje się, gdy wpiszesz ciąg znaków, brzmi to tak, jakby był skompilowany z surowymi typami lub czymś podobnym. – matts

+0

Moja myśl brzmi: załóżmy, że mam prywatną 'Predicate ' w mojej klasie, i chcę zwrócić ją z metody jako 'Funkcja ', nie powinienem wystawiać rozmówcom, że to jest naprawdę 'Predicate '. A ponieważ akceptuje wszystkie 'Number's, będzie również akceptował wszystkie' Integer's. Aby to zrobić teraz, musiałbym przekonwertować go z klasą opakowania lub jawną klasą. – matts

+0

poprawną politycznie odpowiedzią byłoby, że twój typ zwrotu metody powinien być "Funkcja 'zamiast. to po prostu zbyt brzydkie. – irreputable

Powiązane problemy