2009-03-30 8 views
8

Załóżmy Używam interfejsu z rodzajowego parametr typuUkrycie „lokalny” parametr typu w Javie

interface Foo<T> { 
    T getOne(); 
    void useOne(T t); 
} 

Intencją jest, że typ jest abstrakcyjny T: wymusza ograniczenie typu na implementacje Foo , ale kod klienta nie obchodzi dokładnie, co to jest T.

To nie jest problem w kontekście metody rodzajowe:

public <T> void doStuff(Foo<T> foo) { 
    T t = foo.getOne(); 
    /* do stuff */ 
    foo.useOne(t); 
} 

Ale załóżmy, chcę zerwać pracę doStuff, oszczędzając trochę stan w klasie Bar. W tym przypadku wydaje mi się, że muszę dodać parametr typu Foo do Bar.

public class Bar<T> { 
    private Foo<T> foo; 
    private T t; 

    /* ... */ 

    public void startStuff() { 
    t = foo.getOne(); 
    } 

    public void finishStuff() { 
    foo.useOne(t); 
    } 
} 

To jest trochę dziwne, ponieważ parametr typu T nie pojawia się w interfejsie publicznym Bar (to znaczy, nie jest wliczone w dowolnym parametrze metody lub rodzaj powrotu). Czy istnieje sposób na "oszacowanie odległości T"? Czy mogę ustawić, aby parametr T był ukryty w interfejsie Bar, tak jak w poniższym?

public class Bar { 
    <T> { // foo and t have to use the same T 
    private Foo<T> foo; 
    private T t; 
    } // T is out of scope 
    ... 
} 
+0

Czy wiesz, jaki jest typ T, gdy piszesz pasek, czy jest on otwarty? – Eddie

+0

Czy mógłbyś wyjaśnić, w jaki sposób Bar dostaje foo? Jeśli pobierze go jako parametr konstruktora, wówczas T pojawi się w jego interfejsie. Jeśli otrzymamy go z innego źródła, to T jest prawdopodobnie zdefiniowane tam, aw twoim przypadku jest coś konkretnego jak Foo . Jak to zrobić na swój sposób? –

Odpowiedz

4

problem jest podobny do rozwiązany przez "capture helper", ale nie jestem pewien, że można go stosować do drugiego przykładu gdzie stosowane są dwie oddzielne metody. Twoja pierwsza metoda doStuff mogłaby być zdecydowanie lepiej napisana jako public void doStuff(Foo<?> foo), ponieważ działa niezależnie od parametru typu Foo. Wtedy przydałby się wzór "pomocnika przechwytywania".


Aktualizacja: po odrobinie majsterkowania, rozszerzając pomysł pomocnika schwytania Goetza, wpadłem na to. W środku wygląda trochę nieładnie; z zewnątrz, niczego byś nie podejrzewał.

public class Bar { 
    private final Helper<?> helper; 
    public Bar(Foo<?> foo) { 
    this.helper = Helper.create(foo); 
    } 
    public void startStuff() { 
    helper.startStuff(); 
    } 
    public void finishStuff() { 
    helper.finishStuff(); 
    } 
    private static class Helper<T> { 
    private final Foo<T> foo; 
    private T t; 
    private Helper(Foo<T> foo) { 
     this.foo = foo; 
    } 
    static <T> Helper<T> create(Foo<T> foo) { 
     return new Helper<T>(foo); 
    } 
    void startStuff() { 
     t = foo.getOne(); 
    } 
    void finishStuff() { 
     foo.useOne(t); 
    } 
    } 
} 
1

Dlaczego nie ma hierarchii trójwarstwowej:

abstract class Foo 

abstract class FooImplBase<T> extends Foo 

class Bar extends FooImplBase<String> 

Klienci tylko wiedzieć o Foo, która nie zawiera żadnych metod generycznych. Wprowadź dowolne ogólne metody, które potrzebujesz w FooImplBase<T>, a następnie wywodzi się z nich konkretna klasa.

W twoim przykładzie startStuff() i endStuff() będą abstrakcyjne w Foo i zaimplementowane w FooImplBase<T>. Czy to brzmi tak, jakby działało w twojej prawdziwej sytuacji? Zgadzam się, że to trochę uciążliwe.

1

Definiujesz klasę Bar. Dwie rzeczy są prawdziwe ...

