首页 文章

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

提问于
浏览
553

问题:

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

  • 如果我们不能使用原始类型,它有什么替代方案,它是如何更好的?

14 回答

  • 8

    tutorial page .

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

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

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

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

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

    Box rawBox = new Box();
    
  • 10
    private static List<String> list = new ArrayList<String>();
    

    您应该指定type-parameter .

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

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

  • 1

    我在做了一些示例练习并且有完全相同的困惑之后找到了这个页面 .

    ==============我从这个代码中提供了样本===============

    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个小时才能消除哲学......

  • 10

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

  • 9

    编译器要你写这个:

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

    因为否则,您可以将您喜欢的任何类型添加到 list 中,使实例化为 new ArrayList<String>() 无意义 . Java泛型只是一个编译时功能,因此如果分配给"raw type" List 的引用,使用 new ArrayList<String>() 创建的对象将很乐意接受 IntegerJFrame 元素 - 对象本身不知道它应该包含哪些类型,只有编译器才能 .

  • 17

    原始类型是没有任何类型参数的泛型类或接口的名称 . 例如,给定通用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 为您提供 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)
    

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

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

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

    使用-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

  • 4

    什么是原始类型?

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

    JLS 4.8原始类型

    原始类型定义为以下之一:通过获取泛型类型声明的名称而不带伴随类型参数列表而形成的引用类型 . 元素类型为原始类型的数组类型 . 原始类型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 .

    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 手动转换为 String 每个项目 . Even better ,虽然不是使用原始类型,让编译器为您完成所有工作,利用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>作为类型参数不同的原始类型如何?

    以下是Effective Java 2nd Edition的引用,第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 作为参数,那么这将编译,因此您将失去从泛型获得的类型安全性 .

    另见


    如何使用<?>作为类型参数的原始类型?

    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的构造函数,实例方法或非静态字段的类型是对应于在与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第二版也有这个添加:

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

    总之,原始类型不应该在新代码中使用 . You should always use parameterized types .


    没有例外吗?

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

    • 类文字,例如 List.class ,不是 List<String>.class

    • instanceof 操作数,例如 o instanceof Set ,不是 o instanceof Set<String>

    另见

  • 4

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

    例如,反序列化函数可能返回 List ,但它不是't know the list'的元素类型 . 所以 List 是适当的返回类型 .

  • 4

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

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

    案例1

    ArrayList<String> arr 它是 ArrayList 引用变量,类型为 String ,引用 ArralyList 类型的 ArralyList 对象 . 这意味着它只能保存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)
    

    案例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 Type对象引用ArrayList的Strict类型引用变量 .

    案例3

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

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

    它将添加任何类型的Object,因为 arr 是原始类型 .

    警告: - 严格类型对象引用引用的原始类型变量 .

  • 52

    raw-type是使用泛型类型时缺少类型参数 .

    不应该使用Raw-type,因为它可能会导致运行时错误,例如将 double 插入到 Setint 中 .

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

    当从 Set 中检索这些东西时,你不要认为你希望它全部是 int ,你将它投射到 Integer ; double 3.45出现时运行时异常 .

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

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

    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 函数,一个采用 MyCompareAble ,另一个采用 Object 并在投射后将其传递给第一个 .

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

  • 14

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

    "raw type"是泛型类的使用,而没有为其参数化类型指定类型参数,例如,使用 List 而不是 List<String> . 将泛型引入Java时,会更新几个类以使用泛型 . 使用这些类作为"raw type"(不指定类型参数)允许遗留代码仍然编译 .

    “原始类型”用于向后兼容 . 不推荐在新代码中使用它们,因为使用带有类型参数的泛型类允许更强的类型,这反过来可以提高代码可理解性并导致更早地捕获潜在问题 .

    如果我们不能使用原始类型,它的替代方案是什么?它如何更好?

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

    例如,对于程序员想要确保名为“names”的List变量仅包含字符串的方法:

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

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

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

  • 0

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

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

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

相关问题