Як використовувати клас <T> на Java?


247

У цьому питанні добре обговорюється Generics і що вони насправді роблять за кадром , тому ми всі знаємо, що Vector<int[]>це вектор цілих масивів, іHashTable<String, Person> це таблиця, чиї ключі - це рядки та значення Persons. Однак, що мене наштовхує - це використання Class<>.

Клас java Classтакож повинен приймати назву шаблону (або приблизно так мені кажуть жовте підкреслення у затемненні). Я не розумію, що я повинен туди вкласти. Вся суть Classоб'єкта полягає в тому, що ви не маєте в повній мірі інформацію про об'єкт, для відображення тощо. Чому змушує мене вказати, який клас Classбуде містити об'єкт? Я чітко не знаю, або я б не використовував Classоб'єкт, я використовував би конкретний.

Відповіді:


136

Використання узагальненої версії класу Class дозволяє, крім іншого, писати такі речі

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

і тоді ви можете бути впевнені, що отриманий вами об’єкт Class розширюється Collection, а екземпляр цього класу буде (принаймні) колекцією.


183

Все, що ми знаємо, це " Усі екземпляри будь-якого класу мають один і той же об'єкт java.lang.Class цього типу класу "

наприклад)

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

Тоді a.getClass() == b.getClass()це правда.

Тепер припустимо

Teacher t = new Teacher();

без дженериків можливе нижче.

Class studentClassRef = t.getClass();

Але це зараз неправильно ..?

напр.) public void printStudentClassInfo(Class studentClassRef) {}можна викликати сTeacher.class

Цього можна уникнути, використовуючи дженерики.

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

Тепер що таке T ?? T - параметри типу (також називаються змінними типу); розмежоване кутовими дужками (<>), слід за назвою класу.
T - просто символ, подібно до імені змінної (може бути будь-яке ім'я), оголошеного під час написання файлу класу. Пізніше T буде замінено
дійсним іменем класу під час ініціалізації ( HashMap<String> map = new HashMap<String>();)

наприклад) class name<T1, T2, ..., Tn>

Отже, Class<T>представляє об’єкт класу конкретного типу класу 'T '.

Припустимо, що методи вашого класу повинні працювати з невідомими параметрами типу, як показано нижче

