问题:
Java中的原始类型是什么?为什么我经常听到不应该在新代码中使用它们?如果我们不能使用原始类型,还有什么替代方法?如何更好?
问题:
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一起使用。这一事实是泛型的主要驱动因素。它将运行时错误源更改为可在编译时检查的内容。
什么是原始类型,为什么我经常听到不应该在新代码中使用它们?
“原始类型”是使用泛型类而不为其参数化类型指定类型参数,例如使用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
原始类型是在使用泛型类型时缺少类型参数。
不应使用原始类型,因为它可能会导致运行时错误,例如将双精度值插入到一组int中。
Set set = new HashSet();
set.add(3.45); //ok
当从集合中检索内容时,你不知道会发生什么。让我们假设您希望它是所有整数,您将其转换为整数;当出现双3.45时,运行时出现异常。
将类型参数添加到Set后,您将立即收到编译错误。这种抢先错误允许您在运行时发生问题之前修复问题(从而节省时间和精力)。
Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.
什么是原始类型?
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具有原始类型(并生成编译警告),由上述定义中的第一个要点表示;客栈也有第三个要点的原始类型。
MyType.Nnested不是参数化类型,即使它是参数化类型MyType<E>的成员类型,因为它是静态的。
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
现在我们在运行时遇到了麻烦,因为名称包含的东西不是String的实例。
大概,如果您希望名称仅包含String,那么您可能仍然可以使用原始类型,并手动检查每个添加,然后手动将名称中的每个项转换为String。更好的做法是不要使用原始类型,让编译器为您完成所有工作,利用Java泛型的强大功能。
List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!
当然,如果您确实希望名称允许布尔值,那么可以将其声明为List<Object>names,上面的代码将编译。
另请参见
Java教程/泛型
原始类型与使用<Object>作为类型参数有何不同?
以下是有效Java第二版第23项的引用:不要在新代码中使用原始类型:
原始类型List和参数化类型List<Object>之间有什么区别?粗略地说,前者选择了泛型类型检查,而后者明确告诉编译器它可以保存任何类型的对象。虽然可以将List<String>传递给List类型的参数,但不能将其传递给List<Object>类型的参数。泛型有子类型规则,List<String>是原始类型List的子类型,但不是参数化类型List<Object>的子类型。因此,如果使用原始类型(如List),则会失去类型安全性,但如果使用参数化类型(如List<Object>),则不会。
为了说明这一点,请考虑以下方法,该方法接受List<Object>并附加一个新的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>和<Number>之间的区别是什么?java泛型(非)协方差
原始类型与使用<?>有何不同作为类型参数?
List<Object>、List<String>等都是List<?>,所以说他们只是List可能很有诱惑力。然而,有一个主要的区别:由于List<E>只定义add(E),所以不能只向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<String>名称的类型不变量。
原始类型是删除该类型
返回JLS 4.8:
可以使用参数化类型的删除或元素类型为参数化类型数组类型的删除作为类型。这种类型称为原始类型。[...]原始类型的超类(分别是超接口)是泛型类型的任何参数化的超类的擦除。未从其超类或超接口继承的原始类型C的构造函数、实例方法或非静态字段的类型是对应于C的泛型声明中删除其类型的原始类型。
简单地说,当使用原始类型时,构造函数、实例方法和非静态字段也会被删除。
以以下示例为例:
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编程语言的未来版本可能会禁止使用原始类型。
有效的Java第二版还添加了以下内容:
既然你不应该使用原始类型,为什么语言设计者允许它们?提供兼容性。当引入泛型时,Java平台即将进入第二个十年,并且存在大量不使用泛型的Java代码。人们认为,所有这些代码都是合法的,并且可以与使用泛型的新代码互操作。将参数化类型的实例传递给设计用于普通类型的方法是合法的,反之亦然。这一被称为迁移兼容性的需求推动了支持原始类型的决定。
总之,新代码中不应使用原始类型。您应该始终使用参数化类型。
没有例外吗?
不幸的是,由于Java泛型是非具体化的,所以在新代码中必须使用原始类型时有两个例外:
类文本,例如List.Class,而不是List<String>.Class操作数实例,例如o instanceof Set,而不是o instanceofSet<String>
另请参见
为什么集合<字符串>.class非法?
Java中的原始类型是什么?为什么我经常听到不应该在新代码中使用它们?
原始类型是Java语言的古老历史。最初有集合,它们只持有对象。对集合的每个操作都需要将对象强制转换为所需类型。
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
Comparable接口更复杂:
//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)实现CompareAbble接口。为什么不应该使用它们:
存储在集合中的任何对象都必须在使用之前进行强制转换使用泛型启用编译时检查使用原始类型与将每个值存储为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函数,一个接受MyCompareAbl,另一个接受Object并在强制转换后将其传递给第一个。
原始类型的替代方法是什么:使用泛型
private static List<String> list = new ArrayList<String>();
您应该指定类型参数。
警告建议,应将定义为支持泛型的类型参数化,而不是使用其原始形式。
List被定义为支持泛型:公共类List<E>。这允许在编译时检查许多类型安全操作。
意思是你的列表是一个未指定对象的列表。也就是说,Java不知道列表中有什么类型的对象。然后,当您想要迭代列表时,必须强制转换每个元素,以便能够访问该元素的财产(在本例中为String)。
一般来说,将集合参数化是一个更好的主意,因此您不会遇到转换问题,您只能添加参数化类型的元素,编辑器将为您提供合适的选择方法。
private static List<String> list = new ArrayList<String>();
编译器希望您编写以下内容:
private static List<String> list = new ArrayList<String>();
因为否则,您可以将任何您喜欢的类型添加到列表中,使实例化为新的ArrayList<String>()变得毫无意义。Java泛型只是一个编译时特性,因此,如果将新的ArrayList<String>()创建的对象分配给“原始类型”List的引用,它将欣然接受Integer或JFrame元素-对象本身不知道应该包含什么类型,只有编译器知道。
原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定泛型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类)在JDK5.0之前不是通用的。当使用原始类型时,基本上会得到预泛型行为——一个Box会给你对象。为了向后兼容,允许将参数化类型分配给其原始类型:
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:未选中以获取详细信息。
当使用对原始类型进行操作的旧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:未检查标志。@SuppressWarnings(“unchecked”)注释可抑制未选中的警告。如果您不熟悉@SuppressWarnings语法,请参阅注释。
原始来源:Java教程
我在做了一些示例练习后发现了这一页,并有着完全相同的困惑。
===============我根据示例提供的代码===============
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它是一个类型为String的ArrayList引用变量,它引用的是一个字符串类型的ArralyList对象。这意味着它只能保存String类型的Object。
它是一个Strict to String,而不是Raw类型,因此它不会引发警告。
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是严格类型。所以,当添加整数时,它会引发编译时错误。
警告:-原始类型对象引用到ArrayList的严格类型引用变量。
案例3
在本例中,ArrayList arr是一个原始类型,但Object new ArrayList<String>();是严格类型。
arr.add("hello");
arr.add(23); //compiles fine but raise the warning.
它将向其中添加任何类型的对象,因为arr是一种原始类型。
警告:-严格类型对象引用到原始类型引用的变量。
教程页面。
原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定泛型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<String>是参数化类型。
当JDK1.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<E>的原始类型),不应该使用原始类型。它们的存在是为了与旧版本的Java兼容。我们希望尽快(编译时)发现错误,使用原始类型可能会在运行时导致错误。在两种情况下,我们仍然需要使用原始类型:
类文字的用法(List.class)instanceof的用法
示例:
//Use of raw type : don't !
private final Collection stamps = ...
stamps.add(new Coin(...)); //Erroneous insertion. Does not throw any error
Stamp s = (Stamp) stamps.get(i); // Throws ClassCastException when getting the Coin
//Common usage of instance of
if (o instanceof Set){
Set<?> = (Set<?>) o;
}