Java vs. C# - Part 1 - Generics

Java vs. C# - Part 1 - Generics
   After I published part 0 I was criticized for being biased. I was told that the title suggested neutrality but the actual article is not neutral and "Why C# Is Better than Java" would have been a better title. In fact "Java vs. C#" was just a working title that I forgot to change and I agree that the suggested title would have been better. I was criticized for exaggerating the damage caused by the Java approach and overstating the benefits of the C# approach. While this may be true it should be clear that these are real problems and I am not making them up. I do not pretend to have discovered them. In fact I have read about most of the issues listed here in interviews and articles by both Java and C# designers. The reader is also advised to read part 0 where my motivation is clearly described.

   And now lets get into today's topic - generics.

   Java generics are broken. Admittedly this is more a flaw in the runtime rather than in the language but it is closely related. Consider the following code:

   public static <T> T foo(T arg) {
       try {
           Object o = "42";
           T t = (T) o;
           return t;
       } catch (ClassCastException ex) {
           return arg;
       }
   }

   …

   Object o = foo(42);
   System.out.println(o.getClass());

   Oh my Google! I just managed to return a String from a method that returns an Integer! Even the die hard Java fans should admit that this means something is broken. What is broken is the way generics are implemented. There are no actual types in place of generic type arguments at runtime only Object. This s why the cast to T does not actually exist and this is why no ClassCastException is caught. If the result was used at a place where Integer was expected there would be a ClassCastException on a line where there is no cast but hey, at least we got a warning!

   I believe Java designers implemented generics this way so existing runtime implementations would not require additional work on the complex machinery needed for real generics. Probably they were afraid that it would slow the adoption of Java 5 on platforms like mobile phones. By implementing generics only at compiler level they allowed runtime implementations to be up to date with minimal effort. Maybe this was the right decision for the platform as a whole but it surely hurt the language. On the bright side this can easily be fixed in the runtime and is something that is considered for Java 7.

   .NET solves this problem by having real generics.

   Warning: If you do not know what generics covariance and contravariance is and how it works in C# and Java you may want to find out before reading on. You can easily find explanation of the concept on Wikipedia and many others describing how each language solved the problem. You can also read my explanation of the concept and how it works in C#.

   This is not all on generics. The way covariance and contravariance is handled in Java increases the complexity of the language. Of course every feature added to a language increases its complexity but this case is especially interesting for two reasons. Covariant and contravariant generics are something that is hard to understand on its own. The concept can make your head hurt. I believe it is much more complex than other concepts like lambdas and multiple inheritance for example. This is why I think that in this particular case a more simplistic approach should be favored. On top of this Java is a language that has always been conservative and took pride in its simplicity. In fact the main criticism Java proponents hold against C# is its complexity. Multiple inheritance is not in Java because it is considered complex and the addition of lambdas to the language has been debated for many years because (according the opponents of the idea) they are too complex. In the light of this implementing a feature like generic variance in a complex way should be considered a mistake.

   So why do I claim that Java's approach is complex? What is Java's approach anyway? It is the so called user-site variance. When you use an interface or class you define a variable or a parameter and state the variance using a special syntax:

   Iterable<Derived> derivedIterable = new ArrayList<Derived>();
   Iterable<? extends Base> baseIterable = derivedIterable;

(extends is used for covariant and super for contravariant type arguments)

   This allows for basically the same things as the C# approach but it puts the responsibility in the hands of the developer who uses the interface instead of the developer who designed the interface. I think we can agree that people who design variant interfaces are fewer than people who use them. What is more people who design variant interfaces are on average more qualified than people who use them. After all variance is mostly used with collections and they are in the Java class library itself. This is why I believe C#'s approach of leaving the responsibility and complexity in the hands of the interface developers is better especially when the language tries to be conservative.

   That being said the Java approach has some benefits. First of all it increases the flexibility by allowing variance to be used when the interface developer forgot or did not know how to put variance into the interface itself. Java also allows classes to exhibit variance. C# only supports it for interfaces and delegates due to limitation of the underlying CLR. It seems like the Java implementation of generics actually has some benefits. However I believe that the thing that justifies complicating the language by adding variance are interfaces or if I have to be more specific one interface in particular - IEnumerable (Iterable in Java). Everything else will rarely be used in scenarios complicated enough to justify introducing additional syntax to the languages. Maybe I am not entirely correct and C#'s delegates and Java interfaces in the Observer Pattern can benefit from variance when used to handle events but both of these are not classes anyway.

   Update: Part 2 has been published.
