什么是原始类型,为什么我们不应该使用它呢?


662

问题:

  • Java中的原始类型是什么?为什么我经常听到不应该在新代码中使用它们的信息?
  • 如果我们不能使用原始类型,那有什么选择呢?有什么更好的选择?

Java教程仍然使用导致此警告的JComboBox。哪个版本的组合框不会导致此警告?docs.oracle.com/javase/tutorial/uiswing/components/…–
SuperStar

1
请注意,存在原始类型的原因是为了与Java 1.4及更低版本兼容,后者根本没有泛型。
杰斯珀,2016年

Answers:


744

什么是原始类型?

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

mt1mt2都是使用实际类型参数声明的,因此它们不是原始类型。


原始类型有什么特别之处?

本质上,原始类型的行为与引入泛型之前的行为相同。也就是说,以下在编译时完全合法。

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自己,然后手动施放String每一个项目从names更好的是,尽管不要使用原始类型,而让编译器利用Java泛型的强大功能为您完成所有工作

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

当然,如果你不要names允许Boolean,那么你可以将它声明为List<Object> names,和上面的代码将编译。

也可以看看


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

以下是有效Java 2nd Edition条款23的引文:不要在新代码中使用原始类型

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

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

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

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

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

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

也可以看看


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

List<Object>List<String>等等都是List<?>,所以说它们只是List替代品可能很诱人。但是,有一个主要区别:由于List<E>仅定义add(E),因此您不能仅将任意对象添加到List<?>。另一方面,由于原始类型List没有类型安全性,所以您几乎可以add对a进行任何操作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 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时MyTypegetNames也会被擦除,因此它返回raw 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 2nd Edition也要添加以下内容:

既然您不应该使用原始类型,那么语言设计者为什么要允许它们呢?提供兼容性。

引入泛型后,Java平台即将进入第二个十年,并且存在大量不使用泛型的Java代码。至关重要的是,所有这些代码都必须合法并可以与使用泛型的新代码互操作。将参数化类型的实例传递给设计用于普通类型的方法必须是合法的,反之亦然。这项称为迁移兼容性的要求决定了支持原始类型的决定。

总而言之,绝对不要在新代码中使用原始类型。您应该始终使用参数化类型


有没有例外?

不幸的是,由于Java泛型是非泛型的,因此在新代码中必须使用原始类型有两个例外:

  • 类文字,例如List.class,notList<String>.class
  • instanceof操作数,例如o instanceof Set,不是o instanceof Set<String>

也可以看看


16
您是什么意思,“ Java泛型是非泛型的”?
卡尔·G

7
对于第二个例外,o instanceof Set<?>还允许使用语法以避免原始类型(尽管在这种情况下它只是表面的)。
Paul Bellora

1
原始类型非常有用,可以在扩展接口的Bean的JNDI查找中减少模板代码。这解决了n为每个实现类使用相同代码编写远程bean 的需求。
djmj 2014年

8
“未修饰”是另一种说法,即它们已被删除。编译器知道通用参数是什么,但是该信息不会传递给生成的字节码。JLS要求类文字没有类型参数。
Erick G. Hagstrom

2
@OldCurmudgeon有趣。我的意思是正式它既不的,因为一个类常量定义为只是TypeName.class,这里TypeName是一个普通的标识符(JLS)。假设地说,我想可能是这样。也许可以作为线索,List<String>.class是JLS特别调用编译器错误的变体,因此,如果他们将其添加到语言中,我希望那是他们所使用的语言。
Radiodef '16

62

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

比较接口比较复杂:

//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)。为什么不应该使用它们:

  • 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函数,一个函数采用a,另一个函数MyCompareAble采用an Object并将其在转换后传递给第一个函数。

原始类型的替代方法是什么:使用泛型


30

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

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

要创建的参数化类型Box<T>,请为形式类型参数提供实际的类型参数T

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

如果省略了实际的type参数,则将创建原始类型Box<T>

Box rawBox = new Box();

因此,Box是泛型类型的原始类型Box<T>。但是,非泛型类或接口类型不是原始类型。

原始类型显示在旧版代码中,因为在JDK 5.0之前,许多API类(例如Collections类)不是通用的。使用原始类型时,您实际上会获得泛型行为-a Box给您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)

该警告表明原始类型会绕过通用类型检查,从而将不安全代码的捕获推迟到运行时。因此,应避免使用原始类型。

