40

如果我将一个类声明为一个字段:

Class fooClass;

Eclipse 给了我警告:

类是原始类型。对泛型类的引用应该被参数化

这在实践中意味着什么?为什么我要这样做?如果我要求 Eclipse 进行“快速修复”,它会给我:

Class<?> fooClass;

这似乎并没有增加太多价值,但不再发出警告。

编辑:为什么类是通用的?您能否举一个参数化的例子,即是否可以有效地使用除 之外的东西<?>

编辑:哇!我还没有意识到这其中的深度。我还看过 Java Puzzler,它肯定让我对捕熊陷阱感到害怕。所以我会一直使用

Class<MyString> myStringClass = MyString.class;

而不是

Class myStringClass = MyString.class;

(但是从第一天开始就使用 Java,我并没有真正注意到 Class 何时变为泛型);

注意:我接受了@oxbow_lakes,因为这对我来说很有意义,但这显然是一个非常复杂的领域。我会敦促所有程序员使用特定的Class<MyString>而不是Class. 并且Class<?>Class.

4

5 回答 5

59

原始类型和无界通配符

之前的答案都没有真正解决您为什么应该更喜欢Class<?>Class因为从表面上看,前者似乎没有提供比后者更多的信息。

原因是原始类型ieClass阻止编译器进行泛型类型检查。也就是说,如果你使用原始类型,你就颠覆了类型系统。例如:

public void foo(Class<String> c) { System.out.println(c); }

可以这样调用(它将编译运行):

Class r = Integer.class
foo(r); //THIS IS OK (BUT SHOULDN'T BE)

但不是:

Class<?> w = Integer.class
foo(w); //WILL NOT COMPILE (RIGHTLY SO!)

通过始终使用非原始形式,即使?由于无法知道类型参数是什么(或受其限制)而必须使用,与使用原始类型相比,您允许编译器更充分地推断程序的正确性.


为什么有原始类型?

Java 语言规范说:

只允许使用原始类型作为对遗留代码兼容性的让步

您应该始终避免使用它们。无界通配符?可能在其他地方得到了最好的描述,但本质上意味着“这是在某种类型上参数化的,但我不知道(或关心)它是什么”。这与原始类型不同,原始类型令人厌恶,并且在其他具有泛型的语言中不存在,例如Scala


为什么类参数化?

好吧,这是一个用例。假设我有一些服务接口:

public interface FooService

我想注入它的实现,使用系统属性来定义要使用的类。

Class<?> c = Class.forName(System.getProperty("foo.service"));

在这一点上,我不知道我的班级是正确的类型

//next line throws ClassCastException if c is not of a compatible type
Class<? extends FooService> f = c.asSubclass(FooService.class); 

现在我可以实例化一个FooService

FooService s = f.newInstance(); //no cast
于 2010-12-02T16:43:43.377 回答
3

这似乎并没有增加太多价值,但不再发出警告。

你是对的。但这可能会增加价值:

Class<FooClass> fooClass;

或者,如果更合适的话:

Class<? extends FooClass> fooClass;

或者

Class<FooInterface> fooClass;

与一般的泛型一样,您可以通过指定您希望变量包含哪种类来提高类型安全性。针对原始类型的警告只是为了捕获未使用此潜力的代码片段。通过声明Class<?>您基本上是在说“这是为了举办任何类型的课程”。

于 2010-12-02T16:38:57.833 回答
2

因为,从 JDK 5 开始,Class现在已经有了参数化类型,这使得Class通用对象。这对于编译器进行类型检查(当然是在编译时)是必要的(因为引入了泛型)。

Class<?>表示“未知类”,其中?是泛型通配符。这意味着 a that fooClassof typeClass<?>接受Class其类型匹配任何内容的 a。

典型例子:

Class<?> classUnknown = null;
classUnknown = ArrayList.class; //This compiles.

您可以有效地提供更具体的参数化类型,例如:

Class<ArrayList> = ArrayList.class;

PS请记住,这Class<List> listClass = ArrayList.class;不会编译(即使 ArrayList 属于 List)但(正如 Mark Peters 在评论中提到的那样)Class<? extends List> listClass = ArrayList.class;确实编译(感谢通配符)。

于 2010-12-02T16:36:11.153 回答
1

Class类的 Javadoc确实提供了一些关于为什么此类存在类型参数的想法:

ClassT - 此对象建模的类的类型。例如,如果要建模的类未知,则类型String.classClass<String>. Use 。Class<?>

这个类型参数的使用不是很明显,但是粗略的看一下这个类的源代码,就知道为什么有时需要这个类型参数了。考虑newInstance方法的实现:

    public T newInstance() 
        throws InstantiationException, IllegalAccessException
    {
    if (System.getSecurityManager() != null) {
        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
    }
    return newInstance0();
    }

如果没有注意到,这个方法返回的对象的类型是类型参数的类型。这在使用大量反射的代码中很有用,并且在代码中需要格外小心以确保实例化正确类型的对象。

考虑问题中的示例,如果将类实例声明为:

Class<String> fooClass;
Class<Integer> barClass;
String aString;

那么,几乎不可能编译以下代码:

aString = barClass.newInstance();

In short, if you're going to be working with class hierarchies and you wish to impose strict compile time checks to ensure that your code does not need to perform a lot of instanceof checks, then you're better off specifying the type of the class that you wish to utilize. Specifying ? allows all types, but there would be cases when you'll need to be more specific.

于 2010-12-02T16:52:44.633 回答
0

因为使用原始类型而不是参数化类型有很多陷阱。

其中之一是如果使用原始类型,则该类上的所有泛型都将丢失。甚至那些按方法定义的。

于 2010-12-02T16:39:59.593 回答