Jak korzystać z klasy <T> w Javie?

247

W tym pytaniu jest dobra dyskusja na temat Generics i tego, co naprawdę robią za kulisami , więc wszyscy wiemy, że Vector<int[]>jest to wektor tablic liczb całkowitych i HashTable<String, Person>tabela, której klucze to ciągi znaków i wartości Person. Jednak to, co mnie zaskakuje, to użycie Class<>.

Klasa java Classpowinna również przyjmować nazwę szablonu (przynajmniej tak mówi żółte podkreślenie w zaćmieniu). Nie rozumiem, co powinienem tam umieścić. Istotą Classobiektu jest to, że nie masz w pełni informacji o obiekcie, do refleksji i tym podobnych. Dlaczego każe mi określić, jaką klasę Classbędzie przechowywać obiekt? Najwyraźniej nie wiem lub nie używałbym tego Classobiektu, użyłbym tego konkretnego.

Karl
źródło

Odpowiedzi:

136

Korzystanie z generowanej wersji klasy Class umożliwia między innymi pisanie takich rzeczy

Class<? extends Collection> someCollectionClass = someMethod();

a wtedy możesz być pewien, że otrzymany obiekt Class rozszerzy się Collection, a instancja tej klasy będzie (przynajmniej) Kolekcją.

Yuval
źródło
183

Wiemy tylko: „ Wszystkie wystąpienia dowolnej klasy współużytkują ten sam obiekt java.lang.Class tego typu klasy

na przykład)

Student a = new Student();
Student b = new Student();

To a.getClass() == b.getClass()jest prawda.

Załóżmy teraz

Teacher t = new Teacher();

bez leków generycznych możliwe jest poniżej.

Class studentClassRef = t.getClass();

Ale teraz to źle…

np.) public void printStudentClassInfo(Class studentClassRef) {}można wywołać za pomocąTeacher.class

Można tego uniknąć za pomocą ogólnych.

Class<Student> studentClassRef = t.getClass(); //Compilation error.

Co to jest T ?? T to parametry typu (zwane również zmiennymi typu); rozdzielone nawiasami kątowymi (<>), następuje po nazwie klasy.
T jest tylko symbolem, podobnie jak nazwa zmiennej (może być dowolną nazwą) zadeklarowana podczas pisania pliku klasy. Później T zostanie zastąpione
prawidłową nazwą klasy podczas inicjalizacji ( HashMap<String> map = new HashMap<String>();)

na przykład) class name<T1, T2, ..., Tn>

Zatem Class<T>reprezentuje obiekt klasy określonego typu klasy „ T”.

Załóżmy, że metody klas muszą działać z nieznanymi parametrami typu, jak poniżej

/**
 * Generic version of the Car class.
 * @param <T> the type of the value
 */
public class Car<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

Tutaj T może być użyty jako Stringtyp CarName

LUB T może być użyte jako Integertyp jako modelNumber ,

LUB T może być użyte jako Objecttyp jako poprawna instancja samochodu .

Teraz tutaj powyższe jest prostym POJO, którego można używać inaczej w czasie wykonywania.
Kolekcje np. List, Set, Hashmap są najlepszymi przykładami, które będą działać z różnymi obiektami zgodnie z deklaracją T, ale kiedy zadeklarujemy T jako String
np.) HashMap<String> map = new HashMap<String>();Wtedy zaakceptuje tylko obiekty instancji klasy String.

Metody ogólne

Metody ogólne to metody, które wprowadzają własne parametry typu. Jest to podobne do deklarowania typu ogólnego, ale zakres parametru typu jest ograniczony do metody, w której jest zadeklarowany. Dozwolone są statyczne i niestatyczne metody ogólne, a także konstruktory klas ogólnych.

