103

I read a tweet today that said:

It's funny when Java users complain about type erasure, which is the only thing Java got right, while ignoring all the things it got wrong.

Thus my question is:

Are there benefits from Java's type erasure? What are the technical or programming style benefits it (possibly) offers other than the JVM implementations preference for backwards compatibility and runtime performance?

nbro
  • 12,226
  • 19
  • 85
  • 163
vertti
  • 6,847
  • 3
  • 43
  • 77
  • 6
    If you don't want your Question closed as "opinion based", you should ask it differently. (Like "what are the technical advantages of type erasure"). Better still, ask the tweeter what he is really saying. – Stephen C Jan 04 '14 at 08:38
  • 2
    The original tweeter added - In general, because I get huge benefits (e.g. parametricity) and nil cost (alleged cost is a limit of imagination). – Elliott Frisch Jan 04 '14 at 08:44
  • @StephenC I did tweet the original tweeter and asked and pointed him here for hopefully elaborate on his thoughts. I've now hopefully improved the question per your suggestions. – vertti Jan 04 '14 at 08:44
  • 3
    Remove the "why is it a good thing". The "good thing" is a patently subjective characterization, and it (obviously) begs the question that type erasure *is* a good thing ... which is contentious. – Stephen C Jan 04 '14 at 08:50
  • 3
    God help us if this site is going to degenerate into questions about everything that has ever been posted on Twitter. At least cite a reputable source for your question. – user207421 Jan 04 '14 at 09:16
  • 22
    Why does question need to be from "reputable source"? That's just silly. – vertti Jan 04 '14 at 09:20
  • 2
    @vertti Because otherwise it is usually just a waste of time. You can't just take random text from a random source and expect professionals to waste their time telling you what's wrong with it. This question is no exception. The part about 'the only thing Java got right' is enough of a hint in itself. – user207421 Jan 04 '14 at 09:42
  • 4
    Well most problems here are not from ANY source, except the questioners own problem with specific software/language/whatnot, and fortunately there's a lot of professionals that "waste" their time helping these people. – vertti Jan 04 '14 at 09:47
  • 1
    @Vertii I agree. If the question originates with you, by all means ask it. If it originates with some random drivel on the Internet, it is up to you to filter that out first and make a real question out of it, if there is one. That's part of being a professional. – user207421 Jan 04 '14 at 09:51
  • 3
    @EJP yes, on that part we can agree. I chose the "drivel from twitter" because it made an intriguing claim I had no answer for so I brought it here. And I have now done several edits towards what hopefully is a better question. – vertti Jan 04 '14 at 10:08

11 Answers11

96

Type Erasure Is Good

Let's stick to the facts

A lot of the answers thus far are overly concerned with the Twitter user. It's helpful to keep focused on the messages and not the messenger. There is a fairly consistent message with even just the excerpts mentioned thus far:

It's funny when Java users complain about type erasure, which is the only thing Java got right, while ignoring all the things it got wrong.

I get huge benefits (e.g. parametricity) and nil cost (alleged cost is a limit of imagination).

new T is a broken program. It is isomorphic to the claim "all propositions are true." I am not big into this.

A goal: reasonable programs

These tweets reflect a perspective that is not interested in whether we can make the machine do something, but more whether we can reason that the machine will do something we actually want. Good reasoning is a proof. Proofs can be specified in formal notation or something less formal. Regardless of the specification language, they must be clear and rigorous. Informal specifications are not impossible to structure correctly, but are often flawed in practical programming. We end up with remediations like automated and exploratory tests to make up for the problems we have with informal reasoning. This is not to say that testing is intrinsically a bad idea, but the quoted Twitter user is suggesting that there is a much better way.

So our goal is to have correct programs that we can reason about clearly and rigorously in a way that corresponds with how the machine will actually execute the program. This, though, is not the only goal. We also want our logic to have a degree of expressivity. For example, there's only so much we can express with propositional logic. It's nice to have universal (∀) and existential (∃) quantification from something like first-order logic.

Using type systems for reasoning

These goals can be very nicely addressed by type systems. This is especially clear because of the Curry-Howard correspondence. This correspondence is often expressed with the following analogy: types are to programs as theorems are to proofs.

This correspondence is somewhat profound. We can take logical expressions, and translate them through the correspondence to types. Then if we have a program with the same type signature that compiles, we have proven that the logical expression is universally true (a tautology). This is because the correspondence is two-way. The transformation between the type/program and the theorem/proof worlds are mechanical, and can in many cases be automated.

