Skip to main content

Java Generics and the Kiss principle

25 replies [Last post]
aschunkjava
Offline
Joined: 2014-06-24

Hi,

this is a general thread on the new Java Generics feature as it was introduced in Tiger and implemented in Mustang.

I post this because i come from a C++ background where Generics - or Templates - were introduced in order to be able to write one class template or method template that could be used for any types of objects of type T.

However, the Generics approach in C++ was - no matter how powerful it was - it did not lead programmers to write more readable or simpler code rather than more complicated and confusing code. I know this from experience ;)

And - as i have read in some whitepapres on java.sun.com - i thought this was one the reasons to invent Java.

But what happened? Time went by and with the Tiger 1.5 releaseo the JDK, Generics were introduced in the Java language.

In most cases, the Generics as they are used in the JDK src are readable and understandable but the JDK also contains bad style Generics code like in the following example:

public static > T valueOf(Class enumType, String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum const " + enumType +"." + name);
}

This kind of code is neither easy to read nor easy to understand - what kind of object/class is T and why does it need to extend Enum.

The use of T o or Enum indicating that T may be of any kind of Object is straightforward and may be a good add-value to OOP programming but it should be constrained to that.

If you start applying things like polymorphism to Generics - > - things are getting complicated.

In fact i do not see a benefit to use Generics at all and they do not make Java programming any easier. I would love to see that Java keeps on going the Kiss way.

Message was edited by: alexanderschunk

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
kriegj
Offline
Joined: 2005-01-11

> I have used Generics in a major project and they
> definitely helped. Collections definitely benefit
> from them as well as reflection.
>
> The problem with generics is that there is no easy
> way to integrate them with legacy code.

This can be true. One way I've integrated them is, for example, if the API doc says "This method returns a List of Strings", I take them at their word and cast the return value to a List. As long as the API is true to its word, then this works fine. That at least prevents me from breaking the contract specified in the documentation - even if the implementer breaks it.

