什么是原始类型?为什么不使用它?

uwopmtnx  于 2021-06-26  发布在  Java
关注(0)|答案(3)|浏览(293)

问题:

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

0ve6wy6x

0ve6wy6x1#

什么是原始类型?

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!)
    }
}

在这里, 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 然后手动将 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 ,上述代码将编译。

另请参见

java教程/泛型

原始类型与使用作为类型参数有何不同?

以下是对《有效java第2版》第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 作为参数,则这将编译,因此您将失去从泛型获得的类型安全性。

另请参见

两者有什么区别 <E extends Number> 以及 <Number> ?
java泛型(非)协方差

原始类型与使用<?>作为类型参数有何不同? 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!

编译器在保护您避免潜在地违反 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);
    }
}

当我们使用生的 MyType , getNames 也会被擦除,因此它返回一个原始值 List !
jls 4.6继续解释以下内容:
类型擦除还将构造函数或方法的签名Map到没有参数化类型或类型变量的签名。删除构造函数或方法签名 s 是由与相同的名称组成的签名 s 以及中给出的所有形式参数类型的删除 s .
如果方法或构造函数的签名被删除,则方法的返回类型和泛型方法或构造函数的类型参数也会被删除。
泛型方法签名的擦除没有类型参数。
以下错误报告包含编译器开发人员maurizio cimadamore和jls作者之一alex buckley关于为什么应该发生这种行为的一些想法:https://bugs.openjdk.java.net/browse/jdk-6400189. (简而言之,它使规范更简单。)

如果不安全,为什么允许使用原始类型?

以下是jls 4.8的另一句话:
只允许使用原始类型作为对遗留代码兼容性的让步。强烈反对在java编程语言中引入泛型之后编写的代码中使用原始类型。java编程语言的未来版本可能会禁止使用原始类型。
有效的java第2版还需要添加以下内容:
既然你不应该使用原始类型,为什么语言设计者允许它们呢?提供兼容性。
当泛型被引入时,java平台即将进入第二个十年,并且存在大量不使用泛型的java代码。所有这些代码都必须合法,并且可以与使用泛型的新代码进行互操作。将参数化类型的示例传递给设计用于普通类型的方法必须是合法的,反之亦然。这个被称为迁移兼容性的需求推动了支持原始类型的决定。
总之,新代码中不应使用原始类型。应该始终使用参数化类型。

没有例外吗?

不幸的是,由于java泛型是非具体化的,所以在新代码中必须使用原始类型时有两个例外:
类文字,例如。 List.class ,不是
List.class instanceof 操作数,例如。 o instanceof Set ,不是 o instanceof Set<String> ####另请参见
为什么是 Collection<String>.class 非法的?

eoigrqb6

eoigrqb62#

原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定泛型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> . 但是,非泛型类或接口类型不是原始类型。
原始类型出现在遗留代码中,因为在JDK5.0之前,许多api类(如collections类)不是泛型的。当使用原始类型时,基本上可以得到预泛型

9rbhqvlz

9rbhqvlz3#

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 函数,一个取一个 MyCompareAble 另一个拿着枪 Object 把它扔给第一个。
原始类型的替代方法是什么:使用泛型

相关问题