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

What is a raw type and why shouldn't we use it?

问题:

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


什么是原始类型?

Java语言规范定义了如下的原始类型:好的。JLS 4.8原始类型

A raw type is defined to be one of:

Ok.

  • The reference type that is formed by taking the name of a generic type declaration without an accompanying type argument list.

    Ok.

  • An array type whose element type is a raw type.

    Ok.

  • A non-static member type of a raw type R that is not inherited from a superclass or superinterface of R.

    Ok.

下面是一个例子来说明:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
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是一个参数化类型(jls 4.5)。通常将这种类型简称为MyType,但从技术上讲,它的名称是MyType。好的。

mt由上述定义中的第一个项目符号点具有原始类型(并生成编译警告);inn也由第三个项目符号点具有原始类型。好的。

MyType.Nested不是参数化类型,尽管它是参数化类型MyType的成员类型,因为它是static。好的。

mt1mt2都是用实际类型参数声明的,因此它们不是原始类型。好的。原始类型有什么特别之处?

从本质上讲,原始类型的行为就像在引入泛型之前一样。也就是说,以下内容在编译时完全合法。好的。

1
2
3
4
List names = new ArrayList(); // warning: raw type!
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // not a compilation error!

上面的代码运行得很好,但是假设您还具有以下功能:好的。

1
2
3
4
5
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,然后手动将names中的每个项目强制转换为String。更妙的是,不是使用原始类型,而是让编译器为你做所有的工作,利用Java泛型的力量。好的。

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

当然,如果您希望names允许Boolean,那么您可以将其声明为List names,并编译上述代码。好的。也见

  • Java教程/泛型

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

以下是有效Java第二版的引用,项目23:不要在新代码中使用原始类型:好的。

Just what is the difference between the raw type List and the parameterized type List? Loosely speaking, the former has opted out generic type checking, while the latter explicitly told the compiler that it is capable of holding objects of any type. While you can pass a List to a parameter of type List, you can't pass it to a parameter of type List. There are subtyping rules for generics, and List is a subtype of the raw type List, but not of the parameterized type List. As a consequence, you lose type safety if you use raw type like List, but not if you use a parameterized type like List.

Ok.

为了说明这一点,考虑以下方法,它采用一个List并附加一个new Object()。好的。

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

Java中的泛型是不变量的。List不是List,因此以下将生成编译器警告:好的。

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

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

  • 有什么区别?
  • Java泛型(非)协方差

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

ListList等都是List,所以可能会说他们只是List。但是,有一个主要的区别:由于List只定义add(E),所以不能向List添加任意对象。另一方面,由于原始类型List不具有类型安全性,因此可以对List执行任何操作。好的。

考虑前面代码段的以下变化:好的。

1
2
3
4
5
6
7
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 names的类型不变量。好的。原始类型是删除该类型

回到JLS 4.8:好的。

It is possible to use as a type the erasure of a parameterized type or the erasure of an array type whose element type is a parameterized type. Such a type is called a raw type.

Ok.

[...]

Ok.

The superclasses (respectively, superinterfaces) of a raw type are the erasures of the superclasses (superinterfaces) of any of the parameterizations of the generic type.

Ok.

The type of a constructor, instance method, or non-static field of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

Ok.

更简单地说,当使用原始类型时,构造函数、实例方法和非static字段也将被删除。好的。

take the following example:>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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茶叶MyTypegetNamesbecomes erased so that as哦,恩在RAW List归来!>

4.6 continues to the following JLS解释:>

Type erasure also maps the signature of a constructor or method to a signature that has no parameterized types or type variables. The erasure of a constructor or method signature s is a signature consisting of the same name as s and the erasures of all the formal parameter types given in s.

Ok.

The return type of a method and the type parameters of a generic method or constructor also undergo erasure if the method or constructor's signature is erased.

Ok.

The erasure of the signature of a generic method has no type parameters.

Ok.

下面的错误报告cimadamore contains some思想从毛里求斯,编译器的开发,和Alex Buckley,one of the作家of the sort of this JLS,为什么在线:一个行为的occur HTTPS浏览:/ / / / bugs.openjdk.java.net - 6400189 JDK。(在EN makes the short,简单的内部规范。)>

如果这是unsafe,为什么是它允许使用的RAW型?

这里有一股来自JLS 4.8>

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of genericity into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

Ok.

Java第二版has this also effective to add:>

Given that you shouldn't use raw types, why did the language designers allow them? To provide compatibility.

Ok.

The Java platform was about to enter its second decade when generics were introduced, and there was an enormous amount of Java code in existence that did not use generics. It was deemed critical that all this code remains legal and interoperable with new code that does use generics. It had to be legal to pass instances of parameterized types to methods that were designed for use with ordinary types, and vice versa. This requirement, known as migration compatibility, drove the decision to support raw types.

Ok.

在总结不好,应该用RAW类型在美国队列。你应该总是使用parameterized types。>

有了是不?

不幸的是他/她,因为Java泛型are there are两非reified了RAW,must be used types -代码:纽约>

  • List.class文字类,例如,List.classnot
  • instanceofo instanceof Set操作数,例如,o instanceof Setnot

see also

  • Collection.class为什么是非法的?

好吧。


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

原始类型是Java语言的古老历史。起初有以东十一〔0〕人,他们把以东十一〔1〕一点也不多,一点也不少。在Collections上的每个操作都需要从Object转换到所需类型。

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

虽然这在大多数时候都起作用,但确实发生了错误。

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

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

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

作比较:

1
2
3
4
// 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

比较复杂的界面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//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;}
}

