Skip to content

Instantly share code, notes, and snippets.

@Theartbug
Last active November 1, 2020 16:37
Show Gist options
  • Save Theartbug/5ff71fa6ce007249cb95061b2d0266c0 to your computer and use it in GitHub Desktop.
Save Theartbug/5ff71fa6ce007249cb95061b2d0266c0 to your computer and use it in GitHub Desktop.
java fundamentals generics

Advanced topics

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

Reflection

  • 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]

Raw Types and Compatibility

  • 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
  • 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;
  }
}

Wildcards

  • where ? exist within generics
  • wildcards can be bounded:
    • upper bounded: List<? extends Comparator>
    • lower bounded: List<? super Person>
    • unbounded: List<?>

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
  • use List instead if you do not want covariance
    • but sometimes you want subclasses in these Lists, so upper bounded wildcards help

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
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
public void loadAll(final List<? super Person> people) { ... }

unbounded wildcards

  • the <?> by itself, syntactic sugar for <? extends Object>
    • Class<?> often confused with Class<Object>, which is not the same
      • Class<Object> represents the class of Object from Java.lang
      • Class<?> 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);

Generics on Methods

  • 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;
}

Generic Classes and Interfaces

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; )
}

Java's Generic Collections and Friends

  • 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

what / why generics

  • 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]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment