753

问题:

  • Java 中的原始类型是什么,为什么我经常听说它们不应该在新代码中使用?
  • 如果我们不能使用原始类型,还有什么替代方案,它有什么更好的选择?
4

15 回答 15

829

什么是原始类型?

Java 语言规范定义了一个原始类型,如下所示:

JLS 4.8 原始类型

原始类型定义为以下之一:

  • 通过采用泛型类型声明的名称而不附带类型参数列表形成的引用类型。

  • 元素类型为原始类型的数组类型。

  • 未从 的超类或超接口继承static的原始类型的非成员类型。RR

这里有一个例子来说明:

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然后手动从. 更好的是,虽然不是使用原始类型,而是让编译器为您完成所有工作,利用 Java 泛型的力量。Stringnames

List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!

当然,如果你确实names允许 a Boolean,那么你可以将它声明为List<Object> names,上面的代码就会编译通过。

也可以看看


原始类型与<Object>用作类型参数有何不同?

以下是来自Effective Java 2nd Edition 第 23 条的引述:不要在新代码中使用原始类型

原始类型List和参数化类型之间有什么区别List<Object>?粗略地说,前者选择退出泛型类型检查,而后者明确告诉编译器它能够保存任何类型的对象。虽然您可以将 a 传递List<String>给 type 的参数List,但不能将它传递给 type 的参数List<Object>。泛型有子类型规则,List<String>是原始类型的子类型List,但不是参数化类型的子类型List<Object>。因此,如果使用原始类型 like List,则会失去类型安全性,但如果使用参数化类型 like 则不会List<Object>

为了说明这一点,请考虑以下采用 aList<Object>并附加 a 的方法new Object()

void appendNewObject(List<Object> list) {
   list.add(new Object());
}

Java 中的泛型是不变的。AList<String>不是 a List<Object>,因此以下会生成编译器警告:

List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!

如果您已声明appendNewObject将原始类型List作为参数,那么它将编译,因此您将失去从泛型获得的类型安全性。

也可以看看


原始类型与<?>用作类型参数有何不同?

List<Object>, List<String>, 等都是List<?>, 所以说它们只是List相反可能很诱人。但是,有一个主要区别:由于 aList<E>仅定义add(E),因此您不能将任意对象添加到 a List<?>。另一方面,由于原始类型List不具有类型安全性,因此您几乎可以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<?>!的类型不变性方面做得非常出色。如果您已将参数声明为原始类型List list,则代码将编译,并且您将违反List<String> names.


原始类型是该类型的擦除

回到 JLS 4.8:

可以将参数化类型的擦除或元素类型为参数化类型的数组类型的擦除用作类型。这种类型称为原始类型

[...]

原始类型的超类(分别为超接口)是泛型类型的任何参数化的超类(超接口)的擦除。

未从其超类或超接口继承的原始类型的构造函数、实例方法或非static字段的类型是对应于在对应的泛型声明中擦除其类型的原始类型。CC

简单来说,当使用原始类型时,构造函数、实例方法和非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时MyTypegetNames也会被擦除,因此它返回一个 raw List

JLS 4.6继续解释以下内容:

类型擦除还将构造函数或方法的签名映射到没有参数化类型或类型变量的签名。构造函数或方法签名的擦除是由与 中给出的所有形式参数类型s相同的名称和擦除组成的签名。ss

如果方法或构造函数的签名被擦除,则方法的返回类型和泛型方法或构造函数的类型参数也会被擦除。

泛型方法签名的擦除没有类型参数。

以下错误报告包含编译器开发人员 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<String>.class
  • instanceof操作数,例如o instanceof Set,不o instanceof Set<String>

也可以看看

于 2010-05-05T04:50:35.487 回答
67

What are raw types in Java, and why do I often hear that they shouldn't be used in new code?

Raw-types are ancient history of the Java language. In the beginning there were Collections and they held Objects nothing more and nothing less. Every operation on Collections required casts from Object to the desired type.

List aList = new ArrayList();
String s = "Hello World!";
aList.add(s);
String c = (String)aList.get(0);

While this worked most of the time, errors did happen

List aNumberList = new ArrayList();
String one = "1";//Number one
aNumberList.add(one);
Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here

The old typeless collections could not enforce type-safety so the programmer had to remember what he stored within a collection.
Generics where invented to get around this limitation, the developer would declare the stored type once and the compiler would do it instead.

List<String> aNumberList = new ArrayList<String>();
aNumberList.add("one");
Integer iOne = aNumberList.get(0);//Compile time error
String sOne = aNumberList.get(0);//works fine

For Comparison:

// 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

More complex the Compareable interface:

//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;}
}

Note that it is impossible to implement the CompareAble interface with compareTo(MyCompareAble) with raw types. Why you should not use them:

  • Any Object stored in a Collection has to be cast before it can be used
  • Using generics enables compile time checks
  • Using raw types is the same as storing each value as Object

What the compiler does: Generics are backward compatible, they use the same java classes as the raw types do. The magic happens mostly at compile time.