1) W bar nie ma żadnego parametru parametrycznego. Oznacza to, że członkowie foo i t mają jeden typ, na przykład U, który jest ustalony dla definicji. Jeśli zostanie Ci przekazany Foo, który chcesz przypisać do foo, musi to być Foo < U>. Jeśli to wszystko prawda, to tak, nie jest częścią publicznego interfejsu - wszystko ma określony typ. Wtedy nie jestem pewien, co masz na myśli przez "quantify", ponieważ nie ma żadnych zmiennych typu. Jeśli masz na myśli uniwersalną kwantyfikację, to jak pogodzić fakt, że Bar nie ma pisania parametrycznego, więc musiał mieć konkretny typ dla każdego z jego członków?

2) W bar występuje typ parametryczny. To może nie być oczywiste w interfejsie publicznym, ale może przekazujesz w Foo < T>, więc chcesz utworzyć instancję klasy Bar z więcej niż jednym typem. Następnie, jak już wspomniano, jest to w interfejsie publicznym i musisz ustawić parametr parametryczny w T z generycznymi. Daje to pewną formę uniwersalnej kwantyfikacji dla definicji: "Dla wszystkich typów T ta definicja jest prawdziwa".

-2

Parametr T w tym przypadku staje się bezużyteczny w odniesieniu do paska, ponieważ zostanie usunięty do obiektu w czasie kompilacji.Więc można także „zaoszczędzić sobie kłopotu”, a zrobić skasowaniu wczesne:

public class Bar { 

    private Foo<Object> foo; 
    private Object t; 

    ... 
} 
6

Aby być przydatnym, w pewnym momencie ustawisz pole foo. W tym momencie powinieneś wiedzieć (lub być w stanie przechwycić) T. Sugerowałbym to zrobić w konstruktorze, a następnie miałoby sens, aby Bar miał parametr rodzajowy. Można nawet użyć interfejsu, więc kod klienta nie musi widzieć typu. Zakładam jednak, że nie skorzystasz z mojej rady i naprawdę nie chcesz setFoo. Dodaj tylko jeden punkt do przełączalnej implementacji:

/* pp */ class final BarImpl<T> { 
    private final Foo<T> foo; 
    private T t; 

    BarImpl(Foo<T> foo) { 
     this.foo = foo; 
    } 

    public void startStuff() { 
     t = foo.getOne(); 
    } 

    public void finishStuff() { 
     foo.useOne(t); 
    } 
} 

public final class Bar { 
    private BarImpl<?> impl; 

    /* ... */ 

    // Need to capture this wildcard, because constructors suck (pre-JDK7?). 
    public void setFoo(Foo<?> foo) { 
     setFooImpl(foo); 
    } 
    private <T> void setFooImpl(Foo<T> foo) { 
     impl = new BarImpl<T>(foo); 
    } 

    public void startStuff() { 
     impl.startStuff(); 
    } 

    public void finishStuff() { 
     impl.finishStuff(); 
    } 
} 
+0

Przepraszam Tom, kiedy wróciłem, aby edytować moją odpowiedź, nie zauważyłem, że opublikowałeś już zasadniczo to samo rozwiązanie. +1 na cześć św. Brendana i Wikingów. – erickson

0

Gdzieś musiała zostać podjęta decyzja, jakiego rodzaju użyć do "T" w klasie Bar. Tak więc musisz wybrać go w definicji Bar (zastępując Foo przez Foo w definicji klasy) lub pozostawić go klientowi klasy Bar: W tym przypadku Bar musi być generyczny.

Jeśli chcesz mieć interfejs do Bar nie opierając się na T i móc wybrali różne typy dla T, należy użyć nierodzajową interfejs lub abstrakcyjną klasę bazową, na przykład:

interface Bar { 
    void startStuff(); 
    // ... 
} 

class BarImplement<T> { 
    private Foo<T> foo; 
    // ... 
} 
0

Jeśli naprawdę chcesz narysować trochę gniewu, możesz umieścić klasę Bar w interfejsie Foo i ssać T w ten sposób. Zobacz this article, aby uzyskać więcej informacji o klasach wewnątrz interfejsów. Może to jedyny przypadek, w którym ma to sens?

interface Foo<T> { 
    T getOne(); 
    void useOne(T t); 

    public class Bar<T> { 
    private Foo<T> foo; 
    private T t; 
    public void startStuff() { 
     t = foo.getOne(); 
    } 
    public void finishStuff() { 
     foo.useOne(t); 
    } 
    } 
} 
Powiązane problemy