从一个通用类返回一个数组。如何在主方法中打印数组数据?

3

我不太明白应该如何在主方法中使用returnItems()返回的数组。泛型让我感到有些害怕,我开始怀疑自己能否继续成为一名Java开发者。看一些专业人士编写的代码时,他们似乎很喜欢使用泛型,但我觉得这非常令人生畏。如果能给我一个易于理解的泛型参考资料,我会非常感激。

public class Generic<T> {
    private int pos;
    private final int size;
    private T[] arrayOfItems;
    public Generic(int size)
    {
        this.size = size;
        pos = 0;
        arrayOfItems = (T[]) new Object[size];
    }
    public void addItem(T item)
    {
        arrayOfItems[pos] = item;
        pos++;
    }
    public void displayItems()
    {
        for(int i = 0;i<pos;i++){
        System.out.println(arrayOfItems[i]);
        }
    }
    public T[] returnItems()
    { 
        return arrayOfItems;
    }
}


public class GenericTesting {
    public static void main(String[] args) {
        Generic<String> animals = new Generic<String>(5);
        Generic<Integer> numbers = new Generic<Integer>(5);
        animals.addItem("Dog");
        animals.addItem("Cat");
        animals.addItem("Bird");
        animals.addItem("Mouse");
        animals.addItem("Elephant");
        animals.displayItems();

        numbers.addItem(1);
        numbers.addItem(2);
        numbers.addItem(3);
        numbers.displayItems();

        for(int i=0; i < animals.returnItems().length;i++)
        {
            System.out.println(animals.returnItems[i]);
        }
    }

}

2
泛型和数组不太搭配。使用List<T>代替T[]。 - JB Nizet
3
@JBNizet 你的意思是它们“不搭配”吗? - chancea
实际上,mbomb007,JB Nizet是正确的,T[]被类型擦除为Object[]。http://docs.oracle.com/javase/tutorial/java/generics/genMethods.html - KyleM
1
@mbomb007 不,那是无效的。看一下 ArrayList<T> 的源代码:它不使用 T[],而是使用 Object[]。 - JB Nizet
是的,List<T> 更好。 - mbomb007
显示剩余7条评论
5个回答

3

returnItems()是一个返回数组的方法,而不是一个数组本身,因此您不能像在这里尝试的那样引用它的索引:

        System.out.println(animals.returnItems[i]);

你需要做的是像这样引用从方法返回的数组中的索引:
        System.out.println(animals.returnItems()[i]);

编辑

您在存储和返回数据方面也存在问题。在构造函数中,您创建了arrayOfItems作为Object数组:

        arrayOfItems = (T[]) new Object[size];

...但是你试图将其作为returnItems()中的T数组返回:

public T[] returnItems()
{ 
    return arrayOfItems;
}

当您尝试运行代码时,您将收到一个异常:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;

这是由于一种叫做类型擦除的东西。基本上,Java编译器将用普通类(在这种情况下为Object)替换所有泛型类型,并插入强制转换以保持类型安全性。
因此,您的main()方法中的这行代码:
    for(int i=0; i < animals.returnItems().length;i++)

将会看到animals是一个Generic<String>,并转换为以下内容:

    for(int i=0; i < ((String[])animals.returnItems()).length;i++)

但是你返回的数组是作为一个新的Object[]创建的,而不是一个新的String[],除非对象实际上是子类型,否则无法向下转换,因此会产生异常。
为了消除ClassCastException,你可以像这样更改returnItems()来声明它的实际返回类型:
    public Object[] returnItems()
    { 
        return arrayOfItems;
    }

这将防止编译器尝试插入数组的非法转换,但是您需要手动将每个元素转换为适当的类型,这违背了使用泛型的初衷。
正如JBNizet在上面的评论中指出的那样,数组和泛型“不太搭配”,因此最好使用ArrayList<T>而不是T[]来存储数据。

它仍然不起作用。我之前已经尝试过了,它返回一个ClassCastException。 - user4461760
我尝试过这样做,但仍然不起作用: String[] animal = (String[]) animals.returnItems(); for(int i=0; i < animals.returnItems().length;i++) { System.out.println(animal[i]); } - user4461760
它不起作用是因为你返回了一个 Object[],而你使用元素作为 String[]。改用 ArrayList<> - Giulio Biagini
另一个选择可能是只声明 public Object[] returnItems() - chancea
你在“编辑”部分的解释是错误的。ClassCastException并不是因为你所说的“它实际上不是T[]”。ClassCastException也不会发生,因为他像你所暗示的那样“将其转换为T[]”。事实上,Java编译器会擦除T[]部分,并在字节码中用Object[]替换它。请参见下面的答案。 - KyleM
@KyleM 我并不是想暗示在创建数组时造成错误的原因是强制转换,而是由于returnItems()声明返回类型为T[]而不是Object[]导致调用代码中的强制转换引起的。我知道这有点模糊,我会尝试澄清一下。 - azurefrog