Curry-Howard plays nicely into what we'd like to do with specifications for a program.

Are type systems useful in Java?

Even with an understanding of Curry-Howard, some people find it easy to dismiss the value of a type system, when it

  1. is extremely hard to work with
  2. corresponds (through Curry-Howard) to a logic with limited expressivity
  3. is broken (which gets to the characterization of systems as "weak" or "strong").

Regarding the first point, perhaps IDEs make Java's type system easy enough to work with (that's highly subjective).

Regarding the second point, Java happens to almost correspond to a first-order logic. Generics give use the type system equivalent of universal quantification. Unfortunately, wildcards only give us a small fraction of existential quantification. But universal quantification is pretty good start. It's nice to be able to say that functions for List<A> work universally for all possible lists because A is completely unconstrained. This leads to what the Twitter user is talking about with respect to "parametricity."

An often-cited paper about parametricity is Philip Wadler's Theorems for free!. What's interesting about this paper is that from just the type signature alone, we can prove some very interesting invariants. If we were to write automated tests for these invariants we would be very much wasting our time. For example, for List<A>, from the type signature alone for flatten

<A> List<A> flatten(List<List<A>> nestedLists);

we can reason that

flatten(nestedList.map(l -> l.map(any_function)))
    ≡ flatten(nestList).map(any_function)

That's a simple example, and you can probably reason about it informally, but it's even nicer when we get such proofs formally for free from the type system and checked by the compiler.

Not erasing can lead to abuses

From the perspective of language implementation, Java's generics (which correspond to universal types) play very heavily into the parametricity used to get proofs about what our programs do. This gets to the third problem mentioned. All these gains of proof and correctness require a sound type system implemented without defects. Java definitely has some language features that allow us to shatter our reasoning. These include but are not limited to:

  • side-effects with an external system
  • reflection

Non-erased generics are in many ways related to reflection. Without erasure there's runtime information that's carried with the implementation that we can use to design our algorithms. What this means is that statically, when we reason about programs, we don't have the full picture. Reflection severely threatens the correctness of any proofs we reason about statically. It's no coincidence reflection also leads to a variety of tricky defects.

So what are ways that non-erased generics might be "useful?" Let's consider the usage mentioned in the tweet:

<T> T broken { return new T(); }

What happens if T doesn't have a no-arg constructor? In some languages what you get is null. Or perhaps you skip the null value and go straight to raising an exception (which null values seem to lead to anyway). Because our language is Turing complete, it's impossible to reason about which calls to broken will involve "safe" types with no-arg constructors and which ones won't. We've lost the certainty that our program works universally.

Erasing means we've reasoned (so let's erase)

So if we want to reason about our programs, we're strongly advised to not employ language features that strongly threaten our reasoning. Once we do that, then why not just drop the types at runtime? They're not needed. We can get some efficiency and simplicity with the satisfaction that no casts will fail or that methods might be missing upon invocation.

Erasing encourages reasoning.

Community
  • 1
  • 1
