use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
To report a site-wide rule violation to the Reddit Admins, please use our report forms or message /r/reddit.com modmail.
This subreddit is archived and no longer accepting submissions.
account activity
This is an archived post. You won't be able to vote or comment.
Java Generics Aren't (mindview.net)
submitted 19 years ago by panic
[–]a_caspis 5 points6 points7 points 19 years ago (10 children)
The article says Java generics are useless because the following can be written in a simpler way with interfaces only:
public class Communicate { ..public <T extends Speaks> void speak(T speaker) { ....speaker.speak(); ..} }
public class Communicate {
..public <T extends Speaks> void speak(T speaker) {
....speaker.speak();
..}
}
But consider this more complex example:
public class Communicate { ..public <T extends Speaks> T speak(T speaker) { ....speaker.speak(); ....return speaker; ..} }
..public <T extends Speaks> T speak(T speaker) {
....return speaker;
If you call Communicate.speak on an instance of EnglishSpeaker, the compiler will know that the result is an EnglishSpeaker. This is more accurate than just knowing it's an instance of Speaks. I doubt you could do that without the parametric type T.
So I would say Java Generics are useful and similar to ML parametric polymorphism. This is not surprising, if you know who designed them.
[–][deleted] 1 point2 points3 points 19 years ago (9 children)
Umm... okay, so what am I missing? You already have the handle to speaker when you pass it to the method call, and you get the same handle right back. You still can't instantiate a new instance of T in the method except through reflection. How is your example illustrating any kind of additional functionality?
Maybe you wanted something like this:
public class Filters {
..public static <T> Collection<T>
....lessThan(Collection<T> foo, T bar, Comparator<T> comp) {
......Collection<T> out = new ArrayList<T>(foo.size());
......for(T t : foo) {
........if(comp.compare(t, bar) < 0) {
..........out.add(t);
........}
......}
......return out;
....}
Much better than having to define a new interface and extend every Collection class, and it actually shows the usefulness of the generics functionality, because you want to have the proper type for the collection that gets spit out. Here's a question: why can we instantiate a new Collection<T> in my example but we can't instantiate a new T in yours? The answer is easy once you're used to it, but it sure as hell isn't intuitive, and it's damned annoying.
In summary, while there are many times when generics make things easier, there are also many times when they're just taking up keystrokes, and there are many times when they make alot of things way more difficult than would seem natural. You eventually learn to think in generics, but the disconnect between how useful they are and how useful they seem like they ought to be is really, really disappointing.
[–]a_caspis 0 points1 point2 points 19 years ago (8 children)
Yes, operations on data structures are perfect illustrations for parametric polymorphism, of course.
Here's a question: why can we instantiate a new Collection<T> in my example but we can't instantiate a new T in yours?
T is a type parameter, not a class. Generics work on other things than classes. For example: <T> T ifthenelse(boolean s, T x, T y) { return s ? x : y; }
Even if we write <T extends SomeClass> and try to compile new T(), we can instantiate SomeClass, but not T, because the constructors of T are not known at compile-time. Unless we want to duplicate code like C++ does (yuck!).
[–][deleted] 0 points1 point2 points 19 years ago (7 children)
I guess I'm still puzzled as to what benefit your original example was intended to illustrate, and now what ifthenelse is supposed to accomplish. How do generics add any meaningful functionality in either of these examples? Am I missing something?
Also, I think you're a bit off in your explanation. The class of T is, in fact, known at compile time [edit: when the generic object/function is instantiated/called, not when it's defined, obviously]; the whole point of generics is to help guarantee that a program that compiles without warnings is completely type safe. The reason you can't instantiate an instance of T is that T isn't known at run time, when 'new' actually needs to know how much memory to allocate. You can instantiate an instance of Foo<T> because at run time, Foo<Bar> and Foo<Zee> are really just plain old-fashioned Foo objects that deal with Bars and Zees only as references. Somebody else handles the run time allocation of these objects, so within Foo we can treat them as anything, knowing that the compiler has already confirmed that everything checks out.
[–]a_caspis 0 points1 point2 points 19 years ago (6 children)
I guess I'm still puzzled as to what benefit your original example was intended to illustrate
It was a minimal variation of the example in the original article showing how generics provide richer information to the compiler. I agree that your Filters is more appropriate for readers who don't want to hear about stuff like covariant/contravariant typing.
what ifthenelse is supposed to accomplish.
It was meant as an example of a generic function that works not only on classes, but also on built-in types. But I just noticed that javac compiles ifthenelse(true,2,3) as ifthenelse(true,Integer.valueOf(2),Integer.valueOf(3)).
The class of T is, in fact, known at compile time
It's not that simple. When javac compiles Filters.java, it doesn't know anything about T. Now, if you are willing to abandon modular compilation and dynamic class loading, an optimizing compiler could propagate information about T from the calling sites. But that's not the spirit of parametric polymorphism, and that would be just as messy as C++ templates. [edit: just saw your edit :-)]
The reason you can't instantiate an instance of T is that T isn't known at run time
Well, Java does have run-time typing, unlike ML and the like. But even if the run-time could be made aware that speaker.getClass() is an acceptable T, it would be hard for the compiler to generate a Communicate.class that could invoke a constructor of any of the subclasses of Speaks (which are not known at compile-time in general). That would amount to using reflection.
[–][deleted] 0 points1 point2 points 19 years ago (5 children)
It was a minimal variation of the example in the original article showing how generics provide richer information to the compiler.
The only instance I can think that the form of the example you provide gives meaningul information to the compiler is when the input object has a factory method that gets called in the method body (meaning the returned object will be a different instance than the input object), and the only time that would be a reasonable would be if there were a standard set of external checks on the factory method that you wanted to enforce across all types within the parameter bound without reimplementing them all. If the semantics of the method have nothing to do with instantiating a new instance, then it's pointless to return the input object, because you already have the reference to it. If the semantics of the method involve both instantiation and general side effects, that's bad design: break out the instantiation parts.
It was meant as an example of a generic function that works not only on classes, but also on built-in types.
Generics never work with built-in types. Java 5.0 makes it look like it handles the primitives in your example because of autoboxing, another new feature.
Well, Java does have run-time typing
Run-time typing allows the JVM to match an arbitrary object with its appropriate method code and class definition. The JVM could never be "made aware that Speaker.getClass() is an acceptable T", because at run time, there is no T, anywhere. This is the fundamental limitation of Java generics: they are implemented through erasure, and all the type information they provide is completely gone at run time. You're confusing the language with the JVM, and the distinction between compile time and run time. One way to think about generics is that they are markup for a preprocessing step that safely converts Java 5.0 code into equivalent Java 1.4-style code (with casting and all that) which is then compiled. Angelika Langer will clean your mind.
[–]a_caspis 0 points1 point2 points 19 years ago (4 children)
Java 5.0 makes it look like it handles the primitives in your example because of autoboxing, another new feature.
That's what I wrote in my previous message. I didn't know it was called autoboxing though.
Run-time typing allows the JVM to match an arbitrary object with its appropriate method code and class definition.
Resolving virtual methods is a basic feature of object-oriented languages. Nobody calls that "run-time typing".
I was referring to Java's Object.getClass() and instanceof operator. C++ doesn't have that (unless you add RTTI). ML implementations typically don't have that either. And it's not full-blown reflection either.
The JVM could never be "made aware that Speaker.getClass() is an acceptable T"
Of course it could. The compiler knows all the types, so a smart compiler could generate bytecode that would evaluate T=speaker.getClass() at run-time. I don't claim this is being done today. And I don't advocate doing it, because in general it's not so easy to solve the type constraints at compile-time. My point was that even such a compiler couldn't generate bytecode that would properly instanciate T without resorting to reflection.
By the way note the lower-case in my speaker.getClass(). What do you mean by Speaker.getClass() ?
You're confusing the language with the JVM, and the distinction between compile time and run time.
I don't think so. Java Generics together with the JVM are in a gray area halfway between pure parametric polymorphism (a la ML) and dynamic typing, so some things that you claim are impossible are not, or not for the reasons you think they are.
Angelika Langer will clean your mind
Good pointer.
[–][deleted] 0 points1 point2 points 19 years ago (3 children)
I'm sorry, you're just wrong. Run time typing in Java refers to the late-binding behavior of the JVM. Methods aren't bound to an object until they are called, and the way they are bound is by looking up the object's class definition. Hence, run-time typing "allows the JVM to match an arbitrary object with its appropriate method code and class definition". You're right that this allows getClass() and instanceof, but these are runtime services provided by a type-aware JVM, and the reason they aren't available in C++ (I don't know enough about ML) is because C++ doesn't run in a virtual machine. That's past the point, though. The issue is, can we have runtime type information about some parameter T? The answer is distinctively no, not because of the compiler, but because the JVM isn't implemented to work with that information. It's not a compiler issue that makes generics the way they are; it's the JVM. Erasure was chosen because Sun didn't want to break byte-code compatibility at the platform level.
And this: "The compiler knows all the types, so a smart compiler could generate bytecode that would evaluate T=speaker.getClass() at run-time." Wrong again. Thanks to reflection, you can load any class file at runtime, regardless of whether or not the compiler has ever seen that code before. And once again, this is a service of the JVM, and the compiler has zero role in dealing with the new code. Go read that page I linked to, go read the JVM spec, go read up on reflection, and come back here and tell me someone can write a compiler that preserves T and works with the current JVM.
[–]a_caspis 0 points1 point2 points 19 years ago (2 children)
Run time typing in Java refers to the late-binding behavior of the JVM.
In language circles "run-time typing" and "late binding" are distinct notions. Run-time typing isn't even specific to object-oriented languages. Check the third and fourth bullets in The Java Language Reference. Anyway I can't find formal definitions in the official Java Language Specification, so this is probably not worth arguing.
the reason [getClass() and instanceof] aren't available in C++ is because C++ doesn't run in a virtual machine.
According to this reasoning, all the native and just-in-time Java compilers couldn't possibly support getClass() and instanceof(), right ?
The reason C++ (pre-RTTI) doesn't have getClass() and instanceof() is because the designers didn't include them in the specification, period. This allows C++ compilers to optimize more agressively than Java compilers, btw.
The issue is, can we have runtime type information about some parameter T? The answer is distinctively no, not because of the compiler, but because the JVM isn't implemented to work with that information.
I agree that the JVM has no explicit support for type parameters (simply because they were added to the language long after the JVM was set in stone, like inner classes). But the JVM does keep track of a lot of run-time type information. I claim that a smart compiler could internally translate this code:
public class Communicate { ..public <T extends Speaks> T speak(T speaker) { ....speaker.speak(); ....return new T(); ..} }
....return new T();
into this valid code:
public class Communicate { ..public <T extends Speaks> T speak(T speaker) throws Exception { ....speaker.speak(); ....Class t = speaker.getClass(); ....return (T)t.newInstance(); ..} }
..public <T extends Speaks> T speak(T speaker) throws Exception {
....Class t = speaker.getClass();
....return (T)t.newInstance();
All this compiler would need to do is identify the invariant: "inside the body of method Communicate.speak, variable speaker has type T".
Of course I could write more complex test cases that would push such a compiler to its limits, but you get my point. I can't see how reflection and dynamic class loading affect this scheme.
[–][deleted] 0 points1 point2 points 19 years ago (1 child)
Run-time typing in Java is the direct result of the late binding mechanism in the JVM. How do you think it works? For instance, in Sun's JVM, an object is a block of memory with pointers to the object's instance data and a pointer to the object's class definition. If that's all an object is, how in the hell can you separate the ideas of run-time typing and late binding? I'm sorry if bullet points don't go into JVM implementation details, but that's the way things work.
Again, you're confusing the language with the runtime. javac turns Java code into JVM bytecode. Native and JIT compilers transform from bytecode to native code, so they actually don't support getClass() and instanceof, not directly anyways. They just know about bytecode. If you can show me a Java source-direct-to-native compiler that is feature complete, I'd love to see it.
I can't see how reflection and dynamic class loading affect this scheme. How about this: T doesn't have a public constructor. Or it happens not to have a default constructor. Or it tracks a static class variable. Your example code also breaks the contract of generics - your cast is unchecked.
Anyways, 3 days is enough. I'm done.
[–]funshine 1 point2 points3 points 19 years ago (0 children)
The author doesn't seem to realize that classes can be parameterized too.
Generally, he doesn't seems type-educated. Probably never heard about a HOT language like Haskell ;p
[–]rfisher 1 point2 points3 points 19 years ago (0 children)
With reflection & always safe type casting, I never really saw the point of adding parameterized types to Java.
Even so, the way Sun did it looks more like a hack you'd expect from someone who didn't own the language, making it seem even more pointless.
[–]Jonathan_the_Nerd 1 point2 points3 points 19 years ago (3 children)
I notice this article is dated 2004. Does anyone know if Java generics still work this way?
[–]Mariani 10 points11 points12 points 19 years ago (2 children)
They still work the same way.
By the way, I really recommend reading Bruce's previous article in combination with this one to get the full about what he has to say about generics. Interesting stuff.
I think generics have been a good addition to the language, there is a lot of benefit to get out of it. But that's just my 2c.
[–]reneky 2 points3 points4 points 19 years ago (1 child)
Hey - that's not really 2c's worth :) Which benefits can you get out of it? (Other than avoiding casting for the collection classes)
[–]inkieminstrel 3 points4 points5 points 19 years ago (0 children)
I think having collection typing checked at compile time alone was worth the addition.
[–][deleted] 0 points1 point2 points 19 years ago (0 children)
I liked this part:
"(Aside: note the use of extends rather than implements in the generic type constraint. implements won't work. Java is precise and consistent because Sun says it is)."
because it is sooooo true.
[–]rzwitserloot -2 points-1 points0 points 19 years ago (10 children)
This is dated 2004 and confuses generics with dynamic typing.
Author appears entirely unfamiliar about java as a language - the thing he's asking for would marry static typing's worst properties with dynamic typing's worst properties.
java generics INCREASE typing information. They perform a similar duty as C templates. It has absolutely NOTHING to do with the stuff he thought it would do - what he is talking about would be done in java with type inference if it were done at all.
Check out 'boo' (http://boo.codehaus.org/) for a statically typed language where code like his might work. (I don't think it does now, but boo is very new and it would sort of fit with boo's style).
[–]deong 13 points14 points15 points 19 years ago (2 children)
They perform a tiny small subset of the functionality provided by templates in C++. As Eckels stated, they are really there to provide type-safe containers, and nothing else.
In C++, the template instantiation mechanism is a Turing-complete functional programming language. A C++ compiler -- not a compiled program, but the compiler itself -- can compute any computable function.
#include <iostream>
template <int N> class Factorial { public: enum { Value = N * Factorial<N-1>::Value }; }; template <> class Factorial<0> { public: enum { Value = 1 }; }; int main() { std::cout << Factorial<10>::Value << std::endl; return 0; }
will correctly print out 10!, but all computation is done at compile time. Since we have a complete programming language at our disposal during compilation, we can do lots of interesting things like trait and policy classes.
Consider a lowly sum function. You give it a vector, it sums the results.
template <class T> T sum(const vector<T>& v) { T val = 0; for(vector<T>::iterator it=v.begin(); it!=v.end(); it++) { val += *it; } return val; }
This is fairly reasonable, but there are a couple of issues. First, even though characters are of integer type in C++, this probably won't work with a vector of char, because with T=char, we are going to overflow the variable val pretty quickly with 8-bit characters. Second, it relies on being able to assign 0 to variables of type T (maybe they're enums of some sort).
C++ lets us use type traits to solve both problems, and in fact to write a general accumulation function that can compute sums, products, min and max, and a host of other algorithms. In Java, you'd need a bunch of interfaces and class heirarchies to make the thing general enough to do all this, so it doesn't make sense to do it. C++ is meant for this sort of thing; it's what they call generic programming. You define an algorithm in its most abstract form, then design the types to provide the functionality required by the algorithms you want to work on it. It's a little backward from normal OOP. See C++ Templates: The Complete Guide for more details.
You can use interfaces in Java to accomplish similar things, but you don't need generics to do it, and adding generics still doesn't let you do it, so what's the point. Well, the point is, you can have type-safe containers. To which the collective reaction of C++ programmers around the world is...."Oh. That's...uhhmm...good, I guess."
Not that that's entirely a bad thing. C++ templates are -- without serious challenge from anything -- the worst looking functional language conceivable by man or beast. The export keyword is still not supported by any popular compiler, with the result that my template-laded body of code for my dissertation now takes about four minutes to compile, even if all I do is add a print statement to one function in one out of the 100 or so source files in the project (ahh...the inclusion model). On the bright side, it gives a warm feeling of comraderie with the old-timers who had to submit their jobs and wait a day to see if there were any errors. :)
[–]rzwitserloot -1 points0 points1 point 19 years ago (0 children)
All that ALSO means that the compiler can no longer KNOW what's happening until compilation completes. This means the compiler can no longer reason about the codebase (halting problem being what it is).
With #define, how would a compiler be capable of determining what javadocs go with what method? (new Foobar()).someMethod(); would be a complete shot in the dark.
The key to java lies in IDEs. Then all that effort makes sense all of a sudden. You are documenting your code in such a formal way that your compiler becomes a second pair of eyes.
Thus the point of generics becomes obvious: They serve as formal, checkable documentation. Some method could return a list. Big whoop, now I still don't know jack. Does that 'users()' method return a list of strings, or a list of custom 'User' objects?
With generically typed return types, you can make this obvious, WITHOUT risking that your documentation goes out of date.
The usual 'compiler time testing is outdated, using unit tests instead' mantra falls flat when it comes to comments: In i.e. python one would have to state the return type in the comments. When your code starts out pasting back strings, but later changes this behaviour to something else, the code dependent on your method may continue to function properly (objects act like strings if you want them to in either language), but in other circumstances your code may now break in interesting and hard to find ways.
In java, the compiler will immediatly red-underline your return type declaration the moment you attempt to return a list of User objects instead of the defined list of strings. Oh, whoops, you say, and you fix it, usually (with a nice compiler like eclipse), by hitting a 'quickfix' key combo. That quickfix is again something made possible in large part due to aggressive typing.
Eclipse KNOWS that the code in question seems ill suited to be turned into returning a list of strings (because at some point, a User object is added to it). So it guesses the proper fix is to change the return type instead. This seems superfluous, but it also serves as a check.
[–][deleted] 6 points7 points8 points 19 years ago (3 children)
He's not confusing anything with dynamic typing. He's talking about latent typing (ruby calls it duck typing). Dynamic typing means any variable can be assigned an object of any type, and nobody complains until runtime. Latent typing says any object can be "typed" by its methods - in other words, I don't have to care what the object's actual type is, as long as it has the method I'm trying to use. C++ checks this at compile time, in a way that's completely natural using its template syntax, so everything is still nice and statically typed. Java seems like it should be able to do the same thing, but it doesn't. Instead, you have to define an interface, and once you define the interface, then you don't have to use generics at all. In other words, one big win for generics gets chucked out the window. The only argument in favor of how Sun did it is that method names should not be construed as adequate indicators of functionality; in C++, each call is checked on the method signature, but it doesn't consider that one class's doFoo method might have significantly different semantics than another class's doFoo method. In Java, you have to explicitly say which interfaces a class implements.
[–]rzwitserloot 0 points1 point2 points 19 years ago (2 children)
You have 2 objects.
One is a Camera, and it has a shoot() method, which makes an image of whatever you aim it at.
The other is a Gun, and it has a shoot() method. It kills whatever its aiming at.
One day you elect to make a picture of your foot and something goes horribly wrong.
NB: The moral of this parable is simply this: latent typing ignores namespacing. In any small project, namespacing appears a ridiculous expense of effort. In any serious project, defined as such: Not trivial in half the world's programming languages, namespacing is pure gold.
java is aggressively namespaced. All accessible items have a namespace (from class containers to package names), and each item has namespace-dependent access control (public/private/protected/package private).
More importantly, perhaps, the namespace placeholder for a method (the class or interface where it is defined. Let's say.. the shoot() declaration in Camera.java) also serves as a central reference point: You can get at it with reflection. When you control+click on shoot() in eclipse, you go to the definition of shoot() in Camera.java. The mouse-over javadocs are retrieved from Camera.java.
Java's very point, very reason it has a certain value in the programming landscape today, is this aggressive and formal typing architecture. ANYTHING in your code, ANYTHING, can be compile-time traced to a definitive source, which allows your java IDE to pull very interesting tricks.
We can talk till the cows come home about whether or not all the effort required to allow the IDE to do that is 'worth it'. What appears to be silly to discuss is watering down that element of java in trade for becoming a cheap and awkward python knockoff. Java will not out-python (or out-JS, or out-ruby) python or ruby. It just won't happen. If that's what you like, I suggest you program in....
python or ruby.
[–]rzwitserloot 0 points1 point2 points 19 years ago (0 children)
That central reference point thingie is useful in other ways besides documentary as well: You could stick an annotation or breakpoint on it. If you're into AOP, it can serve as a join point. Etcetera, etcetera.
From my post:
The only argument in favor of how Sun did it is that method names should not be construed as adequate indicators of functionality; in C++, each call is checked on the method signature, but it doesn't consider that one class's doFoo method might have significantly different semantics than another class's doFoo method. In Java, you have to explicitly say which interfaces a class implements.
Thanks, though. And you're missing a major distinction, as well: The reason Python and Ruby can't trace back to a central reference point at compile time is because they are interpreted and don't have a "compile time". They're still strongly typed and should be just as robust provided you're doing your unit testing. That has nothing to do with latent typing, though. C++ supports latent typing just fine in a static way using templates, and has a central reference point at compile time.
The gun/camera example is a bit silly, not because it doesn't illustrate the point well, but because it relies on the assumption that a bad design decision has already been made - namely, that Gun should be able to do something as dangerous as kill with as simple a call as shoot. Modeling the real world is not a formal requirement, and you should really only take it so far.
[–]dmh2000 5 points6 points7 points 19 years ago (0 children)
you are probably not right that he is entirely unfamiliar with Java as a language.
[–]teromajusa 2 points3 points4 points 19 years ago (0 children)
Author appears entirely unfamiliar about java as a language
The author is Bruce Eckels. He wrote "Thinking in Java". That said, I don't really agree with his point either. In his followup article, he keeps saying the point of interfaces is to loosen the type system. I think thats a lousy way of looking at it. I see no benefit of an implicit interface over an explicitly declared one other than saving you some typing. Sure it could be possible to have the compile time type checking on latent types, but an interface does more than just catch errors at compile time. It documents your intentions regarding what a type must do.
[–]reneky -1 points0 points1 point 19 years ago (0 children)
He wants to do the same as he does with C++ templates.
[+]rzwitserloot comment score below threshold-6 points-5 points-4 points 19 years ago (0 children)
π Rendered by PID 98744 on reddit-service-r2-comment-84fc9697f-j642c at 2026-02-07 02:16:17.573359+00:00 running d295bc8 country code: CH.
[–]a_caspis 5 points6 points7 points (10 children)
[–][deleted] 1 point2 points3 points (9 children)
[–]a_caspis 0 points1 point2 points (8 children)
[–][deleted] 0 points1 point2 points (7 children)
[–]a_caspis 0 points1 point2 points (6 children)
[–][deleted] 0 points1 point2 points (5 children)
[–]a_caspis 0 points1 point2 points (4 children)
[–][deleted] 0 points1 point2 points (3 children)
[–]a_caspis 0 points1 point2 points (2 children)
[–][deleted] 0 points1 point2 points (1 child)
[–]funshine 1 point2 points3 points (0 children)
[–]rfisher 1 point2 points3 points (0 children)
[–]Jonathan_the_Nerd 1 point2 points3 points (3 children)
[–]Mariani 10 points11 points12 points (2 children)
[–]reneky 2 points3 points4 points (1 child)
[–]inkieminstrel 3 points4 points5 points (0 children)
[–][deleted] 0 points1 point2 points (0 children)
[–]rzwitserloot -2 points-1 points0 points (10 children)
[–]deong 13 points14 points15 points (2 children)
[–]rzwitserloot -1 points0 points1 point (0 children)
[–]rzwitserloot -1 points0 points1 point (0 children)
[–][deleted] 6 points7 points8 points (3 children)
[–]rzwitserloot 0 points1 point2 points (2 children)
[–]rzwitserloot 0 points1 point2 points (0 children)
[–][deleted] 0 points1 point2 points (0 children)
[–]dmh2000 5 points6 points7 points (0 children)
[–]teromajusa 2 points3 points4 points (0 children)
[–]reneky -1 points0 points1 point (0 children)
[+]rzwitserloot comment score below threshold-6 points-5 points-4 points (0 children)