java - 什么是原始类型,为什么我们不应该使用它?

标签 java generics raw-types

问题:

  • Java 中的原始类型是什么,为什么我经常听到他们不应该在新代码中使用?
  • 如果我们不能使用原始类型,有什么替代方法,它如何更好?
  • 最佳答案

    什么是原始类型?
    Java 语言规范定义了一个原始类型,如下所示:
    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.


    这里有一个例子来说明:
    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!)
        }
    }
    
    在这里,MyType<E>是参数化类型 ( JLS 4.5 )。通常将这种类型简单地称为 MyType简而言之,但技术上的名称是 MyType<E> .mt上面定义中的第一个要点具有原始类型(并生成编译警告); inn第三个要点也有一个原始类型。MyType.Nested不是参数化类型,即使它是参数化类型的成员类型 MyType<E> , 因为它是 static .mt1 , 和 mt2都使用实际类型参数声明,因此它们不是原始类型。

    原始类型有什么特别之处?
    本质上,原始类型的行为就像引入泛型之前一样。也就是说,以下在编译时是完全合法的。
    List names = new ArrayList(); // warning: raw type!
    names.add("John");
    names.add("Mary");
    names.add(Boolean.FALSE); // not a compilation error!
    
    上面的代码运行得很好,但假设您还有以下代码:
    for (Object o : names) {
        String name = (String) o;
        System.out.println(name);
    } // throws ClassCastException!
      //    java.lang.Boolean cannot be cast to java.lang.String
    
    现在我们在运行时遇到了麻烦,因为 names包含不是 instanceof String 的内容.
    想必,如果你想要 names仅包含 String ,您也许仍然可以使用原始类型并手动检查每个 add自己,然后手动转换到 String来自 names 的每件商品. 更好 ,虽然不是使用原始类型,而是让编译器为您完成所有工作,利用 Java 泛型的力量。
    List<String> names = new ArrayList<String>();
    names.add("John");
    names.add("Mary");
    names.add(Boolean.FALSE); // compilation error!
    
    当然,如果您确实想要 names允许 Boolean ,那么您可以将其声明为 List<Object> names ,上面的代码会编译。
    也可以看看
  • Java Tutorials/Generics

  • 原始类型与使用 <Object> 有何不同作为类型参数?
    以下引用自 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>.


    为了说明这一点,请考虑以下采用 List<Object> 的方法。并附加一个 new Object() .
    void appendNewObject(List<Object> list) {
       list.add(new Object());
    }
    
    Java 中的泛型是不变的。 A List<String>不是 List<Object> ,因此以下将生成编译器警告:
    List<String> names = new ArrayList<String>();
    appendNewObject(names); // compilation error!
    
    如果您已声明 appendNewObject取原始类型 List作为参数,那么这将编译,因此您将失去从泛型获得的类型安全性。
    也可以看看
  • What is the difference between <E extends Number> and <Number> ?
  • java generics (not) covariance

  • 原始类型与使用 <?> 有何不同作为类型参数?List<Object> , List<String>等都是List<?> ,所以可能很想说他们只是 List反而。但是,有一个主要区别:因为 List<E>仅定义 add(E) ,您不能将任意对象添加到 List<?> .另一方面,由于原始类型 List没有类型安全,你可以 addList 的任何事情.
    考虑上一个片段的以下变体:
    static void appendNewObject(List<?> list) {
        list.add(new Object()); // compilation error!
    }
    //...
    
    List<String> names = new ArrayList<String>();
    appendNewObject(names); // this part is fine!
    
    编译器在保护您免于违反 List<?> 的类型不变性方面做得非常出色。 !如果您已将参数声明为原始类型 List list ,然后代码将被编译,并且您将违反 List<String> names 的类型不变量.

    原始类型是该类型的删除
    回到 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.


    简单来说,当使用原始类型时,构造函数、实例方法和非 static字段也被删除。
    以下面的例子为例:
    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);
        }
    }
    
    当我们使用原始 MyType , getNames也被删除,因此它返回原始 List !
    JLS 4.6继续解释以下内容:

    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.


    以下错误报告包含来自编译器开发者 Maurizio Cimadamore 和 JLS 的作者之一亚历克斯巴克利关于为什么应该发生这种行为的一些想法:https://bugs.openjdk.java.net/browse/JDK-6400189 . (简而言之,它使规范更简单。)

    如果不安全,为什么允许使用原始类型?
    这是 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 还添加了以下内容:

    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.


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

    没有异常(exception)吗?
    不幸的是,因为 Java 泛型是非具体化的,所以在新代码中必须使用原始类型的情况有两个异常(exception):
  • 类文字,例如List.class ,不是 List<String>.class
  • instanceof操作数,例如o instanceof Set ,不是 o instanceof Set<String>

  • 也可以看看
  • Why is Collection<String>.class Illegal?
  • 关于java - 什么是原始类型,为什么我们不应该使用它?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36718288/

    相关文章:

    java - 有没有办法用 xPath 获取节点值而不需要这种丑陋的翻筋斗?

    .net - 使用泛型创建有意义的参数名称

    java - 如何向此通用集合添加子类型?

    java - 什么是原始类型,为什么我们不应该使用它呢?

    java.lang.AbstractMethodError : abstract method not implemented

    java - Android java-grpc 客户端到 python-grpc 服务器

    java - 什么是原始类型,为什么我们不应该使用它呢?

    java - 如何在对象实例化之前确保参数化类型的有效性

    java - 尝试更改将多个音频文件合并为一个的代码以适用于两个以上的文件

    java - 构造函数未定义错误和泛型类