提问人:polygenelubricants 提问时间:5/5/2010 最后编辑:Tarynpolygenelubricants 更新时间:12/7/2022 访问量:352430
什么是原始类型,为什么我们不应该使用它?
What is a raw type and why shouldn't we use it?
答:
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。这一事实是仿制药的主要驱动因素。它将运行时错误源更改为可以在编译时检查的内容。
评论
?
什么是原始类型,为什么我经常听到它们不应该在新代码中使用?
“原始类型”是指使用泛型类而不为其参数化类型指定类型参数,例如,使用 而不是 .当泛型被引入 Java 时,几个类被更新为使用泛型。将这些类用作“原始类型”(不指定类型参数)允许遗留代码仍可编译。List
List<String>
“原始类型”用于向后兼容。不建议在新代码中使用它们,因为使用带有类型参数的泛型类可以进行更强的类型化,这反过来可能会提高代码的可理解性并导致更早地发现潜在问题。
如果我们不能使用原始类型,有什么替代方案,如何更好?
首选的替代方案是按预期使用泛型类 - 带有合适的类型参数(例如)。这允许程序员更具体地指定类型,向未来的维护者传达更多关于变量或数据结构的预期用途的含义,并允许编译器强制执行更好的类型安全性。这些优点结合在一起可以提高代码质量,并有助于防止引入一些编码错误。List<String>
例如,对于程序员希望确保名为“names”的 List 变量仅包含字符串的方法:
List<String> names = new ArrayList<String>();
names.add("John"); // OK
names.add(new Integer(1)); // compile error
评论
polygenelubricants
原始类型是指使用泛型类型时缺少类型参数。
不应使用 Raw-type,因为它可能会导致运行时错误,例如将 a 插入到 s 中。double
Set
int
Set set = new HashSet();
set.add(3.45); //ok
当从 中检索内容时,您不知道会发生什么。让我们假设你期望它是全 s,你正在将其投射到 ;当 3.45 出现时,运行时出现异常。Set
int
Integer
double
将 type 参数添加到 中后,您将立即收到编译错误。这种先发制人的错误可以让你在运行时发生爆炸之前解决问题(从而节省时间和精力)。Set
Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.
什么是原始类型?
Java 语言规范定义原始类型如下:
JLS 4.8 原始类型
原始类型定义为以下类型之一:
通过采用泛型类型声明的名称而不附带类型参数列表而形成的引用类型。
元素类型为原始类型的数组类型。
原始类型的非成员类型,它不是从 的 的超类或超接口继承的。
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!)
}
}
这里是一个参数化类型(JLS 4.5)。通常通俗地将这种类型简称为简称,但从技术上讲,名称是 .MyType<E>
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
据推测,如果您只想包含 ,您也许仍然可以使用原始类型并手动检查每个项目,然后手动将 转换为 中的每个项目。更好的是,不要使用原始类型,让编译器为你完成所有工作,利用 Java 泛型的强大功能。names
String
add
String
names
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 项的引述:不要在新代码中使用原始类型:
原始类型和参数化类型有什么区别?粗略地说,前者选择退出泛型类型检查,而后者则明确告诉编译器它能够保存任何类型的对象。虽然您可以将 a 传递给 类型的参数,但不能将其传递给 类型的参数。泛型有子类型规则,是原始类型的子类型,但不是参数化类型的子类型。因此,如果使用原始类型(如 List),则会失去类型安全性,但如果使用参数化类型(如
List<Object>
,则不会丢失类型安全性。
List
List<Object>
List<String>
List
List<Object>
List<String>
List
List<Object>
为了说明这一点,请考虑以下方法,该方法采用 a 并附加 .List<Object>
new Object()
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Java 中的泛型是不变的。A 不是 ,因此以下代码将生成编译器警告:List<String>
List<Object>
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
如果您声明将原始类型作为参数,那么这将编译,因此您将失去从泛型中获得的类型安全性。appendNewObject
List
另请参阅
原始类型与用作类型参数有何不同?<?>
List<Object>
、 等都是 ,所以可能很容易说它们只是相反。但是,有一个主要区别:由于 a 仅定义 ,因此不能将任何任意对象添加到 .另一方面,由于原始类型没有类型安全,因此几乎可以对 .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!
编译器在保护您免受可能违反 !如果已将参数声明为 raw 类型,则代码将编译,并且会违反 的类型不变性。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 时,也会被擦除,因此它返回一个 raw !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 泛型是未统一的,因此在新代码中必须使用原始类型有两种例外:
- 类文字,例如 , not
List.class
List<String>.class
instanceof
操作数,例如 , noto instanceof Set
o instanceof Set<String>
另请参阅
评论
o instanceof Set<?>
TypeName.class
TypeName
List<String>.class
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;}
}
请注意,不可能使用原始类型实现接口。
为什么你不应该使用它们:CompareAble
compareTo(MyCompareAble)
- 任何存储在 a 中的东西都必须先被铸造,然后才能使用
Object
Collection
- 使用泛型启用编译时检查
- 使用原始类型与将每个值存储为
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
原始类型的替代方法有哪些:使用泛型
private static List<String> list = new ArrayList<String>();
应指定 type-parameter。
该警告建议,应参数化定义为支持泛型的类型,而不是使用其原始形式。
List
定义为支持泛型:。这允许许多类型安全的操作,这些操作在编译时进行检查。public class List<E>
评论
private static List<String> list = new ArrayList<>();
说的是,你是一个未指定的对象。也就是说,Java 不知道列表中有什么样的对象。然后,当你想要迭代列表时,你必须强制转换每个元素,以便能够访问该元素的属性(在本例中为 String)。list
List
一般来说,参数化集合是一个更好的主意,这样你就不会有转换问题,你只能添加参数化类型的元素,你的编辑器将为你提供适当的方法来选择。
private static List<String> list = new ArrayList<String>();
编译器希望你写这个:
private static List<String> list = new ArrayList<String>();
因为否则,你可以添加任何你喜欢的类型,使实例化变得毫无意义。Java 泛型只是一个编译时特性,因此,如果将对象分配给“原始类型”的引用,则用 或 元素创建,它将很乐意接受 or 元素 - 对象本身对它应该包含的类型一无所知,只有编译器知道。list
new ArrayList<String>()
new ArrayList<String>()
Integer
JFrame
List
原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定泛型 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 之前不是通用的。当使用原始类型时,你基本上会得到前泛型行为——a 给你 s。为了向后兼容,允许将参数化类型分配给其原始类型: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 编译器如何使用原始类型的更多信息。
未经检查的错误消息
如前所述,将旧代码与泛型代码混合使用时,可能会遇到类似于以下内容的警告消息:
注意:示例 .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
在做了一些示例练习并有完全相同的困惑之后,我找到了这个页面。
================ 我从示例提供的这段代码中获取 ===============
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 个小时来糊化哲学......
在这里,我正在考虑多种情况,通过这些情况可以明确概念
1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();
案例 1
ArrayList<String> arr
它是一个引用变量,其类型引用 Object 的 类型。这意味着它只能容纳 String 类型的 Object。ArrayList
String
ArralyList
String
它是严格而不是原始类型,因此,它永远不会发出警告。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
在本例中,它是严格类型,但 Object 是原始类型。ArrayList<String> arr
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)
这是一个 Strict 类型。因此,在添加 .arr
integer
警告 :- 类型对象引用的类型为 的引用变量。
Raw
Strict
ArrayList
案例 3
在本例中,对象是原始类型,但 Object 是 Strict 类型。ArrayList arr
new ArrayList<String>();
arr.add("hello");
arr.add(23); //compiles fine but raise the warning.
它会将任何类型的对象添加到其中,因为是原始类型。arr
警告 :- 类型对象被引用到类型引用的变量。
Strict
raw
评论
教程页面。
原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定泛型 Box 类:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
若要创建 Box 的参数化类型,请为形式类型参数 T 提供实际类型参数:
Box<Integer> intBox = new Box<>();
如果省略实际类型参数,则创建 Box 的原始类型:
Box rawBox = new Box();
这是原始类型会咬你的另一种情况:
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
当原始类型表达您想要表达的内容时,它们很好。
例如,反序列化函数可能返回 ,但它不知道列表的元素类型。此处的适当返回类型也是如此。List
List
评论
避免使用原始类型。
原始类型是指在不指定类型参数的情况下使用泛型类型。
例如:
A 是原始类型,而 是参数化类型。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
上一个:如何将输入读取为数字?
评论