Type Erasure部分提供了有关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();
    }
}

术语“未检查”是指编译器没有足够的类型信息来执行确保类型安全所需的所有类型检查。尽管编译器会给出提示,但是默认情况下“ 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")注释抑制unchecked警告。如果您不熟悉@SuppressWarnings语法,请参见注释。

原始资料:Java教程


21

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


3
更具体地说,当您简单地省略泛型类型的类型参数时,原始类型就是您得到的。原始类型实际上仅是向后兼容的功能,并且可能会被删除。您可以使用?获得类似的行为。通配符参数。
John Flatness 2010年

@zerocrates:相似但不同!使用?仍然提供类型安全性。我在答案中涵盖了它。
多基因润滑剂2010年

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

您应该指定类型参数。

该警告建议应定义参数以支持泛型,而不要使用其原始格式。

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


3
现在由Java 7中的钻石推理取代了–private static List<String> list = new ArrayList<>();
伊恩·坎贝尔

14

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

“原始类型”是使用通用类,而没有为其参数化类型指定类型实参,例如,使用List代替List<String>。当将泛型引入Java时,几个类已更新为使用泛型。将这些类用作“原始类型”(不指定类型实参)允许遗留代码继续编译。

“原始类型”用于向后兼容。不建议在新代码中使用它们,因为将泛型类与类型参数一起使用可实现更强的键入,进而可以提高代码的易懂性并导致更早地发现潜在问题。

如果我们不能使用原始类型,那有什么选择呢?有什么更好的选择?

首选的替代方法是按预期使用通用类-带有适当的类型参数(例如List<String>)。这使程序员可以更具体地指定类型,将有关变量或数据结构的预期用途的更多含义传达给未来的维护者,并允许编译器强制实施更好的类型安全性。这些优点一起可以提高代码质量,并有助于防止某些编码错误的引入。

例如,对于程序员希望确保名为“名称”的List变量仅包含字符串的方法:

List<String> names = new ArrayList<String>();
names.add("John");          // OK
names.add(new Integer(1));  // compile error

1
嗯,所以很想将polygenelubricants来自stackoverflow.com/questions/2770111/…的“原始类型”引用复制到我自己的答案中,但是我想我会将其留给他/她自己使用。
Bert F'5

1
是的,我实质上一直在人们在stackoverflow上使用原始类型的任何地方复制并粘贴该段,最终决定从现在开始仅提及一个问题。我希望这是对社区的良好贡献。
polygenelubricants 2010年

1
我注意到@polygenelubricants-我们遇到了一些相同的问题:-)
Bert F 2010年

1
@ ha9u63ar:的确如此。一般而言,简明扼要的答案至少与长久以来所接受的答案一样好。
displayName

什么是“强类型”?
carloswm85

12

编译器希望您编写以下代码:

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

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


12

在这里,我正在考虑多种情​​况,通过这些情况您可以澄清概念

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,其引用一个ArralyList类型的对象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)

情况二

在这种情况下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)

arr是严格类型。因此,添加时会产生编译时错误integer

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

情况3

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

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

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

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


8

型是缺少一个的类型参数使用通用类型时。

不应使用Raw-type,因为它可能会导致运行时错误,例如将a double插入应该为Setints中。

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

从中检索内容时Set,您不知道会发生什么。假设您期望它全部为ints,并且将其转换为Integer; double3.45出现时在运行时异常。

type参数添加到中后Set,您将立即获得编译错误。这种先发制人的错误使您可以在运行时发生故障之前解决问题(从而节省了时间和精力)。

Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.

7

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

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


5

这说明您list是一个未List指定的对象。那就是Java不知道列表中包含哪种对象。然后,当您要遍历列表时,必须强制转换每个元素,以便能够访问该元素的属性(在本例中为String)。

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

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

4

教程页面

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

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

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

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

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

Box rawBox = new Box();

2

避免原始类型

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

例如

列表是原始类型,而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


1

在进行一些示例练习并且完全相同的困惑之后,我发现了此页面。

===========================================================================================================================================我==========

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个小时才使这个哲学混乱。


0

当原始类型表示要表达的内容时,原始类型很好。

例如,反序列化函数可能返回List,但它不知道列表的元素类型。所以List在这里适当的返回类型。


您可以使用 ?类型参数
丹尼尔KIS

是的,但是要输入的更多,我反对输入更多。:)
Stefan Reich
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.