> Sure,
> @SuppressWarnings works, but if you ever muck up a
> @SuppressWarnings (e.g. make an assumption about a
> type that doesn't hold) things get really bad.

This is true, but then this same problem existed in the pre-generics days. Pre-generics days is like running with "suppresswarnings" set to true for all warnings.

> Using
> the type checked Collections utilities (they perform
> type checking at runtime) don't work with all cases.
> You can't make a type checked
> d List> for example. As far as I
> see there is no way to create a class that can type
> check types that are more than one level of generics
> deep - basically because at runtime we can check if
> an object is of a Map type but can't access the type
> parameters. This all comes down to no runtime
> support.

It is [i]always[/i] possible to add runtime support for type parameter checking - it's just that you have to do it "by hand", so to speak. This is effectively what the Collections utilities (checkedList(), etc) do.

For your example of List>, you would need to derive a new List implementation that intercepts the "add" method(s). This method would first iterate through the Map doing an "instanceof" test on the keys/values - throwing a ClassCastException if they are not Strings. Example:

public class CheckedListOfMaps implements List>
{
private List> myBackingList;

public CheckedListOfMaps(List> backingList)
{
myBackingList = backingList;
}

public boolean add(int index, Map element)
{
// If it's null, we can skip the type checking.
if (element == null)
return super.add(index, null);

// Do the type checking before adding it.
if (!(element instanceof Map))
throw new ClassCastException();

for (Map.Entry entry : element.entrySet())
{
if (entry.getKey() != null && !(entry.getKey() instanceof String))
throw new ClassCastException();

if (entry.getValue() != null && !(entry.getValue() instanceof String))
throw new ClassCastException();
}
return myBackingList.add(index, element);
}
}

(Sorry if the formatting of the above is bad - I didn't know how to TT it.)

This is of course not a complete implementation (most of the methods are missing and would need to be passed through to the backing list, and I'd recommend more detailed exception messages), but it should be enough to give you the idea. Hope that helps.

prunge
Offline
Joined: 2004-05-06

> It is [i]always[/i] possible to add runtime support
> for type parameter checking - it's just that you have
> to do it "by hand", so to speak. This is effectively
> what the Collections utilities (checkedList(), etc)
> do.

Not automatically. Yes, you can write by hand your own type checking collection, but this defeats the purpose. There are an infinite possible number of combinations of nested types that can be used for collections. Having a slighty different combination requires coding up a brand new collection implementation every time.

Collections.checkedList(), etc. are restricted because they take a Class argument to use as the type for checking. Unfortunately, this only allows one level since Classes don't represent generic types). It also does not allow checking of bounded types (cannot create a type checked list that checks ? super Number for example).

cowwoc
Offline
Joined: 2003-08-24

fcmmok,

1) Don't try to squeeze as much as is humanly possible on a simple line. It leads to unreadable code which you should be avoiding anyway. If a NPE occurs, you'll thank your lucky stars if you space the calls across multiple lines so you know which dereferenced component was null.

2) Like I said, I am all for Generics in Collections; but no where else.

kriegj
Offline
Joined: 2005-01-11

> kriegj,
>

Hey cowwoc, thanks for responding.

> Generics work very well for Collections, which are by
> far the best-defined easiest use-case for them. The
> problem is that Generics are very deceiving. They
> look like they could be useful for more complex
> use-cases but you quickly discover that you can
> *almost* express a constraint in Generics but not
> quite.
>

I simply disagree. I have found them extremely useful in expressing several different types of constraints, outside of the Collections model. Code that follows the factory pattern (of which there is a lot in my code) is but one example.

I do acknowledge that there are some more esoteric constraints that can't quite be specified using generics. Some (but not all) of these could be fixed with minor modifications. But this hardly nullifies the usefulness of generics for those cases where constraints [i]can[/i] be specified. These are constraints that had hitherto been embedded in comments (if at all), and which the compiler had no way of knowing about or enforcing. It was left entirely up to the good will and competency of the user

You seem to be reasoning that because Generics can't help with [i]all[/i] constraint types, that it is therefore useless. All is better than some. but some is better than nothing - which is what we had before Generics.

The bottom line is this: our code had some bugs in it that Generics allowed me to find. Without Generics, they'd still be there. This makes them useful.

> It would be nice if Generics (like operator
> overloading) were standard for predefined API classes
> (such as Collections) but were not added as a
> language feature. That is, Sun could define a small
> set of Generics uses but no user could create new
> classes which use them. As well, Sun could define a
> small set of operator overloading (i.e. String +
> operator) but no user can add their own.
>

The whole point about the Collections classes (indeed, about all of the Java API classes) is that they are implemented using plain-old Java syntax, and that there [i]aren't[/i] any special cases made for them in the language syntax specification. What you suggest is an ugly, ugly hack that effectively defines two versions of the JLS - one that Sun gets to use for the API classes, and restricted JLS that the rest of us plebs are restricted from using "for our own good", in case we hurt ourselves. What a nightmare.

No, Sun did exactly the right thing.

> More often than not, user-based Generics have turned
> to ash in my mouth.
>

And what about all the times that user-based code has turned to ash in your mouth because Generics weren't available and users have violated constraints that were specified in documentation only?

It seems to me that your problem is with [i]users who write bad code[/i] - not with Generics. Generics or not, there will always be people who write bad code. I had plenty of experience with bad user code that "turned to ash in my mouth" before Generics came along.

Bottom line: don't blame Generics for a problem caused by bad programmers.

prunge
Offline
Joined: 2004-05-06

I have used Generics in a major project and they definitely helped. Collections definitely benefit from them as well as reflection.

The problem with generics is that there is no easy way to integrate them with legacy code. Sure, @SuppressWarnings works, but if you ever muck up a @SuppressWarnings (e.g. make an assumption about a type that doesn't hold) things get really bad. Using the type checked Collections utilities (they perform type checking at runtime) don't work with all cases. You can't make a type checked List> for example. As far as I see there is no way to create a class that can type check types that are more than one level of generics deep - basically because at runtime we can check if an object is of a Map type but can't access the type parameters. This all comes down to no runtime support.

alexlamsl
Offline
Joined: 2004-09-02

I have to say that Generics has been really useful in my last few projects since.

The only argument I can think of against it thus far is type-erasure; but then I can understand the JSR group trying to introduce the feature & minimising its possible level of impact to the language.

Type-erasure can be removed (hence Generics augmented) in a backward-compatible manner anyway ;)

ewin
Offline
Joined: 2005-07-15

> For those who would like to ignore it all together,
> all you need is an option on the compiler to suppress
> generics related warnings

Only that Sun screwed that one up, too. The @SuppressWarnings annotation is broken in 5.0 and will only be fixed in 6.0.

Sun's QA was apparently on holiday when they build 5.0. It is hard to belief that this was not caught in a unit test or regression test.

mthornton
Offline
Joined: 2003-06-10

> > For those who would like to ignore it all
> together,
> > all you need is an option on the compiler to
> suppress
> > generics related warnings
>
> Only that Sun screwed that one up, too. The
> @SuppressWarnings annotation is broken in 5.0 and
> will only be fixed in 6.0.

Which is why I implied it was still needed.

kriegj
Offline
Joined: 2005-01-11

Looks like this thread is a bit stale now, but I couldn't help but add my two bits anyway...

Without meaning to pick on anyone in particular, I see a lot of moaning about generics here, and hardly anything good being said. Well, since I started using them at the start of this year I've been extremely impressed with the implementation that the designers have come up with. I have found it much more easy to understand than C++ templates, for example.

In all of my development, I often found that I would leave comments in the JavaDoc like "This must only contain objects of type [blah]". Generics gives me a simple way to let the compiler know these restrictions, so that it can detect violations at compile time. Previously, these bugs would not be caught until runtime. In fact, as a result of my efforts to retrofit generics to all of our code I managed to find one or two bugs of this type that had hitherto remained hidden. I have managed to specify our APIs a lot more rigorously and I have a lot more confidence in the robustness of our implementation.

Yes, some (though by no means all) generics implementations can be complicated. But they aren't complicated because of generics - they are complicated because the underlying problems that they describe are complicated. That's where *comments* come in. There is a JavaDoc facility to explain what the various type parameters are and what they do for the API user, and any good developer should comment the code itself for the benefit of maintainers. And for those who *still* find it too complicated - well, you can always ignore generics altogether and program as if they didn't exist.

Those who want runtime support - I agree that this might be a nice *additional* feature in some cases, but I completely disagree with any assertion that generics are useless without run-time support, or that generics should not have been added without run-time support. I mean, which would you prefer: a language that had compile-type type checking but no run-time checking, or one that had no type checking at all? Is it not worth adding compile-type type checking to a language, even if you don't add run-time type checking? I think the answer is obvious, and the same is true of generics.

Of course, the current implementation is probably not perfect. There are one or two areas where I think that the generics implementation could be improved. I'm submitted a couple of RFEs on some of these points, and most of them have been accepted. If anyone has any helpful criticisms, they should do the same.

cowwoc
Offline
Joined: 2003-08-24

kriegj,

Generics work very well for Collections, which are by far the best-defined easiest use-case for them. The problem is that Generics are very deceiving. They look like they could be useful for more complex use-cases but you quickly discover that you can *almost* express a constraint in Generics but not quite.

It would be nice if Generics (like operator overloading) were standard for predefined API classes (such as Collections) but were not added as a language feature. That is, Sun could define a small set of Generics uses but no user could create new classes which use them. As well, Sun could define a small set of operator overloading (i.e. String + operator) but no user can add their own.

More often than not, user-based Generics have turned to ash in my mouth.

Gili

fcmmok
Offline
Joined: 2005-05-12

agree !
if u was forced to write something like:
out.println(((Vector)((Vector)categoryTree.get(treeI)).get(categoryI)).get(0));
out.println(((Vector)((Vector)categoryTree.get(treeI)).get(categoryI)).get(1));

Generic is ur best friend !

cowwoc
Offline
Joined: 2003-08-24

fcmmok,

1) Don't try to squeeze as much as is humanly possible on a simple line. It leads to unreadable code which you should be avoiding anyway. If a NPE occurs, you'll thank your lucky stars if you space the calls across multiple lines so you know which dereferenced component was null.

