Functional Interfaces
- usecase for wildcards
- lambda expressions have types
- good for library building
// has ingoing type T and return type R
Function<T, R> -> R apply(T arg)
// use wildcard generics
// super for ingoing
// extends for return
Function<? super Foo, ? extends Bar>
Type Inference
- Example: generics and type inference
Predicate<T>
is a functional interface from java 8- a way to have a function without creating a class yourself!
Predicate<Person> isOld = person -> person.getAge() > 80;
// place people by age in a map
Map<Boolean, Long> peopleAges =
people.stream()
// will throw a compile error since person is unknown
// will have to fix the target type of the Map from List<Person> to Long
// since partitioningBy returns a Long
.collect(partitioningBy(person -> person.getAge() > 80, counting()));
Intersection Types
- advanced generics feature
- a type that is a subtype of two other types
<T extends A & B>
- used to fake missing types for backward compatibility
- especially in APIs where you do not have access to the code
- how to gain information back about a generic type that went through erasure
- inspect application at runtime
- how many constructors
- what methods
- what return types
Class Literals
- pattern for generic instantiation
- Example: class injector
public class Injector {
private Map<Class<?>, Object> objectGraph = new HashMap<>();
public Injector with(Object value) {
objectGraph.put(value.getClass(), value);
return this;
}
publicv <T> T newInstance(final Class<T> type) {
// old way
// if (objectGraph.containsKey(type)) return objectGraph.get(type);
// instantiate(type);
// new java 8 way, casted to T
return (T) objectGraph.computeIfAbsent(type, this::instantiate(type));
}
private Object instantiate(Class<?> type) { ... }
}
// useage of logger
public class Main {
public static void main(String[] args) {
Injector injector = new Injector().with("Hello World");
Logger logger = injector.newInstance(Logger.class); // obtains the classname
logger.log();
}
}
Reflecting Types
- Reified: to materialize, to create, to make real
- Reifiable types: anything that has all its information within itself
- primitives: int, long
- non parameterized class / interface: String, ActionListener
- all type arguments are unbounded wildcards: List, Map
- RawTypes: List, Map
- arrays of reifiable components: int[][], List<?>[]
- non reifiable types: objects carrying generic type information
- type variables: T
- parameterized type with parameters: ArrayList, Map<Integer, String>
- Parameterized type with bounds: List<? extends Number>, Consumer<? super String>
- typically cannot use reflection to get generic types at runtime
Reflecting generic information
- there are workarounds
- create a class that extends the class with generics
- use
.toGenericString()
to get full type information - use
.getGenericSuperclass()
to get full type information - use
ParameterizedType
to access
public static class StringList extends ArrayList<String> {}
final ParameterizedType arrayListOfString = (ParameterizedType) StringList.class.getGenericSuperclass();
System.out.println(Arrays.toString(arrayListOfString.getActualTypeArguments()));
// prints out an array of generics!
// [class java.lang.String]
- java maintains migration compatibility
- can upgrade to a new java version without breaking code
RawTypes
- types that exist to allow interoperability with legacy libraries
- java 4 was prior to generic types, so after generic types were added, utilities like
List
became a raw type - useage of a generic type without any generic parameters
- not equivalent to
List<Object>
List<Object> list = new ArrayList<>();
List rawTypeList = new ArrayList<>();
// would create a compile error
List<String> strings = list;
// would not generate an error
List<String> strings = rawTypeList;
- rawTypes are unsafe, can introduce runtime errors, do not use intentionally nowadays, only for legacy libraries
Erasure
- how generics are implemented under the hood
- removes the generic at compile time
- erase type parameters, add casts, add bridge methods
List<String>, List<Integer>, List<List<Integer>> -> List
T without bounds -> Object
T extends Foo -> Foo
- Java compiler will do a
checkcast
under the hood- makes sure the Object matches expected
implications of generics
overloads
- same method names with different type parameters
- cannot use with types that have same erasure (generics)
- Example: List
public void print(List<String> param) { ... } // these two throw compile error, same erasure to List<Object> public void print(List<Integer> param) { ... }
- cannot use generics with exceptions
- checking the type of an instance
- cannot check
instanceof
a generic type
- cannot check
- performace
- hard on CPU and expensive for lookups
- erasure forces Object types that are stored as pointers
Reified Types and Arrays
- array type predates generics, are covariant
- an array of String is a distinct type, while a List of String is not
- Example: ArrayStoreException error
String[] strings = new String[3];
Object[] objects = strings;
objects[1] = 1; // throws error;
- how to interoperate between Arrays and generic types?
- Example: CustomArrayList with Array under the hood utilizing generics
public class CustomArrayList<T> extends AbstractList<T> {
// can add type cast this way
// or cast on the way out (when getting the values)
private T[] values;
public CustomArrayList() {
// does not work due to erasure
// values = new T[0];
// must type cast to Array type
values = (T[]) new Object[0];
}
public T get(final int index) {
// can cast here instead
// return (T) values[index];
return values[index];
}
public boolean add(final T o) {
T[] newValues = (T[]) new Object[size() + 1]; // resize the array
for (int i = 0; i < size(); i++) {
// copy over old values
newValues[i] = values[i];
}
// add the new object at the end
newValues[size()] = o;
values = newValues;
return true;
}
}
- where
?
exist within generics - wildcards can be bounded:
- upper bounded:
List<? extends Comparator>
- lower bounded:
List<? super Person>
- unbounded:
List<?>
- upper bounded:
substitution principle
- whenever an argument is passed in as a parameter to a method, a subclass can also be passed
subclasses
extend the parent class
Array
in java are covariant, can place subclassed Objects within parent class of arrays- dangerous, not type safe and will throw runtime error of
ArrayStoreException
- dangerous, not type safe and will throw runtime error of
- use
List
instead if you do not want covariance- but sometimes you want subclasses in these
Lists
, so upper bounded wildcards help
- but sometimes you want subclasses in these
Upper bounded wildcards
- used when you want something to accept a Class and subclass of that Class
- when you want flexibility within a method, but don't need to refer to the class within the method and don't need extra type parameters
- known as covariant
- get data out of parameter
- Example: Person
- only safe to use when getting things out of the parameter
List
- only safe to use when getting things out of the parameter
public void saveAll(final List<? extends Person> persons) { ... }
// also works as
public <T extends Person> void saveAll(final List<T> persons) { ... }
// but is clumsy
- cant always use wildcards
- if you want to refer to that type elsewhere in the code
static class PersonHolder<? extends Person> {
// this is not doable
? get() { ... }
}
static class PersonHolder<T extends Person> {
// whereas this is
T get() { ... }
}
lower bounded wildcard
- use when you want something to accept Classes and their parent Classes
- known as contravariant
- put data into parameter
- Example: person
- only safe to use when putting things into the parameter
List
- only safe to use when putting things into the parameter
public void loadAll(final List<? super Person> people) { ... }
unbounded wildcards
- the
<?>
by itself, syntactic sugar for<? extends Object>
Class<?>
often confused withClass<Object>
, which is not the sameClass<Object>
represents the class of Object from Java.langClass<?>
represents the class of any Object
- use when you will have children and parents of Classes mixed together
- like when pulling things out of a list and reading them
- Example: only time you can add into a
List<?>
// can only add null
// since it is the only type that is safe to be coerced into any other type
List<?> objects = new ArrayList<>();
objects.add(null);
- allows type to be used within method
- example: min method
// type parameters go between method quantifiers and return type
public static <T> T min(List<T> values, Comparator<T> comparator) {
if (values. isEmpty()) throw new IllegalArugmentException("List is empty");
T lowestElement = values.get(0);
for (int = 1; i < values.size(); i++) {
final T element = values.get(i);
if (comparator.compare(element, lowestElement) < 0)
lowestElement = element;
}
return lowestElement;
}
Implementing a Generic Type
public class AgeComparator implements Comparator<Person> {...}
- Comparator is a generic that accepts any type
Passing a parameter to a generic type
- Ex: if you wanted to flip the order of the comparator
// do in place where the comparator is, copy / paste to make a new comparator
// not as good way, creates redundant code
return -1 Integer.compare(left.getAge(), right.getAge());
// create a reverse comparator instead
public class ReverseComparator<T> implements Comparator<T> {
// will wrap another comparator to reverse
private final Comparator<T> delegateComparator;
// will pass the delegate into the constructor
public ReverseComparator(final Comparator<T> delgateComparator) {
this.delegateComparator = delegateComparator;
}
public int compare(final T left, final T right) {
return -1 * delegateComparator.compare(left, right);
}
}
// implementation
Collections.sort(madMen, new ReverseComparator<>(new AgeComparator()));
type bounds
- instead of allowing generic type to be anything, create some bounds if we know what will be accepted
- Example: sortable generic
// limits T to anything that extends Comparable
// will throw type out of bounds error otherwise
// the Comparable must also have type T, since it is only comparable to itself
public class SortedPair<T extends Comparable<T>> {
private final T first;
private final T second;
public SortedPair(T left, T right) {
// can now safely assume the type will have this method
if (left.compareTo(right) < 0) {
first = left;
second = right;
} else {
first = right;
second = left;
}
}
public T getSecond() { return second; }
public T getFirst() { return first; )
}
- collections is most common use case for generics
- you can have as many generic parameters on a class as needed, but not recommended above 2-3
Lists
List<Person> madMen = new List<Person>();
- cannot instantiate this manner as List is just an interface
List<Person> madMen = new ArrayList<Person>();
- Proper implementation of List with ArrayList
- uses an Array underneath
madMen.add(new Object());
- will have a compile error as is expecting a Person
List<Person> madMen = new ArrayList<>();
- will not add generics in explicitly, will infer from context
- take generic paramater from left side and use it on right side parameter
Sets
Set<Person> madMen = new HashSet<>();
- can iterate over a set like a list
- can efficiently check membership with
.contains()
Maps
Map<String, Person> madMen = new HashMap<>();
- each key must be unique
Collections
are inherently heterogenous, can contain any type of Object- creates type saftey
- add generics to stop runtime errors at compile time
- you can add a generic type to anything
public class CircularBuffer<T> { ... }
// instansiate
CircularBuffer<String> buffer = new CircularBuffer<String>(10);
- will not allow
new T[size]
will have to be(T[]) new Object[size]