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

pol*_*nts 617 java generics raw-types

问题:

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

pol*_*nts 709

什么是原始类型?

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

JLS 4.8原始类型

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

  • 通过获取泛型类型声明的名称而不带伴随类型参数列表而形成的引用类型.

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

  • static成员类型的原始类型R,不是从超类或超接口继承的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!)
    }
}
Run Code Online (Sandbox Code Playgroud)

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!
Run Code Online (Sandbox Code Playgroud)

上面的代码运行得很好,但假设你还有以下内容:

for (Object o : names) {
    String name = (String) o;
    System.out.println(name);
} // throws ClassCastException!
  //    java.lang.Boolean cannot be cast to java.lang.String
Run Code Online (Sandbox Code Playgroud)

现在我们在运行时遇到麻烦,因为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!
Run Code Online (Sandbox Code Playgroud)

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

也可以看看


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

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

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

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

void appendNewObject(List<Object> list) {
   list.add(new Object());
}
Run Code Online (Sandbox Code Playgroud)

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

List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
Run Code Online (Sandbox Code Playgroud)

如果你声明appendNewObject要将原始类型List作为参数,那么这将编译,因此你将失去从泛型中获得的类型安全性.

也可以看看


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

List<Object>,List<String>等等都是List<?>,所以说它们只是List反而可能很诱人.但是,存在一个主要区别:因为List<E>只定义了一个add(E),所以不能只添加任意对象List<?>.另一方面,由于原始类型List没有类型安全性,你几乎可以做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!
Run Code Online (Sandbox Code Playgroud)

编译器做了很好的工作,保护您免受潜在的违反类型的不变性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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当我们使用原始时MyType,getNames也会被删除,以便它返回原始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第二版也有这个添加:

鉴于您不应该使用原始类型,为什么语言设计者允许它们?提供兼容性.

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

总之,原始类型不应该在新代码中使用.您应该始终使用参数化类型.


没有例外吗?

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

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

也可以看看

  • 你是什​​么意思,"Java泛型是不具体化的"? (14认同)
  • 对于第二个例外,也允许使用语法`o instanceof Set <?>`来避免原始类型(尽管在这种情况下它只是肤浅的). (7认同)
  • "未具体化"是说它们被抹去的另一种方式.编译器知道通用参数是什么,但该信息不会传递给生成的字节码.JLS要求类文字没有类型参数. (7认同)
  • @OldCurmudgeon那很有意思.我的意思是正式它是***因为一个类文字定义为`TypeName.class`,其中`TypeName`是一个普通的标识符([jls](https://docs.oracle.com/javase/specs/jls/ SE8/HTML/JLS-15.html#JLS-15.8.2)).假设,我猜它可能真的是.也许作为一个线索,`List <String> .class`是JLS专门调用编译器错误的变体,所以如果他们将它添加到语言中,我希望它是他们使用的那个. (2认同)
  • @Jwan622,因为该程序“不太可能”让开发人员的本地 PC 仍处于该状态。 (2认同)

jos*_*efx 58

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

原始类型是Java语言的古老历史.一开始就有Collections,他们Objects没有更多,也没有更少.每次操作都Collections需要从Object所需的类型转换到所需的类型.

List aList = new ArrayList();
String s = "Hello World!";
aList.add(s);
String c = (String)aList.get(0);
Run Code Online (Sandbox Code Playgroud)

虽然这在大多数时间都有效,但确实发生了错误

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

旧的无类型集合无法强制执行类型安全,因此程序员必须记住他在集合中存储的内容.
为了解决这个限制而发明的泛型,开发人员会声明存储类型一次,而编译器会这样做.

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

比较:

// 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
Run Code Online (Sandbox Code Playgroud)

比较可靠的界面更复杂:

//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;}
}
Run Code Online (Sandbox Code Playgroud)

请注意,无法使用原始类型实现CompareAble接口compareTo(MyCompareAble).为什么你不应该使用它们:

  • Object存储在a中的任何内容Collection必须先进行投射才能使用
  • 使用泛型可以进行编译时检查
  • 使用原始类型与将每个值存储为相同 Object

编译器的作用:泛型向后兼容,它们使用与原始类型相同的java类.神奇主要发生在编译时.

List<String> someStrings = new ArrayList<String>();
someStrings.add("one");
String one = someStrings.get(0);
Run Code Online (Sandbox Code Playgroud)

将编译为:

List someStrings = new ArrayList();
someStrings.add("one"); 
String one = (String)someStrings.get(0);
Run Code Online (Sandbox Code Playgroud)

如果直接使用原始类型,则与编写的代码相同.以为我不确定CompareAble接口会发生什么,我猜它会创建两个compareTo函数,一个接受一个MyCompareAble,另一个接受Object并在转换后将其传递给第一个.

原始类型有哪些替代方法:使用泛型


Ade*_*lin 26

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

public class Box<T> {
    public void set(T t) { /* ... */ }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

Box<Integer> intBox = new Box<>();
Run Code Online (Sandbox Code Playgroud)

如果省略实际的类型参数,则创建一个原始类型Box<T>:

Box rawBox = new Box();
Run Code Online (Sandbox Code Playgroud)

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

原始类型显示在遗留代码中,因为许多API类(例如Collections类)在JDK 5.0之前不是通用的.当使用原始类型时,你基本上会获得前泛型行为 - 一个Box给你Object的.为了向后兼容,允许将参数化类型分配给其原始类型:

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK
Run Code Online (Sandbox Code Playgroud)

但是,如果将原始类型分配给参数化类型,则会收到警告:

Box rawBox = new Box();           // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox;     // warning: unchecked conversion
Run Code Online (Sandbox Code Playgroud)

如果使用原始类型调用相应泛型类型中定义的泛型方法,也会收到警告:

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8);  // warning: unchecked invocation to set(T)
Run Code Online (Sandbox Code Playgroud)

警告显示原始类型绕过泛型类型检查,将不安全代码的捕获推迟到运行时.因此,您应该避免使用原始类型.

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

未选中的错误消息

如前所述,在将遗留代码与通用代码混合时,您可能会遇到类似于以下内容的警告消息:

注意:Example.java使用未经检查或不安全的操作.

注意:使用-Xlint重新编译:取消选中以获取详细信息.

使用对原始类型进行操作的旧API时会发生这种情况,如以下示例所示:

public class WarningDemo {
    public static void main(String[] args){
        Box<Integer> bi;
        bi = createBox();
    }

    static Box createBox(){
        return new Box();
    }
}
Run Code Online (Sandbox Code Playgroud)

术语"未选中"表示编译器没有足够的类型信息来执行确保类型安全所必需的所有类型检查.默认情况下,"unchecked"警告被禁用,尽管编译器提供了提示.要查看所有"未选中"警告,请使用-Xlint重新编译:取消选中.

使用-Xlint重新编译上一个示例:unchecked显示以下附加信息:

WarningDemo.java:4: warning: [unchecked] unchecked conversion
found   : Box
required: Box<java.lang.Integer>
        bi = createBox();
                      ^
1 warning
Run Code Online (Sandbox Code Playgroud)

要完全禁用未检查的警告,请使用-Xlint:-unchecked标志.该@SuppressWarnings("unchecked")注释抑制unchecked警告.如果您不熟悉@SuppressWarnings语法,请参阅注释.

原始资料来源:Java教程


Boz*_*zho 19

 private static List<String> list = new ArrayList<String>();
Run Code Online (Sandbox Code Playgroud)

您应该指定type-parameter.

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

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

  • 现在由Java 7中的*diamond inference*替换 - `private static List <String> list = new ArrayList <>();` (3认同)

And*_*ite 16

Java中的"原始"类型是非泛型的类,它处理"原始"对象,而不是类型安全的泛型类型参数.

例如,在Java泛型可用之前,您将使用如下集合类:

LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject)list.get(0);
Run Code Online (Sandbox Code Playgroud)

