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

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

问题描述

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

推荐答案

什么是原始类型?

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

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

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.

原始类型 R 的非 static 成员类型,它不是从 R 的超类或超接口继承的.

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!)
    }
}

这里,MyType 是一个参数化类型(JLS 4.5).通常将这种类型简单地简称为 MyType,但技术上的名称是 MyType.

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 有一个原始类型(并生成编译警告)在上述定义中的第一个要点;inn 也有第三个要点的原始类型.

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 不是参数化类型,即使它是参数化类型 MyType 的成员类型,因为它是 static>.

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

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

mt1, and mt2 are both declared with actual type parameters, so they're not 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

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

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

大概,如果您希望 names 只包含 String,您可以也许仍然使用原始类型并手动检查每个 add 自己,然后手动转换String names 中的每一项.更好,不过不是使用原始类型,而是让编译器为您完成所有工作,利用 Java 泛型的强大功能.

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!

当然,如果您DO希望names允许Boolean,那么您可以将其声明为List;名称,上面的代码就会编译.

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.

以下引用自 Effective Java 2nd Edition, Item 23:不要在新代码中使用原始类型:

原始类型List和参数化类型List有什么区别?粗略地说,前者选择退出泛型类型检查,而后者明确告诉编译器它能够保存任何类型的对象.虽然您可以将 List 传递给 List 类型的参数,但不能将其传递给 List 类型的参数代码>.泛型有子类型规则,List是原始类型List的子类型,但不是参数化类型List的子类型代码>.因此,如果你使用像 List 这样的原始类型,你会失去类型安全,但如果你使用像 List 这样的参数化类型,则不会.

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 并附加一个 new 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());
}

Java 中的泛型是不变的.List 不是 List,因此以下将生成编译器警告:

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!

如果你已经声明 appendNewObject 接受一个原始类型 List 作为参数,那么这将被编译,因此你会失去从泛型中获得的类型安全性.

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.

ListList 等都是 List,所以可能只是说他们只是 List 而不是.但是,有一个主要区别:由于 List 仅定义了 add(E),因此您不能将任意对象添加到 List<;?>.另一方面,由于原始类型 List 没有类型安全性,您可以将任何内容addList.

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!

编译器在保护您免于违反List 的类型不变性方面做得非常出色!如果您将参数声明为原始类型 List list,那么代码将被编译,并且您将违反 List 的类型不变量;名称.

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.

回到 JLS 4.8:

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.

未从其超类或超接口继承的原始类型 C 的构造函数、实例方法或非 static 字段的类型是原始类型对应于 C 对应的泛型声明中其类型的擦除.

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.

以下面的例子为例:

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

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

JLS 4.6继续解释以下内容:

类型擦除还将构造函数或方法的签名映射到没有参数化类型或类型变量的签名.构造函数或方法签名的擦除s是一个签名由与 s 相同的名称和 s 中给出的所有形式参数类型的擦除组成.

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 erasure of the signature of a generic method has no type parameters.

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

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.)

这是 JLS 4.8 中的另一句话:

Here's another quote from JLS 4.8:

仅允许使用原始类型作为对遗留代码兼容性的让步.强烈反对在将泛型引入 Java 编程语言之后编写的代码中使用原始类型.Java 编程语言的未来版本可能会禁止使用原始类型.

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.

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

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.

不幸的是,因为 Java 泛型是非具体化的,所以在新代码中必须使用原始类型的情况有两个例外:

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

  • 类文字,例如List.class,而不是 List.class
  • instanceof 操作数,例如o instanceof Set,不是o instanceof Set
  • Class literals, e.g. List.class, not List<String>.class
  • instanceof operand, e.g. o instanceof Set, not o instanceof Set<String>

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

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