问题:
Java 中的原始类型是什么,为什么我经常听说它们不应该在新代码中使用?
如果我们不能使用原始类型,还有什么替代方案,它有什么更好的选择?
什么是原始类型?
Java 语言规范定义了一个原始类型,如下所示:
JLS 4.8 原始类型
原始类型被定义为以下之一: 引用类型,它是通过采用没有伴随类型参数列表的泛型类型声明的名称而形成的。元素类型为原始类型的数组类型。原始类型 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
,然后 手动投射到 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 教程/泛型
原始类型与使用
以下是来自 Effective Java 2nd Edition 第 23 条的引述:不要在新代码中使用原始类型:
原始类型 List 和参数化类型 List
为了说明这一点,请考虑以下采用 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
作为参数,那么它将编译,因此您将失去从泛型获得的类型安全性。
也可以看看
java泛型(非)协方差
原始类型与使用 > 作为类型参数有何不同?
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:
可以将参数化类型的擦除或元素类型为参数化类型的数组类型的擦除用作类型。这种类型称为原始类型。 [...] 原始类型的超类(分别为超接口)是泛型类型的任何参数化的超类(超接口)的擦除。未从其超类或超接口继承的原始类型 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 继续解释以下内容:
类型擦除还将构造函数或方法的签名映射到没有参数化类型或类型变量的签名。构造函数或方法签名 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
instanceof 操作数,例如 o instanceof Set,而不是 o instanceof Set
也可以看看
为什么 Collection
Java 中的原始类型是什么,为什么我经常听说它们不应该在新代码中使用?
原始类型是 Java 语言的古老历史。一开始有Collections
,他们持有Objects
,不多也不少。 Collections
上的每个操作都需要将 Object
强制转换为所需类型。
List aList = new ArrayList();
String s = "Hello World!";
aList.add(s);
String c = (String)aList.get(0);
虽然这在大多数情况下都有效,但确实发生了错误
List aNumberList = new ArrayList();
String one = "1";//Number one
aNumberList.add(one);
Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here
旧的无类型集合无法强制类型安全,因此程序员必须记住他存储在集合中的内容。为了解决这个限制而发明了泛型,开发人员将声明存储的类型一次,编译器会这样做。
List<String> aNumberList = new ArrayList<String>();
aNumberList.add("one");
Integer iOne = aNumberList.get(0);//Compile time error
String sOne = aNumberList.get(0);//works fine
比较:
// 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
更复杂的 Compareable 接口:
//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;}
}
请注意,使用原始类型的 compareTo(MyCompareAble)
实现 CompareAble
接口是不可能的。为什么你不应该使用它们:
存储在集合中的任何对象都必须在使用之前进行强制转换
使用泛型启用编译时检查
使用原始类型与将每个值存储为 Object 相同
编译器的作用:泛型是向后兼容的,它们使用与原始类型相同的 java 类。魔术主要发生在编译时。
List<String> someStrings = new ArrayList<String>();
someStrings.add("one");
String one = someStrings.get(0);
将编译为:
List someStrings = new ArrayList();
someStrings.add("one");
String one = (String)someStrings.get(0);
如果您直接使用原始类型,这与您将编写的代码相同。以为我不确定 CompareAble
接口会发生什么,我猜它创建了两个 compareTo
函数,一个采用 MyCompareAble
另一个采用 Object
并在转换后将其传递给第一个。
原始类型的替代方法是什么:使用 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 之前不是通用的。使用原始类型时,您基本上会获得预泛型行为 — Box
为您提供 Object
。为了向后兼容,允许将参数化类型分配给其原始类型:
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 Tutorials
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>();
您应该指定类型参数。
该警告建议应将定义为支持 generics 的类型参数化,而不是使用其原始形式。
List
被定义为支持泛型:public class List<E>
。这允许许多类型安全的操作,这些操作在编译时进行检查。
private static List<String> list = new ArrayList<>();
什么是原始类型,为什么我经常听说它们不应该在新代码中使用?
“原始类型”是使用泛型类而不为其参数化类型指定类型参数,例如使用 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
polygenelubricants
的“原始类型”引用从 stackoverflow.com/questions/2770111/… 复制到我自己的答案中,但我想我会把它们留给他/她自己的答案使用。
在这里,我正在考虑多种案例,您可以通过这些案例来明确概念
1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();
情况1
ArrayList<String> arr
它是类型为 String
的 ArrayList
引用变量,它引用类型为 String
的 ArralyList
对象。这意味着它只能容纳 String 类型的 Object。
它是 Strict to 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)
案例2
在这种情况下,ArrayList<String> arr
是严格类型,但您的 Object new 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
时会引发编译时错误。
警告:- 原始类型对象被引用到 ArrayList 的严格类型引用变量。
案例3
在这种情况下,ArrayList arr
是原始类型,但您的 Object new ArrayList<String>();
是 Strict 类型。
arr.add("hello");
arr.add(23); //compiles fine but raise the warning.
它将向其中添加任何类型的对象,因为 arr
是原始类型。
警告:- 严格类型对象被引用到原始类型引用的变量。
编译器希望你这样写:
private static List<String> list = new ArrayList<String>();
因为否则,您可以将您喜欢的任何类型添加到 list
中,从而使实例化为 new ArrayList<String>()
毫无意义。 Java 泛型只是一个编译时特性,因此如果分配给“原始类型”List
的引用,使用 new ArrayList<String>()
创建的对象将很乐意接受 Integer
或 JFrame
元素 - 对象本身对此一无所知它应该包含什么类型,只有编译器可以。
这是原始类型会咬你的另一种情况:
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
)。
原始类型是在使用泛型类型时缺少类型参数。
不应使用原始类型,因为它可能会导致运行时错误,例如将 double
插入应该是 int
的 Set
中。
Set set = new HashSet();
set.add(3.45); //ok
从 Set
检索内容时,您不知道会出现什么。假设您希望它全部为 int
,您将其转换为 Integer
;当 double
3.45 出现时,运行时异常。
将 type 参数 添加到 Set
后,您将立即收到编译错误。这种先发制人的错误使您可以在运行时出现问题之前解决问题(从而节省时间和精力)。
Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.
避免原始类型。
原始类型是指使用泛型类型而不指定类型参数。
例如:
list
是原始类型,而 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
意思是您的 list
是未指定对象的 List
。那就是Java不知道列表里面有什么样的对象。然后,当您想要迭代列表时,您必须强制转换每个元素,以便能够访问该元素的属性(在本例中为 String)。
一般来说,参数化集合是一个更好的主意,因此您不会遇到转换问题,您将只能添加参数化类型的元素,并且您的编辑器将为您提供适当的选择方法。
private static List<String> list = new ArrayList<String>();
原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定通用 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 个小时才能解开哲学......
原始类型在表达您想要表达的内容时很好。
例如,反序列化函数可能返回 List
,但它不知道列表的元素类型。所以 List
是这里合适的返回类型。
o instanceof Set<?>
也被允许避免原始类型(尽管在这种情况下它只是表面的)。n
远程 bean 的需要。TypeName.class
,其中TypeName
是一个普通标识符(jls)。假设地说,我想它可能真的是。也许作为一个线索,List<String>.class
是 JLS 专门调用编译器错误的变体,所以如果他们将它添加到语言中,我希望那是他们使用的那个。