当您将对象添加到列表中时,它不关心它是什么类型的对象,当您从列表中获取它时,您必须将它显式地转换为您期望的类型.

使用泛型,可以删除"未知"因子,因为您必须明确指定列表中可以包含的对象类型:

LinkedList<MyObject> list = new LinkedList<MyObject>();
list.add(new MyObject());
MyObject myObject = list.get(0);
Run Code Online (Sandbox Code Playgroud)

请注意,对于泛型,您不必转换来自get调用的对象,该集合是预定义的,仅适用于MyObject.这一事实是仿制药的主要驱动因素.它将运行时错误的源更改为可在编译时检查的内容.

  • 更具体地说,当您简单地省略泛型类型的类型参数时,就会获得原始类型.原始类型实际上只是一种向后兼容功能,可能会被删除.你可以使用类似的行为吗?通配符参数. (3认同)

Ber*_*t F 12

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

"原始类型"是泛型类的使用,而不为其参数化类型指定类型参数,例如使用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
Run Code Online (Sandbox Code Playgroud)


Mic*_*rdt 12

编译器要你写这个:

private static List<String> list = new ArrayList<String>();
Run Code Online (Sandbox Code Playgroud)

因为否则,您可以添加任何您喜欢的类型list,使实例化变得new ArrayList<String>()毫无意义.Java泛型只是一个编译时功能,因此如果new ArrayList<String>()将一个对象分配给"原始类型"的引用,那么创建的对象将很乐意接受Integer或者JFrame元素List- 对象本身对它应该包含的类型一无所知,只有编译器才知道.


Vik*_*yap 12

在这里,我考虑了多个案例,您可以通过这些案例来澄清这个概念

1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();
Run Code Online (Sandbox Code Playgroud)

情况1

ArrayList<String> arr它是一个ArrayList引用变量,其类型String引用了ArralyListType of Object 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)
Run Code Online (Sandbox Code Playgroud)

案例2

在这种情况下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)
Run Code Online (Sandbox Code Playgroud)

arr是严格的类型.因此,添加时会引发编译时错误integer.

警告: - Raw类型对象引用Strict类型为Referenced Variable of ArrayList.

案例3

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

    arr.add("hello");  
    arr.add(23);  //compiles fine but raise the warning.
Run Code Online (Sandbox Code Playgroud)

它会添加任何类型的Object,因为它arr是Raw Type.

警告: - Strict类型对象引用raw引用的变量类型.


Lar*_*ren 8

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

原始型不应该被使用,因为它可能会导致运行时错误,如插入double到什么应该是一个Setint秒.

Set set = new HashSet();
set.add(3.45); //ok
Run Code Online (Sandbox Code Playgroud)

从中检索东西时Set,你不知道会发生什么.让我们假设你期望它是全部int的,你将它投射到Integer; double3.45出现时运行时异常.

添加一个类型参数后Set,您将立即收到编译错误.此抢先错误可让您在运行期间爆炸之前解决问题(从而节省时间和精力).

Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.
Run Code Online (Sandbox Code Playgroud)


Ash*_*ish 8

避免原始类型。

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

例如:

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));
    }
}
 
Run Code Online (Sandbox Code Playgroud)

供参考:https ://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html


Guy*_*ock 6

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

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

正如在已接受的答案中所提到的,您在原始类型的代码中失去了对泛型的所有支持.每个类型参数都转换为其擦除(在上面的例子中只是Object).


pak*_*ore 5

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

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

private static List<String> list = new ArrayList<String>();
Run Code Online (Sandbox Code Playgroud)


Myk*_*ych 5

教程页面

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

public class Box<T> {
    public void set(T t) { /* ... */ }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

Box<Integer> intBox = new Box<>();
Run Code Online (Sandbox Code Playgroud)

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

Box rawBox = new Box();
Run Code Online (Sandbox Code Playgroud)