/*
# Generics
Introduced in Java 5 to allow more compile time checks.
Incurs not runtime cost because of type erasure.
# Templates
Superficially similar concept in C++, but in C++ templates compile exactly to code,
while Java also relies on polymorphism TODO check.
Both types and methods can be generic. both will be commented here.
# Vs polymorphism
In many cases, are the same.
In a few cases, generics are more powerful.
Only use them if you need the extra capabilities,
as they make the code harder to read.
Cases where they are needed:
- the type of a function parameter is parametrized by the type
- return value
TODO understand why in those cases are necessary.
# Parametrized types
Types that depend on other types, e.g. `LinkedList`.
# Type arguments
Types passed to parametrized types. E.g. `String` in `LinkedList`.
*/
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class GenericsCheat {
public static T genericMethod(T i) {
return i;
}
public static String genericGetClass(T i) {
return i.getClass().getSimpleName();
}
//public static T genericGetInstance() {
//return what?
//}
//public static Class genericGetClassDotClass(T t) {
//return T.class;
//}
public static String genericMethodGenericArgument(List l) {
return l.getClass().getSimpleName();
}
public static class GenericStatic {
static int i = 0;
}
public static void main(String[] args) {
/*
# Raw types
Generic types but without passing the generic arguments.
This is how Java was before 1.5: the syntax is only kept for backwards compatibility.
Never use this on new code, as you lose compile time checks.
The main reason that generics are useful is that most collections contain a single type of objects.
Generics then add typecasts and typechecks automatically for us.
*/
{
// The pre 1.5 raw types way.
{
@SuppressWarnings({ "rawtypes" })
ArrayList names = new ArrayList();
@SuppressWarnings({ "unchecked" })
boolean b = names.add("abc");
@SuppressWarnings({ "unchecked" })
boolean b2 = names.add(1);
// Object returned by default.
Object name = names.get(0);
// We've done this cast correctly.
name = (String)name;
assert(name.equals("abc"));
boolean fail = false;
try {
name = (String) names.get(1);
} catch (ClassCastException e) {
fail = true;
}
assert fail;
}
// After generics: see how much better this is than with raw types
// if our container is supposed to contain only a single data type.
{
ArrayList names = new ArrayList<>();
names.add("abc");
// No cast needed on the output!
assert(names.get(0).equals("abc"));
// Fail on compile time.
//names.add(1);
//names.add((String)1);
boolean fail = false;
try {
// Compiles, but we've asked for trouble with a double cast.
// And now the exception happens before insertion, not after retrival.
names.add((String)(Object)1);
} catch (ClassCastException e) {
fail = true;
}
assert fail;
}
}
/*
Primitive types cannot be passed as type arguments.
What happens in most cases is that primitive wrappers are used instead,
and boxing and unboxing conversions produce the eye candy.
*/
{
// ERROR: unexpected type.
//LinkedList l;
LinkedList m;
}
/*
# Generic method
*/
{
Object o = new Object();
assert GenericsCheat.