Sukant Hajra
  • 2,154
  • 1
  • 12
  • 8
  • 8
    In the time I took to cobble together this response, some other people replied with similar answers. But having written this much, I decided to post it rather than discard it. – Sukant Hajra Feb 18 '14 at 03:54
  • 3
    Well... in theory you could statically constrain T to types that do have a no-arg constructor. From memory, C# has this feature. It's not that dissimilar to having a type restriction of a typeclass that has a "default" method - e.g. mempty on Haskell's Data.Monoid. Still, you're specialising the type - you need to gain something from that. – techtangents Feb 18 '14 at 10:34
  • 2
    Indeed, .NET can safely restrict type parameters to types with no-arg constructors. e.g. in C# `where T: new()` http://msdn.microsoft.com/en-us/library/d5x73970.aspx . That's still very limited of course. – Mauricio Scheffer Feb 18 '14 at 14:04
  • 37
    With respect, type erasure was a half-assed attempt to bring a feature to Java without breaking backwards compatibility. It's not a strength. If the major concern were that you might try to instantiate an object with no parameterless constructor, the right answer is to allow a constraint of "types with parameterless constructors", not to cripple a language feature. – Basic Dec 29 '14 at 19:48
  • 6
    Basic, with respect, you're not making a cohesive argument. You're merely asserting that erasure is "crippling" with a complete disregard for the value of compile-time proofs as a compelling tool for reasoning about programs. Furthermore, these benefits are completely orthogonal of the crooked path and motivation Java took for implementing generics. Also, these benefits hold for languages with far more enlightened implementation of erasure. – Sukant Hajra Jan 01 '15 at 03:39
  • 2
    WRT to Java having "got type erasure right", I would be interested to find out why this is right: `public void doStuff(List collection) { } public void doStuff(List collection) // ERROR: a method can't have overloads which only differ in type parameters` – Marko Topolnik Jul 06 '15 at 11:21
  • 2
    Hi Marko, ad hoc polymorphism forcefully induces ambiguity for a syntactic convenience (using the same name for different type signatures) at the expense of reasoning. Some languages have *type classes* or are able to encode them. This is a superior solution, that gives you name reuse without breaking what we can reason about statically. So I'm not suggesting Java's implementation is perfect, but that the choice to reify generics is unreasonable (literally). – Sukant Hajra Oct 17 '15 at 19:49
  • 48
    The argument presented here is not against erasure, it's against reflection (and runtime type checking in general) - it basically asserts that reflection is bad, so the fact that erasure doesn't play well with them is good. It's a valid point on its own (although many people would disagree); but it doesn't make much sense in context, because Java already has reflection / runtime checks, and they didn't and won't go away. So to have a construct that doesn't play with that existing, non-deprecated part of the language _is_ a limitation, no matter how you look at it. – Pavel Minaev May 12 '16 at 00:38
  • 2
    Hi Pavel. I think your point is fairly presented. I would suggest that what you call a "limitation" is for me similar to how guardrails "limit" me from running my car off a cliff. Some limitations are better viewed as architectural constraints, from which we derive useful system invariants. But I'll agree, that if you're already launched off the cliff, erasure serves as no parachute. – Sukant Hajra May 15 '16 at 20:36
  • 10
    Your answer is simply off-topic. You enter a long-winded (though interesting on its own terms) explanation of why type erasure as an abstract concept is generally useful in the design of type systems, but the topic is the benefit of a specific characteristic of Java Generics labeled "type erasure", which only superficially resembles the concept you describe, failing completely at providing the interesting invariants and introducing unreasonable constraints. – Marko Topolnik Jun 19 '16 at 09:34
  • 3
    I believe Phillip Wadler (who had a lot of input in Java Generics with Pizza) and is also has published a good bit on free theorems and parametricity would disagree that the formalism and and implementation relate only with a "superficial resemblance." The relationship is very clear and there are a number of libraries that capitalize on the correspondence on the JVM (Functional Java, Scalaz, Cats, etc.). – Sukant Hajra Jun 19 '16 at 23:22
  • No, Java's subtyping as nothing to do with the "theorems for free" paper. Please, please, please read more on parametricity and free theorems before telling me what I /must/ mean. Wadler and Reynolds have published a lot. – Sukant Hajra Jun 23 '16 at 16:31
  • The paper doesn't talk about type erasure, but about what theorems can be proven _before_ type erasure is applied. Applying Java's "type erasure" does not erase types, it just cripples them to raw types. It also does a bunch of other legacy-induced weirdness, like preventing the "colliding" signatures I mention above. The continued existence of things like raw types and reflection makes the whole type safety edifice crumble. No theorems can be proven about a program that contains a single instance of reflective or raw type access. – Marko Topolnik Aug 02 '16 at 14:15
  • 1
    Therefore, when you say "the relationship is very clear", you _must_ be referring to something else than Java's type erasure, which is the topic here. You must also be referring to something else than the totality of the Java type system, which allows the conversion from a raw type into a parameterized type. – Marko Topolnik Aug 02 '16 at 14:33
  • 4
    "No theorems can be proven about a program that contains a single instance of reflective or raw type access." I agree. Don't do it. Type erasure rewards people that don't, and penalizes people that do. That is the very point of this post. It's true to get the benefit of free theorems, you may have to build walls in which code is not reflective. This can/should/is done in many JVM-based projects. This is done to get free theorems at all, rather giving up on the process of engineering well. – Sukant Hajra Aug 02 '16 at 21:30
  • Actually, true type erasure would _prevent_ people from doing bad things. That's my point all along: Java's type erasure isn't a feature but a hack forced upon it by legacy. Compare it with the true erasure of primitive types. So, while there are benefits to Generics, there are zero benefits to "erasure". – Marko Topolnik Aug 13 '16 at 16:04
  • Type erasure prevents me from doing bad things all the time. I don't reflect, because I don't want to, nor do I have to, except for in a few cases where the "legacy" as you call it forces me to. And as a result, in my walled garden, I get free theorems, which I use constantly. If you want to claim "zero" benefits, you are making all programs I write in this way of "zero" beneift -- which is beyond the pale. – Sukant Hajra Aug 16 '16 at 10:32
  • Your free theorems are not the result of type erasure. Name one bad thing type erasure prevents you from doing. – Marko Topolnik Aug 24 '16 at 12:26
  • No, they aren't the result, but type erasure is a distraction. Reified types only make for a world where people can break free theorems. Also, they come with a lot of compilation headaches (though granted these are behind the scenes for a lot of casual users). The part that bothers me is 1) I don't need them 2) I don't want them 3) they only help break my programs. This, by the way, is the point of this post (as I've said before). I think all your points have been made, and I've responded. If we're to continue this, can we find a new angle of discussion? – Sukant Hajra Aug 25 '16 at 15:59
  • 1
    Your post contains statements such as "Without erasure there's runtime information that's carried with the implementation that we can use to design our algorithms". This implies that _with_ erasure, there's no runtime type information. This is either false, or inapplicable to Java because it _does_ have "type erasure" and also _does_ carry runtime type information. You say "not erasing leads to abuses", as if Java _prevents_ abuses by sporting erasure. All this leads me to characterize your post as off-topic at best (applying to true erasure), but misleading would be a more honest description. – Marko Topolnik Aug 26 '16 at 10:09
  • Java's implementation of erasure is partial, it's true. I'm not interested in semantic debate of what aspects of "erasure" are theoretic. Java certainly is not the reference implementation. I have a feeling most readers can figure out the details, certainly with the trail of comments we've left behind. I really don't think there's much more to say on this topic. – Sukant Hajra Sep 03 '16 at 21:44
  • 1
    I have made no such admission. You should cite literature if you wish to claim an exact semantics for "type erasure." I should just let this go, because your argument has dwindled back into very crooked rhetoric. I suppose this is the norm for Stackoverflow comments. Just make one citation from an established PL researcher -- and we can continue this with civility. If you say "no, I'm from industry, so I get to say what I want without consequence," then I suppose you won't be the first. – Sukant Hajra Sep 06 '16 at 03:49
  • 1
    Let me remind you that this topic is about _Java's_ type erasure. If it weren't for my extra care to give you latitude and allow that you are talking about _some_ definition of type erasure, I could only conclude that your post is full of falsehood and inconsistency. The onus is on _you_ to produce statements and citations relevant to the context. – Marko Topolnik Sep 06 '16 at 07:39
  • lol. What holds you from using reflection? Type erasure? I guess no. You yourself repeat several times that you don't need/want/will ever use reflection. So what's holding you from using reflection is (surprise) your mindset. Java features don't. In your case type erasure simply doesn't matter a bit. It will make no difference for you, if they'll make it deprecated tomorrow. You can easily employ aforementioned "coding style" in languages that don't have type erasure. So the answer is offtopic. – jungle_mole Jun 01 '17 at 04:59
  • 3
    ` T broken { return new T(); }` Yeah. Make up a piece of bad code and blame a language. May be also blame it for allowing to use signed integer as index in array? `int i=-1; array[i];` -- see? The language is flawed! Also FYI, take c# for example. Compiler will give you an error if you try to use `return new T();` while not having parameterless constructor constraint on your generic. – jungle_mole Jun 01 '17 at 05:12
  • My mindset is based on the benefit of proof. I use proof. If you don't, then that's your choice. Erasure is merely something we can only do when we actually have the proof we can. This proof has benefits well beyond erasure (parametricity is a common example). And requiring that we have "reified generics" is merely proof we don't have such a proof. There is lot of research in this area. And people that have mastered these techniques are often well aware of the alternatives. – Sukant Hajra Jun 02 '17 at 20:43
  • 1
    Maybe Java has its reason to ban `new T()`, but why also ban `new T[n]`? – Aetherus Mar 07 '20 at 02:35
