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

问题:

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

#1楼

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

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

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

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

若是省略实际类型参数,则建立Box<T>的原始类型: 程序员

Box rawBox = new Box();

所以, Box是通用类型Box<T>的原始类型。 可是,非泛型类或接口类型不是原始类型。 编程

原始类型显示在旧版代码中,由于在JDK 5.0以前,许多API类(例如Collections类)不是通用的。 使用原始类型时,您实际上会得到泛型行为Box为您提供Object 。 为了向后兼容,容许将参数化类型分配给其原始类型: 数组

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)

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

Type Erasure部分提供了有关Java编译器如何使用原始类型的更多信息。 app

未检查的错误消息

如前所述,将旧代码与通用代码混合时,您可能会遇到相似于如下内容的警告消息: 编程语言

注意: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")注释禁止未检查的警告。 若是您不熟悉@SuppressWarnings语法,请参阅注释。

原始资料: Java教程


#2楼

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楼

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

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

“原始类型”用于向后兼容。 不建议在新代码中使用它们,由于将泛型类与类型参数一块儿使用可实现更强的键入,这反过来又能够提升代码的可理解性并致使更早地发现潜在的问题。

若是咱们不能使用原始类型,那有什么选择呢?有什么更好的选择?

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

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

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

#4楼

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

不该使用Raw-type,由于它可能会致使运行时错误,例如将double插入到应该为intSet中。

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

Set检索内容时,您不知道会发生什么。 假设您指望它所有是int ,而且将其强制转换为Integerdouble精度3.45出现时在运行时发生异常。

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

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

#5楼

什么是原始类型?

Java语言规范将原始类型定义以下:

JLS 4.8原始类型

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

  • 经过采用通用类型声明的名称而没有随附的类型参数列表造成的引用类型。

  • 数组类型,其元素类型为原始类型。

  • 原始类型R的非static成员类型,该成员类型不继承自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在上述定义的第一个要点以前具备原始类型(并生成编译警告); 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 Stringinstanceof 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>之间有什么区别? 松散地说,前者选择了泛型类型检查,然后者则明确告诉编译器它可以保存任何类型的对象。 虽然能够经过一个List<String>到类型的参数List ,则不能将它传递给类型的参数List<Object> 。 有泛型的子类型化规则,而且List<String>是原始类型List的子类型,但不是参数化类型List<Object>类型。 所以, 若是使用像List这样的原始类型则会失去类型安全性,可是若是使用像List<Object>这样的参数化类型,则不会失去类型安全性

为了说明这一点,请考虑如下方法,该方法采用List<Object>并附加一个new 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做为参数,则它将被编译,所以您将失去从泛型得到的类型安全性。

也能够看看


原始类型与使用<?>做为类型参数有何不一样?

List<Object>List<String>等都是List<?> ,所以极可能会说它们只是List而已。 可是,有一个主要区别:因为List<E>仅定义add(E) ,所以您不能仅将任意对象添加到List<?> 。 另外一方面,因为原始类型List没有类型安全性,所以您几乎能够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<?>的类型不变性! 若是您已将参数声明为原始类型List list ,则代码将编译,而且违反了List<String> names的类型不变式。


原始类型是该类型的擦除

返回JLS 4.8:

这是可能做为一种类型的使用参数化的类型或数组类型,其元素类型是参数化类型的擦除的擦除这种类型称为原始类型

[...]

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

未从其超类或超接口继承的原始类型C的构造函数,实例方法或非static字段的类型为原始类型,该原始类型对应于在与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);
    }
}

当咱们使用原始的MyTypegetNames将被擦除,所以它返回原始的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 ,而不是List<String>.class
  • instanceof操做数,例如o instanceof Set ,而不是o instanceof Set<String>

也能够看看

相关文章
相关标签/搜索