如何在Java中使用Class<T>?

300
这个问题的讨论中,有关泛型及其背后的真正作用的讨论得很好,因此我们都知道Vector<int[]>是整数数组的向量,HashTable<String,Person>是键为字符串和值为Person的表格。 然而,让我困惑的是Class<>的用法。
Java类Class还应该采用模板名称(或者我正在eclipse中被黄色下划线告知)。 我不明白我应该放什么。 Class对象的整个重点在于当你没有完全了解一个对象的信息时,可以用于反射等操作。为什么让我指定Class对象将持有哪个类? 我显然不知道,否则我就不会使用Class对象,而会使用具体的对象。
11个回答

239
我们知道的是:“任何一个类的所有实例都共享该类型的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 map = new HashMap())。
例如:class name 因此,Class表示特定类类型'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类型,如modelNumber,

或者T可以作为Object类型,如valid car instance

现在以上是简单的POJO,它可以在运行时以不同的方式使用。
例如,集合类如List、Set、HashMap就是最好的例子,根据T的声明将与不同的对象一起工作,但是一旦我们将T声明为String,
例如:HashMap<String> map = new HashMap<String>();那么它只能接受String类实例对象。

泛型方法

泛型方法是引入自己的类型参数的方法。这类似于声明泛型类型,但类型参数的范围限于声明它的方法。允许静态和非静态泛型方法,以及泛型类构造函数。

泛型方法的语法包括一个类型参数,在尖括号内,并出现在方法的返回类型之前。对于泛型方法,类型参数部分必须出现在方法的返回类型之前。

 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
我的关于有界通配符的回答:https://dev59.com/wXM_5IYBdhLWcg3wdy5g#15252219 - Kanagavelu Sugumar
"Class<T> 代表特定类别 'T' 的类对象。" 这是有意义的。谢谢。 - codezoner
2
这个答案在使用“类”(来自学校)的问题中混淆了“类”(在Java中)的方式,非常令人困惑。很难从一句话到另一句话知道作者在谈论什么。 - Echox
@Echox,很抱歉,如果您有任何具体问题,我可以改进它。 - Kanagavelu Sugumar
1
https://dev59.com/q2445IYBdhLWcg3wfKcZ - Kanagavelu Sugumar
@Echox 也许是命名方案的问题?还有其他人注意到使用类型“Teacher”和“Student”来解释类引用的讽刺吗?LMAO - Nate T

159

使用泛型版本的Class类,可以让你像这样编写代码:

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

然后你可以确信你收到的Class对象是Collection的扩展,而这个类的实例将会(至少)是一个集合。


40

来自Java文档:

[...] 更令人惊讶的是,类Class已经实现了泛型化。类文字现在作为类型标记,提供运行时和编译时类型信息。这使得一种静态工厂的风格成为可能,例如新的AnnotatedElement接口中的getAnnotation方法:

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

这是一种通用方法。它从其参数中推断出其类型参数T的值,并返回T的适当实例,如下面的代码片段所示:

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

在泛型出现之前,你需要将结果转换为Author类型。同时你也无法让编译器检查实际参数是否代表Annotation子类。[...]
嗯,我从来没有使用过这种东西。有人用过吗?

2
我(认为我)做到了。我使用的某种框架要求您传递模块所依赖的服务类名。我在此基础上构建了一个层,它接受Class对象,以限制选择数量。使用“Class <? extends X>”符号,我想我可以将其限制为仅限于“服务”类型。但是,没有通用的“服务”类型,所以我只能使用“Class <?>”。唉。 - cthulhu

11

我发现在创建服务注册查找时使用class<T>非常有用。例如:

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

6

继 @Kire Haglin 的回答之后,泛型方法的另一个示例可以在 JAXB unmarshalling 文档 中看到:

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内容树类型的文档。

6

正如其他答案所指出的,有许多很好的原因使得这个class变成了泛型。然而,在许多情况下,你没有办法知道在Class<T>中使用哪种泛型类型。在这些情况下,你可以简单地忽略黄色的Eclipse警告,或者你可以使用Class<?>... 这就是我做的方式 ;)


1
@SuppressWarnings("unchecked") 来拯救!(只需小心地将其应用于尽可能小的范围,因为它确实会掩盖代码中潜在的问题。) - Donal Fellows

5

在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的说法-泛型类型是一个泛型类或接口,其在类型上进行参数化


3

在使用 Class 时,通常需要使用通配符。例如,Class<? extends JComponent>,可以指定该类是 JComponent 的某个子类。如果您已经通过 Class.forName 获取了 Class 实例,则可以使用 Class.asSubclass 在尝试构造实例之前进行类型转换。


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

-1

一开始可能会有些困惑,但在以下情况下会有所帮助:

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对象的非常复杂的方式吗? - Karl
1
Action a = new Action()Action是一个接口,我们正在尝试获取SomeAction的实例。我们只有在运行时可用的SomeAction名称。 - fastcodejava
这段代码无法通过类型检查:Java编译器无法确定 <code>Class.forName("SomeAction")</code> 将会是 <code>Class<Action></code> 类型,因为这只有在运行时才能确定。 - tonio
@tonio,没错,你可能需要在第一行代码外面加上一些try/catch语句。但是假设没有任何异常抛出,第二行代码肯定会正常工作。 - Tyler
2
你实际上描述的是 SomeAction.class 与模式 Class<? extends Action> 匹配 - 也就是说,如果你有一个方法 useAction(Class<? extends Action> klass),你可以调用 useAction(SomeAction.class)。 - Thomas Andrews
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接