Data Structures

I always get frustrated by generics when I try and build them into my own classes. They have bad limitations with arrays, and the <> tags become messy as soon as you start having nested generified types like lists of lists.

This was never such a big problem that required all the nasty warnings to be turned on by default. RHS generics on initialization bug the crap out of me.

No, it won’t catch runtime errors - that’s one of the main problems with Java generics. All type information is lost at runtime, so you might actually be getting runtime errors instead of compiler errors, and compiler errors are typically better, no? Not to forget that Java generics do a whole lot of type casting under the hood, so you might as well just make your own ArrayList implementation (this is what Kev always does) to avoid all potential problems and overhead.

More details:
http://www.facsim.org/node/77

I personally use generics usually, but that’s because they typically save me programming time. The whole reason I posted the FYI was so that Dreamcatchermatt would know all the facts before spending the time learning generics.

Yes generics are not available at runtime, but I still disagree that they can’t prevent runtime errors.

The old example of using generics with a list shows this. This non-generics code will fail at runtime:


List ls = new List();
ls.add( "a string" );
Integer i = (Integer) ls.get(0); // I'd expect a ClassCastException would be thrown here

We also don’t know if ls should contain only Integers or Strings. The code does not express enough information for you to tell.

Where as if you add generics it will instead fail at compile time:


List<Integer> ls = new List<Integer>();
ls.add( "a string" ); // I'd expect a compiler error here
Integer i = ls.get(0);

Maybe unlikely to happen that often with lists (how many people actually mix types in them?), but it does show moving a potential runtime error to compile time.

Good point about performance, since that isn’t at all obvious from the beginning. I typically only avoid generics for performance critical things, since using generics is quicker to code for most situations. You might have to explain the “getting runtime errors instead of compiler errors”. Looking at your code, changing:


        doubleQueue.push (new Double (2.0));

to


        doubleQueue.push (new Integer (2));

will create a compile time error when you use generics, but a runtime error in your other example when you didn’t use generics. And that is probably what JL235 was talking about. I am sure he meant “catch” as in prevent, not “try - catch”). I don’t see how generics could create a ClassCastException even though the the Lists loose the type after compile, since you can’t create code with different types for the same List.

For completeness sake… this results in a runtime exception :slight_smile:


   static <T> T horrible(Object t)
   {
      return (T) t;
   }

   static
   {
      String s = horrible(Integer.valueOf(13)); // b00m!
      System.out.println(s);
   }

Yeah, I mean you guys are right that they certainly prevent you from making most mistakes, but I would argue that the runtime exceptions that you will get from generics are much more difficult to track down because in order to make them happen you need to have some pretty complicated (or weird) code paths. They’re not catch-all because they don’t actually make an object that only allows one type, they just constrain it when you happen to be modifying that object in a scope that has generics defined for it. I have had several cases over the years where I am somehow modifying a Collection that has generics but without using generics. i.e. it gets modified like Collection. This has required some tricky code paths and passing of the Collection between lots of functions and other things, but it has indeed happened to me. The fact of the matter is, if you can somehow “trick” the compiler that you have a Collection even though you may have a Collection, then you can put whatever you want in there with no problems, and expect it to be perfect because there are no compiler errors reported.

Anyway. Let me reiterate - I almost always use generics. I never make code critical-path enough to worry about the flaws of them.

@Riven: Yeah, but ‘horrible’ should also emit an unchecked cast warning (or error depending on your settings).

Where this is silently accepted:


  static Object horrible(Object t)
  {
     return t;
  }

  static
  {
     String s = (String)horrible(Integer.valueOf(13)); // b00m!
     System.out.println(s);
  }

To be ultra safe, you can use:

List<Integer> list = Collections.checkedList(new ArrayList<Integer>(), Integer.class)

Using the standard java.util collections both with or without generics should always give the same performance.

At a guess I believe what you might actually have found an issue with is the overhead of auto-boxing primitive types. I’m pretty skeptical generics itself was causing any performance issues in your code, as all it adds are a few extra casts which you’d typically need to add to your non-generics code anyway. I’ve also rolled plenty of my own collections to get this small performance gain.

What about this:



//Server
private ArrayList<Comparable>gameStuff;

public void doServerStuff()
{
    try
    {
        ObjectOutputStream os = new ObjectOutputStream(clientAOutputStream);
        os.writeObject(gameStuff);
        os.close();
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}


//Client
private ArrayList<Integer>gameStuff;

public void doServerStuff()
{
    try
    {
        ObjectInputStream is = new ObjectOutputStream(serverInputStream);
        gameStuff = (ArrayList <Integer>) is.readObject();
        is.close();
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}


What’s sent is an array list of Object, even though Comparable is passed as the template. So what comes in is also an ArrayList of Object, even though it gets cast (yes, you will get a compiler warning) to an ArrayList of Integer. The above would obviously be a silly mistake, but situations like these in complex projects are more what I’m talking about. It makes much more sense to just have like a ServerDataList object that has customer methods to only allow what you want and then send that around instead. Or just stick with ArrayList and know that it may not be safe (like you would always assume with ArrayList) so you can check each and every Object’s type.

You could also hide the generics using inheritance…


public class GameStuff extends ArrayList<Comparable>
{ }

and this could also be achieved through composition, or by creating your own object input/output streams which solved the type issues for you (i.e. they only took and returned a type of ArrayList allowing developers to avoid doing the cast themselves).

I do see what your saying, it is an example of generics failing to solve a type problem. But this issue will just as easily crop up in a system which didn’t use generics.

If you use generics and you have no compiler warnings then you can never get a ClassCastException. Ever. However, jumping through all the hoops and typing all the goddamn pointy brackets is not worth it to solve this “ClassCastException problem”, which never really existed.

My approach is to turn off the warnings that are stupid, and to just use generics for syntatic sugar. Doing this you are no worse off ClassCastException-wise than pre-Java 1.5, but you have a little sugar. We all know Java could use some sugar.

Wow, I seem to have poked a sleeping bear :smiley:

Thaks for all the different ideas and views though!

This sort of discussion helps me get a more rounded idea of what I’m dealing with, rather than just reading through books/tuts.