18

This covariance is possible in C#:

IEnumerable<A> a = new List<A>();
IEnumerable<B> b = new List<B>();

a = b;

...

class A {
}

class B : A {
}

This is not possible in Java: (Iterable: Seen in this question Java Arrays & Generics : Java Equivalent to C# IEnumerable<T>).

Iterable<A> a = new ArrayList<A>();
Iterable<B> b = new ArrayList<B>();

a = b;

...

class A {
}

class B extends A {
}

With Iterable, Java doesn't see those two collection are covariance

Which iterable/enumerable interface in Java that can facilitate covariance?


Another good example of covariance, given the same A class and B class above, this is allowed on both Java and C# :

   A[] x;
   B[] y = new B[10];

   x = y;

That capability is on both languages from their version 1. It's nice that they are making progress to make this a reality on generics. C# has lesser friction though in terms of syntax.

Covariance is a must on all OOP languages, otherwise OOP inheritance will be a useless exercise, e.g.

 A x;
 B y = new B();

 x = y;

And that power should extend to generics as well.



Thanks everyone for the answer and insights. Got now a reusable method with covariant-capable Java generics now. It's not the syntax that some of us wants, but it(<? extends classHere>) certainly fits the bill:

import java.util.*;    
public class Covariance2 {  
  
        public static void testList(Iterable<? extends A> men) {                 
                for(A good : men) {
                        System.out.println("Good : " + good.name);
                }
        }    

        public static void main(String[] args) {    
                System.out.println("The A"); 
                {
                        List<A> team = new ArrayList<A>();
                        { A player = new A(); player.name = "John"; team.add(player); }
                        { A player = new A(); player.name = "Paul"; team.add(player); }
                        testList(team);
                }

                System.out.println("The B");
                {
                        List<B> bee = new ArrayList<B>();
                        { B good = new B(); good.name = "George"; bee.add(good); }
                        { B good = new B(); good.name = "Ringo"; bee.add(good); }
                        testList(bee);
                }    
        }
}

class A { String name; }    
class B extends A {}

Output:

The A
Good : John
Good : Paul
The B
Good : George
Good : Ringo

In case anyone are interested how it look like in C#

using System.Collections.Generic;
using System.Linq;

public class Covariance2 {  

        internal static void TestList(IEnumerable<A> men) {                 
                foreach(A good in men) {
                        System.Console.WriteLine("Good : " + good.name);
                }
        }    

        public static void Main(string[] args) {    
                System.Console.WriteLine("The A"); 
                {
                        IList<A> team = new List<A>();
                        { A player = new A(); player.name = "John"; team.Add(player); }
                        { A player = new A(); player.name = "Paul"; team.Add(player); }
                        TestList(team);
                }

                System.Console.WriteLine("The A"); 
                {
                        IList<B> bee = new List<B>();
                        { B good = new B(); good.name = "George"; bee.Add(good); }
                        { B good = new B(); good.name = "Ringo"; bee.Add(good); }
                        TestList(bee);
                }    
        }
}

class A { internal string name; }    
class B : A {}
Community
  • 1
  • 1
Hao
  • 8,047
  • 18
  • 63
  • 92
  • 1
    In your example with arrays, the problem is that it reduces compile time type safety - some operations (see comment on mikera's answer) are allowed at compile time but fail at runtime (`ArrayStoreException` in Java). Apparently, the designers of Java generics considered compile time type safety more important than covariance (and implicitly must consider array covariance a design error). – Michael Borgwardt May 19 '12 at 12:24
  • @MichaelBorgwardt It's not a design error, in fact Java can see the covariance between superclass and subclass, when the type is simple(one variable), and array(see my edited question), Java compiles the array version just fine like its single variable counterpart. And there's no `ArrayStoreException` during runtime, I run it just now. As long as the variable that's receiving the assignment is the superclass, there won't be any `ArrayStoreException` – Hao May 19 '12 at 12:42
  • 1
    add the line `x[0] = new A();` to your array code and there most definitely will be an `ArrayStoreException` – Michael Borgwardt May 19 '12 at 12:45
  • @MichaelBorgwardt Ah.. now I know :-) Thanks for pointing that out – Hao May 19 '12 at 12:51
  • @MichaelBorgwardt Hmm... so basically, covariance(be it an array, or from generics), must be used for read-only activities. Now, I'm seeing the Java's rationale for not allowing to call a method(with a generic signature) of an instance variable that came from ` extends classHere>`, e.g. `.add(...)` – Hao May 19 '12 at 12:59

3 Answers3

15

Java generics allow covariance only if explicitly declared via wildcards in order to provide stricter type safety. This works:

    Iterable<? extends A> a = new ArrayList<A>();
    Iterable<B> b = new ArrayList<B>();
    a = b;

However, note that you now cannot add anything via the reference a since it's declared to contain instances of some specific but unknown class, which might be A or any subclass thereof. The behaviour of wildcards is often counter-intuitive and can get very complex, so they should be used in moderation.

Michael Borgwardt
  • 342,105
  • 78
  • 482
  • 720
1

Generics are not covariant in Java. You'll have to do it the old way, like it was when C# didn't support covariance in generics.

However, in Java, you can pretend a generic iterable is an iterable of anything, denoted by a question mark. A list of anything contains only objects.

Iterable<A> a = new ArrayList<A>();
Iterable<?> b = a;
Daniel A.A. Pelsmaeker
  • 47,471
  • 20
  • 111
  • 157
0

There's a good reason why this kind of covariance in generic collections is a bad idea:

Suppose you do:

ArrayList<A> a = new ArrayList<A>();
ArrayList<B> b = new ArrayList<B>();

a.add(new A()); 
b = a;
B item=b.get(0); 

Whoops - a function that was meant to return only objects of type B has returned type A. This clearly can't work. So the Java compiler disallows it in order to ensure type safety.

It's not a big deal though - easy workarounds are to just use the non-generic collection classes or restrict the generic type argument to the common superclass (A in this case).

mikera
  • 105,238
  • 25
  • 256
  • 415
  • 1
    Covariance is not a bad idea. It follows the Liskov substitution principle in SOLID principle. B extends A. A can/should at least be able to hold a reference to B. Though of course any statically-typed language can prevent assigning A to B, i.e. `b = a;` is and should not be allowed. Your example is a bit contrived, any statically-typed language will not allow that, I'm thinking about `a = b;`, not `b = a;`. C# 4 at least facilitated Liskov substitution principle, it's ok to assign B to A, i.e. this is allowed in C# now: `a = b;` – Hao May 19 '12 at 12:03
  • @Hao: problem is: after that, `a.add(new A())` would allow you to add an `A` to an `ArrayList` – Michael Borgwardt May 19 '12 at 12:19
  • @MichaelBorgwardt I already tried it, it's not allowed on Java. I'd seen your answer on the book I'm reading, ` extends classHere>` is for **getting** values, ` super classHere>` is for **putting**, so `a.add` won't be allowed by the compiler, hence we're safe with that. Unless it's pure wildcard(is the terminology *unbounded* ?) like this: `>`. I asked the Java question, upon seeing C# lesser friction syntax for covariance – Hao May 19 '12 at 12:31
  • @Hao Even if B is a subclass of A, Foo is not a subclass of Foo, that's why covariance doesn't apply here. – Puce May 19 '12 at 12:59
  • 1
    @Hao The Java language designers didn't want to allow this because it's not type-safe. (See comment by MichaelBorgwardt regarding ArrayStoreException). It's not type-safe because they are not subclasses. The provided solution is using wildcards. I'm not sure how C# gets around this issue or if it throws some equivalent to ArrayStoreException as well (which would mean it's not type-safe). – Puce May 19 '12 at 13:54
  • 1
    @Puce Java language designers allow it. This compiles: `A[] a = new B[10];` and has no runtime error, no exception thrown, and they extend this capability to generics as well. But you cannot change the collection's element(B) to an object that is its superclass(A) and incompatible types; for example, this is not allowed(will compile though): `a[0] = new A();`, it will cause `ArrayStoreException`. If B has a subclass let's say C (`class C extends B`), this is allowed: `a[0] = new C();`, this is safe, no runtime error, no exception thrown. And this is safe too: `a[0].setFirstname("James");` – Hao May 19 '12 at 15:42
  • What both Java and C# didn't allow, and can't be caught during compile-time, is when you change the collection's element to an object that is its superclass and type incompatible, in Java it causes `ArrayStoreException`, in C# is `ArrayTypeMismatchException`. – Hao May 19 '12 at 15:43
  • 1
    @Hao Again, Java only allows this for arrays, but not for generic types, because it's not type-safe. (I don't know the reason why it is allowed for arrays but it can't be changed now.) Java generics never throw ArrayStoreException or ClassCastException if you don't use the raw version of the class. Only arrays throw ArrayStoreException. – Puce May 19 '12 at 16:34
  • 1
    @Puce Generics covariance is allowed now in Java(look at @MichaelBorgwardt answer), and it is type-safe to boot; for example, it won't allow you to call a method that has a potential to manipulate the collection(e.g. add). Java generic covariance's syntax is just not as natural as compared to C# syntax though. Anyway, I'll just go with what we have for now, as suggested by @MichaelBorgwardt this would suffice `Iterable extends A> a = b` – Hao May 20 '12 at 12:01
  • @Hao yes, as mentioned before, using wildcards ('?') is the provided solution in Java. – Puce May 20 '12 at 12:19
  • Out of interest: does somebody know how C# solves this problem? Is it type-safe? – Puce May 20 '12 at 12:21
  • @Puce I'm ok now with @MichaelBorgwardt answer ;-) Regarding C#, it's more typesafe than Java. With Java's generics you can still store an `A` object in a `B` collection deliberately by doing casting voodoos; with C#,the casting is stopped dead in its tracks(C# generics carries types);with Java,non-safe casting is allowed(hmm... type erasure woes),consequently it will let you add an incompatible type to the collection,Java will only know the two generics assignment are not really compatible(e.g. `Subclass = Superclass;`)when you loop the collection,the exception: `java.lang.ClassCastException` – Hao May 20 '12 at 12:47
  • @Puce I put on my edited question what I wanted(generics covariance) to achieve in Java in the same vein that generics covariance is allowed in C#. It's ok now, thanks everybody! Happy coding! – Hao May 20 '12 at 12:51