什么是原始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

mt1mt2都用实际的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自己,然后手动转换Stringnames每个项目。 更好的办法是不使用原始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

原始typesList和参数化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任何内容addList

考虑以前代码片段的以下变化:

 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的原始typesC的构造函数,实例方法或非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); } } 

当我们使用原始的MyTypegetNames也会被擦除,所以它返回一个原始的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的引用,将会高兴地接受IntegerJFrame元素 – 对象本身对什么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 ,引用了Stringtypes的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时会导致编译时错误。

警告 : – Rawtypes对象被引用到ArrayListStricttypes引用variables。

案例3

在这种情况下ArrayList arr是一个原始types,但是您的对象new ArrayList<String>(); 是一个严格的types。

  arr.add("hello"); arr.add(23); //compiles fine but raise the warning. 

它将添加任何types的对象,因为arr是一个原始types。

警告 : – Stricttypes对象被引用到rawtypes引用的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个小时来破坏哲学。