2) Like I said, I am all for Generics in Collections; but no where else.

markswanson
Offline
Joined: 2003-06-11

Like any process that purposefully chokes its own feedback loop the JCP seriously limited its ability to produce and implement great ideas the community actually wanted implemented. It's been painful to watch Sun shoot itself in the foot for so long by totally failing to tap into such a brilliant and vibrant community.

I can agree the Generic's Javadocs are _utterly_ gross and non-standard and assembler-like with the single uppercase letters for type names. After a year with Generics on a major project my brain still grosses out over it.

But, while Generics are implemented as a simple auto-casting mechanism you have to give it some credit for the simplicity of the approach. Are you going to miss the space for the auto cast? Never. Are you going to worry over the speed lost to the cast? Never.

Generics have been a huge win for me. My code and any code that uses Generics that I read has become much clearer simply because I know what types are allowed inside containers.

Generics have also saved me enormous amounts of time after large refactoring sessions because previously all sorts of problems would occur after refactoring **during runtime** due to subtle casting problems. Now these subtle errors are caught at compile time.

Also, the error messages given by Generics are fantastic compared to the C++ template error messages.

Furthermore, Besides the _gross_ documentation and some subtle issues with arrays even newbies can easily benefit from being able to explicitly define containers. My gut tells me that due to the complexity of overly complicated Generics you aren't going to see much of the complicated cases anyway. Though if we do hopefully folks will stop using single character uppercase letters...

