问题:
最佳答案
什么是原始类型?
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 typeR
that is not inherited from a superclass or superinterface ofR
.
这是一个例子说明:
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
,并且上面的代码将进行编译。也可以看看
原始类型与使用
<Object>
作为类型参数有何不同?以下是来自Effective Java 2nd Edition,项目23的引用:不要在新代码中使用原始类型:
Just what is the difference between the raw type
List
and the parameterized typeList<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 aList<String>
to a parameter of typeList
, you can't pass it to a parameter of typeList<Object>
. There are subtyping rules for generics, andList<String>
is a subtype of the raw typeList
, but not of the parameterized typeList<Object>
. As a consequence, you lose type safety if you use raw type likeList
, but not if you use a parameterized type likeList<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
作为参数,那么它将进行编译,因此您将失去从泛型获得的类型安全性。也可以看看
<E extends Number>
and <Number>
? 原始类型与使用
<?>
作为类型参数有何不同?List<Object>
,List<String>
等都是List<?>
,因此很可能会说它们只是List
而已。但是,有一个主要区别:由于List<E>
仅定义add(E)
,因此您不能仅向List<?>
中添加任何对象。另一方面,由于原始类型List
不具有类型安全性,因此您可以将add
与List
差不多。请考虑以下片段的以下变体:
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 typeC
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 toC
.
简单来说,当使用原始类型时,构造函数,实例方法和非
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 ass
and the erasures of all the formal parameter types given ins
.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>
也可以看看
Collection<String>.class
Illegal? 关于java - 什么是原始类型,为什么我们不应该使用它呢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45809139/