【问题标题】:Generic Array Creation通用数组创建
【发布时间】:2012-04-19 09:07:20
【问题描述】:

这是我正在使用的代码

public class aClass<T> {

    private T[] elements;

    public aClass(T[] elements) {
        this.elements = elements;
    }

    public void doSomething() {
        T[] newArray = (T[]) new Object[5];
        ...
    }

}

我看到有人说创建这样的数组是个坏主意,因为它不是类型安全的。但是,每次我使用它时,我都没有遇到任何问题。什么时候创建这样的数组会导致问题?

谢谢

【问题讨论】:

标签: java arrays generics


【解决方案1】:

您可能在比较和打印方面遇到问题.. 基本上任何时候知道类型对于格式化很重要。你拥有它的方式系统不知道数组中的数据是什么

【讨论】:

  • -1 这是不正确的。借助泛型,要实例化此类,您需要提供具体类型:AClass&lt;Employee&gt; employees = new AClass&lt;Employee&gt;(new Employee[5]);,因此特定方法(如比较)将作用于特定类型实现(例如 employee.equals(...))。
  • 我不同意,他可能仍然会遇到这些问题。也许这并不完全正确?
【解决方案2】:

不是类型安全的东西本身不会产生问题。但它可以在编译时隐藏问题,直到适当的时候才会弹出。

您可以在非类型安全的环境中完全工作而不会出现问题,但这是一个坏习惯,因为类型安全的环境确实可以保证您不会遇到一组常见的运行时错误,但您不会'目前没有,但您没有任何保证,这些非常重要,您可以信任的任何安全都意味着更少的努力。

