3

In the following code example:

interface Eatable{ public void printMe();}
class Animal { public void printMe(){System.out.println("Animal object");}}
class Dog extends Animal implements Eatable{ public void printMe(){System.out.println("Dog object");}}
class BullTerrier extends Dog{ public void printMe(){System.out.println("BullTerrier object");}}

public class ZiggyTest{

    public static void main(String[] args) throws Exception{

        Object[] objArray = new Object[]{new Object(), new Object()};
        Collection<Object> objCollection = new ArrayList<Object>();

        Animal[] animalArray = new Animal[]{new Animal(),new Animal(),new Animal()};
        Collection<Animal> animalCollection = new ArrayList<Animal>();      

        Dog[] dogArray = new Dog[]{new Dog(),new Dog(),new Dog()};
        Collection<Dog> dogCollection = new ArrayList<Dog>();

        System.out.println(forArrayToCollection(animalArray,animalCollection).size());
        // System.out.println(forArrayToCollection(dogArray,dogCollection).size());  #1 Not valid

        System.out.println(genericFromArrayToCollection(animalArray,animalCollection).size());
        System.out.println(genericFromArrayToCollection(dogArray,dogCollection).size());  


        System.out.println(genericFromArrayToCollection(animalArray,objCollection).size()); //#2 
        System.out.println(genericFromArrayToCollection(dogArray,animalCollection).size()); //#3 
        // System.out.println(genericFromArrayToCollection(objArray,animalCollection).size()); //#4

    }

    public static Collection<Animal> forArrayToCollection(Animal[] a, Collection<Animal> c){
        for (Animal o : a){
            c.add(o);
        }

        return c;
    }

    static <T> Collection<T> genericFromArrayToCollection(T[] a, Collection<T> c) {
        for (T o : a) {
            c.add(o); 
        }

        return c;
    }

}

Why does the compiler allow the call to the genericFromArrayToCollection() method only if the declared type of the collection is the parent of the declared type of the array (See lines marked #2, #3 and #4) . Why is this please?

Thanks

Edit

When i uncomment the line marked #4 i get the following error

ZiggyTest.java:34: <T>genericFromArrayToCollection(T[],java.util.Collection<T>) in ZiggyTest cannot be applied to (java.lang.Object[],java.util.Collection<Animal>)
                System.out.println(genericFromArrayToCollection(objArray,animalCollection).size()); //#4
                                   ^
1 error

Edit 2

@Tudor i tried the following method using this statement

System.out.println(method1(new ArrayList<String>()).size());

The compiler complained with an error saying that cannot be applied to java.util.ArrayList

public static Collection<Object> method1(ArrayList<Object> c){
        c.add(new Object());
        c.add(new Object());        
        return c;
}
ziggy
  • 15,677
  • 67
  • 194
  • 287
  • oops sorry i forgot that. see the edit. Thanks – ziggy Dec 03 '11 at 12:24
  • You are right, for some reason there was probably something wrong in my test. I was also surprised because I knew that you cannot assign to a collection of a type a collection of a different type, even though they are related. Sorry for the noise! I've updated my answer because it only applies to arrays. The rest of my explanation still holds though... – Tudor Dec 03 '11 at 13:25

3 Answers3

1

So the static types you are giving as arguments to the method are in order:

For forArrayToCollection.

Animal[], Collection<Animal>
Dog[], Collection<Dog>

This method has parameter types Animal[], Collection<Animal>. First call matched exactly. Second call attempts to assign a Collection<Animal> to Collection<Dog> which is wrong (I can add a Cat to a Collection<Animal>, which I shouldn't be able to do to a Collection<Dog>).

For genericFromArrayToCollection.

Animal[], Collection<Animal>
Dog[], Collection<Dog>

Animal[], Collection<Object>
Dog[], Collection<Animal>
Object[], Collection<Animal>

In all cases, T must substitute for the generic argument of the collection. First two calls match exactly. For the third call, T is Object and, because of the odd way arrays behave in Java, Animal[] can be assigned to an Object[] (but you'll get an unchecked ArrayStoreException if you try to store a NumberFormat in it). Similarly, for the fourth, Dog[] can be assigned to Animal[]. For the last Object[] cannot be assigned to Animal[] (reads from an array should never throw a ClassCastException unless you've done some unsound with a generic array cast monstrosity (you'll get a javac warning - take note of it)).

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
1

To answer your question, let's first establish the premise: we know that if you have a method taking an array as a parameter, you can pass an array of a subtype:

public void method(Object[] list) // can be called with String[]

but the reverse is not true:

public void method(String[] list) // cannot be called with Object[]

Then, it boils down to how the parameters of a generic method are actually instantiated, that is how the parameter type inference works. In your case #3, it is inferred to Animal, so the method declaration really looks like:

static Collection<Animal> genericFromArrayToCollection(Animal[] a, 
                                                       Collection<Animal> c) {

and since Dog is a subtype of Animal, it can fit nicely instead of the Animal[] array. Same for case #2.

However, in case #4, the type is inferred to be Animal again, so the method looks like above, but you cannot put an Object[] array in place of an Animal[] array because Object is not a subtype of Animal.

Tudor
  • 61,523
  • 12
  • 102
  • 142
  • Are you sure about the first two examples? I just tried calling a method that expects an ArrayList passing it an ArrayList and the compiler didn't like that. This is why it is confusing because it is never clear when generics can be used polymorpically. See the second edit in my original post – ziggy Dec 03 '11 at 13:18
  • @ziggy: You are right, It only holds for arrays. I've edited. The rest of the explanation holds though. – Tudor Dec 03 '11 at 13:28
1

basically, genericFromArrayToCollection() defines T as a type parameter and that is used to define the 2 method parameters (i.e. T[] a and Collection<T> c). Both a and c must be based on the same type, so Dog[] and Collection<Dog> would work, Animal[] and Collection<Animal> would work, but Object[] and Collection<Animal> would not, because now T is Object whereas the collection is based on Animal. Had your method signature had Collection<? extends T>, this may have worked I think.

aishwarya
  • 1,970
  • 1
  • 14
  • 22