42

Types are a construct used for writing programs in a manner that allows the compiler to check the correctness of a program. A type is a proposition on a value - the compiler verifies that this proposition is true.

During the execution of a program, there should be no need for type information - this has already been verified by the compiler. The compiler should be free to discard this information in order to perform optimisations on the code - make it run faster, generate a smaller binary etc. Erasure of type parameters facilitates this.

Java breaks static typing by allowing type information to be queried at runtime - reflection, instanceof etc. This allows you to construct programs that cannot be statically verified - they bypass the type system. It also misses opportunities for static optimisation.

The fact that type parameters are erased prevents some instances of these incorrect programs to be constructed, however, more incorrect programs would be disallowed if more type information was erased and the reflection and instanceof facilities were removed.

Erasure is important for upholding the property of "parametricity" of a data type. Say I have a type "List" parameterised over component type T. i.e. List<T>. That type is a proposition that this List type works identically for any type T. The fact that T is an abstract, unbounded type parameter means that we know nothing about this type, therefore are prevented from doing anything special for special cases of T.

e.g. say I have a List xs = asList("3"). I add an element: xs.add("q"). I end up with ["3","q"]. Since this is parametric, I can assume that List xs = asList(7); xs.add(8) ends up with [7,8] I know from the type that it doesn't do one thing for String and one thing for Int.

Furthermore, I know that the List.add function can not invent values of T out of thin air. I know that if my asList("3") has a "7" added to it, the only possible answers would be constructed out of the values "3" and "7". There is no possibility of a "2" or "z" being added to the list because the function would be unable to construct it. Neither of these other values would be sensible to add, and parametricity prevents these incorrect programs from being constructed.

Basically, erasure prevents some means of violating parametricity, thus eliminating possibilities of incorrect programs, which is the goal of static typing.

techtangents
  • 445
  • 3
  • 3
16

(Although I already wrote an answer here, revisiting this question two years later I realize there is another, completely different way of answering it, so I'm leaving the previous answer intact and adding this one.)


It is highly arguable whether the process done on Java Generics deserves the name "type erasure". Since generic types are not erased but replaced with their raw counterparts, a better choice seems to be "type mutilation".

The quintessential feature of type erasure in its commonly understood sense is forcing the runtime to stay within the boundaries of the static type system by making it "blind" to the structure of the data it accesses. This gives full power to the compiler and allows it to prove theorems based on static types alone. It also helps the programmer by constraining the code's degrees of freedom, giving more power to simple reasoning.

Java's type erasure does not achieve that—it cripples the compiler, like in this example:

void doStuff(List<Integer> collection) { 
}

void doStuff(List<String> collection) // ERROR: a method cannot have 
                   // overloads which only differ in type parameters

(The above two declarations collapse into the same method signature after erasure.)

On the flip side, the runtime can still inspect the type of an object and reason about it, but since its insight into the true type is crippled by erasure, static type violations are trivial to achieve and hard to prevent.

To make things even more convoluted, the original and erased type signatures co-exist and are considered in parallel during compilation. This is because the whole process is not about removing type information from the runtime, but about shoehorning a generic type system into a legacy raw type system to maintain backwards compatibility. This gem is a classic example:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)

(The redundant extends Object had to be added to preserve backward compatibility of the erased signature.)

Now, with that in mind, let us revisit the quote:

It's funny when Java users complain about type erasure, which is the only thing Java got right

What exactly did Java get right? Is it the word itself, regardless of meaning? For contrast take a look at the humble int type: no runtime type check is ever performed, or even possible, and the execution is always perfectly type-safe. That's what type erasure looks like when done right: you don't even know it's there.

Marko Topolnik
  • 179,046
  • 25
  • 276
  • 399
10

A subsequent post by the same user in the same conversation:

new T is a broken program. It is isomorphic to the claim "all propositions are true." I am not big into this.