【讨论】:

    【解决方案3】:

    其他人都错了。除非您在创建时拥有该类型的实例,否则没有其他方法可以创建数组。另见How to create a generic array in Java?

    如果您有类型实例(= 类型为 Class&lt;T&gt; 的东西),您可以调用 java.lang.reflect.Array.newInstance(Class&lt;?&gt;, int),但即便如此,您仍需要强制转换,何必费心呢?

    如果elements 的类型是Object 而不是T,我会说情况会有所不同,但由于情况并非如此,因此代码完全没问题。

    如果你想进一步隐藏这个,你可以写一个辅助方法:

      @SuppressWarnings( "unchecked" )
      public static <T> T[] createArray( int size ) {
          return (T[]) new Object[size];
      }
    

    这会创建一个数组,而无需在调用时进行强制转换。

    【讨论】:

    • 当我写以下内容时,怎么会得到 ClassCastException 呢? public static class A { } public static &lt;T&gt; T[] createArray(int size) { return (T[]) new Object[size]; } public static void main(String[] args) { A[] a = createArray(3); }
    • @Aaron 我看不出这怎么可能,你的代码编译得很好,但不会运行,它会在运行时导致ClassCastException,因为Object[] 的数组不是T[]。这是不可能的。
    【解决方案4】:

    在这种情况下使用Collection 会更安全。做类似的东西

    ...
    Collection<T> newElements = new ArrayList<T>(5);
    ...
    

    无论如何,据我所知,创建这样的通用数组不会给您带来真正的问题,但“闻起来很糟糕”,因为它需要显式类型转换。在这种情况下你真的需要一个数组吗?

    【讨论】:

      【解决方案5】:

      您不能创建 T 数组,因为 Java 在运行时不知道 T 的类型是什么。这是因为在 Java 中泛型是通过类型擦除实现的。这意味着编译器在确保一切正常后会丢弃大部分泛型类型信息。

      对于数组,情况就不同了,因为 Java 需要知道 T 的确切类型才能创建给定的数组,而且由于无法确定此类事情,因此您无法创建泛型类型的数组。

      您可以做的是提供您要使用的实际数组的实例,Java 编译器可以确保它是适当的类型:

      public static <T> void fillWith(T[] destiny, List<? extends T> source){
          for(int i=0; i<= destiny.length; i++){
              destiny[i] = source.get(i);
          }
      }
      

      java.utils.Arrays.copy 方法提供了一种谨慎使用泛型和反射的替代方法,您可以将其用作您想做的事情的参考。

      public static <T> T[] copyOf(T[] original, int newLength) {
         return (T[]) copyOf(original, newLength, original.getClass());
      }
      
      
      public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
              T[] copy = ((Object)newType == (Object)Object[].class)
                  ? (T[]) new Object[newLength]
                  : (T[]) Array.newInstance(newType.getComponentType(), newLength);
              System.arraycopy(original, 0, copy, 0,
                               Math.min(original.length, newLength));
              return copy;
      }
      

      【讨论】:

        【解决方案6】:

        这是一个导致问题的示例:

        public class Main {
        
            public static class List<E extends Number> {
        
                private E[] myE;
        
                public void hello(E e) {
                    E[] newArray = (E[]) new Object[5];
                    for (int i = 0; i < newArray.length; i++) {
                        newArray[i] = e;
                    }
                    myE = newArray;
                }
        
            }
        
            public static <T> T[] createArray(int size) {
                return (T[]) new Object[size];
            }
        
            public static void main(String[] args) throws IOException {
                List<Integer> integers = new List<Integer>();
                integers.hello(5);
            }
        }
        

        您的代码有效,因为当您声明泛型参数&lt;T&gt; 时,它是未绑定的,这意味着它扩展了Object。当您将数组转换为(T[])时,您实际上是在转换为(Object[]),因为这是编译器可以做的最好的事情。现在,如果您将数组保存在代码中,您应该不会遇到太多问题。但是,如果您的代码之外的某个人可以检索该数组并使用对象以外的类型实例化您的类,他将有 ClassCastException。

        【讨论】:

          【解决方案7】:

          我看到有人说创建这样的数组很糟糕 想法,因为它不是类型安全的。但是,每次我使用它时,我 没有问题。什么时候会创建这样的数组 有问题吗?

          人们说这是一个坏主意,因为从逻辑上讲,如果 T 不是 Object,则将运行时类型为 Object[] 的对象强制转换为类型 T[] 是不正确的。

          但是,您看不到任何直接的问题,因为在类内部(在T 范围内),T 已被删除。所以不会检查从Object[]T[] 的演员表。

          如果你所做的只是在你的类中使用这个变量(在类型变量T 的范围内),你永远不会将它返回给类外的人,而且类外的人也无法访问它,那么它不会造成任何问题。

          事实上,你这样做的方式是有好处的——在获取和设置数组的元素时,你可以获得泛型类型检查的所有好处,如果你只是遵循完全的“类型-safe" 使用类型为Object[] 的变量的解决方案,在这种情况下,您将不得不手动转换您从中得到的东西。

          如果您将该数组变量作为T[] 类型返回到外部(或以其他方式允许外部访问它),您将遇到问题,因为调用代码将期望某种类型的数组,而泛型将当它收到这个数组时在调用代码中插入一个强制转换,并且强制转换将在运行时失败(因为例如Object[] 不是String[];它们在运行时是不同的类型)。这是一个简单的例子:

          public class aClass<T> {
          
              private T[] elements;
          
              public aClass(T[] elements) {
                  this.elements = elements;
              }
          
              public void doSomething() {
                  elements = (T[]) new Object[5];
                  ...
              }
          
              public T[] getArray() {
                  return elements;
              }
          
          }
          
          // some other code...
          aClass<String> foo = new aClass<String>(new String[2]);
          foo.doSomething();
          String[] bar = foo.getArray(); // ClassCastException here
          

          【讨论】:

          • 在您的示例中,与添加使用 Object[] 相比,演员 (T[]) 允许您做更多的事情?
          • 我的意思是分配给elements,对不起
          • 重点是变量elements的类型是T[]而不是Object[],这样你就可以获取T类型的元素并将其放入其中,并在编译时进行泛型类型检查时间
          • 对不起,我不想看起来像我想辩论,但我不明白你可以通过转换到 (T[]) 来达到哪些方法,或者如果你能将转换为T 的上限。充其量你知道 T 的上限并且你实例化并转换为该类型(如果T extends Number 你可以使用new Number[5] 而不进行转换并访问Number 的所有方法。我真的想看看我是否错过了东西。
          • 您是对的,您将无法访问更多方法。我所说的区别是泛型类型检查。例如,当您从数组中取出内容时,它将是类型 T 而不是上限的类型(您必须将其转换为 T 才能存储在 T 类型的变量中或将其作为T) 类型返回;或者当你把东西放进去时,它会检查它们是否输入T,而不是允许一切。所以只是方便一点
          猜你喜欢
          • 1970-01-01
          • 2011-10-31
          • 2011-04-23
          • 1970-01-01
          相关资源
          最近更新 更多