1
你写的代码无法正常工作,因为T[]被初始化为Object[]。当你在returnItems()函数中返回T[]时,实际上返回的是一个Object[],因此你不能将其视为String[]

请使用ArrayList<T>代替T[](实际上是Object[])。通过使用diamond interfacenew ArrayList<>()),它会自动推断出items变量的类型:

import java.util.ArrayList;


public class Generic<T>
{
    private final int size;
    private ArrayList<T> items;
    private int pos;

    public Generic(int size)
    {
        this.size = size;
        items = new ArrayList<>(size);
        pos = 0;
    }

    public void addItem(T item)
    {
        items.add(item);
        pos++;
    }

    public void displayItems() {
        for(int i = 0; i < pos; i++)
            System.out.println(items.get(i));
    }

    public ArrayList<T> returnItems() {
        return items;
    }
}

然后使用foreach
public class Main
{
    public static void main(String[] args) {
        Generic<String> animals = new Generic<String>(5);
        animals.addItem("Dog");
        animals.addItem("Cat");
        animals.addItem("Bird");
        animals.addItem("Mouse");
        animals.addItem("Elephant");
        animals.displayItems();

        Generic<Integer> numbers = new Generic<Integer>(5);
        numbers.addItem(1);
        numbers.addItem(2);
        numbers.addItem(3);
        numbers.displayItems();

        for(String animal : animals.returnItems())
            System.out.println(animal);

    }
}

这是用户编写的相同代码,只需将“T []”替换为“ArrayList <>”,就像注释中所说的那样。 - Giulio Biagini
1
没错。不需要全部粘贴在那里。 - mbomb007

1
import java.util.Arrays;

// ...

// in your main:
System.println(Arrays.toString(returnItems));

如果您定义类的toString()方法以进行打印(如果它不是原始类型),则此方法有效。


这并没有解决问题,也与泛型无关。 - chancea

1
你的代码无法运行,与泛型关系不大。考虑以下示例:
public static void main(String[] args) {
        Object[] objArr = new Object[]{"1", "2", "3"};
        String[] strArr = new String[]{"1", "2", "3"};

        //the line below doesn't work, it has nothing to do with Generics
        String[] castedStr = (String[])objArr;

        //These lines do work
        Object[] castedObj = (Object[]) strArr;
        String[] newCastStr = (String[]) castedObj;
    }

书籍《Core Java 2 - Fundamentals》指出:

"Java数组记住其条目的类型,也就是在创建它的new[]表达式中使用的元素类型。将Employee[]临时转换为Object[]并将其转换回来是合法的,但始于Object[]的数组永远不能转换为Employee[]。"

现在,请考虑Java编译器如何处理您的代码。它将您的"Generic<T>"类转换为以下内容:

public class Generic {
    private int pos;
    private final int size;
    private Object[] arrayOfItems;
    public Generic(int size)
    {
        this.size = size;
        pos = 0;
        arrayOfItems = (Object[]) new Object[size];
    }
    public void addItem(Object item)
    {
        arrayOfItems[pos] = item;
        pos++;
    }
    public void displayItems()
    {
        for(int i = 0;i<pos;i++){
        System.out.println(arrayOfItems[i]);
        }
    }
    public Object[] returnItems()
    { 
        return arrayOfItems;
    }
}

正如您所看到的,当您调用returnItems()方法时,它实际上返回一个Object[]。在您的代码中,您做了以下操作:
Generic<String> animals = new Generic<String>(5);
        Generic<Integer> numbers = new Generic<Integer>(5);
        animals.addItem("Dog");

        for(int i=0; i < animals.returnItems().length;i++)
        {

        }

你的代码实际上将最初的 Object[] 转换为 String[](我相信这是 Java 编译器在类型检查中插入的强制转换)。因此,Java 编译器将你上面的循环转换为以下内容:
for(int i=0; i < ((String[])animals.returnItems()).length;i++)
            {

            }

如果您将这些概念结合起来,就会发现您已经陷入了一种糟糕的情况。实际上,您可以通过在上面的循环中插入显式转换为Object [](我写String [])来解决问题。这样,您的问题就会消失。

实际上,如果你只是在循环中添加一个 Object[] 的转换,你仍然会得到 ClassCastException。至少我在 Java 7 中是这样的。 - azurefrog
@azurefrog,这很奇怪,我正在使用JDK1.7和编译器兼容级别1.7,对于for(int i=0; i < ((Object[])animals.returnItems()).length;i++)的代码没有任何问题。它可以编译并运行,可能你在其他地方得到了一种ClassCastException? - KyleM
哦,你是对的,不是循环,而是紧随其后的println也需要强制转换为对象(System.out.println(((Object[])animals.returnItems())[i]);)。我没有注意到异常中的行号增加了一行。 - azurefrog

0
无法将Object[]发送回String[]。相反,先将数组返回为Object[],然后依赖于对象继承来使用正确的toString()方法。
    Object[] s = animals.returnItems();
    for(int i=0; i < s.length;i++)
    {
        System.out.println(s[i]);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接