什么是原始类型,为什么我们不应该使用它? [英] What is a raw type and why shouldn't we use it?

查看:128
本文介绍了什么是原始类型,为什么我们不应该使用它?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题:




  • Java中的原始类型是什么,为什么我经常听到它们不应该用于新代码?

  • 如果我们不能使用原始类型,那么有什么替代方法?它是如何更好的?


解决方案

什么是原始类型?



Java语言规范定义了一个原始类型如下所示:

JLS 4.8原始类型




原始类型定义为以下之一: / p>


  • 通过使用不带伴随类型参数列表的泛型类型声明的名称形成的引用类型。


  • 一个数组类型,其元素类型是一个原始类型。

  • c>静态原始类型 R 的成员类型不是从超类或超级接口继承的 R



下面是一个例子来说明:

  public class MyType< E> {
class Inner {}
静态类嵌套{}

public static void main(String [] args){
MyType mt; //警告:MyType是原始类型
MyType.Inner inn; //警告:MyType.Inner是原始类型

MyType.Nested嵌套; //没有警告:没有参数化类型
MyType< Object> MT1; //无警告:给出的类型参数
MyType<?> MT2; //无警告:给出的类型参数(通配符OK!)
}
}

这里, MyType< E> 是一个参数化类型 JLS 4.5 )。通俗地说,这种类型简单地简称为 MyType ,但技术上名称是 MyType< E>

mt 具有上述定义中第一个项目符号点的原始类型(并生成编译警告) ; inn 在第三个要点中也有一个原始类型。


$ b

MyType.Nested 不是参数化类型,即使它是参数化类型 MyType< E> 的成员类型,因为它是 static



mt1 mt2 都是用实际的类型参数声明的,所以它们不是原始类型。






原始类型有什么特别之处?



基本上,原始类型的行为就像在引入泛型之前一样。也就是说,下面的代码在编译时是完全合法的。

  List names = new ArrayList(); //警告:原始类型! 
names.add(John);
names.add(Mary);
names.add(Boolean.FALSE); //不是编译错误!

上面的代码运行得很好,但是假设您还有以下内容:

  for(Object o:names){
String name =(String)o;
System.out.println(name);
} //抛出ClassCastException!
// java.lang.Boolean不能转换为java.lang.String

现在我们在运行时遇到了麻烦,因为 names 包含的内容不是 instanceof String



据推测,如果您希望名称仅包含字符串,您 可能仍然使用原始类型,手动检查每个 添加,然后手动转换 em>到 String names 中的每个项目。 甚至更好,但不是使用原始类型,而是让编译器为您完成所有工作,利用Java泛型的强大功能。

 列表< String> names = new ArrayList< String>(); 
names.add(John);
names.add(Mary);
names.add(Boolean.FALSE); //编译错误!

当然,如果您 DO 想要名称允许一个布尔值,那么你可以声明它为 List< Object>名称,上面的代码将被编译。



另见








原始类型与使用< Object> 作为类型参数吗?



下面是来自 Effective Java 2nd Edition的一句话,第23项:不要在新代码中使用原始类型


