2009-06-16 15 views
49

Jeśli tworzę klasy java być ogólne, takie jak:Java Generic Class - Określić typ

public class Foo<T> 

Jak można ustalić wewnętrznie do tej klasy, co 'T' okazał się być?

public ???? Bar() 
{ 
    //if its type 1 
    // do this 
    //if its type 2 
    // do this 
    //if its type 3 
    // do this 
    //if its type 4 
    // do this 
} 

Mam szturchnął wokół Java API i grał z rzeczy odbicie, instanceof, getClass, .class, itp, ale nie mogę wydają się monetą z nich. Czuję, że jestem blisko i po prostu potrzebuję połączyć kilka telefonów, ale nie nadążam.

Mówiąc dokładniej, próbuję ustalić, czy klasa została utworzona z jednym z 3 możliwych typów.

+15

Chęć zrobienia tego jest niemal gwarancją zły projekt wybór. Istnieje duże prawdopodobieństwo, że instrukcja if wymaga znajomości każdego "typu" w celu implementacji, więc prawdopodobnie wszystkie te klasy powinny być tego samego typu, a "to zrobić" powinno być metodą wirtualną. Może to dotyczyć zawijania klas, ale ogólnie twój projekt poprawi się, jeśli pójdziesz tą drogą. –

+2

Możliwości w instrukcjach if są przypadkami brzegowymi spośród wszystkich możliwych typów wywołań, które wymagają całkowicie odrębnych implementacji. Zgadzam się z tym, co tu mówisz, ale konkretna aplikacja to skała i ciężkie miejsce. –

+1

Potrzeba uzyskania klasy dla parametru rodzajowego nie zawsze jest złą decyzją projektową. Jednym z uzasadnionych wymagań (moje! :) może być wywołanie metody, które wymaga parametru klasy <>. – dahvyd

Odpowiedz

58

Użyłem podobnego rozwiązania do tego, co tutaj wyjaśnia dla kilku projektów i okazało się, że jest całkiem użyteczne.

http://blog.xebia.com/2009/02/07/acessing-generic-types-at-runtime-in-java/

jist z niego korzysta, co następuje:

public Class returnedClass() { 
    ParameterizedType parameterizedType = (ParameterizedType)getClass() 
               .getGenericSuperclass(); 
    return (Class) parameterizedType.getActualTypeArguments()[0]; 
} 
+0

+1! miałem zamiar napisać o tym –

+0

+1 I to jest właśnie rozwiązanie, po którym poszedłem po dalszych badaniach, więc zmieniłem go na moją akceptowaną odpowiedź. –

+0

+1 doskonały! Szukałem go przez wiele godzin ... –

12

Z powodu , nie można tego zrobić bezpośrednio. Możesz jednak przejść do konstruktora i zabrać go do wnętrza klasy. Następnie możesz sprawdzić to na trzech możliwych dozwolonych typach Class.

Jednakże, jeśli istnieją tylko trzy możliwe typy, można rozważyć refaktoryzację zamiast enum.

+0

To wydaje się najlepszym rozwiązaniem, choć mniej ładne niż prawdziwa niebieska klasa generyczna. Dzięki za wejście. –

+0

Ponownie, zachęcam do silnego rozważenia polimorfizmu, a nie wyrażenia if opartego na typie. –

+0

Niestety przedmiotowe elementy nie są w żaden sposób powiązane, więc rozwiązanie polimorficzne nie jest praktyczne. Możliwymi elementami, które wpadłyby w logikę instrukcji if, są przypadki krańcowe wszystkich innych "normalnych" procesów, które muszą być traktowane zupełnie inaczej, w żaden relacyjny sposób. –

0

Sensem rodzajowego klasy jest to, że nie trzeba znać typ, który jest używany ....

+2

To prawda, jednak jeśli zaprojektuję klasę, która może być generowana w sposób ogólny, dowolnego typu, chcę obsłużyć konkretny podzbiór wszystkich możliwych typów jako przypadki brzegowe z określoną logiką. –

+0

Następnie utwórz różne podtypy. Lub deleguj do strategii (lub podobnych). –

3

Problem polega na tym, że większość Generic rzeczy znikną podczas kompilacji.