(This was in response to a statement by another user, namely that "it seems in some situations 'new T' would be better", the idea being that new T() is impossible due to type erasure. (This is debatable — even if T were available at runtime, it could be an abstract class or interface, or it could be Void, or it could lack a no-arg constructor, or its no-arg constructor could be private (e.g., because it's supposed to be a singleton class), or its no-arg constructor could specify a checked exception that the generic method does not catch or specify — but that was the premise. Regardless, it's true that without erasure you could at least write T.class.newInstance(), which handles those issues.))

This view, that types are isomorphic to propositions, suggests that the user has a background in formal type theory. (S)he very likely does not like "dynamic types" or "runtime-types" and would prefer a Java without downcasts and instanceof and reflection and so on. (Think of a language like Standard ML, which has a very rich (static) type system and whose dynamic semantics do not depend on any type information whatsoever.)

It's worth keeping in mind, by the way, that the user is trolling: while (s)he likely sincerely prefers (statically) typed languages, (s)he is not sincerely trying to persuade others of that view. Rather, the main purpose of the original tweet was to mock those who disagree, and after some of those disagree-ers chimed in, the user posted follow-up tweets such as "the reason java has type erasure is that Wadler et al know what they are doing, unlike users of java". Unfortunately, this makes it hard to find out what (s)he's actually thinking; but fortunately, it also likely means that it's not very important to do so. People with actual depth to their views don't generally resort to trolls that are quite this content-free.

ruakh
  • 156,364
  • 23
  • 244
  • 282
10

The one thing I don't see considered here at all is that OOP's runtime polymorphism is fundamentally dependent on the reification of types at runtime. When a language whose backbone is held in place by refied types introduces a major extension to its type system and bases it on type erasure, cognitive dissonance is the inevitable outcome. This is precisely what happened to the Java community; it is why type erasure has attracted so much controversy, and ultimately why there are plans to undo it in a future release of Java. Finding something funny in that complaint of Java users betrays either an honest misunderstanding of the spirit of Java, or a consciously deprecating joke.

The claim "erasure is the only thing Java got right" implies the claim that "all languages based on dynamic dispatch against the runtime type of function argument are fundamentally flawed". Although certainly a legitimate claim on its own, and one which can even be considered as valid criticism of all OOP languages including Java, it cannot lodge itself as a pivotal point from which to evaluate and criticise features within the context of Java, where runtime polymorphism is axiomatic.

In summary, while one may validly state "type erasure is the way to go in language design", positions supporting type erasure within Java are misplaced simply because it is much, much too late for that and had already been so even at the historical moment when Oak was embraced by Sun and renamed to Java.



As to whether static typing itself is the proper direction in the design of programming languages, this fits into a much wider philosophical context of what we think constitutes the activity of programming. One school of thought, clearly deriving from the classical tradition of mathematics, sees programs as instances of one mathematical concept or other (propositions, functions, etc.), but there is an entirely different class of approaches, which see programming as a way to talk to the machine and explain what we want from it. In this view the program is a dynamic, organically growing entity, a dramatic opposite of the carefully erected aedifice of a statically typed program.

It would seem natural to regard the dynamic languages as being a step in that direction: the consistency of the program emerges from the bottom up, with no a priori constrants which would impose it in a top-down manner. This paradigm can be seen as a step towards modeling the process whereby we, the humans, become what we are through development and learning.

Marko Topolnik
  • 179,046
  • 25
  • 276
  • 399
  • Thanks for that. It is the exact kind of deeper understanding of the philosophies involved that I expected to see for this question. – Daniel Langdon Nov 14 '14 at 22:54
  • 1
    Dynamic dispatch has no dependence whatsoever on reified types. It often relies on what the programming literature calls "tags," but even then, it doesn't need to use tags. If you make an interface (for an object), and make anonymous instances of that interface, then you are doing dynamic dispatch with no need for reified types at all. – Sukant Hajra Jun 17 '16 at 20:01
  • @sukanthajra That's just hair splitting. The tag is the runtime information you need to resolve the kind of behavior the instance exhibits. It is out of the static type system's reach, and that's the essence of the concept under discussion. – Marko Topolnik Jun 17 '16 at 21:18
  • 2
    There exists a very static type designed for exactly this reasoning called the "sum type." It's called a "variant type" also (in Benjamin Pierce's very good Types and Programming Languages book). So this isn't hair splitting at all. Also, you can use a catamorphism/fold/visitor for a completely completely tagless approach. – Sukant Hajra Jun 18 '16 at 20:21
  • Thank you for the theory lesson, but I don't see the relevance. Our topic is Java's type system. – Marko Topolnik Jun 18 '16 at 20:27
  • 1
    Well more than two years later and still not sign of undoing type erasure in JVM. Quite contrary we have even more JVM languages living happily with it. I think history proved you wrong. – anthavio Dec 28 '18 at 11:38
  • @anthavio You seem to fundamentally misunderstand the concept. What Java has is just _called_ type erasure, but it's not what it actually is. `List` and `Set` are still reified types and nothing will change that. In Kotlin you can get reified generic types as well, although in a quite limited form --- up to what the underlying JVM allows without breaking interoperability. – Marko Topolnik Dec 28 '18 at 11:43
  • 1
    Geez saying "List and Set are still reified types" and the Earth is flat. In Kotlin reified is only available when inlining and it is just compiler trick without preserving type for runtime. – anthavio Dec 28 '18 at 17:38
  • I'd like to respond to your first sentence, but I can't make any sense of it. As for your second sentence, you can read the spec document, quote `Goal: support run-time access to types passed to functions, as if they were reified`. In a world of JVM languages that "live happily with type erasure", I wonder how that got in. – Marko Topolnik Dec 29 '18 at 07:54