注意,使用原始类型的compareTo(MyCompareAble)实现CompareAble接口是不可能的。为什么不使用它们:

  • 任何存储在Collection中的Object在使用前必须进行铸造。
  • 使用泛型可以启用编译时检查
  • 使用原始类型与存储每个值相同,如Object

编译器的作用:泛型是向后兼容的,它们使用与原始类型相同的Java类。这种魔力主要发生在编译时。

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

将编译为:

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

这与直接使用原始类型时编写的代码相同。我想我不知道CompareAble接口会发生什么,我猜它会创建两个compareTo函数,一个接受MyCompareAble,另一个接受Object,并在投射后将其传递给第一个函数。

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


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

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

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

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

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

1
Box rawBox = new Box();

因此,Box是通用类型Box的原始类型。但是,非泛型类或接口类型不是原始类型。

原始类型出现在遗留代码中,因为许多API类(如集合类)在JDK5.0之前不是通用的。当使用原始类型时,基本上会得到预泛型行为-Box给您提供Objects。为了向后兼容,允许将参数化类型分配给其原始类型:

1
2
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK

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

1
2
Box rawBox = new Box();           // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox;     // warning: unchecked conversion

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

1
2
3
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8);  // warning: unchecked invocation to set(T)

警告显示原始类型绕过了一般类型检查,将不安全代码的捕获延迟到运行时。因此,应该避免使用原始类型。

类型擦除部分有更多关于Java编译器如何使用原始类型的信息。

未选中的错误消息

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

Note: Example.java uses unchecked or unsafe operations.

Note: Recompile with -Xlint:unchecked for details.

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

1
2
3
4
5
6
7
8
9
10
public class WarningDemo {
    public static void main(String[] args){
        Box<Integer> bi;
        bi = createBox();
    }

    static Box createBox(){
        return new Box();
    }
}

术语"unchecked"意味着编译器没有足够的类型信息来执行确保类型安全所必需的所有类型检查。默认情况下,"unchecked"警告被禁用,但编译器会给出提示。要查看所有"unchecked"警告,请使用-xlint:unchecked重新编译。

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

1
2
3
4
5
6
WarningDemo.java:4: warning: [unchecked] unchecked conversion
found   : Box
required: Box<java.lang.Integer>
        bi = createBox();
                      ^
1 warning

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

原始源代码:Java教程


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

您应该指定类型参数。

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

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


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

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

1
2
3
LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject)list.get(0);

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

使用泛型,可以删除"未知"因素,因为必须显式指定可以在列表中放入的对象类型:

1
2
3
LinkedList<MyObject> list = new LinkedList<MyObject>();
list.add(new MyObject());
MyObject myObject = list.get(0);

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


编译器希望您编写以下内容:

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

因为否则,您可以在List中添加任何类型,使得作为new ArrayList()的实例化毫无意义。Java泛型只是一个编译时特性,所以如果使用EDCOX1的18个对象创建一个对象,它将很乐意接受EDCOX1×20或EDCOX1×21个元素,如果分配给"原始类型"EDCOX1 13的引用,对象本身就不知道它应该包含什么类型,只有编译器才知道。


在这里,我正在考虑多个案例,通过这些案例,您可以清楚地了解这个概念。

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

案例1

ArrayList arrString型的ArrayList参考变量,它引用String型的ArralyList对象。这意味着它只能保存字符串类型的对象。

这是对String的严格要求,而不是原始类型,因此,它永远不会发出警告。

1
2
3
4
    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 arr是严格类型,但您的对象new ArrayList();是原始类型。

1
2
3
    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时会导致编译时错误。

Warning :- A Raw Type Object is referenced to a Strict type Referenced Variable of ArrayList.

案例3

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

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

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

Warning :- A Strict Type Object is referenced to a raw type referenced Variable.


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

"原始类型"是在不为参数化类型指定类型参数的情况下使用泛型类,例如使用List而不是List。当泛型被引入Java时,更新了几个类来使用泛型。将这些类用作"原始类型"(不指定类型参数),允许遗留代码仍然编译。

"原始类型"用于向后兼容。不建议在新代码中使用它们,因为使用带有类型参数的泛型类可以实现更强的类型化,从而提高代码的可理解性,并导致更早地发现潜在的问题。

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

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

例如,对于程序员希望确保名为"name"的列表变量只包含字符串的方法:

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


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

不应使用原始类型,因为它可能会导致运行时错误,例如将double插入到intSet中。

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

当你从Set上取东西时,你不知道会发生什么。假设您希望它都是int,您将它强制转换为Integer;运行时double3.45出现时除外。

Set中添加一个类型参数后,您将立即得到一个编译错误。这个先发制人的错误允许您在运行时发生问题之前解决问题(从而节省时间和精力)。

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

这里还有另一种情况,原始类型会咬你:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)。


意思是你的List是一个由未指定对象组成的List。也就是说,Java不知道列表中的对象是什么类型。然后,当您想要迭代列表时,您必须强制转换每个元素,以便能够访问该元素的属性(在本例中是字符串)。

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

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


教程页。

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

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

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

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

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

1
Box rawBox = new Box();

我在做了一些样本练习和有完全相同的困惑之后找到了这个页面。

=======我从示例提供的代码中删除了这段代码=========

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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());
    }

对这段代码的修改==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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个小时的时间来破译哲学…


避免原始类型

Raw types refer to using a generic type without specifying a type parameter.

例如,

列表是原始类型,而List是参数化类型。

当在JDK 1.5中引入泛型时,保留原始类型,以保持与旧版本Java的向后兼容性。尽管使用原始类型仍然是可能的,

应避免:

  • 他们通常需要石膏
  • 它们不是类型安全的,一些重要的错误只会在运行时出现。
  • 它们的表达能力较低,并且不会像参数化类型那样自我记录。例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    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


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

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