Jednym typowym rozwiązaniem jest zapisanie typu podczas tworzenia obiektu.

Dla krótkiego wprowadzenia w zachowaniach typu Erasure java przeczytać page

+1

Jest to bardzo pomocne - widziałem rozwiązania, które to wiążą i mogą skończyć na tej trasie. –

+1

Prawdopodobnie oznacza, że ​​Twój projekt jest marny. –

0

Wygląda na to, co chcesz, to w rzeczywistości nie Generic klasy, ale interfejs z wielu różnych implementacjach. Ale może stanie się bardziej przejrzyste, jeśli podasz swój faktyczny, konkretny cel.

44

W przeciwieństwie do .NET Java generics są implementowane za pomocą techniki zwanej "type erasure".

Oznacza to, że kompilator będzie używał informacji o typie podczas generowania plików klas, ale nie będzie przekazywał tych informacji do kodu bajtowego. Jeśli spojrzysz na skompilowane klasy za pomocą javap lub podobnych narzędzi, okaże się, że List<String> jest prosty List (z Object) w pliku klasy, tak jak był w kodzie przed-Java-5.

Kod uzyskujący dostęp do ogólnej listy zostanie "przepisany" przez kompilator w celu uwzględnienia rzutów, które należałoby napisać we wcześniejszych wersjach. W efekcie dwa następujące fragmenty kodu są identyczne z punktu widzenia kodu bajtowego, gdy kompilator jest z nimi zrobić:

Java 5:

List<String> stringList = new ArrayList<String>(); 
stringList.add("Hello World"); 
String hw = stringList.get(0); 

Java 1.4 i wcześniej:

List stringList = new ArrayList(); 
stringList.add("Hello World"); 
String hw = (String)stringList.get(0); 

Podczas czytania wartości z klasy ogólnej w Javie 5 konieczne rzutowanie do zadeklarowanego parametru typu jest wstawiane automatycznie. Podczas wstawiania kompilator sprawdzi wartość, którą próbujesz umieścić i przerwie z błędem, jeśli nie jest to String.

Podjęto wszelkie starania, aby stare biblioteki i nowy generowany kod były interoperacyjne bez potrzeby rekompilacji istniejących bibliotek. Jest to główna zaleta w stosunku do metody .NET, gdzie klasy ogólne i nietypowe sąsiadują ze sobą, ale nie mogą być swobodnie wymieniane.

Obie metody mają swoje wady i zalety, ale tak właśnie jest w Javie.

Aby wrócić do pierwotnego pytania: Nie można uzyskać informacji o typie w czasie wykonywania, ponieważ po prostu już go nie ma, po wykonaniu kompilatora. Jest to z pewnością ograniczone pod pewnymi względami i istnieją pewne marne sposoby, które zwykle opierają się na przechowywaniu gdzieś instancji klasy, ale nie jest to standardowa funkcja.

+2

+1 za obszerne informacje i kontrast z C# - dziękuję –

+1

Dziękuję. Przykro mi, że nie ma przycisku +10. –

0

zgadzam się z Visage. Generics służy do sprawdzania poprawności w czasie kompilacji, a nie do wpisywania dynamicznego w czasie wykonywania. Wygląda na to, że potrzebujesz tylko fabrycznego wzoru. Ale jeśli twoje "robienie tego" nie jest instancją, wtedy najprawdopodobniej zadziała zwykła Enum. Tak jak powiedział Michael, jeśli masz nieco bardziej konkretny przykład, otrzymasz lepsze odpowiedzi.

+0

Muszę się tu nie zgodzić, a było kilka wzmianek o rozwiązaniu nie-polimorficznym, które są niezbędne w konkretnej przestrzeni problemowej, i jestem bardzo szczęśliwy z odpowiedzi, które otrzymałem. –

1

Jeśli znasz kilka konkretnych typów, które są znaczące, powinieneś utworzyć implementację podklas swojego rodzaju.

Więc

public class Foo<T> 

public ???? Bar() 
{ 
    //else condition goes here 
} 

A potem

public class DateFoo extends Foo<Date> 

public ???? Bar() 
{ 
    //Whatever you would have put in if(T == Date) would go here. 
}