为什么Class <! - ? - >首选Class

时间:2010-12-02 16:30:22

标签: java generics raw-types

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

Class fooClass;

Eclipse给了我警告:

  

Class是原始类型。参考   泛型类应该是   参数化

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

Class<?> fooClass;

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

编辑:为什么Class是通用的?您能否举一个参数化的例子,即是否可以有效使用<?>以外的其他内容?

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

Class<MyString> myStringClass = MyString.class;

而不是

Class myStringClass = MyString.class;

(但是从第一天起就使用了Java,我没注意到Class何时成为泛型);

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

5 个答案:

答案 0 :(得分:44)

原始类型和无界通配符

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

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

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 Language Specification说:

  

原始类型的使用仅允许作为遗留代码兼容性的特许

你应该总是避免它们。无界通配符?可能在别处最好描述,但实质上意味着“这是在某种类型上参数化,但我不知道(或关心)它是什么”。这与原始类型不同,后者是令人厌恶的,并且在其他语言中不存在,如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

答案 1 :(得分:3)

  

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

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

Class<FooClass> fooClass;

或者,如果更合适:

Class<? extends FooClass> fooClass;

Class<FooInterface> fooClass;

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

答案 2 :(得分:2)

因为,自JDK 5起,Class现在已经具有参数化类型,这使得Class成为通用对象。这是必要的(自Generics引入以来)编译器进行类型检查(当然是在编译时)。

Class<?>表示“{1}}为泛型wildcard的”未知类别“。这意味着?类型fooClass接受类型与任何内容匹配的Class<?>

典型示例:

Class

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

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

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

答案 3 :(得分:1)

因为使用原始类型而不是参数化类型存在许多缺陷。

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

答案 4 :(得分:1)

Class类的Javadoc确实给出了为什么这个类存在类型参数的想法:

  

T - 由类建模的类型   这个Class对象。例如,   String.class的类型为Class<String>.   如果班级正在使用Class<?>   建模是未知的。

使用此类型参数并不是那么明显,但粗略地查看类的源代码,表明有时需要类型参数的原因。考虑newInstance方法的实现:

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

如果没有注意到,此方法返回的对象类型是type参数的类型。这在使用大量反射的代码中非常有用,并且需要特别注意确保正确实例化正确类型的对象。

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

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

然后,几乎不可能有以下代码进行编译:

aString = barClass.newInstance();

简而言之,如果您要使用类层次结构,并且希望执行严格的编译时检查以确保您的代码不需要执行大量instanceof检查,那么您就是最好指定您希望使用的类的类型。指定?允许所有类型,但有些情况下您需要更具体。