由于 Java 泛型的实现,你不能有这样的代码:
public class GenSet<E> {
private E a[];
public GenSet() {
a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
}
}
如何在保持类型安全的同时实现这一点?
我在 Java 论坛上看到了这样的解决方案:
import java.lang.reflect.Array;
class Stack<T> {
public Stack(Class<T> clazz, int capacity) {
array = (T[])Array.newInstance(clazz, capacity);
}
private final T[] array;
}
但我真的不明白发生了什么。
作为回报,我必须问一个问题:您的 GenSet
是“选中”还是“未选中”?这意味着什么?
检查:强类型。 GenSet 明确知道它包含什么类型的对象(即,它的构造函数是用 Class
未选中:弱类型。实际上没有对作为参数传递的任何对象进行类型检查。 -> 在这种情况下,你应该写 public class GenSet
所有这些都源于 Java 中泛型的一个已知且有意的弱点:它是使用擦除实现的,因此“泛型”类不知道它们在运行时使用什么类型参数创建,因此无法提供类型-除非实现了某种明确的机制(类型检查),否则安全。
你可以这样做:
E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];
这是在Effective Java 中实现泛型集合的建议方法之一;第 26 项。没有类型错误,无需重复转换数组。 但是这会触发警告,因为它具有潜在的危险性,应谨慎使用。如评论中所述,此 Object[]
现在伪装成我们的 E[]
类型,如果使用不安全,可能会导致意外错误或 ClassCastException
。
根据经验,只要强制转换数组在内部使用(例如,支持数据结构),并且不返回或暴露给客户端代码,这种行为是安全的。如果您需要将泛型类型的数组返回给其他代码,那么您提到的反射 Array
类是正确的方法。
值得一提的是,如果您使用泛型,只要有可能,使用 List
而不是数组会更愉快。当然,有时您别无选择,但使用集合框架要健壮得多。
test()
方法中的 String[] s=b;
,这将不起作用。那是因为 E 的数组不是真的,它是 Object[]。如果您需要,这很重要,例如 List<String>[]
- 您不能为此使用 Object[]
,您必须专门使用 List[]
。这就是为什么你需要使用反射类<?>数组创建。
internalArray
键入为 E[]
时,如果您想要执行 public E[] toArray() { return (E[])internalArray.clone(); }
,因此实际上是 Object[]
。这在运行时会因类型转换异常而失败,因为无法将 Object[]
分配给 E
恰好是任何类型的数组。
E[] b = (E[])new Object[1];
中,您可以清楚地看到创建的数组的唯一引用是 b
,而 b
的类型是 E[]
。因此,您不会有通过不同类型的不同变量意外访问同一个数组的危险。相反,如果您有 Object[] a = new Object[1]; E[]b = (E[])a;
,那么您将需要对如何使用 a
保持偏执。
以下是如何使用泛型来获取您正在寻找的类型的数组,同时保持类型安全(与其他答案相反,这将给您返回一个 Object
数组或在编译时导致警告):
import java.lang.reflect.Array;
public class GenSet<E> {
private E[] a;
public GenSet(Class<E[]> clazz, int length) {
a = clazz.cast(Array.newInstance(clazz.getComponentType(), length));
}
public static void main(String[] args) {
GenSet<String> foo = new GenSet<String>(String[].class, 1);
String[] bar = foo.a;
foo.a[0] = "xyzzy";
String baz = foo.a[0];
}
}
编译时没有警告,正如您在 main
中看到的,对于您声明 GenSet
实例的任何类型,您都可以将 a
分配给该类型的数组,并且您可以从 {3 } 到该类型的变量,这意味着数组和数组中的值是正确的类型。
它通过使用类文字作为运行时类型标记来工作,如 Java Tutorials 中所述。编译器将类文字视为 java.lang.Class
的实例。要使用一个,只需在类名后面加上 .class
。因此,String.class
充当表示类 String
的 Class
对象。这也适用于接口、枚举、任意维数组(例如 String[].class
)、基元(例如 int.class
)和关键字 void
(即 void.class
)。
Class
本身是通用的(声明为 Class<T>
,其中 T
代表 Class
对象所代表的类型),这意味着 String.class
的类型是 Class<String>
。
因此,每当您调用 GenSet
的构造函数时,您都会为第一个参数传入一个类文字,该参数表示 GenSet
实例的声明类型的数组(例如 String[].class
代表 GenSet<String>
)。请注意,您将无法获得基元数组,因为基元不能用于类型变量。
在构造函数内部,调用方法 cast
会将传递的 Object
参数转换为调用该方法的 Class
对象表示的类。在 java.lang.reflect.Array
中调用静态方法 newInstance
以 Object
的形式返回一个数组,该数组的类型由作为第一个参数传递的 Class
对象表示,长度由作为第二个参数传递的 int
指定。调用方法 getComponentType
会返回一个 Class
对象,该对象表示由调用该方法的 Class
对象表示的数组的组件类型(例如,String.class
代表 String[].class
,null
如果 Class
对象不代表数组)。
最后一句话并不完全准确。调用 String[].class.getComponentType()
返回一个表示类 String
的 Class
对象,但它的类型是 Class<?>
,而不是 Class<String>
,这就是您不能执行以下操作的原因。
String foo = String[].class.getComponentType().cast("bar"); // won't compile
Class
中返回 Class
对象的每个方法也是如此。
关于 Joachim Sauer 对 this answer 的评论(我自己没有足够的声誉来评论它),使用强制转换为 T[]
的示例将导致警告,因为在这种情况下编译器无法保证类型安全。
编辑关于 Ingo 的评论:
public static <T> T[] newArray(Class<T[]> type, int size) {
return type.cast(Array.newInstance(type.getComponentType(), size));
}
这是唯一类型安全的答案
E[] a;
a = newArray(size);
@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
return Arrays.copyOf(array, length);
}
Arrays#copyOf()
的第二个“长度”参数与作为第一个参数提供的数组的长度无关。这很聪明,尽管它确实支付了调用 Math#min()
和 System#arrayCopy()
的费用,这两者都不是完成这项工作所必需的。 docs.oracle.com/javase/7/docs/api/java/util/…
E
是类型变量,这不起作用。当 E
是类型变量时,可变参数创建一个擦除 E
的数组,使其与 (E[])new Object[n]
没有太大区别。请参阅http://ideone.com/T8xF91。它绝不比任何其他答案都更安全。
new E[]
一样好。您在示例中显示的问题是一般擦除问题,并非此问题和此答案所独有。
要扩展到更多维度,只需将 []
和维度参数添加到 newInstance()
(T
是类型参数,cls
是 Class<T>
,d1
到 d5
是整数):
T[] array = (T[])Array.newInstance(cls, d1);
T[][] array = (T[][])Array.newInstance(cls, d1, d2);
T[][][] array = (T[][][])Array.newInstance(cls, d1, d2, d3);
T[][][][] array = (T[][][][])Array.newInstance(cls, d1, d2, d3, d4);
T[][][][][] array = (T[][][][][])Array.newInstance(cls, d1, d2, d3, d4, d5);
有关详细信息,请参阅 Array.newInstance()
。
在 Java 8 中,我们可以使用 lambda 或方法引用来创建一种通用数组。这类似于反射方法(通过 Class
),但这里我们不使用反射。
@FunctionalInterface
interface ArraySupplier<E> {
E[] get(int length);
}
class GenericSet<E> {
private final ArraySupplier<E> supplier;
private E[] array;
GenericSet(ArraySupplier<E> supplier) {
this.supplier = supplier;
this.array = supplier.get(10);
}
public static void main(String[] args) {
GenericSet<String> ofString =
new GenericSet<>(String[]::new);
GenericSet<Double> ofDouble =
new GenericSet<>(Double[]::new);
}
}
例如,这由 <A> A[] Stream.toArray(IntFunction<A[]>)
使用。
这也可以在 Java 8 之前使用匿名类完成,但更麻烦。
ArraySupplier
这样的特殊接口,您可以将构造函数声明为 GenSet(Supplier<E[]> supplier) { ...
并使用与您相同的行调用它。
IntFunction<E[]>
,但是是的,这是真的。
这在 Effective Java, 2nd Edition 的第 5 章(泛型)第 25 项中进行了介绍...首选列表而不是数组
您的代码将起作用,尽管它会生成一个未经检查的警告(您可以使用以下注释来抑制它:
@SuppressWarnings({"unchecked"})
但是,使用列表而不是数组可能会更好。
the OpenJDK project site 上有关于此错误/功能的有趣讨论。
您不需要将 Class 参数传递给构造函数。尝试这个。
public class GenSet<T> {
private final T[] array;
@SafeVarargs
public GenSet(int capacity, T... dummy) {
if (dummy.length > 0)
throw new IllegalArgumentException(
"Do not provide values for dummy argument.");
this.array = Arrays.copyOf(dummy, capacity);
}
@Override
public String toString() {
return "GenSet of " + array.getClass().getComponentType().getName()
+ "[" + array.length + "]";
}
}
和
GenSet<Integer> intSet = new GenSet<>(3);
System.out.println(intSet);
System.out.println(new GenSet<String>(2));
结果:
GenSet of java.lang.Integer[3]
GenSet of java.lang.String[2]
Java 泛型通过在编译时检查类型并插入适当的强制转换来工作,但会删除编译文件中的类型。这使得不理解泛型的代码可以使用泛型库(这是一个深思熟虑的设计决定),但这意味着您通常无法在运行时找出类型是什么。
公共 Stack(Class<T> clazz,int capacity)
构造函数要求您在运行时传递一个 Class 对象,这意味着类信息 在运行时可用于需要它的代码。 Class<T>
形式意味着编译器将检查您传递的 Class 对象是否正是 T 类型的 Class 对象。不是 T 的子类,也不是 T 的超类,而是 T。
这意味着您可以在构造函数中创建适当类型的数组对象,这意味着您存储在集合中的对象的类型将在将它们添加到集合时检查它们的类型。
尽管线程已死,但我想提请您注意这一点。
泛型用于编译期间的类型检查。因此,目的是检查
进来的就是你需要的。
你返回的就是消费者需要的。
检查这个:
https://i.stack.imgur.com/fCXXe.jpg
编写泛型类时不要担心类型转换警告;使用时担心。
这个解决方案怎么样?
@SafeVarargs
public static <T> T[] toGenericArray(T ... elems) {
return elems;
}
它有效,看起来太简单了,不可能是真的。有什么缺点吗?
T[]
的新实例,则无法以编程方式构建 T[] elems
以传递给函数。如果可以,您将不需要该功能。
该示例使用 Java 反射创建一个数组。通常不建议这样做,因为它不是类型安全的。相反,您应该做的只是使用内部列表,并完全避免使用数组。
另请查看此代码:
public static <T> T[] toArray(final List<T> obj) {
if (obj == null || obj.isEmpty()) {
return null;
}
final T t = obj.get(0);
final T[] res = (T[]) Array.newInstance(t.getClass(), obj.size());
for (int i = 0; i < obj.size(); i++) {
res[i] = obj.get(i);
}
return res;
}
它将任何类型的对象列表转换为相同类型的数组。
List
中有不止一种类型的对象,这也可能会失败,例如 toArray(Arrays.asList("abc", new Object()))
将抛出 ArrayStoreException
。
for
循环和其他循环,我使用了 Arrays.fill(res, obj);
,因为我希望每个索引都具有相同的值。
我找到了一种对我有用的快速简便的方法。请注意,我只在 Java JDK 8 上使用过它。我不知道它是否适用于以前的版本。
虽然我们不能实例化特定类型参数的泛型数组,但我们可以将已经创建的数组传递给泛型类构造函数。
class GenArray <T> {
private T theArray[]; // reference array
// ...
GenArray(T[] arr) {
theArray = arr;
}
// Do whatever with the array...
}
现在主要我们可以像这样创建数组:
class GenArrayDemo {
public static void main(String[] args) {
int size = 10; // array size
// Here we can instantiate the array of the type we want, say Character (no primitive types allowed in generics)
Character[] ar = new Character[size];
GenArray<Character> = new Character<>(ar); // create the generic Array
// ...
}
}
为了让您的数组更加灵活,您可以使用链表,例如。 Java.util.ArrayList 类中的 ArrayList 和其他方法。
传递值列表...
public <T> T[] array(T... values) {
return values;
}
我制作了这个代码片段来反射地实例化一个为简单的自动化测试实用程序传递的类。
Object attributeValue = null;
try {
if(clazz.isArray()){
Class<?> arrayType = clazz.getComponentType();
attributeValue = Array.newInstance(arrayType, 0);
}
else if(!clazz.isInterface()){
attributeValue = BeanUtils.instantiateClass(clazz);
}
} catch (Exception e) {
logger.debug("Cannot instanciate \"{}\"", new Object[]{clazz});
}
请注意此部分:
if(clazz.isArray()){
Class<?> arrayType = clazz.getComponentType();
attributeValue = Array.newInstance(arrayType, 0);
}
用于数组初始化 where Array.newInstance(数组的类,数组的大小)。类既可以是原始(int.class)也可以是对象(Integer.class)。
BeanUtils 是 Spring 的一部分。
其他人建议的强制施法对我不起作用,抛出非法施法的异常。
但是,这种隐式转换效果很好:
Item<K>[] array = new Item[SIZE];
其中 Item 是我定义的包含成员的类:
private K value;
这样,您将获得 K 类型的数组(如果该项只有值)或您希望在 Item 类中定义的任何泛型类型。
实际上,一种更简单的方法是创建一个对象数组并将其转换为您想要的类型,如下例所示:
T[] array = (T[])new Object[SIZE];
其中 SIZE
是常量,T
是类型标识符
没有其他人回答您发布的示例中发生了什么的问题。
import java.lang.reflect.Array;
class Stack<T> {
public Stack(Class<T> clazz, int capacity) {
array = (T[])Array.newInstance(clazz, capacity);
}
private final T[] array;
}
正如其他人所说,泛型在编译期间被“删除”。所以在运行时,一个泛型的实例不知道它的组件类型是什么。这样做的原因是历史原因,Sun 希望在不破坏现有接口(源代码和二进制文件)的情况下添加泛型。
另一方面,数组在运行时确实知道它们的组件类型。
这个例子通过让调用构造函数(它知道类型)的代码传递一个参数来告诉类所需的类型来解决这个问题。
所以应用程序会用类似的东西来构造这个类
Stack<foo> = new Stack<foo>(foo.class,50)
并且构造函数现在(在运行时)知道组件类型是什么,并且可以使用该信息通过反射 API 构造数组。
Array.newInstance(clazz, capacity);
最后我们有一个类型转换,因为编译器无法知道 Array#newInstance()
返回的数组是正确的类型(即使我们知道)。
这种风格有点难看,但它有时可能是创建泛型类型的最不坏的解决方案,无论出于何种原因(创建数组或创建其组件类型的实例等),它们都需要在运行时知道其组件类型。
我找到了一种解决这个问题的方法。
下面的行抛出通用数组创建错误
List<Person>[] personLists=new ArrayList<Person>()[10];
但是,如果我将 List<Person>
封装在一个单独的类中,它就可以工作。
import java.util.ArrayList;
import java.util.List;
public class PersonList {
List<Person> people;
public PersonList()
{
people=new ArrayList<Person>();
}
}
您可以通过 getter 公开 PersonList 类中的人员。下面的行将为您提供一个数组,每个元素中都有一个 List<Person>
。换句话说,List<Person>
的数组。
PersonList[] personLists=new PersonList[10];
在我正在处理的一些代码中,我需要这样的东西,这就是我为让它工作所做的。到目前为止没有问题。
java中不允许创建通用数组,但您可以这样做
class Stack<T> {
private final T[] array;
public Stack(int capacity) {
array = (T[]) new Object[capacity];
}
}
根据 vnportnoy 的语法
GenSet<Integer> intSet[] = new GenSet[3];
创建一个空引用数组,填充为
for (int i = 0; i < 3; i++)
{
intSet[i] = new GenSet<Integer>();
}
这是类型安全的。
您可以创建一个 Object 数组并将其转换为 E 无处不在。是的,这不是很干净的方法,但它至少应该有效。
尝试这个。
private int m = 0;
private int n = 0;
private Element<T>[][] elements = null;
public MatrixData(int m, int n)
{
this.m = m;
this.n = n;
this.elements = new Element[m][n];
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
this.elements[i][j] = new Element<T>();
}
}
}
Element
类来自哪里?
一个简单但混乱的解决方法是在主类中嵌套第二个“持有者”类,并使用它来保存数据。
public class Whatever<Thing>{
private class Holder<OtherThing>{
OtherThing thing;
}
public Holder<Thing>[] arrayOfHolders = new Holder<Thing>[10]
}
new Holder<Thing>[10]
是一个通用数组创建。
也许与这个问题无关,但是当我得到“generic array creation
”错误使用
Tuple<Long,String>[] tupleArray = new Tuple<Long,String>[10];
我通过 @SuppressWarnings({"unchecked"})
发现了以下作品(并为我工作):
Tuple<Long, String>[] tupleArray = new Tuple[10];
我想知道这段代码是否会创建一个有效的泛型数组?
public T [] createArray(int desiredSize){
ArrayList<T> builder = new ArrayList<T>();
for(int x=0;x<desiredSize;x++){
builder.add(null);
}
return builder.toArray(zeroArray());
}
//zeroArray should, in theory, create a zero-sized array of T
//when it is not given any parameters.
private T [] zeroArray(T... i){
return i;
}
编辑:也许创建这样一个数组的另一种方法是,如果您需要的大小已知且很小,那么只需将所需数量的“null”输入 zeroArray 命令?
虽然显然这不像使用 createArray 代码那样通用。
您可以使用演员表:
public class GenSet<Item> {
private Item[] a;
public GenSet(int s) {
a = (Item[]) new Object[s];
}
}
a
暴露在课堂外!
我实际上找到了一个非常独特的解决方案来绕过无法启动通用数组。您需要做的是创建一个接收泛型变量 T 的类,如下所示:
class GenericInvoker <T> {
T variable;
public GenericInvoker(T variable){
this.variable = variable;
}
}
然后在你的数组类中让它像这样开始:
GenericInvoker<T>[] array;
public MyArray(){
array = new GenericInvoker[];
}
启动 new Generic Invoker[]
会导致未选中的问题,但实际上不应该有任何问题。
要从数组中获取,您应该像这样调用 array[i].variable:
public T get(int index){
return array[index].variable;
}
其余的,例如调整数组的大小可以使用 Arrays.copyOf() 来完成,如下所示:
public void resize(int newSize){
array = Arrays.copyOf(array, newSize);
}
add 函数可以像这样添加:
public boolean add(T element){
// the variable size below is equal to how many times the add function has been called
// and is used to keep track of where to put the next variable in the array
arrays[size] = new GenericInvoker(element);
size++;
}
T
类型的数组,而不是某个参数化类型的数组。
如果您真的想包装一个固定大小的通用数组,您将有一个方法来向该数组添加数据,因此您可以在那里正确初始化数组,执行如下操作:
import java.lang.reflect.Array;
class Stack<T> {
private T[] array = null;
private final int capacity = 10; // fixed or pass it in the constructor
private int pos = 0;
public void push(T value) {
if (value == null)
throw new IllegalArgumentException("Stack does not accept nulls");
if (array == null)
array = (T[]) Array.newInstance(value.getClass(), capacity);
// put logic: e.g.
if(pos == capacity)
throw new IllegalStateException("push on full stack");
array[pos++] = value;
}
public T pop() throws IllegalStateException {
if (pos == 0)
throw new IllegalStateException("pop on empty stack");
return array[--pos];
}
}
在这种情况下,您使用 java.lang.reflect.Array.newInstance 创建数组,它不会是 Object[],而是真正的 T[]。您不必担心它不是最终的,因为它是在您的班级内部管理的。请注意,您需要 push() 上的非 null 对象才能获取要使用的类型,因此我添加了对您推送的数据的检查并在那里引发异常。
这仍然有些毫无意义:您通过 push 存储数据,它是保证只有 T 元素会进入的方法的签名。因此,数组是 Object[] 还是 T[] 或多或少无关紧要。
不定期副业成功案例分享
Object[] EMPTY_ELEMENTDATA = {}
用于存储的 ArrayList 的实现。我可以在不知道使用泛型的类型的情况下使用这种机制来调整大小吗?public void <T> T[] newArray(Class<T> type, int length) { ... }