为什么我们不能在泛型类中创建一个“Concrete”类数组?

时间:2013-07-20 05:14:18

标签: java arrays class generics

public class GenericClass<T> {

    class MyClass {
    }

    public GenericClass(final T[] param) {
        MyClass myObject = new MyClass();                       // OK
        MyClass[] myArray = { new MyClass(), new MyClass() };   // Cannot create a generic array of GenericClass<T>.MyClass
    }
}

这不是创建通用数组。编译器在理解/确定MyClass时应该没有问题,不是吗?

6 个答案:

答案 0 :(得分:2)

内部类“知道”封闭类的哪个实例创建它们,并且可以访问此实例的字段/成员。就好像它们有第二个this变量,其类型是封闭类的具体类型(例如GenericClass<String>)。

要克服这种困境,您可以MyClass static。这将使它完全与封闭类的任何实例分离(即:它不会有第二个this),因此它们可以自由地实例化:

public class GenericClass<T> {

  static class MyClass {
  }

  public GenericClass(final T[] param) {
    MyClass myObject = new MyClass();                       // OK
    MyClass[] myArray = { new MyClass(), new MyClass() };   
  }
}

答案 1 :(得分:1)

这里有一些additional information。从链接...

  

Java数组携带标识其类型的运行时类型信息   包含的元素

编译器的代码如下所示:

MyClass[] myArray = {new GenericClass<T>.MyClass(), ..} //T is unknown

答案 2 :(得分:1)

{ new MyClass(), new MyClass() }; //new MyClass() => new GenericClass<T>.MyClass()

上面的代码将被视为对象数组,因为T是未知的,由于实现了泛型的方式(通过擦除),数组的类型没有明确定义。一方面,它应该是一个MyClass数组,另一方面,它应该是一个Object数组

创建对象类型数组并将其强制转换为类型

Object[] arr=new Object[]{this.new MyClass(), this.new MyClass()};
MyClass[]  myArray = Arrays.copyOf(arr,arr.length, Item.MyClass[].class);   

如果你让它静止它将起作用,因为 - 静态嵌套类或嵌套接口(顺便说一下,它总是静态的)除了命名空间嵌套和访问私有变量之外与其外部类(或接口)无关。 作为标准API中的一个示例,查找嵌套在Map接口内的接口Map.Entry,但是无法访问其类型参数,需要再次声明它们。

答案 3 :(得分:1)

涵盖此内容的JLS部分为10.6。具体来说,这是因为:

  

如果ClassOrInterfaceType不表示可重新类型(第4.7节),则为编译时错误。否则,ClassOrInterfaceType可以命名任何命名引用类型,甚至是抽象类类型(第8.1.1.1节)或接口类型(第9节)。

     

上述规则意味着数组创建表达式中的元素类型不能是参数化类型,而不是无界通配符。

因为MyClass是非静态的,所以它依赖于外层类;它实际上是GenericClass<T>.MyClass,因此是参数化类型。声明它static会删除该依赖项并解决问题。

如果你这样做会变得奇怪;

class MyClass<T> {
}

public GenericClass(final T[] param) {
    MyClass[] myArray = { new MyClass(), new MyClass() };  
}

这是合法的。笨拙,有点笨拙但合法。因为你重新声明了类型,它隐藏了外部类型。然后...数组和泛型不混合......除非你使用原始类型。为了向后兼容,您可以拥有一个最终保持MyClass<Object>的rawtype数组。这是一个非常糟糕的事情,但它确实编译。你可以在这里摆脱创意铸造,但最终......只是......不要。

答案 4 :(得分:0)

这里的问题是编译器无法在编译时确定数组myArray的信息。它被认为是通用的,因为(正如eclipse所示)它在{new GenericClass&lt; T&gt; .MyClass(),...}中转换。这是因为您将MyClass类放在泛型类中。

此代码无效:

package my.stuff;

public class GenericClass<T> {

    class MyClass {
        static MyClass[] myArray = { new MyClass(), new MyClass() };;
    }

    public GenericClass(final T[] param) {
        MyClass myObject = new MyClass();
    }
}

但是此代码有效:

package my.stuff;

public class GenericClass<T> {
    public GenericClass(final T[] param) {
        MyClass myObject = new MyClass();
        MyClass[] myArray = { new MyClass(), new MyClass() };
    }
}

class MyClass {
}

因为您在MyClass中没有使用泛型,所以最好的办法可能就是第二个。

如果您将其声明为静态,则编译器知道MyClass不是通用的,它知道该怎么做。

此外,在java中创建泛型数组的唯一方法是创建一个原始类型,然后将其强制转换为泛型(参见此处:"Cannot create generic array of .." - how to create an Array of Map<String, Object>?)。因此,如果你绝对需要在通用的myClass中使用myClass,你应该在MyClass&lt; T&gt;中使用它,然后使用技巧:创建一个原始类型并将其转换为MyClass&lt; T&gt;:

package my.stuff;

public class GenericClass<T> {

    class MyClass<T> {
    }

    @SuppressWarnings("unchecked")
    public GenericClass(final T[] param) {
        MyClass<T> myObject = new MyClass<T>();
        MyClass<T>[] myArray = new MyClass[]{ new MyClass<T>(), new MyClass<T>() };
    }
}
即使你没有在MyClass类中使用T。

答案 5 :(得分:0)

@ItayMaman有正确的理由。基本上,MyClass 不是可再生类型。

MyClass是一个非静态的内部类。由于它是非静态的,因此它在其封闭类的类型参数范围内。每当您在MyClass的实例方法中单独编写GenericClass时,它实际上是GenericClass<T>.MyClass的缩写。因此,即使它可能看起来不是, MyClass(单独)实际上是参数化类型(由T参数化),类似于List<String> 。因此,当您执行new MyClass[2]时,您正尝试创建一个参数化类型的数组,就像new List<String>[2]一样。我想你已经知道这是不允许的。

你应该怎么做?这一切都取决于你的意图。人们建议的一件事是使MyClass静止。当然,这将超出T的范围。但这可能是也可能不是你想要的,因为它完全改变了它与GenericClass的关系。非静态内部类可以访问封闭类的实例,这也许就是为什么你首先这样做的原因。如果你从来没有打算让它变成非静态的(并且错误地做了),那么这显然是要走的路。

如果你想要一个非静态的内部类,并且你只是想创建一个这种类型的数组,那么让我们考虑你通常如何处理参数化类型的数组,例如: List<String>[]

  • 一种解决方案是改为创建原始类型的数组,例如: List[] foo = new List[2];。对我们的案例执行此操作的等效方法是GenericClass.MyClass[] foo = new GenericClass.MyClass[2];。注意我们在这里做了什么。为了编写原始类型,我们必须使用 unparameterized 外部类名明确限定MyClass。如果我们没有明确限定它,那么它将被GenericClass<T>隐含地限定,如上所述,这不是我们想要的。将其翻译为示例中的代码,您可以编写GenericClass.MyClass[] myArray = { new MyClass(), new MyClass() };

  • 同样,如果我们想要避免原始类型,我们可以创建一个通配类型的数组,例如: List<?>[] foo = new List<?>[2];。对我们的案例执行此操作的等效方法是GenericClass<?>.MyClass[] foo = new GenericClass<?>.MyClass[2];。因此,将其翻译为示例中的代码,您可以编写GenericClass<?>.MyClass[] myArray = { new MyClass(), new MyClass() };

  • 最后,我们可能想要创建一个通配类型的数组,但后来转换回参数化类型的数组,以方便以后使用。例如List<String>[] foo = (List<String>[])new List<?>[2];。对我们的案例执行此操作的等效方法是MyClass[] myArray = (MyClass[])new GenericClass<?>.MyClass[] { new MyClass(), new MyClass() };。请注意,演员表是未选中的演员。这样做的好处是,当您从myArray中获取信息时,它将是MyClass类型,而不是上述方法中的原始类型GenericClass.MyClass或通配类型GenericClass<?>.MyClass