什么是原始types,为什么我们不应该使用它?
问题:
- Java中的原始types是什么?为什么我经常听说他们不应该用在新代码中?
- 如果我们不能使用原始types,那么有什么替代方法?
什么是原始types?
Java语言规范定义了一个原始types ,如下所示:
JLS 4.8原始types
原始types被定义为以下之一:
通过采用genericstypes声明的名称形成的引用types,不带伴随types参数列表。
元素types为原始types的数组types。
未从
R
的超类或超接口inheritance的原始typesR
的非static
成员types。
下面是一个例子来说明:
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>
是一个参数化types ( JLS 4.5 )。 通俗地说,这种types简单地称为MyType
,但技术上这个名称是MyType<E>
。
mt
在上面定义的第一个项目符号点有一个原始types(并生成一个编译警告) inn
也有第二个项目点的原始types。
MyType.Nested
不是参数化types,即使它是参数化typesMyType<E>
的成员types,因为它是static
。
mt1
和mt2
都用实际的types参数声明,所以它们不是原始types。
原始types有什么特别之处?
基本上,原始types的行为就像在generics引入之前一样。 也就是说,以下在编译时是完全合法的。
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
一个instanceof String
。
大概,如果你想要names
只包含String
,你可能仍然可以使用一个原始types,并手动检查每个 add
自己,然后手动转换为String
从names
每个项目。 更好的办法是不使用原始types, 让编译器为你做所有的工作 ,利用Javagenerics的力量。
List<String> names = new ArrayList<String>(); names.add("John"); names.add("Mary"); names.add(Boolean.FALSE); // compilation error!
当然,如果你想让names
允许一个Boolean
,那么你可以声明它为List<Object> names
,并且上面的代码将被编译。
也可以看看
- Java教程/generics
原始types与使用<Object>
作为types参数有什么不同?
以下是有效的Java第二版,第23项的引用:不要在新代码中使用原始types :
原始types
List
和参数化typesList<Object>
之间有什么区别? 松散地说,前者select了genericstypes检查,而后者明确地告诉编译器它能够保存任何types的对象。 虽然可以将List<String>
传递给List<String>
types的参数,但不能将其传递给List<Object>
types的参数。 generics有子types规则,List<String>
是原始typesList
的子types,但不是参数化typesList<Object>
。 因此, 如果使用像List
这样的原始types , 那么就会失去types安全性,但是如果使用像List<Object>
这样的参数化types,则不会这样 。
为了说明这一点,请考虑下列方法,它接受一个List<Object>
并附加一个new Object()
。
void appendNewObject(List<Object> list) { list.add(new Object()); }
Java中的generics是不变的。 一个List<String>
不是一个List<Object>
,所以下面会产生一个编译警告:
List<String> names = new ArrayList<String>(); appendNewObject(names); // compilation error!
如果你已经声明appendNewObject
接受一个原始types的List
作为参数,那么这将编译,你会因此失去了generics的types安全。
也可以看看
-
<E extends Number>
和<Number>
之间有什么区别? - javagenerics(不)协方差
原始types与使用<?>
作为types参数有什么不同?
List<Object>
, List<String>
等都是List<?>
,所以可能只是说他们只是List
而已。 但是,有一个主要区别:由于List<E>
仅定义了add(E)
,所以不能将任何任意对象添加到List<?>
。 另一方面,由于原始typesList
不具有types安全性,因此可以add
任何内容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<?>
的types不变性的影响! 如果你已经将参数声明为原始typesList list
,那么代码将被编译,并且会违反List<String> names
的types不variables。
原始types是该types的擦除
回到JLS 4.8:
可以使用参数化types的擦除或者元素types为参数化types的数组types的擦除。 这种types被称为原始types 。
[…]
原始types的超类(分别为超接口)是通用types的任何参数化的超类(超接口)的删除。
未从其超类或超接口inheritance的原始types
C
的构造函数,实例方法或非static
字段的types是对应于在与C
对应的generics声明中删除其types的原始types。
简单来说,当使用原始types时,构造函数,实例方法和非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继续解释如下:
types擦除还将构造函数或方法的签名映射到没有参数化types或typesvariables的签名。 构造函数或方法签名
s
删除是一个与s
同名的签名,以及s
给出的所有forms参数types的删除。如果方法或构造函数的签名被擦除,方法的返回types和generics方法或构造函数的types参数也会被擦除。
删除通用方法的签名没有types参数。
以下错误报告包含来自编译器开发人员Maurizio Cimadamore和JLS作者之一Alex Buckley关于为什么会发生这种行为的一些想法: https : //bugs.openjdk.java.net/browse / JDK-6400189 。 (简而言之,它使规范更简单。)
如果不安全,为什么它允许使用原始types?
这里是JLS 4.8的另一个引用:
只允许使用原始types作为遗留代码兼容性的一个让步。 强烈build议不要在将通用性引入到Java编程语言后编写的代码中使用原始types。 未来版本的Java编程语言可能会禁止使用原始types。
有效的Java第二版也有这个补充:
鉴于你不应该使用原始types,为什么语言devise者允许他们? 提供兼容性。
Java平台即将进入第二个十年,当时generics被引入,而且现在有大量的Java代码没有使用generics。 所有这些代码都是合法的,并且可以与使用generics的新代码进行互操作。 将参数化types的实例传递给为普通typesdevise的方法必须合法,反之亦然。 这个被称为迁移兼容性的要求驱使决定支持原始types。
总之,原始types不应该用在新代码中。 你应该总是使用参数化的types 。
没有例外吗?
不幸的是,因为Javagenerics是非泛化的,所以在新代码中必须使用原始types有两个例外:
- 类文字,例如
List.class
,而不是List<String>.class
-
instanceof
操作数,例如o instanceof Set
,而不是o instanceof Set<String>
也可以看看
- 为什么
Collection<String>.class
非法?
Java中的原始types是什么?为什么我经常听说他们不应该用在新代码中?
原始types是Java语言的古老历史。 一开始就有Collections
,他们没有任何东西也没有。 Collections
上的每个操作都需要从Object
转换为所需的types。
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
旧的无types集合不能强制types安全,因此程序员必须记住他在集合中存储的内容。
仿制药是为了解决这个限制而发明的,开发人员只会声明一次存储的types,而编译器会这样做。
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
比较接口更复杂:
//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;} }
请注意,使用原始types与compareTo(MyCompareAble)
实现CompareAble
接口是不可能的。 为什么你不应该使用它们:
- 存储在
Collection
任何Object
在被使用之前必须被投射 - 使用generics可以编译时间检查
- 使用原始types与将每个值存储为
Object
编译器的function:generics向后兼容,它们使用与原始types相同的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);
如果您直接使用原始types,则这是与编写相同的代码。 以为我不确定CompareAble
接口会发生什么,我想它会创build两个compareTo
函数,一个接受一个MyCompareAble
,另一个接受一个Object
并在投射之后将其传递给第一个。
什么是原始types的替代品:使用generics
原始types是没有任何types参数的generics类或接口的名称。 例如,给定通用Box类:
public class Box<T> { public void set(T t) { /* ... */ } // ... }
要创buildBox<T>
的参数化types,可以为formstypes参数T
提供实际的types参数:
Box<Integer> intBox = new Box<>();
如果省略实际的types参数,则创build一个原始types的Box<T>
:
Box rawBox = new Box();
因此, Box
是genericsBox<T>
的原始types。 但是,非generics类或接口types不是原始types。
原始types显示在传统代码中,因为很多API类(如Collections类)在JDK 5.0之前不是通用的。 使用原始types时,您基本上会获得预generics的行为 – 一个Box
会为您提供Object
。 为了向后兼容,允许将参数化types分配给其原始types:
Box<String> stringBox = new Box<>(); Box rawBox = stringBox; // OK
但是,如果将原始types分配给参数化types,则会收到警告:
Box rawBox = new Box(); // rawBox is a raw type of Box<T> Box<Integer> intBox = rawBox; // warning: unchecked conversion
如果使用原始types来调用在相应generics中定义的generics方法,也会得到警告:
Box<String> stringBox = new Box<>(); Box rawBox = stringBox; rawBox.set(8); // warning: unchecked invocation to set(T)
该警告显示原始types绕过了genericstypes检查,将不安全代码的捕获推迟到运行时。 因此,你应该避免使用原始types。
“types擦除”部分包含有关Java编译器如何使用原始types的更多信息。
未经检查的错误消息
如前所述,将遗留代码与通用代码混合在一起时,可能会遇到类似于以下内容的警告消息:
注意:Example.java使用未经检查或不安全的操作。
注意:使用-Xlint重新编译:取消选中以获取详细信息。
在使用原始types的旧API时,可能会发生这种情况,如以下示例所示:
public class WarningDemo { public static void main(String[] args){ Box<Integer> bi; bi = createBox(); } static Box createBox(){ return new Box(); } }
术语“未选中”表示编译器没有足够的types信息来执行确保types安全所需的所有types的检查。 虽然编译器提供了一个提示,但是默认情况下,“unchecked”警告被禁用。 要查看所有“未检查”警告,请使用-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教程
private static List<String> list = new ArrayList<String>();
您应该指定types参数。
该警告build议,定义为支持generics的types应该参数化,而不是使用其原始forms。
List
被定义为支持generics: public class List<E>
。 这允许执行编译时检查的许多types安全的操作。
Java中的“raw”types是非generics类,处理“原始”对象,而不是types安全的genericstypes参数。
例如,在Javagenerics可用之前,您可以使用这样的集合类:
LinkedList list = new LinkedList(); list.add(new MyObject()); MyObject myObject = (MyObject)list.get(0);
当你将对象添加到列表中时,它不关心它是什么types的对象,并且当你从列表中获得对象时,必须明确地将它转换为你期待的types。
使用generics,您将删除“未知”因素,因为您必须明确指定列表中可以包含哪些types的对象:
LinkedList<MyObject> list = new LinkedList<MyObject>(); list.add(new MyObject()); MyObject myObject = list.get(0);
请注意,对于generics,您不必强制从get调用来的对象,集合已预先定义为只能与MyObject一起使用。 这个事实是仿制药的主要驱动因素。 它将运行时错误的来源改变成可以在编译时检查的东西。
什么是原始types,为什么我经常听到他们不应该用在新代码中?
“原始types”是使用generics类,而不指定参数化types的types参数,例如使用List
而不是List<String>
。 当generics被引入到Java时,几个类被更新为使用generics。 使用这些类作为“原始types”(不指定types参数)允许遗留代码仍然编译。
“原始types”用于向后兼容。 不推荐在新代码中使用它们,因为使用具有types参数的generics类可以实现更强的typesinput,从而可以提高代码的可理解性,并可以在早期捕获潜在的问题。
如果我们不能使用原始types,那么有什么替代方法?
首选的方法是按照预期使用generics类 – 使用合适的types参数(例如List<String>
)。 这允许程序员更具体地指定types,向将来的维护者传达更多关于variables或数据结构的预期使用的意义,并允许编译器执行更好的types安全性。 这些优点一起可以提高代码质量,并有助于防止引入一些编码错误。
例如,对于程序员希望确保名为“names”的Listvariables仅包含Strings的方法:
List<String> names = new ArrayList<String>(); names.add("John"); // OK names.add(new Integer(1)); // compile error
编译器希望你写这个:
private static List<String> list = new ArrayList<String>();
因为否则,你可以添加任何你喜欢的types到list
,使得实例化成new ArrayList<String>()
毫无意义。 Javagenerics只是一个编译时的特性,所以使用new ArrayList<String>()
创build的对象如果分配给“原始types” List
的引用,将会高兴地接受Integer
或JFrame
元素 – 对象本身对什么types一无所知它应该包含,只有编译器。
在这里,我正在考虑多种情况,通过它你可以清除这个概念
1. ArrayList<String> arr = new ArrayList<String>(); 2. ArrayList<String> arr = new ArrayList(); 3. ArrayList arr = new ArrayList<String>();
情况1
ArrayList<String> arr
它是一个ArrayList
引用variables,其types为String
,引用了String
types的ArralyList
对象。 这意味着它只能保存Stringtypes的对象。
这是一个严格的String
不是一个原始types,所以,它永远不会提出警告。
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
是一个严格的types,但你的对象new ArrayList();
是一种原始types。
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
是一个严格的types。 所以,当添加一个integer
时会导致编译时错误。
警告 : –
Raw
types对象被引用到ArrayList
的Strict
types引用variables。
案例3
在这种情况下ArrayList arr
是一个原始types,但是您的对象new ArrayList<String>();
是一个严格的types。
arr.add("hello"); arr.add(23); //compiles fine but raise the warning.
它将添加任何types的对象,因为arr
是一个原始types。
警告 : –
Strict
types对象被引用到raw
types引用的variables。
raw -type是使用genericstypes时缺less的一个types参数 。
原始types不应该被使用,因为它可能会导致运行时错误,比如在应该是一个int
Set
中插入一个double
。
Set set = new HashSet(); set.add(3.45); //ok
当从Set
获取东西时,你不知道什么出来。 假设你期望它全部是int
,你将它转换为Integer
; 在运行时double
3.45出现exception。
如果将一个types参数添加到您的Set
,您将立即得到一个编译错误。 这种先发制人的错误可以让你在运行期间爆炸之前解决问题(从而节省时间和精力)。
Set<Integer> set = new HashSet<Integer>(); set.add(3.45); //NOT ok.
现在说的是你的list
是一个未指定对象的List
。 那就是Java不知道列表里面有什么样的对象。 然后,当你想迭代列表,你必须投入每个元素,以便能够访问该元素的属性(在这种情况下,string)。
一般来说,参数化集合是一个更好的主意,所以你没有转换问题,你只能添加参数化types的元素,编辑器会为你提供合适的方法来select。
private static List<String> list = new ArrayList<String>();
教程页面 。
原始types是没有任何types参数的generics类或接口的名称。 例如,给定通用Box类:
public class Box<T> { public void set(T t) { /* ... */ } // ... }
要创buildBox的参数化types,可以为formstypes参数T提供实际的types参数:
Box<Integer> intBox = new Box<>();
如果省略实际的types参数,则创build一个原始types的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个小时来破坏哲学。