Tags:   english programming 
Last edited by:   Stilgar
on   01:36 06.07.2010
Posted by:   Stilgar
01:05 21.06.2010

Comments:

First Previous 1 Next Last 

Posted by   ivayloslavov (Unregistered)   on   12:23 17.08.2010

Hi, I am a .NET developer, I know that C# generics are better than the Java ones and generally speaking the language features of C# are superior. But, I also must point out here, that the code example about generics is wrong in the Java context - the behavior is correct and I can clarify it here:

Object o = foo(42);

is equal to the C# call

object o = foo<object>(42);

therefore

System.out.println(o.getClass());

returns the type of the boxed item returned, currently the string "42".
The confusion here is that in .NET, the generic type is being taken as the most concrete type while in Java - to the most abstract (probably this is due to the different generics implementation). In C# this call is expected to be:

object o = foo<int>(32);

because .NET takes into account the method parameter, while Java considers the object (the return value) as a more-appropriate choice. Well, if a Java developer is aware of this, all the possible cases of invalid cast exceptions will have a logical explanation. I think this is a good thing for .NET developers to know when using Java and vice-versa.

Posted by   Stilgar   on   12:59 17.08.2010

Hmm interesting point. I need to test this when I get home. So do you suggest that if I write it as foo.<Integer>(42) it will work as expected?

Posted by   ivayloslavov (Unregistered)   on   13:05 17.08.2010

Well, I have used Java before they added their generics, so I am not sure how can I specify the type of the generic upon method call, but yes, thats my point.
Well, I personally noticed that

int i = foo(42);

or

Integer i = foo(42);

return integer, therefore the invalid cast is being encountered.
Anyway I must say that when not manually specifying the type, C# indeed tends to be
the more strongly-typed.

Posted by   ivelinka   on   13:12 17.08.2010

Ivaylo, if your point is that calling the method like this <Integer>foo(42) will fix the problem then you are wrong. I've tested it and it still prints "class java.lang.String".

Posted by   Stilgar   on   13:54 17.08.2010

Please point out how the generic method above would return an Integer value.

Posted by   ivayloslavov (Unregistered)   on   15:09 17.08.2010

@Stilgar, when you get in
...
      catch (ClassCastException ex) {
          return arg;
      }
...
which as I have to admit - never happens :),

@ivelinka, yes, it does not - the reason is because it is probably always called like <Object>foo(...) and the runtime casts the result to the specified type, therefore the example actually is correct, it was my mistake, the ClassCastException in the method will never be caught in this code.

I was just hoping there was some type-safety in all this generic thing Java has, and my first point made sense, although it was just a mistake.
Anyway it was nice to learn something new.

Posted by   Guest (Unregistered)   on   14:34 20.11.2010

This has nothing to do with generics. You can just as easily do:

public static Integer foo(Integer arg) {    
          Object o = "42";
          Integer t = (Integer) o;
          return t;    
}

The problem here is Unchecked Type Cast - compiler will warn you about this. UTC is part of the Java before generics and is here for a reason.

This has nothing to with Java Generics or proving how broken they are. Imho they are broken because they are overly complex not because they work incorrectly.

Next time before you make big claims just Google around a bit.

Posted by   Stilgar   on   15:08 20.11.2010

The problem is not being able to do the cast. The problem is that the exception is not thrown. If you rewrite your example like this:

  public static Integer foo(Integer arg) {
      try {
          Object o = "42";
          Integer t = (Integer) o;
          return t;
      }
      catch(ClassCastException ex) {
          return arg;
      }  
  }

There will be an exception, it will be caught and the catch block will execute. Here is a challenge for you - provide an example where a non-generic method with return type of Integer would return a non-Integer object. Then I will agree that the problem is not with generics. The problem with generics is that a method that returns an Integer returns something else instead and there is no exception.

Posted by   Guest (Unregistered)   on   23:30 09.12.2010

Pretty good stuff here.  As a die hard Java developer I would love to poke holes in your arguments, but I can't find any holes :)

Your last point is also on the money.  Covariance in Java is a an advantage (huge if you ask me).  I stumbled across your article as I was fuming at the discovery of no covariance for method overriding in C#.  I am now stuck with crappy C# base code that returns base types that I cannot narrow in my derived classes.  Yuk!

Posted by   Stilgar   on   13:45 10.12.2010

Hmm that's quite interesting. I didn't know that Java could do return type covariance for overriden methods. I have to dig more information about this and especially why this is not allowed in .NET

First Previous 1 Next Last 


Post as:



Post a comment: