什么是原始类型?
Java 语言规范定义了raw type如下:
JLS 4.8 原始类型 https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.8
原始类型定义为以下之一:
下面举一个例子来说明:
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 参数化类型 (JLS 4.5 https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.5)。通常通俗地将此类型简单地称为MyType
简而言之,但从技术上讲,名称是MyType<E>
.
mt
上述定义中的第一个项目符号点具有原始类型(并生成编译警告);inn
第三个要点也有一个原始类型。
MyType.Nested
不是参数化类型,即使它是参数化类型的成员类型MyType<E>
, 因为它是static
.
mt1
, and 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
, you could也许仍然使用原始类型并且手动检查每个 add
你自己,然后手动施放 to String
每件物品来自names
. 更好,但不是使用原始类型并且让编译器为你完成所有工作,利用 Java 泛型的力量。
List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!
当然,如果你DO want names
允许一个Boolean
,那么你可以将其声明为List<Object> names
,上面的代码可以编译。
See also
- Java 教程/泛型 https://docs.oracle.com/javase/tutorial/java/generics/
raw 类型与 using 有何不同<Object>
作为类型参数?
以下是引自《Effective Java》第二版,第 23 条:不要在新代码中使用原始类型:
到底和raw类型有什么区别List
和参数化类型List<Object>
?宽松地说,前者选择退出泛型类型检查,而后者明确告诉编译器它能够保存任何类型的对象。虽然您可以通过List<String>
到一个类型的参数List
,你不能将它传递给类型的参数List<Object>
。泛型有子类型规则,并且List<String>
是原始类型的子类型List
,但不是参数化类型List<Object>
。作为结果,如果你使用像这样的原始类型,你就会失去类型安全性List
,但如果您使用像这样的参数化类型,则不会List<Object>
.
为了说明这一点,请考虑以下方法,该方法采用List<Object>
并附加一个new Object()
.
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Java 中的泛型是不变的。 AList<String>
不是一个List<Object>
,因此以下内容将生成编译器警告:
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
如果你已经声明appendNewObject
采用原始类型List
作为参数,那么这将编译,因此您将失去从泛型获得的类型安全性。
See also
- 有什么区别<E extends Number> and <Number>? https://stackoverflow.com/questions/2770264/what-is-the-difference-between-e-extends-number-and-number/
- java 泛型(非)协方差 https://stackoverflow.com/questions/2660827/java-generics-covariance
raw 类型与 using 有何不同<?>
作为类型参数?
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:
可以用作类型擦除参数化类型的删除或元素类型为参数化类型的数组类型的擦除。这样的类型称为raw type.
[...]
原始类型的超类(分别是超级接口)是泛型类型的任何参数化的超类(超级接口)的擦除。
构造函数、实例方法或非构造函数的类型static
原始类型的字段C
不是从其超类或超接口继承的是原始类型,它对应于泛型声明中其类型的擦除,对应于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 https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.6继续解释如下:
类型擦除还将构造函数或方法的签名映射到没有参数化类型或类型变量的签名。构造函数或方法签名的擦除s
是由相同名称组成的签名s
以及删除中给出的所有形式参数类型s
.
如果擦除方法或构造函数的签名,则方法的返回类型以及泛型方法或构造函数的类型参数也会被擦除。
泛型方法签名的擦除没有类型参数。
以下错误报告包含编译器开发人员 Maurizio Cimadamore 和 JLS 作者之一 Alex Buckley 对于为什么会发生这种行为的一些想法:https://bugs.openjdk.java.net/browse/JDK-6400189 https://bugs.openjdk.java.net/browse/JDK-6400189。 (简而言之,它使规范更加简单。)
如果不安全,为什么允许使用原始类型?
这是 JLS 4.8 的另一段引用:
仅允许使用原始类型作为对遗留代码兼容性的让步。强烈建议不要在 Java 编程语言中引入泛型性之后编写的代码中使用原始类型。 Java 编程语言的未来版本可能会禁止使用原始类型。
有效的Java第二版还有这个要补充:
既然你不应该使用原始类型,为什么语言设计者允许它们呢?提供兼容性。
当泛型被引入时,Java 平台即将进入第二个十年,并且存在大量不使用泛型的 Java 代码。所有这些代码保持合法并可与使用泛型的新代码互操作被认为至关重要。将参数化类型的实例传递给设计用于普通类型的方法必须是合法的,反之亦然。这一要求,称为迁移兼容性,推动了支持原始类型的决定。
总之,原始类型永远不应该在新代码中使用。您应该始终使用参数化类型.
难道就没有例外吗?
不幸的是,由于 Java 泛型是非具体化的,因此有两个例外必须在新代码中使用原始类型:
- 类文字,例如
List.class
, not List<String>.class
-
instanceof
操作数,例如o instanceof Set
, not o instanceof Set<String>
See also
- Why is Collection<String>.class非法的? https://stackoverflow.com/questions/2745193/why-is-collectionstring-class-illegal/