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

问题:

  • Java中的原始类型是什么?为什么我经常听到不应该在新代码中使用它们的原始信息?
  • 如果我们不能使用原始类型,还有什么替代方法呢?
标签:java,generics,raw-types

回答1:

什么是原始类型?

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 参数化类型( JLS 4.5 )。通常将这种类型简称为MyType,但是从技术上讲,名称是MyType

mt在上述定义的第一个要点之前具有原始类型(并生成编译警告); inn在第三个要点之前也具有原始类型。

MyType.Nested不是参数化类型,即使它是参数化类型MyType 的成员类型,因为它是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

现在我们在运行时遇到麻烦,因为名称包含的内容不是instanceofString

大概,如果您希望names仅包含String,则您可以仍然使用原始类型,并手动检查每个 添加自己,然后手动将强制转换为名称中每个项目的String更好,但不要使用原始类型,而让编译器为您完成所有工作,利用Java泛型的力量。

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

当然,如果您 DO 希望names允许Boolean,则可以将其声明为List名称,然后上面的代码就会编译。

另请参见


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

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

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

为说明这一点,请考虑以下方法,该方法采用List并附加newObject()

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

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

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

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

另请参见


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

ListList 等都是List ,所以可能很想说他们只是List而已。但是,有一个主要区别:由于List 仅定义了add(E),因此您不能仅将任意对象添加到List<?> 。另一方面,由于原始类型List不具有类型安全性,因此您可以添加几乎所有内容到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 的类型不变性!如果您已将参数声明为原始类型Listlist,则代码将被编译,并且违反了List 名称 的类型不变式。


原始类型是该类型的擦除

返回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);
    }
}

当我们使用原始的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 2nd Edition 也要添加以下内容:

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

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

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


有没有例外?

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

  • 类文字,例如List.class,而不是List .class
  • instanceof操作数,例如oinstanceofSet,而不是oinstanceofSet

另请参见

回答2:

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

Compareable接口更复杂:

//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类。魔术主要发生在编译时。

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并将其传递给第一个对象。

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

回答3:

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

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

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

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

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

Box rawBox = new Box();

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

原始类型显示在旧版代码中,因为在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)

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

"类型清除"部分提供了有关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();
    }
}

术语"未检查"表示编译器没有足够的类型信息来执行确保类型安全所需的所有类型检查。尽管编译器会给出提示,但默认情况下" 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教程

回答4:

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

您应该指定类型参数。

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

List被定义为支持泛型:公共类List 。这样可以进行许多类型安全的操作,这些操作会在编译时进行检查。

回答5:

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

回答6:

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

"原始类型"是使用通用类,而没有为其参数化类型指定类型实参,例如使用List而不是List 。当将泛型引入Java时,几个类已更新为使用泛型。将这些类用作"原始类型"(不指定类型实参)可以使遗留代码仍然可以编译。

"原始类型"用于向后兼容。不建议在新代码中使用它们,因为将泛型类与类型参数一起使用会增强类型,从而反过来可能会提高代码的易懂性并提前发现潜在问题。

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

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

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

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

回答7:

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

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

否则,您可以将任何所需的类型添加到list中,从而使实例化为newArrayList () 毫无意义。 Java泛型仅是编译时功能,因此使用newArrayList () 创建的对象将很乐意接受IntegerJFrame元素如果将其分配给"原始类型" List的引用,则对象本身对应该包含的类型一无所知,只有编译器知道。

回答8:

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

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

案例1

ArrayList arr 是一个ArrayList引用变量,其类型为String,它引用ArralyList类型为String的对象。这意味着它只能容纳String类型的对象。

严格编码为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)

案例2

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

    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类型的对象引用到ArrayListStrict类型引用变量。 / p>

案例3

在这种情况下,ArrayListarr是原始类型,但是您的Object newArrayList (); 是严格类型。

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

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

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

回答9:

使用通用类型时,原始类型是缺少 type参数的。 不应使用

原​​始类型,因为它可能会导致运行时错误,例如将double插入应该是int的 Set 中的地方。

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

Set中检索内容时,您不知道会发生什么。假设您期望它全部为int,并且将其强制转换为Integerdouble 3.45出现在运行时的异常。

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

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

回答10:

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

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

回答11:

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

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

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

回答12:

教程页面

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

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

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

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

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

Box rawBox = new Box();

回答13:

避免使用原始类型

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

例如

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

在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

回答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());
    }

=====================到此代码======================= ==

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());
    }

}

================================================ ===============================

这可能更安全,但花了四个小时才弄清了这一原理...

回答15:

原始类型可以很好地表达您想要表达的内容。

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

回到顶部