6

One good thing is that there was no need to change JVM when generics were introduced. Java implements generics at compiler level only.

Evgeniy Dorofeev
  • 124,221
  • 27
  • 187
  • 258
5

The reason type erasure is a good thing is that the things it makes impossible are harmful. Preventing the inspection of type arguments at runtime makes understanding and reasoning about programs easier.

An observation that I found somewhat counter-intuitive is that when function signatures are more generic, they become easier to understand. This is because the number of possible implementations is reduced. Consider a method with this signature, which we somehow know has no side effects:

public List<Integer> XXX(final List<Integer> l);

What are the possible implementations of this function? Very many. You can tell very little about what this function does. It could be reversing the input list. It could be pairing ints together, summing them and returning a list half the size. There are many other possibilities that could be imagined. Now consider:

public <T> List<T> XXX(final List<T> l);

How many implementations of this function are there? Since the implementation cannot know the type of the elements, a huge number of implementations can now be excluded: elements cannot be combined, or added to the list or filtered out, et al. We are limited to things like: identity (no change to the list), dropping elements, or reversing the list. This function is easier to reason about based on its signature alone.

Except… in Java you can always cheat the type system. Because the implementation of that generic method can use stuff like instanceof checks and/or casts to arbitrary types, our reasoning based on the type signature can be easily rendered useless. The function could inspect the type of the elements and do any number of things based on the result. If these runtime hacks are allowed, parameterised method signatures become much less useful to us.

If Java did not have type erasure (that is, type arguments were reified at runtime) then this would simply allow more reasoning-impairing shenanigans of this kind. In the above example, the implementation can only violate the expectations set by the type signature if the list has at least one element; but if T was reified, it could do so even if the list were empty. Reified types would just increase the (already very many) possibilities for impeding our understanding of the code.

Type erasure makes the language less "powerful". But some forms of "power" are actually harmful.

Lachlan
  • 3,626
  • 2
  • 23
  • 29
  • 1
    It seems like you're either arguing that generics are too complex for Java devs to "understand and reason" about, or that they can't be trusted not to shoot themselves in the foot if given the chance. The latter does appear to be in sync with Java's ethos (ala no unsigned types, etc) but it seems a little condescending to the developers – Basic Dec 29 '14 at 19:54
  • 1
    @Basic I must have expressed myself very poorly, because that isn't what I meant at all. The problem is not that generics are complex, it's that things like casting and `instanceof` impede our ability to reason about what code does based on types. If Java were to reify type arguments it would just make this problem worse. Erasing types at runtime has the effect of making the type system more useful. – Lachlan Jan 06 '15 at 12:30
  • @lachlan it made perfect sense to me, and I don't think a normal reading of it would pick up any insult to Java devs. – Rob Grant Jan 02 '17 at 15:59
4

This is not a direct answer (OP asked "what are the benefits", I am replying "what are the cons")

Compared to C# type system, Java type erasure is a real pain for two raesons

You can't implement an interface twice

In C# you can implement both IEnumerable<T1> and IEnumerable<T2> safely, especially if the two types do not share a common ancestor (i.e. their ancestor is Object).

Practical example: in Spring Framework, you can't implement ApplicationListener<? extends ApplicationEvent> multiple times. If you need different behaviours based on T you need to test instanceof

You can't do new T()

(and you need a reference to Class to do that)

As others commented, doing the equivalent of new T() can only be done via reflection, only by invoking an instance of Class<T>, making sure about the parameters required by the constructor. C# allows you to do new T() only if you constrain T to parameterless constructor. If T does not respect that constraint, a compile error is raised.

In Java, you will often be forced to write methods that look like the following

public <T> T create(....params, Class<T> classOfT)
{

    ... whatever you do
    ... you will end up
    T = classOfT.newInstance();


    ... or more advanced reflection
    Constructor<T> parameterizedConstructorThatYouKnowAbout = classOfT.getConstructor(...,...);
}

The drawbacks in the above code are:

  • Class.newInstance only works with a parameterless constructor. If none available, ReflectiveOperationException is thrown at runtime
  • Reflected constructor does not highlight problems at compile time like the above. If you refactor, of you swap arguments, you will know only at runtime

If I was the author of C#, I would have introduced the ability to specify one or more constructor constraints that are easy to verify at compile time (so I can require for example a constructor with string,string params). But the last one is speculation

