3

I can't seem to understand how I should be using the returned array from returnItems() in the main method. I am a bit intimidated by generic types. I am losing my confidence if I should continue on my dream becoming a java developer. When I read some code by professionals it seems that they are fond of using generics, but I find it very intimidating. I would appreciate it if I could get a good reference for generics that I can easily understand.

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

}
mbomb007
  • 3,077
  • 2
  • 30
  • 51

5 Answers5

3

returnItems() is a method which returns an array, not an array itself, so you can't try to reference an index on it like you are trying to do here:

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

What you need to do is reference the index on the array returned from the method like so:

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

Edit

You've also got a problem in the way that you store your data and return it. In your constructor you create arrayOfItems as an array of Object:

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

...but you try to return it as an array of T in returnItems():

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

When you try to run your code, you're going to get an exception:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;

This is due to something called type erasure. Basically the Java compiler is going to replace all of your generic types with ordinary classes (Object in this case), and inserts casts to preserve type safety.

So this line from your main() method:

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

is going to see that animals is a Generic<String>, and turn into this:

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

But the array that you are returning was created as a new Object[], not a new String[], and you can't downcast unless the object actually is of the child type, hence the exception.

To eliminate the ClassCastException, you could change returnItems() to declare its actual return type like this:

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

This would keep the compiler from trying to insert an illegal cast of the array, but then you'd need to cast each element to the appropriate type manually, which defeats the purpose of using generics in the first place.

As pointed out by JBNizet in the comments above, arrays and generics "don't go well together", so you'd be better off using an ArrayList<T> instead of a T[] to store your data.

azurefrog
  • 9,994
  • 7
  • 36
  • 51
  • It still doesn't work. I actually tried this already, it returns a ClassCastException. – user4461760 Jan 16 '15 at 16:04
  • I tried doing this, but still doesnt work: String[] animal = (String[]) animals.returnItems(); for(int i=0; i < animals.returnItems().length;i++) { System.out.println(animal[i]); } – user4461760 Jan 16 '15 at 16:11
  • It doesn't work because you return an `Object[]` and you use elements as `String[]`. Use `ArrayList<>` instead. – Giulio Biagini Jan 16 '15 at 16:16
  • Another option could be to just declare `public Object[] returnItems()` – chancea Jan 16 '15 at 16:26
  • Your explanation (under the "edit") section is false. The ClassCastException is not because "it's not really a T[]" as you stated. The ClassCastException does not occur because he "casts it to a T[]" as you imply. In fact, the Java compiler type erases the T[] portion, and replaces it with Object[] in the bytecode. See my answer below. – KyleM Jan 16 '15 at 18:20
  • @KyleM I wasn't trying to imply that the error was due to the cast during the creation of the array, but due to the cast in the invoking code caused by `returnItems()` declaring `T[]` instead of `Object[]` as the return type. I can see that it's sort of vague, though, and try to clean it up. – azurefrog Jan 16 '15 at 18:34
1

The code you wrote doesn't work because T[] is initialised as an Object[]. When you return T[] in the returnItems() function, you actually return an Object[] and so, you can't treat it as a String[].

Use ArrayList<T> instead of T[] (which is actually an Object[]). With its diamond interface (new ArrayList<>()) it automatically infers the type of your items variable:

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

And then use 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);

    }
}
Giulio Biagini
  • 925
  • 5
  • 8
1
import java.util.Arrays;

// ...

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

that works if you define toString() method of the class to be printed (if it's not a primitive type).

Luís Soares
  • 4,466
  • 1
  • 27
  • 47
1

The reason why your code doesn't work has little to do with Generics. Consider the following example:

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

The book "Core Java 2 - Fundamentals" states:

"A java array remembers the type of its entries, that is, the element type used in the new[] expression that created it. It is legal to cast an Employee[] temporarily to an Object[] and cast it back, but an array that started it's life as an Object[] can never be cast into an Employee[]."

Now, consider what the Java compiler does with your code. It turns your "Generic<T>" class into this:

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

As you can see, when you call the returnItems() method, it actually returns an Object[]. In your code, you did:

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++)
        {

        }

Your code actually takes what was initially an Object[] and tries to cast it to a String[] (which is a cast that the Java compiler inserts as part of type checking, I believe). So the Java compiler translates your loop above to:

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

            }

If you put these concepts together, you can see that what you've arrived at is a bad situation. You can actually resolve it without doing anything the other responses have suggested. You can insert an explicit cast to Object[] in your loop above (where I wrote String[]) and your problems will go away.

KyleM
  • 4,115
  • 7
  • 41
  • 77
  • Actually if you just add a cast to `Object[]` in the loop, you'll still get the `ClassCastException`. Or at least I do with Java 7. – azurefrog Jan 16 '15 at 18:55
  • @azurefrog That's strange, I am using JDK1.7 with compiler compliance level of 1.7 and I have no issues with the code for(int i=0; i < ((Object[])animals.returnItems()).length;i++). It compiles and runs without issue. Perhaps you get a ClassCastException elsewhere? – KyleM Jan 16 '15 at 19:30
  • Oh you're right, it's not the loop, it's the println right after it which also needs the cast to object (`System.out.println(((Object[])animals.returnItems())[i]);`). I didn't notice the line number in the exception had bumped up by one. – azurefrog Jan 16 '15 at 19:42
0

You can't send your Object[] back out to a String[]. Instead, return the array once to an Object[], and then depend on Object inheritance to use the correct toString().

    Object[] s = animals.returnItems();
    for(int i=0; i < s.length;i++)
    {
        System.out.println(s[i]);
    }
Ben I.
  • 963
  • 1
  • 11
  • 27