原始类型 List 与参数化类型列表与LT;对象> ?松散地说,前者选择了泛型类型检查,而后者明确地告诉编译器它可以容纳任何类型的对象。虽然您可以将 List< String> 传递给 List 类型的参数,但您无法将它传递给类型列表<对象>< / code>的参数。泛型有子类型规则, List< String> 是原始类型 List 的子类型,但不是参数化类型 List< Object> 。因此,如果您使用原始类型(如 List ),则会失去类型安全性,但如果使用参数化类型(如 List< Object>> ;


为了说明这一点,请考虑以下方法, c $ c> List< Object> 并追加一个 new Object()

  void appendNewObject(List< Object> list){
list.add(new Object());
}

Java中的泛型是不变的。 A List< String> 不是 List< Object> ,所以下面的代码会产生一个编译警告: p>

 列表< String> names = new ArrayList< String>(); 
appendNewObject(names); //编译错误!

如果您声明 appendNewObject 原始类型 List 作为参数,那么这会编译,因此您将失去从泛型中获得的类型安全性。



另见








原始类型与使用不同<?> 作为类型参数?



List< Object> code>, List< String> 等都是 List <?>< / code>,所以它可能是试图说他们只是 List 而不是。但是,有一个主要区别:由于 List< E> 只定义了 add(E) t只需将任意对象添加到 List<?> 中。另一方面,由于原始类型 List 没有类型安全性,所以您可以添加 列表



请考虑以前代码段的以下变化形式:

  static void appendNewObject(List<?> list){
list.add(new Object()); //编译错误!
}
// ...

List< String> names = new ArrayList< String>();
appendNewObject(names); //这部分很好!

编译器做了很好的工作,可以保护您免受可能违反<$ c $的类型不变性C>列表与LT;?> !如果你已经将该参数声明为原始类型 List list ,那么代码将被编译,并且你违反了 List< String>类型不变量;名称






原始类型是该类型的擦除



返回到JLS 4.8:


可以使用删除的参数化类型,或擦除其元素类型为参数化类型的数组类型。 这种类型被称为原始类型



原始类型的超类(分别为超接口)是泛型类型的任何参数化的超类(超接口)的擦除。 b
$ b

原始类型的构造函数,实例方法或非静态字段的类型 C 不是从它的超类或超接口继承的,它是对应于在对应于 C 的泛型声明中删除其类型的原始类型。


简单来说,当使用原始类型时,构造函数,实例方法和非 - static



<$ p <$ p $字段也被删除

$ p> class MyType< E> {
列表< String> getNames(){
返回Arrays.asList(John,Mary);


public static void main(String [] args){
MyType rawType = new MyType();
//未经检查的警告!
//必填:列表< String>找到:列表
列表< String> names = rawType.getNames();
//编译错误!
//不兼容的类型:对象无法转换为String
for(String str:rawType.getNames())
System.out.print(str);


当我们使用原始 MyType getNames 也会被清除,以便它返回一个原始 List



JLS 4.6 继续解释以下内容:
$ b blockquote>

类型擦除还映射构造函数或方法添加到没有参数化类型或类型变量的签名中。删除构造函数或方法签名 s 是一个签名,其名称与 s 以及在 s 中给出的所有形式参数类型的删除。



如果方法或构造函数的签名被擦除,方法的返回类型和泛型方法或构造函数的类型参数也会擦除。

删除一个通用方法的签名od没有类型参数。

以下错误报告包含来自编译器开发人员Maurizio Cimadamore和Alex Buckley的一些想法, JLS的作者,为什么应该发生这种行为: https:// bugs .openjdk.java.net /浏览/ JDK-6400189 。 (简而言之,它使规范变得更简单。)




如果它不安全,为什么它允许使用原始类型?



下面是JLS 4.8的另一个引用:
$ b


使用原始类型只允许作为遗留代码兼容性的让步。在通用性引入到Java编程语言之后编写的代码中使用原始类型是非常令人沮丧的。未来版本的Java编程语言可能会禁止使用原始类型。


有效Java第二版也有这样的补充:


鉴于你不应该使用原始类型,为什么语言设计者允许他们?提供兼容性。



Java平台即将进入第二个十年,当时泛型被引入,并且存在大量的Java代码,它们并没有使用泛型。所有这些代码都保持合法并与使用泛型的新代码进行互操作被认为至关重要。将参数化类型的实例传递给设计用于普通类型的方法必须合法,反之亦然。这一要求被称为迁移兼容性,它决定支持原始类型。


总之,原始类型不应该在新代码中使用。 您应始终使用参数化类型






是否没有例外?



不幸的是,因为Java泛型没有具体化,所以在新代码中必须使用原始类型:


  • 类文字,例如 List.class ,而不是 List< String> .class

  • instanceof 操作数,例如 o instanceof Set ,而不是 o instanceof Set< String>



另见




  • 为什么集合< String> .class 非法?

  • ul>

    Questions:

    • What are raw types in Java, and why do I often hear that they shouldn't be used in new code?
    • What is the alternative if we can't use raw types, and how is it better?

    解决方案

    What is a raw type?

    The Java Language Specification defines a raw type as follows:

    JLS 4.8 Raw Types

    A raw type is defined to be one of:

    • The reference type that is formed by taking the name of a generic type declaration without an accompanying type argument list.

    • An array type whose element type is a raw type.

    • A non-static member type of a raw type R that is not inherited from a superclass or superinterface of R.

    Here's an example to illustrate:

    public class MyType<E> {
        class Inner { }
        static class Nested { }
    
        public static void main(String[] args) {
            MyType mt;          // warning: MyType is a raw type
            MyType.Inner inn;   // warning: MyType.Inner is a raw type
    
            MyType.Nested nest; // no warning: not parameterized type
            MyType<Object> mt1; // no warning: type parameter given
            MyType<?> mt2;      // no warning: type parameter given (wildcard OK!)
        }
    }
    

    Here, MyType<E> is a parameterized type (JLS 4.5). It is common to colloquially refer to this type as simply MyType for short, but technically the name is MyType<E>.

    mt has a raw type (and generates a compilation warning) by the first bullet point in the above definition; inn also has a raw type by the third bullet point.

    MyType.Nested is not a parameterized type, even though it's a member type of a parameterized type MyType<E>, because it's static.

    mt1, and mt2 are both declared with actual type parameters, so they're not raw types.


    What's so special about raw types?

    Essentially, raw types behaves just like they were before generics were introduced. That is, the following is entirely legal at compile-time.

    List names = new ArrayList(); // warning: raw type!
    names.add("John");
    names.add("Mary");
    names.add(Boolean.FALSE); // not a compilation error!
    

    The above code runs just fine, but suppose you also have the following:

    for (Object o : names) {
        String name = (String) o;
        System.out.println(name);
    } // throws ClassCastException!
      //    java.lang.Boolean cannot be cast to java.lang.String
    

    Now we run into trouble at run-time, because names contains something that isn't an instanceof String.

    Presumably, if you want names to contain only String, you could perhaps still use a raw type and manually check every add yourself, and then manually cast to String every item from names. Even better, though is NOT to use a raw type and let the compiler do all the work for you, harnessing the power of Java generics.

    List<String> names = new ArrayList<String>();
    names.add("John");
    names.add("Mary");
    names.add(Boolean.FALSE); // compilation error!
    

    Of course, if you DO want names to allow a Boolean, then you can declare it as List<Object> names, and the above code would compile.

    See also


    How's a raw type different from using <Object> as type parameters?

    The following is a quote from Effective Java 2nd Edition, Item 23: Don't use raw types in new code:

    Just what is the difference between the raw type List and the parameterized type List<Object>? Loosely speaking, the former has opted out generic type checking, while the latter explicitly told the compiler that it is capable of holding objects of any type. While you can pass a List<String> to a parameter of type List, you can't pass it to a parameter of type List<Object>. There are subtyping rules for generics, and List<String> is a subtype of the raw type List, but not of the parameterized type List<Object>. As a consequence, you lose type safety if you use raw type like List, but not if you use a parameterized type like List<Object>.

    To illustrate the point, consider the following method which takes a List<Object> and appends a new Object().

    void appendNewObject(List<Object> list) {
       list.add(new Object());
    }
    

    Generics in Java are invariant. A List<String> is not a List<Object>, so the following would generate a compiler warning:

    List<String> names = new ArrayList<String>();
    appendNewObject(names); // compilation error!
    

    If you had declared appendNewObject to take a raw type List as parameter, then this would compile, and you'd therefore lose the type safety that you get from generics.

    See also


    How's a raw type different from using <?> as a type parameter?

    List<Object>, List<String>, etc are all List<?>, so it may be tempting to just say that they're just List instead. However, there is a major difference: since a List<E> defines only add(E), you can't add just any arbitrary object to a List<?>. On the other hand, since the raw type List does not have type safety, you can add just about anything to a List.

    Consider the following variation of the previous snippet:

    static void appendNewObject(List<?> list) {
        list.add(new Object()); // compilation error!
    }
    //...
    
    List<String> names = new ArrayList<String>();
    appendNewObject(names); // this part is fine!
    

    The compiler did a wonderful job of protecting you from potentially violating the type invariance of the List<?>! If you had declared the parameter as the raw type List list, then the code would compile, and you'd violate the type invariant of List<String> names.


    A raw type is the erasure of that type

    Back to JLS 4.8:

    It is possible to use as a type the erasure of a parameterized type or the erasure of an array type whose element type is a parameterized type. Such a type is called a raw type.

    [...]

    The superclasses (respectively, superinterfaces) of a raw type are the erasures of the superclasses (superinterfaces) of any of the parameterizations of the generic type.

    The type of a constructor, instance method, or non-static field of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

    In simpler terms, when a raw type is used, the constructors, instance methods and non-static fields are also erased.

    Take the following example:

    class MyType<E> {
        List<String> getNames() {
            return Arrays.asList("John", "Mary");
        }
    
        public static void main(String[] args) {
            MyType rawType = new MyType();
            // unchecked warning!
            // required: List<String> found: List
            List<String> names = rawType.getNames();
            // compilation error!
            // incompatible types: Object cannot be converted to String
            for (String str : rawType.getNames())
                System.out.print(str);
        }
    }
    

    When we use the raw MyType, getNames becomes erased as well, so that it returns a raw List!

    JLS 4.6 continues to explain the following:

    Type erasure also maps the signature of a constructor or method to a signature that has no parameterized types or type variables. The erasure of a constructor or method signature s is a signature consisting of the same name as s and the erasures of all the formal parameter types given in s.

    The return type of a method and the type parameters of a generic method or constructor also undergo erasure if the method or constructor's signature is erased.

    The erasure of the signature of a generic method has no type parameters.

    The following bug report contains some thoughts from Maurizio Cimadamore, a compiler dev, and Alex Buckley, one of the authors of the JLS, on why this sort of behavior ought to occur: https://bugs.openjdk.java.net/browse/JDK-6400189. (In short, it makes the specification simpler.)


    If it's unsafe, why is it allowed to use a raw type?

    Here's another quote from JLS 4.8:

    The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of genericity into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

    Effective Java 2nd Edition also has this to add:

    Given that you shouldn't use raw types, why did the language designers allow them? To provide compatibility.

    The Java platform was about to enter its second decade when generics were introduced, and there was an enormous amount of Java code in existence that did not use generics. It was deemed critical that all this code remains legal and interoperable with new code that does use generics. It had to be legal to pass instances of parameterized types to methods that were designed for use with ordinary types, and vice versa. This requirement, known as migration compatibility, drove the decision to support raw types.

    In summary, raw types should NEVER be used in new code. You should always use parameterized types.


    Are there no exceptions?

    Unfortunately, because Java generics are non-reified, there are two exceptions where raw types must be used in new code:

    • Class literals, e.g. List.class, not List<String>.class
    • instanceof operand, e.g. o instanceof Set, not o instanceof Set<String>

    See also

    这篇关于什么是原始类型,为什么我们不应该使用它?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