When generics were introduced to Java in JDK 5, many developers reacted with ambivalent feelings. While introducing generic types to the language seemed at first to solve many lacks in Java's type system, the way generified code appeared caused developers to think that generics were not a well-designed addition to Java.
Since JDK 5, many libraries and APIs have been generified, allowing developers to have first-hand experience with Java generics. One of the more interesting aspects of that experience is illustrated in Brian Goetz's latest IBM developerWorks article, Going Wild With Generics:
Bounded wildcards are extremely useful in making generic APIs more flexible. The biggest impediment to using bounded wildcards correctly is the perception that we don't need to use them! Some situations will call for lower-bounded wildcards, and some for upper-bounded ones...
It turns out some libraries did not at first specify wildcards correctly, making generified versions of those APIs hard to use. The main issue had to do with the fact that Java generics are not covariant, unlike arrays, for instance. Thus, while an array of Integers is also an array of Numbers, a generified List of Integers is not also a List of Numbers, even though Integer is a subtype of Number. Goetz explains this issue in the article:
People can argue over which choice was "right" and which was "wrong"—of course, both have pros and cons—but there is no question that having two similar mechanisms for constructing derived types with subtly different semantics is a substantial source of confusion and mistakes... Bounded wildcards (those funny "? extends T" generic type specifiers) are one of the tools the language provides for dealing with the lack of covariance—they let classes declare when method arguments or return values are covariant (or the opposite, contravariant).
The rest of Goetz's article describes how to use wildcards correctly, including how to specify upper and lower bounds for generic method parameters:
Things get ... complicated ... when we want the API to deal not only in variables of type T, but in types generified over T... Let's say we want to add a new method to [a] Box that allows us to fetch the contents from another Box and put it in this one:
public interface Box<T> {
public T get();
public void put(T element);
public void put(Box<T> box);
}
The problem with this extended Box is that we can only put the contents in a Box whose type parameter is exactly the same as the receiving box... So, for example, the code [below] won't compile:
Box<Number> nBox = new BoxImpl<Number>();
Box<Integer> iBox = new BoxImpl<Integer>();
nBox.put(iBox);
Goetz notes that in order to solve this problem, you can specify upper and lower bounds for the generic type parameter:
public interface Box<T> {
public T get();
public void put(T element);
public void put(Box<? extends T> box);
}
However, this sort of notation can get unwieldy in certain situation, notes Goetz:
In Java 6, this signature [in java.util.concurrent.AbstractExecutorService] was changed to Collection<? extends Callable<T>> — but to illustrate how easy it is to make this mistake, the correct fix would have been to make invokeAll() take an argument of Collection<? extends Callable<? extends T>>. The latter is definitely uglier but has the benefit of not boxing in your clients...
In addition to upper bounds, it is also possible to specify a lower bound for a generic type parameter, and Goetz provides some rules about defining such limits.
What do you think of generics in Java? To what extent do you use generic type parameters when you define your own APIs?
I seldom have a need to add from one Box to another. (More realistically, from one Collection to another). When I do, and I know the casting is o.k., instead of fighting with all the wildcard crap that ends up like using consts in C - once you start, it goes on and on forever!, I go something like
@SuppressWarnings("unchecked") // we know this cast is correct because ... List foolGenerics = theListOfTypeT1; List<T2> listOfT2s = (List<T2>)foolGenerics; // do something with listOfT2s
If this gets done often for one particular List, it becomes a little utility method.
Basically, instead of trying and failing to understand the wildcard syntax, I cheat. :-) The 98% of the time I don't need the wildcards, the generics are simple and useful.
> Basically, instead of trying and failing to understand the > wildcard syntax, I cheat. :-) The 98% of the time I > don't need the wildcards, the generics are simple and > useful.
In your example, is T1 a subtype or supertype of T2?
> In your example, is T1 a subtype or supertype of T2?
Yes. :-) It will always be a subtype or supertype, not a side-type. Looks like it's always a subtype in my actual code, but I think this trick also works for supertypes.
Usually our collections can be of the supertype,and no conversion is necessary. (we apparently do a good job refactoring and organizing the classes, and our hierarchies aren't all that deep) Once in a while, a collection (or the signature from a method call that returns the collection) is of a subtype, (often older code) but, due to context or some earlier check, we "know" that it is "safe" to cast everything.
So, 95% of the lists are List<AbstractFoo>. In the rare cases we really want a List<BlueFoo>, we know that it should be safe because of some database value or whatever.
> > In your example, is T1 a subtype or supertype of T2? > > Yes. :-) It will always be a subtype or supertype, not a > side-type. Looks like it's always a subtype in my actual > code, but I think this trick also works for supertypes.
I'm not so sure it will. If T1 is a supertype of T2, you'll get ClassCastExceptions when you try to read from the collection unless every element in the collection was actually an instance of T2.
To make this more concrete if you do this:
void foo(List<Object> input)
{
List<String> strings = (List<String>)(List)input;
for (String s : strings) { // CCE here, I think
// do stuff with s
}
}
And your trick is only safe with subtype lists if you don't try to write back to the list, of course. From the rest of the response below, I'm guessing you just know that this will work and aren't bothering enforce the typing.
> Usually our collections can be of the supertype,and no > conversion is necessary. (we apparently do a good job > refactoring and organizing the classes, and our > hierarchies aren't all that deep) Once in a while, a > collection (or the signature from a method call that > returns the collection) is of a subtype, (often older > code) but, due to context or some earlier check, we "know" > that it is "safe" to cast everything. > > So, 95% of the lists are List<AbstractFoo>. In the rare > cases we really want a List<BlueFoo>, we know that it > should be safe because of some database value or whatever.
So in effect, what you are saying is that the only value you are getting from generics is getting rid of explicit casts. I wonder how prevalent this is because it's basically a pretty damning critique of Java generics.
It's probably worth discussing this subject in terms of variance on generic types wrt subclassing. As I see it there are four distinct approaches: 1. Use-site variance (wildcards in Java) 2. Definition-site variance (see .NET CLI, Scala) 4. No variance or invariant (see C#) 3. Covariant definitions aka Array-style variance (see Eiffel)
In my opinion the worst possible approach is #1 Use-site variance. Witness the utter failure that is wildcards in Java. Essentially the problem with use-site variance is that it places the burden on the user of a generic type and not the type itself. A terrible design decision. One of my favorite, yet subtle, wildcard warts deals with type-variable references in method signatures. Ever wonder why Object appears in so many method signatures in collection classes e.g., List.indexOf()? Wildcards is to blame.
Definition-site variance is a much more usable design. Generics in Scala uses this approach. Basically a generic type declares whether or not it behaves covariantly or contravariantly. Uses of the type are straight forward. While I find this an improvement over wildcards, it's still very difficult to grasp and often leads to fractured designs in terms of mutable/immutable interfaces; the same sort of problem with Object in method signatures in Java collections.
Interestingly some languages treat generic types invariantly. C# behaves this way. At first it seems this would just not work, but in actuality it works out ok, but makes for some hacky code. It does have the benefit of not confusing normal everyday programmers with the subject -- generic variance is tough to grasp.
Array-style variance is more or less a simplified version of definition site variance. The idea is that all generic types behave covariantly wrt subtyping, just like arrays in Java and other languages. Covariant arrays is to this day a hotly debated issue, yet the "sound type system" side of the argument does not have much of a foundation in terms of practicality -- how often have you ever seen an ArrayStoreException in Java? I've probably seen one or two in my 12 years writing Java. Eiffel chose this approach with generics and I have to admit it's probably the most straight forward, pragmatic concept; it's what most people probably expect and, therefore, it's probably the best approach from a usability standpoint (that's what a language should focus on IMHO). Read the following discussion for more on this topic: http://www.artima.com/forums/flat.jsp?forum=106&thread=222021
> I'm not so sure it will. If T1 is a supertype of T2, > you'll get ClassCastExceptions when you try to read from > the collection unless every element in the collection was > actually an instance of T2.
Correct. But due to some earlier check we "know" that it's "safe".
> And your trick is only safe with subtype lists if you > don't try to write back to the list, of course.
Correct. I should really return an unmodifiableList.
> So in effect, what you are saying is that the only value > you are getting from generics is getting rid of explicit > casts. I wonder how prevalent this is because it's > basically a pretty damning critique of Java generics.
I think you get more than just "no casts", but if your point is that Java generics aren't worth the hassle for really complicated class hierarchies, I'd agree. A unit test should catch you adding a BlueGreenFuzzyFoo to a list of GreenSmoothFoos (by throwing an icky CCE).
What I think you get, all more valuable than no need to type the cast:
1) documentation. you know that is is a List<Foo>. I can't count the times I've run the debugger and looked to find out what was in a list cause the code was so complicated... 2) A lot less classes. You (often) don't need a FooList and a FooEvent and a FooListener and a FooDohickey class, plus a BarList, BarDohickey..., you just need a Dohickey<T> 3) decent compile time checking for the simpler cases. At least you can't add a StripedBar to those lists above. 4) there's probably more but that's a start.
I am one of the few that likes the Java generics implementation. Just stop banging your head against the wall when they start hurting too much. :-)
> 1) documentation. you know that is is a List<Foo>. I > can't count the times I've run the debugger and looked to > find out what was in a list cause the code was so > complicated... > 2) A lot less classes. You (often) don't need a FooList > and a FooEvent and a FooListener and a FooDohickey class, > plus a BarList, BarDohickey..., you just need a > Dohickey<T> > 3) decent compile time checking for the simpler cases. At > least you can't add a StripedBar to those lists above. > 4) there's probably more but that's a start.
Unless I am misunderstanding something, the approach you have described eliminates at least some of the value of 1 & 3 above.
1: Your code might say that it's a List<Foo> but the compiler can't guarantee it because you are adding unsafe casts. I understand that you are doing the work to make sure that this is the case at least to a reasonable level but that's just how things were before generics. A comment can give you this much information.
3: Using these types of casts means that you could potentially add a StripedBar to one of these lists.
I'm not saying this is going to happen but most of the complexity of generics is built around making it so that the compiler guarantees these relationships instead of the developer having to do so. A much simpler generics implementation like one that mimics the variance rules for arrays would fit your approach much better.
So even though you say you like generics, you are actually presenting anecdotal that the Java implementation of generics is not what Java developers want or need.
If they comment, and keep it up to date. Generics have a huge advantage that they stay up to date.
> 3: Using these types of casts means that you could > potentially add a StripedBar to one of these lists.
I guess I'm just missing or misunderstanding all the problems everybody else faces. I use Lists of a fairly abstract class or interface. In general, if a List comes from somewhere else, I do not add to it. (Hmm, with 100 cores coming this convention looks better every day!!!) That's their list anyway, with threading issues I don't know if it's safe to add to it. I may collect a bunch of lists into one, but that's into my list.
Voila - harly any wildcard problems.
As I understand it, the whole wildcard weirdness (gee, how can a List<String> not be a List<Object>???) is due to some use cases that I just avoid. The few cases I can't avoid them, I cheat rather than spend hours adding ? extends only to find that is still doesn't work cause capture #8 in incompatable with something incomprehensible.
I really wish Java had never added wildcards and just used array-style covariance in for everything.
In my experience there's always a temptation to overuse generics to try to eke out every last bit of type safety, because it seems pretty cool when it all fits together. Then I realize that I've just spent two hours tweaking code to make it more fully type safe, that I've left behind an incomprehensible mess for whoever looks at the code next, and that I've done all that to prevent an error that would either be a blatantly-obvious ClassCastException at runtime or that I could have guarded against at runtime with a really stupid, really obvious 30-seconds-of-typing assertion.
Wildcards just kind of make that whole mess worse, and they tend to cascade pretty horribly if you get them even the tiniest bit wrong; if you have to insert a wildcard where there wasn't one before, be prepared to have that change ripple through your entire codebase.
I think there are very good uses for generics, but the wildcards and the covariance behavior makes it incredibly painful to use generics as method return values or, to a lesser degree, method parameters.
As always, type safety is a means to good code, not an end in and of itself, and there are always tradeoffs to be made there. Complicating the language in the name of making it more type safe might look good from a theoretical point of view but often works out very poorly from a practical point of view.
> > A comment can give you this much information. > > If they comment, and keep it up to date. Generics have a > huge advantage that they stay up to date.
I guess.
> > 3: Using these types of casts means that you could > > potentially add a StripedBar to one of these lists. > > I guess I'm just missing or misunderstanding all the > problems everybody else faces. I use Lists of a fairly > abstract class or interface. In general, if a List comes > from somewhere else, I do not add to it. (Hmm, with 100 > cores coming this convention looks better every day!!!) > That's their list anyway, with threading issues I don't > t know if it's safe to add to it. I may collect a bunch > of lists into one, but that's into my list.
If you never add to the list, you don't need anything to prevent the wrong thing from being added to it.
Your approach is sound but I'm trying to point out that your reasoning for why it's OK to break the soundness of generics or 'cheat' is that you don't really need those soundness guarantees. If you don't use them and don't need them, then they aren't providing value. You aren't really using generics the way they are designed to be used, you are working around them. In other words, they are great as long as you ignore all the stuff about them that isn't so great.
> As I understand it, the whole wildcard weirdness (gee, how > can a List<String> not be a List<Object>???) is due to > some use cases that I just avoid. The few cases I can't > avoid them, I cheat rather than spend hours adding ? > extends only to find that is still doesn't work cause > capture #8 in incompatable with something ncomprehensible.
The whole wildcard weirdness is based on highfalutin theories about types and what you have described is the exactly why people dislike Java generics. You are saying you like them and describing why they suck. You are basically using Java generics as if they worked like arrays in Java. This is actually one way that generics could have been implemented in Java and I've come to the conclusion that that would have been the right choice.
> As I understand it, the whole wildcard weirdness (gee, how > can a List<String> not be a List<Object>???) is due to > some use cases that I just avoid. The few cases I can't > avoid them, I cheat rather than spend hours adding ? > extends only to find that is still doesn't work cause > capture #8 in incompatable with something incomprehensible.
It comes up if you try to use generics with interfaces or abstract classes. Say I have a method on the interface that returns List<Object>, but on a new implementing class I've just written I know it'll always be a List<String>. But I can't just keep a List<String> as a local variable on the class, because I can't return it from the method that requires a List<Object>. So I either have to keep a List<Object> on the implementing class and cast everywhere that it's used, which kind of defeats the point of trying to use generics at all, I have to do something abhorent like return (List) _stringList, or I have to go back and change the supertype to return List<? extends Object>. That might mean I have to change every class that implements that interface to change the method signature, and if anything takes that method result and passes it in to something that was written to take List<Object> as an argument, well that's not going to work, etc. So it just keeps cascading through.
So if you ever have to create a method that has a generic type (particularly a Collection) as a return type or an argument parameter, you have to decide at the time you write that method if you'll need to use wildcards and if that method will ever be overridden or pulled out into an interface, and defensively sprinkle them all over the place just in case. Otherwise you have to put hacks all over your classes that implement those interfaces or override those methods. There just aren't any good options.
> basically using Java generics as if they worked like > arrays in Java. This is actually one way that generics > could have been implemented in Java and I've come to the > conclusion that that would have been the right choice. Except that arrays are run time type checked on assignment; an option not available with erasure. I would be rather uncomfortable with allowing unchecked stores into covariant types.
> gee, how can a List<String> not be a List<Object>???
To add to what Alan wrote, in a nutshell, you can't add a Integer to a List<String> but you can add an Integer to a List<Object>. It's the same for arrays. You can't add an Integer to an String[]. The difference is that you can assign an String[] to an Object[] and attempts to add Integers to that array will fail at runtime.
Flat View: This topic has 23 replies
on 2 pages
[
12
|
»
]