There are still people in the world who don't use Java generics in "ordinary coding." I can believe it with C++ templates, but generics? They aren't even hard to learn/use. Seriously the best features of Java and C++ are respectively generics and templates.
The best way to convince people of things is to make a compelling argument, be non-threatening, and be right.
So long as you are not doing something like using templates as your programming language, parametric polymorphism (generics/templates) is almost certainly good.
1. Avoids code duplication.
This is obvious, but polymorphic code is general code. That is why it is called generics.
2. Supports better static checking.
Without parametric polymorphism you end up writing things like public Object clone()
or public boolean equals(object b)
which are not just abominations, they have types that provide no information about what they do, and invariably end up throwing exceptions all over the place. The alternative to parametric polymorphism is casts all over the place
3. Non parametric polymorphism OOP code is basically unable to handle "binary methods" in a correct way.
You use these often.
4. It is best practice
In Java, use of generics is considered best practice (see Effective Java by Josh Bloch). Major C++ thinkers like Sutter and Alexandrescu also encourage the use of templates to solve a variety of problems.
5. It fits the OO paradigm.
People often don't notice this, but the combination of sub-typing and generics produces a system MUCH more powerful expressive, and object oriented than any system with just one of them.
Consider Scala's mixins. These are a nice feature that lets you pull your objects together from component parts. Generics and templates can simulate some of these benefits. For example, say one of your objects uses a database. Good design would have you abstract out the database access into a separate class. If done right this not only lets you mock your data-store (key to testability), it also means that you can add alternative implementations like that new no-sql database. Here though, you might have a problem, mattering which implementation you use you will get different capabilities of your business object.
Generics to the rescue!
public class Business<S extends Datastore>{
private S store; ...
}
Now you can start statically differentiating your Business
objects based on ability to use database specific features. You still need some runtime checks and casting, but you can start building MUCH better code.
and
6. Normal code doesn't exist.
There are only three things in the programming universe:
- libraries,
- configurations, and
- bad code.
If you don't think about your code like it is a library you are in serious trouble when the requirements for your project change. Architecture is (arguably) the art of designing good APIs.
I find this attitude stunning. After you get used to programming with parametrized types, not using them just makes everything a pain. And, Java and C++ have a bunch of rough spots which they help remedy.
Because immutable collections absolutely require sharing to be usable. Otherwise, every single operation drops a whole other list into the heap somewhere. Languages that are entirely immutable, like Haskell, generate astonishing amounts of garbage without aggressive optimizations and sharing. Having collection that's only usable with <50 elements is not worth putting in the standard library.
Further more, immutable collections often have fundamentally different implementations than their mutable counterparts. Consider for example ArrayList
, an efficient immutable ArrayList
wouldn't be an array at all! It should be implemented with a balanced tree with a large branching factor, Clojure uses 32 IIRC. Making mutable collections be "immutable" by just adding a functional update is a performance bug just as much as a memory leak is.
Furthermore, sharing isn't viable in Java. Java provides too many unrestricted hooks to mutability and reference equality to make sharing "just an optimization". It'd probably irk you a bit if you could modify an element in a list, and realize you just modified an element in the other 20 versions of that list you had.
This also rules out huge classes of very vital optimizations for efficient immutability, sharing, stream fusion, you name it, mutability breaks it. (That'd make a good slogan for FP evangelists)
Best Answer
The reason is the underlying object and memory models.
To simplify the reasoning:
In java and C#, objects of a class are managed by reference. Containers do not store directly the object value but a reference that says where to find the value. It is therefore technically easy to mix objects of different types in the same container (polymorphism) or to use the container for objects of covariant types. The only constraint is the language semantics. This facilitates significantly the implementation of covariant containers.
In C++, objects are managed by value, following the rules of its memory model, which basically requires that objects of a given type a stored within a fixed size (which of course can contain pointers to elements having a dynamic size). A container therefore has to know the type of its objects at compile-time. Unfortunately (or not) C++ also allows for separate compilation. So when you compile a container for
Animals
in one translation unit, the compiler might not know the size of aCat
(which might not even yet be developed). All this makes it extremely difficult to implement covariance in the language.Interestingly, in C# you can have objects that are managed by value (in the case of a struct). But as this Microsoft documentation and this SO question explain, variance only applies to reference types.
Of course, all this is simplified explanations and language-lawyers could argue on some details, but i hope it helps to grasp the idea.