7 ms·
Java Generics FAQs
- _lce0 11y agoTIL why class Foo<T> { void test(Foo<T> f) { // ... } static void boom() { Foo<?> f = new Foo<String>(); f.test(f); // <--- boom! compile error } }
- tel 11y agoI'm assuming it's because the T variable isn't generalized over within the definition of Foo, so you can't use (test) at arbitrary types until Foo is closed?
- salmonellaeater 11y agoIn concrete terms, imagine Foo looks like this: private T value; void test(Foo<T> f) { f.value = this.value; } You could then assign anything to a member that should only hold a String. This is the same reason why arrays should not be covariant (even though they are in Java). This compiles: Object[] arr = new Integer[2]; // This should be a compile error, but the language spec allows it arr[0] = "abc"; // This blows up with an ArrayStoreException at runtime
- peeters 11y agoYour example presents zero unsafety for the usage presented by eridal. The only reason it's considered unsafe is because the compiler isn't sophisticated enough to compare captures of wildcards, and in this case, might not be sophisticated enough to know that `f` isn't being reassigned.
- tel 11y agoI understand the covariance bit, but I think I just fail to understand the Java type semantics more broadly. I'd have to just study it and become more familiar
- narrator 11y agoThe key thing to remember is that generics are just for the compiler. There isn't anything actually embedded in the bytecode that knows what type a generic is.
- nicobn 11y agoThis is only partially true. Generic types are accessible through the reflection API. What you are referring to is type erasure. See for yourself: https://gist.github.com/nicobn/8135faeda058fef5b6b9 https://gist.github.com/nicobn/8135faeda058fef5b6b9
- slantedview 11y agoGenerics are captured in various places in bytecode - most importantly in type definitions. This is how the common type token pattern is exploited. Where generics are erased is in local variable declarations.
- peeters 11y agoThere isn't really a good reason. The compiler just isn't sophisticated enough to support it. You could always write a generic method to do it for you: static <T> void testFoo(Foo<T> f) { f.test(f); } static void boom() { Foo<?> f = new Foo<String>(); testFoo(f); // works just fine } So the problem isn't that what you're doing is fundamentally unsafe, it's just that Java has no syntax to capture `?` except through a generic type parameter on a method or class.
- taeric 11y agoThis is a rude response. The reason is quite reasonable, since there is no way that would work in all cases. A case is easily made where Foo<Number> extends Foo<Integer> makes as much sense as Foo<Integer> extends Foo<Number>. So, which would you have chosen?
- twic 11y agoIt didn't come across as rude to me. And i think it's entirely accurate. Moreover, that pattern - of sending a variable with a wildcard type argument into a method with a type parameter so that the type argument gains a name - is a something i've seen and used many times, and is useful to know. I don't know if there is a name for this technique, but i call it "taming a wildcard".
- taeric 11y agoIn the example case, though, it isn't about giving it a name. It is about using the name in the first place. Is no different from: class Foo { void test(String arg) {..} main(..) { Foo foo = new Foo(); Object s = "sd"; foo.test(s); // Boom } } Note that if that were instead: class Foo { void test(Number arg) {..} main(..) { Foo foo = new Foo(); Long n = 4L; foo.test(n); } } Then things work, because the subtyping is chosen. In the generics case, there is no relation between Foo<Number> and Foo<Integer>. If there were, then the capture could potentially treat Foo<?> as Foo<Object> and then depending on if you went co or contra, could allow the call.
- KevinEldon 11y agoIf anyone is curious about this, from the FAQ: "Assignment to a field of the "unknown" type is rejected because the compiler cannot judge whether the object to be assigned is an acceptable one. Read access to a field of the "unknown" type is permitted, but the field has no specific type and must be refered to through a reference of type Object ." http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#In%20a%20wildcard%20instantiation,%20can%20I%20read%20and%20write%20fields%20whose%20type%20is%20the%20type%20parameter http://www.angelikalanger.com/GenericsFAQ/FAQSections/Techni...?
- uv3d779b 11y agoI love Java-style generics! Any other popular languages have anything similar? Besides C# of course.
- Devthrowaway80 11y agoI can't stand Java generics. They're better than nothing, but type erasure really limits their utility.
- tel 11y agoWhy do people find that type erasure is a problem? I'm happy, for instance, writing in languages wherein type erasure is the obvious right answer.
- _asummers 11y agoI don't often find myself needing to get T, but sometimes I do. Consider the case of a Handler<T extends Foo> interface that several classes implement. If I have several of these, for various Foo subtypes, I might want to put them in some sort of collection that I can look into later after receiving a Foo message, so that I can dispatch it appropriately. Why do I have to do this dispatch myself? The types of all the Handlers go away, so I need to hold on to them somehow, and match at runtime. The clear way to do this is to put them in a Map<Class<T>, Handler<T>>. Now how do these Handler objects declare that they're ONLY able to handle a specific Foo subtype (let's call it FooBar)? It'd be REALLY nice if I could just say "Hey handler, what's your generic type? Is the message I just got an instanceof your generic type?" Java won't let you do this due to erasure. Okay we can get around this. Each Handler<T> has to declare a method (say getType()) that returns the Class<T>. Since I'm generically declaring my class FooBarHandler<FooBar>, I can protect myself from returning a BazBat in this method, but there's NO way for me to abstract this method away. Each Handler has to declare a "public Class<T> getType()" that returns T.class. But since T isn't a thing past compile time, I have to repeat this same method implementation for each concrete type. Gross. This has the added irritation of forcing all the parent classes, if they implement this interface, to be abstract, since they can't implement the method appropriately. This isn't necessarily a bad thing, but the limitation is annoying. In an ideal world, I can declare some ParentHandler<T> that has this method, and have all my handlers just extend it, with no duplication.
- halosghost 11y agoPublic, bare getters/setters! Hisss!!!!! To elaborate, this is one of the most common annoyances I have with Java ecosystems. In an attempt to make everything Objectionable (ho-ho!), coders write classes with their internal data structure marked as private but then make getters and setters (which do nothing other than set or retrieve the value) which are public. The result is that their internal data structure is actually public (since its advertised through the getters/setters), but with the added bonus of extra function calls and member offset resolutions (i.e., it makes execution slower while single-handedly destroying the main benefit that OOP offers—backwards compatibility through isolation of the internal data structure). While it doesn't surprise me to see new-to-OOP coders making this kind of mistake, I find it troubling that many FAQs/tutorials/classes actually advocate for these patterns. On the bright side, the web page looks nice :)
- pimlottc 11y agoI can't imagine the minor performance penalty of calling a trivial getter makes any noticeable difference in 99.99% of cases. JIT can inline them anyhow.
- ossreality 11y agoThat's because you're wrong. Really wrong.
- lemming 11y agoTheir internal data structure is not public, since the details of how the value returned by the getter is calculated can be changed later, transparently to calling code. This is the point of encapsulation. Getters and setters can be made to access member variables in thread-safe ways, which you can't do with bare member fields. If you start with bare member fields and then suddenly realise that you need to encapsulate (for example, to obey some internal concurrency invariant) you have a major refactoring on your hands. If the clients of that code are not under your control, you're screwed. The performance impact is total FUD - the JVM eats that stuff for breakfast.
- mercurial 11y ago
- dcposch 11y agoI think this illustrates one reason why Go doesn't have generics. Generics add a lot of complexity to a language. There are two main ways to implement them that I know of--the Java style, with type erasure, and the C++ style, where you essentially generate code for each instantiation--both have significant drawbacks. Either way, it complicates the syntax, complicates the type checker, complicates all the tooling around the language (IDE support, debugger, profiler, linter). OTOH, if you don't have generics, you either just use concrete types or you essentially do type erasure yourself, explicitly (eg with interface{} in Go or void* in C). It costs you a few casts and maybe some code duplication. In return, you get a simple language, where it's easier to write robust tooling. I think erring on the side of simplicity and avoiding generics altogether is more than worth it.
- ane 11y agoJava generics aren't the best example of how to implement generics. C# or Rust are better in this regard, which both have reified generics. Generics do solve a practical problem identified decades ago, ad-hoc code generation or void* aren't the solution. I don't think the omission of generics has any bearing on how easy writing tooling is. You can always omit them, if you're prepared to deal with code duplication, but I think you're downplaying its significance: it is not just "maybe some" code duplication, nor is it a "a few casts". It's sometimes a deep, murky swamp you're forced to trudge in, and it's not pleasant.
- useerup 11y agoTo be fair, generics are only needed for as long as you insist on static typing. Languages with dynamic typing where types are checked at runtime do not need generics. And generics is complicated topic. I am all for static typing, especially for large code bases. But sometimes when I venture too far out in generic land I find myself wondering if it is worth it? A well-designed library that uses generics prudently can be a bliss to use. But the implementation code can get near-unreadable.
- seanmcdirmid 11y ago> There are two main ways to implement them that I know of--the Java style, with type erasure, and the C++ style, where you essentially generate code for each instantiation--both have significant drawbacks. There is also C#/.NET style, which maintains the type system aspect as Java and the run-time reification and efficiency of C++. > OTOH, if you don't have generics, you either just use concrete types or you essentially do type erasure yourself, explicitly (eg with interface{} in Go or void* in C). It costs you a few casts and maybe some code duplication. Exactly like Java pre-generics, I remember the JDK 1.0.2/1.1 days also. > I think erring on the side of simplicity and avoiding generics altogether is more than worth it. They don't need it day one (neither Java or C# had it in 1.0), but they'll eventually need to add something for generic programming.
- DavidPlumpton 11y agoI was dealing with this a couple of days ago: List<List<List<Map<String, Object>>>> Sigh.