/**
 * 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; }
}

Тут T може використовуватися як Stringтип CarName

АБО T можна використовувати як Integerтип як модельNumber ,

АБО Т може використовуватися як Objectтип як дійсний екземпляр автомобіля .

Тепер ось вище - простий POJO, який можна використовувати по-різному під час виконання.
Колекції, наприклад) Список, Set, Hashmap - найкращі приклади, які працюватимуть з різними об'єктами відповідно до декларації T, але як тільки ми оголосили T як String,
наприклад) HashMap<String> map = new HashMap<String>();Тоді він буде приймати лише об'єкти екземпляра String Class.

Родові методи

Загальні методи - це методи, що вводять параметри власного типу. Це схоже на оголошення загального типу, але область параметра параметра обмежена методом, де він оголошений. Допускаються статичні та нестатичні загальні методи, а також конструктори родового класу.

Синтаксис загального методу включає параметр типу, що знаходиться у внутрішніх кутових дужках, і з'являється перед типом повернення методу. Для загальних методів перед типом повернення методу повинен з’явитися розділ параметра типу.

 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;
}

Ось <K, V, Z, Y>декларація типів, що використовуються в аргументах методу, які повинні бути перед типом повернення, який знаходиться booleanтут.

Внизу; Декларація типу <T>не потрібна на рівні методу, оскільки вона вже оголошена на рівні класу.

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

Але нижче це неправильно, оскільки параметри типу K, V, Z та Y на рівні класу не можуть використовуватися в статичному контексті (статичний метод тут).

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());
    }
}

ІНШІ ВАЛИДНІ СЦЕНАРІЇ Є

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;
        }
    }

І нарешті, статичний метод завжди потребує явного <T>декларування; Це не походить від рівня класу Class<T>. Це пояснюється тим, що рівень класу T пов'язаний з екземпляром.

Читайте також обмеження на дженерики

Підстановки та підтипи

аргумент типу для загального методу


2
Моя відповідь на обмежених символи узагальнення stackoverflow.com/questions/1368166 / ...
Kanagavelu Sugumar

"Клас <T> являє собою об'єкт класу певного класу типу" T "." Це має сенс. Дякую ..
bynu022

Ця відповідь страшенно заплутана в тому, як вона використовує Класи (зі школи) у питанні про Класи (на Java). Важко дізнатися, про що говорить автор від речення до іншого.
Ехокс

@Echox Вибачте, я можу вдосконалити це, якщо у вас є конкретні питання.
Канагавелу Сугумар


32

З документації Java:

[...] Ще дивно, що клас Клас був узагальнений. Літеральні класи тепер функціонують як маркери типу, забезпечуючи інформацію про час виконання та час компіляції. Це дозволяє стилю статичних заводів, прикладом якого є метод getAnnotation в новому інтерфейсі AnnotatedElement:

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

Це загальний метод. Він виводить значення параметру типу T зі свого аргументу і повертає відповідний екземпляр T, як проілюстровано наступним фрагментом:

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

До дженериків вам слід було б передати результат Автору. Також у вас не було б можливості змусити компілятор перевірити, чи дійсний параметр являє собою підклас Анотації. [...]

Ну, мені ніколи не доводилося користуватися подібними матеріалами. Хтось?


2
Я (думав, що я) зробив. Рамка (на зразок), з якою я працював, вимагала, щоб ви передали ім'я класів служб, від яких залежав ваш модуль. Я створив шар поверх того, що брав об’єкти Class, щоб обмежити кількість варіантів. Використовуючи Class<? extends X>позначення, я зрозумів, що можу обмежити його лише типом "служби". За винятком того, що не було загального типу "послуги", тож я міг це робити лише за допомогою Class<?>. На жаль
fwielstra

9

Я вважаю class<T>корисним під час створення пошукових записів у реєстрі послуг. Напр

<T> T getService(Class<T> serviceClass)
{
    ...
}

5

Як вказують інші відповіді, є багато і хороших причин, чому це classстало загальним. Однак є багато разів, коли ви не маєте жодного способу пізнати родовий тип, яким слід користуватися Class<T>. У цих випадках ви можете просто ігнорувати попередження жовтого затемнення або можете використовувати Class<?>... Ось так я це роблю;)


@SuppressWarnings("unchecked")приходить на допомогу! (Тільки будьте обережні , щоб завжди застосовувати його як невелика область , наскільки це можливо , як це робить неясні потенційні проблеми в вашому коді.)
Донал Fellows

4

Виходячи з відповіді @Kire Haglin, подальший приклад генеричних методів можна побачити в документації для розблокування 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();
}

Це дозволяє unmarshalповернути документ довільного типу вмісту дерева JAXB.


2

Ви часто хочете використовувати подстановочні символи Class. Наприклад, Class<? extends JComponent>дозволить вам вказати, що клас є деяким підкласом JComponent. Якщо ви отримали Classекземпляр Class.forName, ви можете скористатися Class.asSubclassдля того, щоб зробити команду, перш ніж спробувати створити екземпляр.


2

На java <T>означає загальний клас. Загальний клас - це клас, який може працювати на будь-якому типі даних або іншими словами, ми можемо сказати, що це незалежний тип даних.

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

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

Де T означає тип. Тепер, коли ви створюєте екземпляр цього класу Shape, вам потрібно буде повідомити компілятору, для якого типу даних це буде працювати.

Приклад:

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

Integer - це тип, а String - також тип.

<T>спеціально розшифровується для родового типу. Відповідно до Java Docs - Загальний тип - це загальний клас або інтерфейс, параметризований по типах.


0

Просто на прикладі іншого прикладу, загальна версія Class ( Class<T>) дозволяє записувати загальні функції, такі як наведена нижче.

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();
                }
            });
}

-2

На початку це заплутано. Але це допомагає в наведених нижче ситуаціях:

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.

4
Це не просто неймовірно складний спосіб сказати Action a = new Action ()?
Карл

1
Action a = new Action() ? Actionя інтерфейс, це SomeActionми намагаємося отримати екземпляр. У нас є лише ім’я, SomeActionдоступне під час виконання.
fastcodejava

Це не проходить перевірку типу: компілятор Java не має можливості сказати, що <code> Class.forName ("SomeAction") </code> буде мати тип <code>Class<Action> </code>, оскільки це буде лише бути відомим під час виконання.
tonio

@tonio, правда, тому вам, ймовірно, доведеться загорнути цей перший рядок у якомусь спробі / лові. Але якщо припустити, що там не виняток, другий рядок гарантовано працює.
MatrixFrog

2
Що ви насправді описуєте, це те, що SomeAction.class відповідає шаблону Class <? розширює Action> - тобто, якщо у вас є метод useAction (клас <? розширює дію> klass), ви можете викликати useAction (SomeAction.class).
Томас Ендрюс

-5

Просто використовуйте клас яловичини:

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();
}

4
Це насправді не дає повного відповіді на питання. Якщо ви думаєте, що вам щось додати, відредагуйте цю відповідь.
MasterAM
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.