Składnia metody ogólnej zawiera parametr typu, wewnątrz nawiasów kątowych i pojawia się przed typem zwracanym przez metodę. W przypadku metod ogólnych sekcja parametru typu musi pojawić się przed typem zwracanym przez metodę.

 class Util {
    // Generic static method
    public static <K, V, Z, Y> boolean compare(Pair<K, V> p1, Pair<Z, Y> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

 class Pair<K, V> {

    private K key;
    private V value;
}

Oto <K, V, Z, Y>deklaracja typów użytych w argumentach metody, które powinny występować przed typem zwracanym, który jest booleantutaj.

Poniżej; deklaracja typu <T>nie jest wymagana na poziomie metody, ponieważ jest już zadeklarowana na poziomie klasy.

class MyClass<T> {
   private  T myMethod(T a){
       return  a;
   }
}

Ale poniżej jest źle, ponieważ parametry typu K, V, Z i Y na poziomie klasy nie mogą być używane w kontekście statycznym (tutaj metoda statyczna).

class Util <K, V, Z, Y>{
    // Generic static method
    public static  boolean compare(Pair<K, V> p1, Pair<Z, Y> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

POZOSTAŁY INNE WAŻNE SCENARIUSZE

class MyClass<T> {

        //Type declaration <T> already done at class level
        private  T myMethod(T a){
            return  a;
        }

        //<T> is overriding the T declared at Class level;
        //So There is no ClassCastException though a is not the type of T declared at MyClass<T>. 
        private <T> T myMethod1(Object a){
                return (T) a;
        }

        //Runtime ClassCastException will be thrown if a is not the type T (MyClass<T>).  
        private T myMethod1(Object a){
                return (T) a;
        }       

        // No ClassCastException        
        // MyClass<String> obj= new MyClass<String>();
        // obj.myMethod2(Integer.valueOf("1"));
        // Since type T is redefined at this method level.
        private <T> T myMethod2(T a){
            return  a;
        }

        // No ClassCastException for the below
        // MyClass<String> o= new MyClass<String>();
        // o.myMethod3(Integer.valueOf("1").getClass())
        // Since <T> is undefined within this method; 
        // And MyClass<T> don't have impact here
        private <T> T myMethod3(Class a){
            return (T) a;
        }

        // ClassCastException for o.myMethod3(Integer.valueOf("1").getClass())
        // Should be o.myMethod3(String.valueOf("1").getClass())
    private  T myMethod3(Class a){
        return (T) a;
    }


        // Class<T> a :: a is Class object of type T
        //<T> is overriding of class level type declaration; 
        private <T> Class<T> myMethod4(Class<T> a){
            return  a;
        }
    }

I wreszcie metoda statyczna zawsze wymaga wyraźnej <T>deklaracji; Nie będzie pochodzić z poziomu klasy Class<T>. Wynika to z faktu, że poziom klasy T jest związany z instancją.

Przeczytaj także Ograniczenia dotyczące leków generycznych

Symbole wieloznaczne i subtyping

Argument typu dla metody ogólnej

Kanagavelu Sugumar
źródło
2
Moja odpowiedź na temat ograniczonego wildCards stackoverflow.com/questions/1368166/…
Kanagavelu Sugumar
„Klasa <T> reprezentuje obiekt klasy określonego typu klasy„ T ”.” Ma to sens. Dziękuję ..
bynu022
Ta odpowiedź jest strasznie myląca, ponieważ używa klas (ze szkoły) w pytaniu o klasy (w Javie). Trudno wiedzieć, o czym mówi autor od zdania do zdania.
Echox
@Echox Przykro mi, mogę to poprawić, jeśli masz jakieś konkretne pytania.
Kanagavelu Sugumar
32

Z dokumentacji Java:

[...] Co bardziej zaskakujące, klasa Klasa została wygenerowana. Literały klasowe działają teraz jako tokeny typów, zapewniając informacje zarówno o czasie wykonywania, jak i o czasie kompilacji. Umożliwia to styl statycznych fabryk, których przykładem jest metoda getAnnotation w nowym interfejsie AnnotatedElement:

<T extends Annotation> T getAnnotation(Class<T> annotationType); 

To jest ogólna metoda. Pobiera wartość swojego parametru typu T z argumentu i zwraca odpowiednią instancję T, co ilustruje poniższy fragment:

Author a = Othello.class.getAnnotation(Author.class);

Przed lekami generycznymi musiałbyś przekazać wynik autorowi. Nie miałbyś także sposobu, aby kompilator sprawdził, czy rzeczywisty parametr reprezentuje podklasę Adnotacji. [...]

Cóż, nigdy nie musiałem używać tego rodzaju rzeczy. Ktoś?

raupach
źródło
2
Myślałem że zrobiłem. Szkielet (z pewnego rodzaju), z którym współpracowałem, wymagał od ciebie podania nazwy klasy usług, od których moduł zależał. Zbudowałem na nim warstwę, która zabrała obiekty klasy, aby ograniczyć liczbę wyborów. Korzystając z Class<? extends X>notacji, pomyślałem, że mogę ograniczyć ją tylko do typów „usługowych”. Tyle że nie było powszechnego rodzaju „usługi”, więc mogłem to zrobić tylko z Class<?>. Niestety.
fwielstra
9

Znalazłem class<T>przydatna, gdy tworzę wyszukiwań rejestru usług. Na przykład

<T> T getService(Class<T> serviceClass)
{
    ...
}
Kire Haglin
źródło
5

Jak wskazują inne odpowiedzi, istnieje wiele dobrych powodów, aby classuczynić to ogólnym. Jednak jest wiele razy, że nie masz możliwości poznania ogólnego typu, z którym chcesz korzystać Class<T>. W takich przypadkach możesz po prostu zignorować żółte ostrzeżenia zaćmienia lub użyć Class<?>... Tak to robię;)

Bruno Conde
źródło
@SuppressWarnings("unchecked")przychodzi na ratunek! (Wystarczy być ostrożnym, aby zawsze stosować go jako małego zakresu, jak to możliwe, jak to robi niejasnych problemów potencjał w kodzie.)
Donal Fellows
4

Idąc za odpowiedzią @Kire Haglin, kolejny przykład metod ogólnych można znaleźć w dokumentacji dotyczącej rozłączania JAXB :

public <T> T unmarshal( Class<T> docClass, InputStream inputStream )
         throws JAXBException {
  String packageName = docClass.getPackage().getName();
  JAXBContext jc = JAXBContext.newInstance( packageName );
  Unmarshaller u = jc.createUnmarshaller();
  JAXBElement<T> doc = (JAXBElement<T>)u.unmarshal( inputStream );
  return doc.getValue();
}

Umożliwia unmarshalto zwrócenie dokumentu dowolnego typu drzewa zawartości JAXB.

Gulasz
źródło
2

Często chcesz używać symboli wieloznacznych Class. Na przykład, Class<? extends JComponent>pozwoli ci określić, że klasa jest jakąś podklasą JComponent. Jeśli odzyskałeś Classinstancję Class.forName, możesz użyć Class.asSubclassrzutowania przed próbą, na przykład, skonstruowania instancji.

Tom Hawtin - tackline
źródło
2

W języku Java <T>oznacza klasę ogólną. Klasa ogólna to klasa, która może pracować z dowolnym typem danych lub innymi słowy możemy powiedzieć, że jest niezależna od typu danych.

public class Shape<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

Gdzie T oznacza typ. Teraz, gdy utworzysz instancję tej klasy Shape, będziesz musiał poinformować kompilator o typie danych, nad którym będzie pracował.

Przykład:

Shape<Integer> s1 = new Shape();
Shape<String> s2 = new Shape();

Liczba całkowita jest typem, a ciąg znaków jest również typem.

<T>w szczególności oznacza rodzaj ogólny. Według Java Docs - Typ ogólny to ogólna klasa lub interfejs, który jest parametryzowany względem typów.

Frostbyte1010
źródło
0

Aby rzucić inny przykład, ogólna wersja Class ( Class<T>) pozwala pisać ogólne funkcje, takie jak ta poniżej.

public static <T extends Enum<T>>Optional<T> optionalFromString(
        @NotNull Class<T> clazz,
        String name
) {
    return Optional<T> opt = Optional.ofNullable(name)
            .map(String::trim)
            .filter(StringUtils::isNotBlank)
            .map(String::toUpperCase)
            .flatMap(n -> {
                try {
                    return Optional.of(Enum.valueOf(clazz, n));
                } catch (Exception e) {
                    return Optional.empty();
                }
            });
}
zeronone
źródło
-2

Na początku jest to mylące. Ale pomaga to w poniższych sytuacjach:

class SomeAction implements Action {
}

// Later in the code.
Class<Action> actionClass = Class.forName("SomeAction"); 
Action action = actionClass.newInstance();
// Notice you get an Action instance, there was no need to cast.
fastcodejava
źródło
4
Czy to nie jest niewiarygodnie skomplikowany sposób powiedzenia Action a = new Action ()?
Karl
1
Action a = new Action() ? Actionw interfejsie, SomeActionpróbujemy uzyskać instancję. Mamy tylko nazwę SomeActiondostępną w czasie wykonywania.
fastcodejava
Nie przechodzi to sprawdzania typu: kompilator Java nie ma sposobu, aby powiedzieć, że <code> Class.forName („SomeAction”) </code> będzie typu <code>Class<Action> </code>, ponieważ to tylko być znanym w czasie wykonywania.
tonio
@tonio, racja, więc prawdopodobnie musisz owinąć pierwszą linię w coś w rodzaju try / catch. Ale zakładając, że nie zostanie tu zgłoszony wyjątek, druga linia z pewnością będzie działać.
MatrixFrog
2
To, co naprawdę opisujesz, to to, że SomeAction.class pasuje do wzorca Class <? extends Action> - to znaczy, jeśli masz metodę useAction (klasa <? extends Action> klass), możesz wywołać useAction (SomeAction.class).
Thomas Andrews,
-5

Wystarczy użyć klasy wołowiny:

public <T> T beefmarshal( Class<beef> beefClass, InputBeef inputBeef )
     throws JAXBException {
     String packageName = docClass.getPackage().getBeef();
     JAXBContext beef = JAXBContext.newInstance( packageName );
     Unmarshaller u = beef.createBeef();
     JAXBElement<T> doc = (JAXBElement<T>)u.beefmarshal( inputBeef );
     return doc.getBeef();
}
tak
źródło
4
To tak naprawdę nie zapewnia pełnej odpowiedzi na pytanie. Jeśli uważasz, że masz coś do dodania, edytuj tę odpowiedź.
MasterAM