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>的成员类型,因为它是staticmt1mt2都使用实际的类型参数声明,因此它们不是原始类型。

    原始类型有何特别之处?
    本质上,原始类型的行为与引入泛型之前的行为相同。也就是说,以下在编译时完全合法。
    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,项目23的引用:不要在新代码中使用原始类型:

    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中的泛型是不变的。 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的作者之一Alex Buckley的一些想法,这些想法为什么会发生: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.


    有效的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/45809139/

    相关文章:

    java - 执行通用 Java 类

    java - 使用无限通配符会捕获错误的情况,但只有在使用原始类型时才会被标记为警告

    java - Android Studio 中的新 Activity 无法启动

    java - 如何使用 MOXy 将 json 解码为 java.util.Map<String, Object>?

    java - 如何在我的 Android 项目中使用第二个 Java 类?

    java - 结合原始类型和通用方法

    java - 终止期间线程延迟

    java - 使用 Mockito 模拟泛型类型时出现 ClassCastException

    java - 基本java问题: Type casting