One last thing, some folks like to dis Generics but then fail to come up with a single reason why Generics are bad - which is quite a compliment to Generics. If you do have a reason why you don't like Generics please share it so we can all learn the pros and cons better.

Cheers.

cowwoc
Offline
Joined: 2003-08-24

I like the idea of improving type safety in Java. I have nothing but respect for Gilad Bracha and others that have worked on Generics.

That said, I am strongly against Generics in their current form. Six months ago, I was a vocal supporter. Having finally tried using the thing I advocated I realized what so many were complaining about.

The problem with Generics is that they're so weak (in terms of flexibility) that it was a mistake to introduce them in the first place. They reduce the readability of code and they are extremely inviting to newbies to try to introduce Generics into their user classes -- this, more often than not, turns out to be a *huge* waste of time and effort. The problem with Generics is that they look simple but in actually they are very complicated. Furthermore, they can't represent anything but the simplest use-cases. So they work great for Collections (I love 'em) but awful for anything else.

I strongly believe that Sun should have gone "all the way" or not at all with Generics. The situation as it stands right now is a disaster. While most of the JDK 5.0 features are fantastic, Generics are an eyesore.

I'm hoping that, like System.getenv(), if enough developers push Sun hard to fix it, they'll finally do something about it. In retrospect, manual casting and a loss of some type safety was bad ... but not nearly as bad as the amount of productivity lost dealing with Generics issues. It's *reduced* my productivity!

Gili

mthornton
Offline
Joined: 2003-06-10

Instead of bemoaning the current state of Generics, I suggest we start working out how to move on.

For those who would like to ignore it all together, all you need is an option on the compiler to suppress generics related warnings, plus a similar feature on the JavaDoc tool to erase the generic information.

For those who would like runtime generic type information, we need to work out how to do it and a migration path from where we are now.

If you don't like either of these options, I recommend investing in a time machine company and attempting to alter history! :-!

cowwoc
Offline
Joined: 2003-08-24

Many of the problems I've encountered would potentially be fixed by runtime generic type information, so I'm +1 on that.

I think one of the reasons this was not done in the first place is what happens if you take as input List

from an older API (that is not Generics-aware) and you want to take in List. You know that the original list contains Foos but Java will not let you make that assumption (rightfully so). I'd propose adding some sort of API that facilitates converting from List to List. You pass in the inputs and it does the casting for you, one element at a time. This implication is bad performance when intermixing non-Generics and Generics APIs but I think this is quite alright because a lot of code is being rewritten in terms of Generics at a very fast pace. Secondly, I'm strongly in favour of disallowing construction of Generics classes without their type argument. That is, one should not be able to construct "List" (the raw type). Generics is a very complex matter. I am far from being an expert on the topic. If there are any other implications of runtime Generics, now would be a good place to bring it up. What were the reasons for not doing this "right" the first time around? :)
mthornton
Offline
Joined: 2003-06-10