List<String> someStrings = new ArrayList<String>();
someStrings.add("one");
String one = someStrings.get(0);

Will be compiled as:

List someStrings = new ArrayList();
someStrings.add("one"); 
String one = (String)someStrings.get(0);

This is the same code you would write if you used the raw types directly. Thought I'm not sure what happens with the CompareAble interface, I guess that it creates two compareTo functions, one taking a MyCompareAble and the other taking an Object and passing it to the first after casting it.

What are the alternatives to raw types: Use generics

于 2010-05-05T21:50:47.410 回答
32

原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定通用 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 之前不是通用的。当使用原始类型时,你基本上得到了前泛型行为——aBox给你Objects。为了向后兼容,允许将参数化类型分配给其原始类型:

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 教程

于 2013-08-10T23:58:29.667 回答
29

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。这正是仿制药的主要驱动因素。它将运行时错误的来源更改为可以在编译时检查的内容。

于 2010-05-05T02:58:03.493 回答
20
 private static List<String> list = new ArrayList<String>();

您应该指定类型参数。

该警告建议应将定义为支持泛型的类型参数化,而不是使用其原始形式。

List被定义为支持泛型:public class List<E>. 这允许许多类型安全的操作,这些操作在编译时进行检查。

于 2010-06-16T07:44:53.797 回答
16

什么是原始类型,为什么我经常听说它们不应该在新代码中使用?

“原始类型”是使用泛型类而不为其参数化类型指定类型参数,例如使用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
于 2010-05-05T04:31:57.630 回答
15

在这里,我正在考虑多种案例,您可以通过这些案例来明确概念

1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();

情况1

ArrayList<String> arr它是一个ArrayList类型的引用变量,String它引用了一个ArralyListObject 类型String。这意味着它只能容纳 String 类型的 Object。

它是严格到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是严格类型,但您的 Objectnew 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.

警告:-Raw类型对象被引用到 的Strict类型引用变量ArrayList

案例3

在这种情况下ArrayList arr是原始类型,但您的 Objectnew ArrayList<String>();是 Strict 类型。

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

它将向其中添加任何类型的对象,因为它arr是原始类型。

警告:-Strict类型对象被引用到raw类型引用的变量。

于 2016-05-13T13:51:04.977 回答
13

编译器希望你这样写:

private static List<String> list = new ArrayList<String>();

因为否则,您可以将任何您喜欢的类型添加到list中,从而使实例化变得new ArrayList<String>()毫无意义。Java 泛型只是一个编译时特性,因此创建的对象如果分配给“原始类型”的引用,new ArrayList<String>()将愉快地接受Integer或元素- 对象本身不知道它应该包含什么类型,只有编译器知道。JFrameList

于 2010-06-16T07:53:30.857 回答
13

这是原始类型会咬你的另一种情况:

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)。

于 2017-12-04T04:49:18.990 回答
9

原始类型是在使用泛型类型时缺少类型参数。

不应该使用原始类型,因为它可能导致运行时错误,例如将 adouble插入应该是 aSetints 中。

Set set = new HashSet();
set.add(3.45); //ok

从 中检索内容时Set,您不知道会发生什么。假设您希望它全部为ints,您将其转换为Integer; double当3.45 出现时,运行时异常。

类型参数添加到您的Set中,您将立即收到编译错误。这种先发制人的错误使您可以在运行时出现问题之前解决问题(从而节省时间和精力)。

Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.
于 2010-05-05T04:44:16.737 回答
6

意思是你list是一个List未指定的对象。那就是Java不知道列表里面有什么样的对象。然后,当您想要迭代列表时,您必须强制转换每个元素,以便能够访问该元素的属性(在本例中为 String)。

一般来说,参数化集合是一​​个更好的主意,因此您不会遇到转换问题,您将只能添加参数化类型的元素,并且您的编辑器将为您提供适当的选择方法。

private static List<String> list = new ArrayList<String>();
于 2010-06-16T07:47:59.780 回答
6

避免原始类型。

原始类型是指使用泛型类型而不指定类型参数。

例如:

Alist是原始类型,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

于 2019-03-26T04:50:22.337 回答
5

教程页面

原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定通用 Box 类:

public class Box<T> {
    public void set(T t) { /* ... */ }
    // ...
}

要创建 Box 的参数化类型,您需要为形式类型参数 T 提供一个实际类型参数:

Box<Integer> intBox = new Box<>();

如果省略了实际类型参数,则创建 Box 的原始类型:

Box rawBox = new Box();
于 2016-05-24T13:42:09.843 回答
0

在做了一些示例练习并有完全相同的困惑后,我找到了这个页面。

============== 我从示例中提供的这段代码开始 ===============

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 个小时才能解开哲学......

于 2016-03-07T02:24:43.840 回答
-3

原始类型在表达您想要表达的内容时很好。

例如,反序列化函数可能会返回 a List,但它不知道列表的元素类型。List这里适当的返回类型也是如此。

于 2017-12-04T12:58:32.013 回答