Since they were released simultaneously, I consider them to be tightly coupled. For instance, here are simplified versions of an interface and implementation I recently wrote:
Note: I am having difficulty representing greater-than and less-than symbols in Blogger's editor, so you'll have to do with { and }.
Version 1: Java 5, Generics, Covariant return types
public interface Model{T extends Model{T}} {
T read(InputStream in);
T write(OutputStream out);
}
public class MyModel implements Model{MyModel}Thanks to the covariance, I can write a method chain like this:{
public MyModel read(InputStream in) {
...
}
public MyModel write(OutputStream out) {
...
}
public MyModel setName(String name) {
...
return this;
}
public String getName() { ... }
}
new MyModel()
.read(in)
.setName("foo")
.setStopAtMain(false)
...
.write(out);
With Java 1.4, the code would have to look like this
Version 2: Java 1.4
public interface Model {And the method chain would result in a syntax error:
Model read(InputStream in);
Model write(OutputStream out);
}
class MyModel implements Model {
public Model read(InputStream in) { ... }
public Model write(OutputStream out) { ... }
...
}
public static void foo() {Which you could hack around with an ugly cast:
new MyModel()
.read(in)
.setName("foo")
^ The method setName(String) is undefined
for the type Model.
.write(out);
}
public static void foo() {Back to the Java 5 example: My point is just this: covariant return types don't require generics. All that messy code in version 1 could look much simpler because covariant return types exist on their own without generics:
((MyModel) new MyModel()
.read(in))
.setName("foo")
.write(out);
}
Version 3: Java 5, Covariant return types
public interface Model{{T extends Model{T}}
TModel read(InputStream in);
TModel write(OutputStream out);
}
public class MyModel implements ModelLesson learned: I know generics fairly well, but there's a difference between knowing when it's useful and when it isn't. Said another way: when you have a Generic hammer everything looks like a generic nail.{{MyModel}
public MyModel read(InputStream in) {
...
}
public MyModel write(OutputStream out) {
...
}
public MyModel setName(String name) {
...
return this;
}
public String getName() { ... }
}
Thanks to David Plass for pointing this out.
8 comments:
Oh, how i wish that every Java programmer were required to study this lesson before being given their license to use generics. It's a bit crude, but I've often used this joke to illustrate my feelings about generics:
Q: Why does a dog lick his genitals?
A: Because he can.
That is what I think of when I see the rampant abuse of this language feature - so many programmers are licking themselves just because they can.
I've blogged about it in the past, with references to some "big thinkers" who agree, here and here.
Actually, Rob, Version 1 isn't covariant return types; it's plain old generics. Because the T gets "replaced" by MyModel (or something like that. Maybe I should ask Kevin to tell me a story.) If you had another class (public class DavidsModel extends MyModel) and then the read method returns DavidsModel, then *that* would be covariant return values.
But thanks for the shout-out.
Version one still is covariant return types, it's just masked by the generics. In this example, T read(in) is compiled to
Model read(in)
Model write(out)
as method signatures. Which is the mistake I made.
Now I'm not a language expert, so someone may come along and explain why proper covariance isn't really in Java, or that it's not covariance when an interface is in the equation. Who knows?
Yeah, Rob, you're right about version 1.
Although... it's not quite covariant unless the different T's are subclasses of each other.
For example, you could write
public class Foo implements Model{Integer}
and it would no longer be covariant(ic?) with MyModel.
David, since version one has Model defined as
interface Model{T extends Model{T}}, it's impossible to write
class Foo{Integer} since Integer does not extend Model.
Post a Comment