It is very hard to maintain compatibility with existing code (i.e. can't use an existing library in conjunction with code using generics on a new JVM) if you don't use erasure. This problem would slow the adoption of generics --- you would have trouble using it until all the 3rd party libraries you used had also converted.

Now if in a few years time most libraries have been converted to use generics, then a shift to retaining type information would be less painful. It would be even better if the actual type arguments were placed in the byte code (in a manner ignored by current VMs). Inevitably some code would still have to be changed --- anything using reflection is likely to create an object of the base type (i.e. with all type arguments defaulting to the lowest common type). To make conversion managable, new code would have to tolerate the existence of such objects that did not have the expected type information.

alexanderschunk
Offline
Joined: 2005-03-24

[i]For those who would like runtime generic type information, we need to work out how to do it and a migration path from where we are now.[/i]

That would be hard to achive since the compiler has even problemes to identify existing Objects during runtime when you use a deep enough class hierarchie and casting.

However, it would be great to hear of new improvements on the Generics issue but i think most fixing should be done concerning their syntax.

My personal favorite would be to reduce Generics to collections only - which makes sense.

Or - if you want to create a new Object liek T o of type T.

alexlamsl
Offline
Joined: 2004-09-02

IMO Generics that is added to J2SE 5 is a good enough move - it gives us the developers a chance of utilising this tool whilst keeping every aspect of Java as it was (backward-compatible it's called ;) )

In fact, it is at all possible to provide better support for Generics in latter versions of Java; alternatively, if it turns out to be less popular than it is expected, it can be kept within the language without doing any damages overall. That is the beauty as I see in Java's implementation of Generics so far.

mcnepp
Offline
Joined: 2005-06-22

I wholeheartedly agree: Introducing Generics [b]without proper runtime support[/b] was probably the biggest single mistake in the history of Java.
This hurts all the more because all the other features introduced with Java 5 (annotations, enhanced for, enums, covariant return types, variable argument lists) are huge improvements, each of them easy to use and without negative side-effects on other aspects of the language.
So developers are facing the tough decision of either not using [i]any of the new features[/i] or being flooded by warnings due to a half-baked generics implementation.

mcnepp
Offline
Joined: 2005-06-22

That said (I pressed "Post" inadvertently),

I also have to agree with Mark Thornton:
there was ample time to voice objections to the planned Generics extension before it shipped with Java 5.
I somehow overlooked the severe implications of JSR 14 during the CAP/Early Access phase for Java 5.
So we have to blame ourselves for not speaking up louder when we had the opportunity.
There's no use crying over spilt milk - although it's sometimes fun ;-)

alexanderschunk
Offline
Joined: 2005-03-24

i use Java only since 2 or 3 years or so and i did not really took notice of what was going inside java and new language features simply because i was spending most of my time with learning those langauge features that were available.

However, i am not sure wheather or not Sun would really reject implementing Generics only because of a reasonable number of objections from the community. Even if i would gather a 1000 of emails send to James Gosling to stop considering to implement Generics in Java i am very sure he would persuade it anyway.

mthornton
Offline
Joined: 2003-06-10

Generics didn't appear in Java overnight. JSR 14 was many years in progress, with any number of opportunities for the community to comment on the proposals. Yet vocal opposition was rarely heard until the final months of the JDK 5 beta and after final release.
While generics as delivered in JDK5 isn't quite what I would have liked, I understand why it is as it is. I did make comments on the proposals and received carefully considered replies.
So where were you during those previous years?

Mark Thornton

ewin
Offline
Joined: 2005-07-15

What people tend to forget is that the majority of Java developers is not involved in the JCP at all. It is difficult to participate for individuals (sign a ten page legal document ...), the web site is a disaster (search? what search?), the documents are not obvious, the process is optimized for corporate participants, mostly big guys run the show, individuals can't spend the time to sit through committee meetings etc.

As a result, the majority of Java developers do see new features in detail only when a new Java version is released, or close to be released.

Please note that I said "in detail". The common programmer hears about yet another feature or change earlier, but not in detail. It is just one of thousands of hyped "we have a great new feature for you" messages, without substance.

There were a few voices out there that warned early that generics will be a similar clusterfsck to templates. Since these were outside of Sun's realm, and definitely not part of the JCP, the were not heard.

I think, the blame should not be put on programmers, but on the people in the JCP who pushed their idea without any regard for the common programmer. It might have escaped Sun and the particular JCP members, but the "Java community" is not what can be found at www.java.net, www.jcp.org or other Sun web sites. It is the huge bunch of programmers out there who have to solve real world problems instead of participating in committee work. Sun might like to see itself as the center of the Java universe, but they have no idea of that universe. And the programmers in all corners of the universe have been ignored.

It is now, when 5.0 starts to get a larger user base that people recognized what the JCP people have stirred together. And it doesn't look good.

It was a major mistake to copy the C++ syntax. It was also a major mistake to copy the C++ style of using single uppercase letters for type names. It was a major mistake to not enhance the bytecode to support generics, but instead come up with a huge pile of special rules so that type erasure works - mostly. Generics are implemented as a big fat ugly hack.

It is still a major mistake that there is no good tutorial for generics (the existing PDF file is really not worth the electrons). It still requires extensive study of the language spec. to figure out the finer details (which isn't a really entertaining read). Without Angelika Langer's FAQ most developers would be completely lost. And letting your developers get lost is a very big mistake if you want to gain acceptance of a language feature.

The JCP work didn't contain a requirement to come up with a good generics tutorial. Which shows another shortcomings of the process. It doesn't require to produce quality examples, API and tutorial documentation. It has no QA step to ensure that such documentation is available.

mthornton
Offline
Joined: 2003-06-10

I didn't join the JCP either (I notice that it has become a lot easier recently). Nevertheless I am satisfied that those who bothered to email or contribute to the forum were heard. Indeed there were significant changes at several points in the progress of JSR-14.
However the major constraint was set right at the beginning --- compatibility with existing code.