Java generics-Make Generic to extends 2 interfaces

Jak to działa:

public class Frankenstein<T extends IHuman, IMonster>{
}

Bez tworzenia

public interface Weirdo extends Ihuman, IMonster{
}

Edit

Dlaczego to nie działa?
public <T> void mapThis(
        Class<? extends MyClass<T>> key, Class<? extends T & IDisposable> value) {

}

Otrzymuję oznaczenie wiadomości kompilatora Class<? extends T & IDisposable> jako błąd.

Author: Ilya Gazman, 2012-10-27

2 answers

Reimeus już zauważył, że to, o co prosisz w swojej edycji, nie jest możliwe. Chciałbym tylko trochę wyjaśnić dlaczego.

Można by pomyśleć, że przydałoby się:

public <T, U extends T & IDisposable> void mapThis(
        Class<? extends MyClass<T>> key,
        Class<? extends U> value
) { ... }
To właśnie przyszło mi do głowy, kiedy po raz pierwszy zobaczyłem ten post. Ale to faktycznie daje błąd kompilatora:

Zmienna typu nie może być poprzedzona innymi ograniczeniami

Aby pomóc mi wyjaśnić, dlaczego, chciałbym zacytować Oracle Blogs post przez Victor Rudometov o tym błędzie:

Fakt ten nie zawsze jest jasny, ale jest prawdziwy. Następujący kod nie należy kompilować:

interface I {}

class TestBounds <U, T extends U & I> {

}

Ponieważ JLS Rozdział 4 Typy, wartości i zmienne sekcja 4.4 typy zmiennych Stany: "The bound składa się z albo zmiennej typu, albo klasy lub typu interfejsu T ewentualnie po kolejnych typach interfejsów I1 , ..., I n .". Więc jeden może używać T extends U, T extends SomeClass & i, ale nie T extends U & i. Reguła ta dotyczy wszystkich przypadków, w tym zmiennych typu i ograniczeń w metody i konstruktory.

Przyczyny tego ograniczenia zostały omówione w ściśle powiązanym poście: dlaczego nie mogę użyć argumentu type w parametrze type z wieloma ograniczeniami?

Podsumowując, ograniczenie zostało nałożone w celu " wykluczenia pewnych pojawiają się niezręczne sytuacje " ( JLS §4.9 ).

Jakie niezręczne sytuacje? odpowiedź Chrisa Povirka opisuje jeden:

[powodem ograniczenia jest] możliwość określenia nielegalnych typów. W szczególności rozszerzenie ogólnego interfejsu dwukrotnie o różne parametry. Nie mogę wymyślić przykładu nie wymyślonego, ale:

/** Contains a Comparator<String> that also implements the given type T. */
class StringComparatorHolder<T, C extends T & Comparator<String>> {
  private final C comparator;
  // ...
}

void foo(StringComparatorHolder<Comparator<Integer>, ?> holder) { ... }

Teraz holder.comparator jest Comparator<Integer> i Comparator<String>.

Chris wskazuje również na Sun bug 4899305 , który był błędem kwestionującym to ograniczenie językowe. To było zamknięte, jak nie naprawić z następującym komentarzem:

Jeśli zmienna typu może być poprzedzona zmiennymi typu lub przez (ewentualnie parametryzowanych) interfejsów, prawdopodobnie będzie więcej wzajemnie rekurencyjne zmienne typu, które są bardzo trudne do obsługi. Rzeczy są już skomplikowane, gdy wiązanie jest po prostu parametryzowanym typem, np. <S,R extends Comparable<S>>. W związku z tym granice nie idą na przebierz się. Zarówno javac jak i Eclipse zgadzają się, że S&T i Są nielegalne.

Więc to są powody ograniczenia. Odnosząc się konkretnie do metod generycznych (których dotyczy twoje pytanie), chciałbym jeszcze podkreślić, że wnioskowanie typu teoretycznie spowodowałoby, że takie granice i tak będą bezcelowe.

Jeśli ponownie przyjrzymy się parametrom typu zadeklarowanym w hipotetycznym podpisie Powyżej:

<T, U extends T & IDisposable>

Zakładając, że wywołujący nie określa jawnie T i U, można to zredukować do następujących wartości:

<T, U extends Object & IDisposable>

Lub tylko to (subtelna różnica, ale to jest inny temat):

<T, U extends IDisposable>

Dzieje się tak, ponieważ T nie ma żadnych ograniczeń, więc bez względu na to, jakiego rodzaju argumenty zostaną przekazane, T zawsze może rozwiązać Object co najmniej, a więc może U.

Wróćmy i powiedzmy T jest ograniczone:

<T extends Foo, U extends T & IDisposable>

Można to zmniejszyć w ten sam sposób (Foo może to być klasa lub interfejs):

<T extends Foo, U extends Foo & IDisposable>

Bazując na tym rozumowaniu, składnia, którą próbujesz osiągnąć, jest bezcelowa, jeśli chodzi o ograniczanie rozmówcy do bardziej konkretnych argumentów.

Pre-Java 8 addendum:

Przed Javą 8 istnieje przypadek użycia tego, co próbujesz zrobić. Z powodu ograniczenia w jaki sposób kompilator wywiera ogólne parametry typu metody, moje powyższe rozumowanie, aby wyjść z okna. Weźmy następującą metodę ogólną:

class MyClass {
    static <T> void foo(T t1, T t2) { }
}

Jest to częsty błąd początkującego polegający na próbie wykonania metody, która przyjmuje dwa parametry "tego samego typu". Oczywiście jest to bezcelowe ze względu na sposób dziedziczenia:

MyClass.foo("asdf", 42); // legal

Tutaj T wnioskuje się jako Object - pasuje to do wcześniejszego rozumowania o uproszczeniu parametrów typu mapThis. Należy ręcznie określić parametry typu, aby uzyskać zamierzone sprawdzenie typu:

MyClass.<String>foo("asdf", 42); // compiler error

Jednak i tutaj zaczyna się twój przypadek użycia, to różna materia z wieloma parametrami typu z rozłożonymi granicami:

class MyClass {
    static <T, U extends T> void foo(T t, U u) { }
}

Teraz błąd wywołania:

MyClass.foo("asdf", 42); // compiler error

Tabele się odwróciły - musimy ręcznie rozluźnić parametry typu, aby go skompilować:

MyClass.<Object, Object>foo("asdf", 42); // legal

Dzieje się tak z powodu ograniczonego sposobu, w jaki kompilator wywiera parametry typu metody. Z tego powodu to, co chciałeś osiągnąć, miałoby rzeczywiście zastosowanie w ograniczaniu argumentów rozmówcy.

Jednak ten problem wygląda na to, że została naprawiona w Javie 8, a MyClass.foo("asdf", 42) teraz kompiluje się bez żadnego błędu(dzięki Regentowi za zwrócenie na to uwagi).

 94
Author: Paul Bellora,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:21

Pomyślałem, że podzielę się moim hackiem, którego używam w tych (dość rzadkich) sytuacjach:

    /**
     * This is a type-checking method, which gets around Java's inability
     * to handle multiple bounds such as "V extends T & ContextAware<T>".
     *
     * @param value initial value, which should extends T
     * @param contextAware initial value, which should extend ContextAware<T>
     * @return a new proxy object
     */
    public T blah(T value, ContextAware<T> contextAware) {
        if (value != contextAware) {
            throw new IllegalArgumentException("This method expects the same object for both parameters.");
        }

        return blah(value);
    }

Więc wymagając tego samego obiektu dla każdej z granic, które próbujesz spełnić, otrzymujesz sprawdzanie typu w czasie kompilacji i pojedynczy obiekt, który robi wszystko. Co prawda podanie tego samego obiektu dla każdego parametru jest trochę głupie, ale robię to w moim "wewnętrznym" kodzie całkiem bezpiecznie i wygodnie.

 3
Author: Ben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-28 15:04:56