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

uqzxnwby  于 2022-09-16  发布在  Java
关注(0)|答案(15)|浏览(227)

问题:

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

k3fezbri1#

原始类型是没有任何类型参数的泛型类或接口的名称。例如,给定通用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>的原始类型。但是,非泛型类或接口类型不是原始类型。
原始类型出现在遗留代码中,因为许多API类(如Collections类)在JDK 5.0之前不是泛型的。当使用原始类型时,基本上会得到泛型前的行为-Box给出Objects。为了向后兼容,允许将参数化类型分配给其原始类型:

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编译器如何使用原始类型的更多信息。

未选中的错误消息

如前所述,在将传统代码与通用代码混合时,您可能会遇到类似以下的警告消息:
注:示例。java使用未检查或不安全的操作。
注意:使用-Xlint:取消选中以获取详细信息。
当使用对原始类型操作的旧API时,可能会发生这种情况,如以下示例所示:

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

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

术语“未检查”意味着编译器没有足够的类型信息来执行确保类型安全所需的所有类型检查。默认情况下,“未检查”警告被禁用,但编译器会给出提示。要查看所有“未选中”的警告,请使用-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 Tutorials

rsl1atfo

rsl1atfo2#

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。这一事实是泛型的主要驱动因素。它将运行时错误源更改为可在编译时检查的内容。

lh80um4z

lh80um4z3#

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

您应该指定类型参数。
警告建议定义为支持generics的类型应参数化,而不是使用其原始形式。
List定义为支持泛型:public class List<E>。这允许在编译时检查许多类型安全操作。

hivapdat

hivapdat4#

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

“原始类型”是使用泛型类而不为其参数化类型指定类型参数,例如使用List而不是List<String>。当泛型引入Java时,几个类被更新为使用泛型。使用这些类作为“原始类型”(不指定类型参数)允许遗留代码仍然编译。
“原始类型”用于向后兼容性。不建议在新代码中使用它们,因为使用带有类型参数的泛型类允许更强的类型,这反过来可能会提高代码的可理解性,并导致更早发现潜在问题。

  • 如果我们不能使用原始类型,有什么替代方法,如何更好*

首选的替代方案是按预期使用泛型类-带有合适的类型参数(例如List<String>)。这允许程序员更具体地指定类型,向未来的维护人员传达变量或数据结构的预期用途的更多含义,并允许编译器实施更好的类型安全。这些优点一起可以提高代码质量并有助于防止引入一些编码错误。
例如,对于程序员希望确保名为“名称”的列表变量只包含字符串的方法:

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

kq0g1dla5#

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

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

案例1

ArrayList<String> arr它是一个ArrayList类型的E1d1d 1e引用变量,它引用String型的ArralyList对象。这意味着它只能保存字符串类型的对象。
这是一个严格的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<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)

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

警告:-Raw类型对象被Strict类型引用变量ArrayList引用。

案例3

在本例中,ArrayList arr是原始类型,但对象new ArrayList<String>();是严格类型。

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

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

警告:-Strict类型对象被raw类型引用变量引用。

ghg1uchk

ghg1uchk6#

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

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

否则,您可以将任何类型添加到list中,使示例化为new ArrayList<String>()变得毫无意义。Java泛型只是一种编译时特性,因此使用new ArrayList<String>()创建的对象如果被分配给“原始类型”List的引用,则会愉快地接受IntegerJFrame元素-对象本身不知道它应该包含什么类型,只有编译器知道。

vvppvyoh

vvppvyoh7#

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

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

gmxoilav

gmxoilav8#

当使用泛型类型时,原始类型是缺少类型参数的。
不应使用原始类型,因为它可能会导致运行时错误,例如将double插入到ints的Set中。

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

Set中检索材料时,您不知道会出现什么。假设您期望它都是E1D4D1E,您将其转换为Integerdouble 3.45出现时运行时异常。
Set中添加类型参数后,您将立即得到编译错误。这种先发制人的错误可以让您在运行时发生故障之前修复问题(从而节省时间和精力)。

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

i2byvkas9#

避免使用原始类型

原始类型是指使用泛型类型而不指定类型参数。
例如:
list是原始类型,而List<String>是参数化类型。
在JDK1.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

7kqas0il

7kqas0il10#

也就是说,您的list是未指定对象的List。也就是说,Java不知道列表中有什么类型的对象。然后,当您想要迭代列表时,您必须强制转换每个元素,以便能够访问该元素的属性(在本例中为字符串)。
一般来说,参数化集合是一个更好的主意,这样您就不会有转换问题,您将只能添加参数化类型的元素,并且您的编辑器将为您提供适当的选择方法。

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

5fjcxozz11#

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

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

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

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

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

Box rawBox = new Box();
i1icjdpr

i1icjdpr12#

我在做了一些示例练习后发现了这个页面,并有完全相同的困惑。
==============我从示例提供的代码开始===============

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

}

===============================================================================
这可能更安全,但花了4个小时来改变哲学。。。

pbossiut

pbossiut13#

原始类型在表达您想要表达的内容时很好。
例如,反序列化函数可能返回List,但它不知道列表的元素类型。因此,这里List是合适的返回类型。

dl5txlt9

dl5txlt914#

什么是原始类型?

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 String
假设,如果您希望names只包含String,那么您可以可能仍然使用原始类型,自己手动检查每个add手动将转换为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第二版,第23条:在新代码中不要使用原始类型
原始类型List和参数化类型e1d 25d1e之间的区别是什么?松散地说,前者选择了泛型类型检查,而后者明确地告诉编译器它能够保存任何类型的对象。虽然可以将List<String>传递给List类型的参数,但不能将其传递给ed28d1e型的参数。泛型有子类型规则,List<String>是原始类型List的子类型,但不是参数化类型e1d 31d1e。因此,**如果使用像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转换为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:
可以将参数化类型的擦除用作类型,也可以将元素类型为参数化的数组类型的擦除用作类型****这种类型称为“原始类型”**

  • [...]*

原始类型的超类(分别是超接口)是泛型类型的任何参数化的超类的擦除。
未从其超类或超接口继承的原始类型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继续解释以下内容:

**类型擦除还将构造函数或方法的签名Map到没有参数化类型或类型变量的签名。**构造函数或方法签名s的擦除是由与s同名的签名和s中给出的所有形式参数类型的擦除组成的签名。
如果方法或构造函数的签名被擦除,则方法的返回类型和泛型方法或构造器的类型参数也会被擦除

擦除泛型方法的签名没有类型参数。
以下错误报告包含编译器开发人员Maurizio Cimadamore和JLS作者之一Alex Buckley关于为什么应该发生这种行为的一些想法:https://bugs.openjdk.java.net/browse/JDK-6400189。(简而言之,它简化了规范。)

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

以下是JLS 4.8中的另一句话:
仅允许使用原始类型作为对遗留代码兼容性的让步*在引入generi之后编写的代码中使用原始类型

8aqjt8rx

8aqjt8rx15#

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

请注意,无法使用原始类型的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函数,一个取E1D11E,另一个取一个Object,并在铸造后将其传递给第一个。
原始类型的替代品有哪些:使用generics

相关问题