问题:
- Java 中的原始类型是什么,为什么我经常听说它们不应该在新代码中使用?
- 如果我们不能使用原始类型,还有什么替代方案,它有什么更好的选择?
Java 语言规范定义了一个原始类型,如下所示:
原始类型定义为以下之一:
通过采用泛型类型声明的名称而不附带类型参数列表形成的引用类型。
元素类型为原始类型的数组类型。
未从 的超类或超接口继承
static
的原始类型的非成员类型。R
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
然后手动从. 更好的是,虽然不是使用原始类型,而是让编译器为您完成所有工作,利用 Java 泛型的力量。String
names
List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!
当然,如果你确实想names
允许 a Boolean
,那么你可以将它声明为List<Object> names
,上面的代码就会编译通过。
<Object>
用作类型参数有何不同?以下是来自Effective Java 2nd Edition 第 23 条的引述:不要在新代码中使用原始类型:
原始类型
List
和参数化类型之间有什么区别List<Object>
?粗略地说,前者选择退出泛型类型检查,而后者明确告诉编译器它能够保存任何类型的对象。虽然您可以将 a 传递List<String>
给 type 的参数List
,但不能将它传递给 type 的参数List<Object>
。泛型有子类型规则,List<String>
是原始类型的子类型List
,但不是参数化类型的子类型List<Object>
。因此,如果使用原始类型 likeList
,则会失去类型安全性,但如果使用参数化类型 like 则不会List<Object>
。
为了说明这一点,请考虑以下采用 aList<Object>
并附加 a 的方法new Object()
。
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Java 中的泛型是不变的。AList<String>
不是 a List<Object>
,因此以下会生成编译器警告:
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
如果您已声明appendNewObject
将原始类型List
作为参数,那么它将编译,因此您将失去从泛型获得的类型安全性。
<?>
用作类型参数有何不同?List<Object>
, List<String>
, 等都是List<?>
, 所以说它们只是List
相反可能很诱人。但是,有一个主要区别:由于 aList<E>
仅定义add(E)
,因此您不能将任意对象添加到 a 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:
可以将参数化类型的擦除或元素类型为参数化类型的数组类型的擦除用作类型。这种类型称为原始类型。
[...]
原始类型的超类(分别为超接口)是泛型类型的任何参数化的超类(超接口)的擦除。
未从其超类或超接口继承的原始类型的构造函数、实例方法或非
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);
}
}
当我们使用 raw时MyType
,getNames
也会被擦除,因此它返回一个 raw List
!
JLS 4.6继续解释以下内容:
类型擦除还将构造函数或方法的签名映射到没有参数化类型或类型变量的签名。构造函数或方法签名的擦除是由与 中给出的所有形式参数类型
s
相同的名称和擦除组成的签名。s
s
如果方法或构造函数的签名被擦除,则方法的返回类型和泛型方法或构造函数的类型参数也会被擦除。
泛型方法签名的擦除没有类型参数。
以下错误报告包含编译器开发人员 Maurizio Cimadamore 和 JLS 的作者之一 Alex Buckley 关于为什么会发生这种行为的一些想法:https ://bugs.openjdk.java.net/browse /JDK-6400189。(简而言之,它使规范更简单。)
这是 JLS 4.8 中的另一个引用:
仅允许使用原始类型作为对遗留代码兼容性的让步。强烈反对在将泛型引入 Java 编程语言之后编写的代码中使用原始类型。Java 编程语言的未来版本可能不允许使用原始类型。
Effective Java 2nd Edition还添加了以下内容:
鉴于您不应该使用原始类型,为什么语言设计者允许它们?提供兼容性。
引入泛型时,Java 平台即将进入第二个十年,存在大量不使用泛型的 Java 代码。所有这些代码保持合法并与使用泛型的新代码互操作被认为是至关重要的。将参数化类型的实例传递给设计用于普通类型的方法必须是合法的,反之亦然。这种称为迁移兼容性的要求推动了支持原始类型的决定。
总之,原始类型不应该在新代码中使用。您应该始终使用参数化类型。
不幸的是,由于 Java 泛型是未具体化的,因此有两个例外情况,即必须在新代码中使用原始类型:
List.class
,不List<String>.class
instanceof
操作数,例如o instanceof Set
,不o instanceof Set<String>
What are raw types in Java, and why do I often hear that they shouldn't be used in new code?
Raw-types are ancient history of the Java language. In the beginning there were Collections
and they held Objects
nothing more and nothing less. Every operation on Collections
required casts from Object
to the desired type.
List aList = new ArrayList();
String s = "Hello World!";
aList.add(s);
String c = (String)aList.get(0);
While this worked most of the time, errors did happen
List aNumberList = new ArrayList();
String one = "1";//Number one
aNumberList.add(one);
Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here
The old typeless collections could not enforce type-safety so the programmer had to remember what he stored within a collection.
Generics where invented to get around this limitation, the developer would declare the stored type once and the compiler would do it instead.
List<String> aNumberList = new ArrayList<String>();
aNumberList.add("one");
Integer iOne = aNumberList.get(0);//Compile time error
String sOne = aNumberList.get(0);//works fine
For Comparison:
// Old style collections now known as raw types
List aList = new ArrayList(); //Could contain anything
// New style collections with Generics
List<String> aList = new ArrayList<String>(); //Contains only Strings
More complex the Compareable interface:
//raw, not type save can compare with Other classes
class MyCompareAble implements CompareAble
{
int id;
public int compareTo(Object other)
{return this.id - ((MyCompareAble)other).id;}
}
//Generic
class MyCompareAble implements CompareAble<MyCompareAble>
{
int id;
public int compareTo(MyCompareAble other)
{return this.id - other.id;}
}
Note that it is impossible to implement the CompareAble
interface with compareTo(MyCompareAble)
with raw types.
Why you should not use them:
Object
stored in a Collection
has to be cast before it can be usedObject
What the compiler does: Generics are backward compatible, they use the same java classes as the raw types do. The magic happens mostly at compile time.
List<String> someStrings = new ArrayList<String>();
someStrings.add("one");
String one = someStrings.get(0);
Will be compiled as:
List someStrings = new ArrayList();
someStrings.add("one");
String one = (String)someStrings.get(0);
This is the same code you would write if you used the raw types directly. Thought I'm not sure what happens with the CompareAble
interface, I guess that it creates two compareTo
functions, one taking a MyCompareAble
and the other taking an Object
and passing it to the first after casting it.
What are the alternatives to raw types: Use generics
原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定通用 Box 类:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
要创建 的参数化类型Box<T>
,您需要为形式类型参数提供一个实际类型参数T
:
Box<Integer> intBox = new Box<>();
如果省略了实际的类型参数,则创建一个原始类型Box<T>
:
Box rawBox = new Box();
因此,Box
是泛型类型的原始类型Box<T>
。但是,非泛型类或接口类型不是原始类型。
原始类型出现在遗留代码中是因为许多 API 类(例如 Collections 类)在 JDK 5.0 之前不是通用的。当使用原始类型时,你基本上得到了前泛型行为——aBox
给你Object
s。为了向后兼容,允许将参数化类型分配给其原始类型:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox; // OK
但是,如果将原始类型分配给参数化类型,则会收到警告:
Box rawBox = new Box(); // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox; // warning: unchecked conversion
如果您使用原始类型调用在相应泛型类型中定义的泛型方法,您也会收到警告:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)
警告显示原始类型绕过泛型类型检查,将不安全代码的捕获推迟到运行时。因此,您应该避免使用原始类型。
类型擦除部分有更多关于 Java 编译器如何使用原始类型的信息。
如前所述,将遗留代码与通用代码混合时,您可能会遇到类似于以下内容的警告消息:
注意:Example.java 使用未经检查或不安全的操作。
注意:使用 -Xlint:unchecked 重新编译以获取详细信息。
当使用对原始类型进行操作的旧 API 时,可能会发生这种情况,如以下示例所示:
public class WarningDemo {
public static void main(String[] args){
Box<Integer> bi;
bi = createBox();
}
static Box createBox(){
return new Box();
}
}
术语“未检查”意味着编译器没有足够的类型信息来执行确保类型安全所需的所有类型检查。默认情况下,“未检查”警告是禁用的,尽管编译器会给出提示。要查看所有“未检查”警告,请使用 -Xlint:unchecked 重新编译。
使用 -Xlint:unchecked 重新编译前面的示例会显示以下附加信息:
WarningDemo.java:4: warning: [unchecked] unchecked conversion
found : Box
required: Box<java.lang.Integer>
bi = createBox();
^
1 warning
要完全禁用未经检查的警告,请使用 -Xlint:-unchecked 标志。注释抑制未经检查的@SuppressWarnings("unchecked")
警告。如果您不熟悉@SuppressWarnings
语法,请参阅注解。
原始来源:Java 教程
Java 中的“原始”类型是一个非泛型类,它处理“原始”对象,而不是类型安全的泛型类型参数。
例如,在 Java 泛型可用之前,您将使用这样的集合类:
LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject)list.get(0);
当您将对象添加到列表中时,它并不关心它是什么类型的对象,当您从列表中获取它时,您必须将其显式转换为您期望的类型。
使用泛型,您可以删除“未知”因素,因为您必须明确指定列表中可以包含的对象类型:
LinkedList<MyObject> list = new LinkedList<MyObject>();
list.add(new MyObject());
MyObject myObject = list.get(0);
请注意,使用泛型您不必强制转换来自 get 调用的对象,该集合已预定义为仅适用于 MyObject。这正是仿制药的主要驱动因素。它将运行时错误的来源更改为可以在编译时检查的内容。
private static List<String> list = new ArrayList<String>();
您应该指定类型参数。
该警告建议应将定义为支持泛型的类型参数化,而不是使用其原始形式。
List
被定义为支持泛型:public class List<E>
. 这允许许多类型安全的操作,这些操作在编译时进行检查。
什么是原始类型,为什么我经常听说它们不应该在新代码中使用?
“原始类型”是使用泛型类而不为其参数化类型指定类型参数,例如使用List
代替List<String>
. 当泛型被引入 Java 时,一些类被更新为使用泛型。使用这些类作为“原始类型”(不指定类型参数)允许遗留代码仍然编译。
“原始类型”用于向后兼容。不建议在新代码中使用它们,因为使用带有类型参数的泛型类允许更强的类型化,这反过来可能会提高代码的可理解性并导致更早地发现潜在问题。
如果我们不能使用原始类型,还有什么替代方案,它有什么更好的选择?
首选的替代方法是按预期使用泛型类 - 带有合适的类型参数(例如List<String>
)。这允许程序员更具体地指定类型,向未来的维护者传达更多关于变量或数据结构的预期用途的意义,并且允许编译器强制执行更好的类型安全。这些优点一起可以提高代码质量并有助于防止引入一些编码错误。
例如,对于程序员希望确保名为“names”的 List 变量仅包含字符串的方法:
List<String> names = new ArrayList<String>();
names.add("John"); // OK
names.add(new Integer(1)); // compile error
在这里,我正在考虑多种案例,您可以通过这些案例来明确概念
1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();
ArrayList<String> arr
它是一个ArrayList
类型的引用变量,String
它引用了一个ArralyList
Object 类型String
。这意味着它只能容纳 String 类型的 Object。
它是严格到String
非原始类型的,所以它永远不会发出警告。
arr.add("hello");// alone statement will compile successfully and no warning.
arr.add(23); //prone to compile time error.
//error: no suitable method found for add(int)
在这种情况下ArrayList<String> arr
是严格类型,但您的 Objectnew ArrayList();
是原始类型。
arr.add("hello"); //alone this compile but raise the warning.
arr.add(23); //again prone to compile time error.
//error: no suitable method found for add(int)
这arr
是一个严格的类型。因此,添加integer
.
警告:-
Raw
类型对象被引用到 的Strict
类型引用变量ArrayList
。
在这种情况下ArrayList arr
是原始类型,但您的 Objectnew ArrayList<String>();
是 Strict 类型。
arr.add("hello");
arr.add(23); //compiles fine but raise the warning.
它将向其中添加任何类型的对象,因为它arr
是原始类型。
警告:-
Strict
类型对象被引用到raw
类型引用的变量。
编译器希望你这样写:
private static List<String> list = new ArrayList<String>();
因为否则,您可以将任何您喜欢的类型添加到list
中,从而使实例化变得new ArrayList<String>()
毫无意义。Java 泛型只是一个编译时特性,因此创建的对象如果分配给“原始类型”的引用,new ArrayList<String>()
将愉快地接受Integer
或元素- 对象本身不知道它应该包含什么类型,只有编译器知道。JFrame
List
这是原始类型会咬你的另一种情况:
public class StrangeClass<T> {
@SuppressWarnings("unchecked")
public <X> X getSomethingElse() {
return (X)"Testing something else!";
}
public static void main(String[] args) {
final StrangeClass<String> withGeneric = new StrangeClass<>();
final StrangeClass withoutGeneric = new StrangeClass();
final String value1,
value2;
// Compiles
value1 = withGeneric.getSomethingElse();
// Produces compile error:
// incompatible types: java.lang.Object cannot be converted to java.lang.String
value2 = withoutGeneric.getSomethingElse();
}
}
这是违反直觉的,因为您希望原始类型仅影响绑定到类类型参数的方法,但它实际上也会影响具有自己类型参数的泛型方法。
正如接受的答案中提到的那样,您在原始类型的代码中失去了对泛型的所有支持。每个类型参数都被转换为它的擦除(在上面的例子中只是Object
)。
原始类型是在使用泛型类型时缺少类型参数。
不应该使用原始类型,因为它可能导致运行时错误,例如将 adouble
插入应该是 aSet
的int
s 中。
Set set = new HashSet();
set.add(3.45); //ok
从 中检索内容时Set
,您不知道会发生什么。假设您希望它全部为int
s,您将其转换为Integer
; double
当3.45 出现时,运行时异常。
将类型参数添加到您的Set
中,您将立即收到编译错误。这种先发制人的错误使您可以在运行时出现问题之前解决问题(从而节省时间和精力)。
Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.
意思是你list
是一个List
未指定的对象。那就是Java不知道列表里面有什么样的对象。然后,当您想要迭代列表时,您必须强制转换每个元素,以便能够访问该元素的属性(在本例中为 String)。
一般来说,参数化集合是一个更好的主意,因此您不会遇到转换问题,您将只能添加参数化类型的元素,并且您的编辑器将为您提供适当的选择方法。
private static List<String> list = new ArrayList<String>();
避免原始类型。
原始类型是指使用泛型类型而不指定类型参数。
例如:
Alist
是原始类型,List<String>
而是参数化类型。
在 JDK 1.5 中引入泛型时,保留原始类型只是为了保持与旧版本 Java 的向后兼容性。
尽管仍然可以使用原始类型,但应避免使用它们:
例子:
import java.util.*;
public final class AvoidRawTypes {
void withRawType() {
//Raw List doesn't self-document,
//doesn't state explicitly what it can contain
List stars = Arrays.asList("Arcturus", "Vega", "Altair");
Iterator iter = stars.iterator();
while (iter.hasNext()) {
String star = (String) iter.next(); //cast needed
log(star);
}
}
void withParameterizedType() {
List < String > stars = Arrays.asList("Spica", "Regulus", "Antares");
for (String star: stars) {
log(star);
}
}
private void log(Object message) {
System.out.println(Objects.toString(message));
}
}
供参考:https ://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html
教程页面。
原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定通用 Box 类:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
要创建 Box 的参数化类型,您需要为形式类型参数 T 提供一个实际类型参数:
Box<Integer> intBox = new Box<>();
如果省略了实际类型参数,则创建 Box 的原始类型:
Box rawBox = new Box();
在做了一些示例练习并有完全相同的困惑后,我找到了这个页面。
============== 我从示例中提供的这段代码开始 ===============
public static void main(String[] args) throws IOException {
Map wordMap = new HashMap();
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
countWord(wordMap, args[i]);
}
} else {
getWordFrequency(System.in, wordMap);
}
for (Iterator i = wordMap.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
System.out.println(entry.getKey() + " :\t" + entry.getValue());
}
====================== 至此代码 ========================
public static void main(String[] args) throws IOException {
// replace with TreeMap to get them sorted by name
Map<String, Integer> wordMap = new HashMap<String, Integer>();
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
countWord(wordMap, args[i]);
}
} else {
getWordFrequency(System.in, wordMap);
}
for (Iterator<Entry<String, Integer>> i = wordMap.entrySet().iterator(); i.hasNext();) {
Entry<String, Integer> entry = i.next();
System.out.println(entry.getKey() + " :\t" + entry.getValue());
}
}
==================================================== ==============================
它可能更安全,但需要 4 个小时才能解开哲学......
原始类型在表达您想要表达的内容时很好。
例如,反序列化函数可能会返回 a List
,但它不知道列表的元素类型。List
这里适当的返回类型也是如此。