Community
  • 1
  • 1
usr-local-ΕΨΗΕΛΩΝ
  • 23,317
  • 27
  • 132
  • 255
2

An additional point none of the other answers seem to have considered: if you really need generics with run-time typing, you can implement it yourself like this:

public class GenericClass<T>
{
     private Class<T> targetClass;
     public GenericClass(Class<T> targetClass)
     {
          this.targetClass = targetClass;
     }

This class is then able to do all the things that would be achievable by default if Java did not use erasure: it can allocate new Ts (assuming T has a constructor that matches the pattern it expects to use), or arrays of Ts, it can dynamically test at run time if a particular object is a T and change behaviour depending on that, and so on.

For example:

     public T newT () { 
         try {
             return targetClass.newInstance(); 
         } catch(/* I forget which exceptions can be thrown here */) { ... }
     }

     private T value;
     /** @throws ClassCastException if object is not a T */
     public void setValueFromObject (Object object) {
         value = targetClass.cast(object);
     }
}
Jules
  • 13,417
  • 7
  • 75
  • 117
  • Anyone care to explain the reason for the downvote? This is, as far as I can see, a perfectly good explanation of why the supposed downside of Java's type erasure system is not actually a real limitation at all. So what's the problem? – Jules Feb 19 '14 at 21:33
  • I'm upvoting. Not that I support run-time type checking, but this trick can be handy in the salt mines. – techtangents Feb 19 '14 at 23:03
  • 3
    Java *is* a language of the salt mines, which makes this a bare necessity given the lack of runtime type information. Hopefully, type erasure will be undone in a future version of Java, making generics a much more useful feature. Currently the consensus is that their thrust/weight ratio is below 1. – Marko Topolnik Feb 24 '14 at 09:11
  • Well, sure... As long as your TargetType doesn't use generics itself. But that seems pretty limiting. – Basic Dec 29 '14 at 19:56
  • It works with generic types too. The only constraint is that if you want to create an instance you need a constructor with the right argument types, but every other language I'm aware of has the same constraint, so I don't see the limitation. – Jules Dec 30 '14 at 07:51
0

avoids c++-like code bloat because the same code is used for multiple types; however, type erasure requires virtual dispatch whereas the c++-code-bloat approach can do non-virtually dispatched generics

necromancer
  • 21,492
  • 19
  • 65
  • 111
  • 3
    Most of those virtual dispatches are converted to static ones in runtime, though, so it is not that bad as it may seem. – Piotr Kołaczkowski Jan 28 '14 at 19:02
  • 1
    very true, the availability of a compiler at run-time lets java do a lot of cool things (and achieve high performance) relative to c++. – necromancer Jan 28 '14 at 20:03
  • wow, let me write it down somewhere _java achieves high performance relative to cpp.._ – jungle_mole Jun 01 '17 at 09:27
  • 1
    @jungle_mole yes, thank you. few people realize the benefit of having a compiler available at run-time to compile code again after profiling. (or that garbage collection is big-oh(non-garbage) rather than the naive and incorrect belief that garbage collection is big-oh(garbage), (or that while garbage collection is a slight effort, it compensates by allowing zero-cost allocation)). It is a sad world where people blindly adopt what their community believes and stop reasoning from first principles. – necromancer Jun 10 '17 at 00:42
0

Most answers are more concerned about programming philosophy than the actual technical details.

And although this question is more than 5 years old, the question still lingers: Why is type erasure desireable from a technical point of view? In the end, the answer is rather simple (on a higher level): https://en.wikipedia.org/wiki/Type_erasure

C++ templates don't exist at runtime. The compiler emits a fully optimized version for each invocation, meaning the execution doesn't depend on type information. But how does a JIT deal with different versions of the same function? Wouldn't it be better to just have one function? Wouldn't want the JIT to have to optimize all the different versions of it. Well, but then what about type safety? Guess that has to go out of the window.

But wait a second: How does .NET do it? Reflection! This way they only have to optimize one function and also get runtime type information. And that is why .NET generics used to be slower (though they have gotten much better). I am not arguing that that isn't convenient! But it is expensive and shouldn't be used when it isn't absolutely necessary (it isn't considered expensive in dynamically typed languages because the compiler / interpreter relies on reflection anyway).

This way generic programming with type erasure is close to zero overhead (some runtime checks / casts are still required): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Marvin H.
  • 1,088
  • 1
  • 9
  • 17