Comp215: Thinking Generically Dan S. Wallach (Rice University) Copyright 2015, Dan S. Wallach. All rights reserved.
Functional APIs On Wednesday, we built a list of Objects. This works. But it sucks. class FList { public FList add(object o) {... public boolean contains(object o) {... public Object head() {... public FList tail() {... public boolean empty() {... FList fl = new FList().add("Hello").add("Rice").add("Owls"); System.out.println(fl.head()); // Owls System.out.println(fl.tail().head()); // Rice System.out.println(fl.tail().tail().head()); // Hello
The Problem with Objects When all the world s an Object, what if you want to treat it differently? Old school answer: explicit typecast to the proper type. The problem: what if you re wrong? Good style would require more error checking, makes code ugly. FList fl = new FList().add("Hello").add("Rice").add("Owls"); System.out.println(fl.head()); // Owls System.out.println(fl.tail().head()); // Rice System.out.println(fl.tail().tail().head()); // Hello String foo = (String) fl.head(); // cast to String Yuck! But I know they re all Strings!
Generic APIs Pronounced List of T or T List T is a type parameter. We can now have lists of String, lists of Foo, whatever. No need for typecasting. class FList<T> { final T value; final FList taillist; public FList add(t value) {... public boolean contains(t value) {... public T head() {... public FList tail() {... public boolean empty() {... FList<String> fl = new FList<>().add("Hello").add("Rice").add("Owls"); String foo = fl.head(); // no cast necessary!
Generic APIs Pronounced List of T or T List T is a type parameter. We can now have lists of String, lists of Foo, whatever. No need for typecasting. class FList<T> { final T value; final FList taillist; public FList add(t value) {... public boolean contains(t value) {... public T head() {... public FList tail() {... public boolean empty() {... FList<String> fl = new FList<>().add("Hello").add("Rice").add("Owls"); type parameter <T> String foo = fl.head(); // no cast necessary!
Generic APIs Pronounced List of T or T List T is a type parameter. We can now have lists of String, lists of Foo, whatever. No need for typecasting. FList of String class FList<T> { final T value; final FList taillist; Type inference: Java figures public FList it out add(t from value) context. {... public boolean contains(t value) {... public T head() {... public FList tail() {... public boolean empty() {... FList<String> fl = new FList<>().add("Hello").add("Rice").add("Owls"); String foo = fl.head(); // no cast necessary!
Shirt<T>
Vocabulary Alert Generics = Parametric Polymorphism (Type) Parameter = Type arguments in the brackets (<T>, etc.) Polymorphism = Same code can operate over different types Remember Zen Coding Rule #1: Don t Repeat Yourself. Generics help.
What about those singletons? Beforehand... class FList { final Object value; final FList taillist;... public static class Empty extends FList { private final static Empty singleton = new Empty(); // we don t want others using this protected Empty() { super(null, null); // call this to create an empty list, even // though it just returns one we already have public static FList create() { return singleton;
What about those singletons? Afterward... public static class FList<T> { final T value; final FList taillist; <?>? <T> s spread around in various places. The singleton is a bit weird. There s only one? What s it s instance type?... public static class Empty<T> extends FList<T> { final static FList<?> singleton = new Empty<>(); // we don t want others using this protected Empty() { super(null, null); // call this to create an empty list, even // though it just returns one we already have public static <T> FList<T> create() { @SuppressWarnings("unchecked") FList<T> result = (FList<T>) singleton; return result;
What about those singletons? Afterward... <T> s spread around in various places. The singleton is a bit weird. There s only one? What s it s instance type? Compiler directive. public static class FList<T> { final T value; final FList taillist;... public static class Empty<T> extends FList<T> { final static FList<?> singleton = new Empty<>(); // we don t want others using this protected Empty() { super(null, null); // call this to create an empty list, even // though it just returns one we already have public static <T> FList<T> create() { @SuppressWarnings("unchecked") FList<T> result = (FList<T>) singleton; return result;
What about those singletons? Afterward... <T> s spread around in various places. The singleton is a bit weird. There s only one? What s it s instance type? public static class FList<T> { final T value; final FList taillist;... Type parameter for public static class Empty<T> extends FList<T> { final static FList<?> singleton = static method. new Empty<>(); // we don t want others using this protected Empty() { super(null, null); // call this to create an empty list, even // though it just returns one we already have public static <T> FList<T> create() { @SuppressWarnings("unchecked") FList<T> result = (FList<T>) singleton; return result;
Understanding Java generics Rule #1: there s only one real class (FList, etc.) Java does type erasure, so FList<String> and FList<Foo> etc. become just FList. At runtime, there s no such thing as FList<String> Implications There s only ever one static member variable of a given name. Generic: final static FList<?> singleton = new Empty<>(); Erased: final static FList singleton = new Empty (); We can get away with mixing and matching FList.Empty<A> for FList.Empty<B> @SuppressWarnings("unchecked") At runtime, nobody knows what T is. Not allowed to say new T(...) -- a giant headache, sometimes really ugly workarounds
Java can t always read your mind What you wish you could write: FList<String> list = FList.Empty.create().add("Hello").add("Rice"); Java can t (always) figure out the generic return-type of create(). (Sigh.) Solution: break it into two lines. FList<String> emptylist = FList.Empty.create(); FList<String> list = emptylist.add("hello").add("rice");
Type restrictions Sometimes we don t care, e.g., FList<T> Sometimes we want to constrain T Example: for things you put in a tree, they need to be comparable interface Comparable<T> { int compareto(t o); If we had a tree class, we might say: class FTree<T extends Comparable<T>> {...
Multiple type parameters? No problem. public class Pair<A,B> { final public A a; final public B b; public Pair(A a, B b) { this.a = a; this.b = b; We ll use this when we want to return more than one thing at a time. return new Pair<>(thing1, thing2);
Generics + Class Inheritance = Headache For now, we ll keep it simple but here s the issue. Simplified. class B extends A {... FList<B> flb =...; fla = flb; // error! flb = fla; // error! Anybody who can deal with A can also deal with B, because B extends A. But, List of B doesn t drop in where List of A normally goes. Why? fla = fla.add(new A(...)); // fine fla = fla.add(new B(...)); // also fine, because B extends A fla = flb; // error, but let s pretend it s okay fla = fla.add(new A(...)); // you just added an A onto a list of B!
Generics + Class Inheritance = Headache For now, we ll keep it simple but here s the issue. Simplified. FList<B> class B extends expects A { B,... not B s supertype A If FList<B> you could flb = assign...; fla = flb, then you could violate the rule at fla = flb; // error! runtime. Java forbids this. flb = fla; // error! Anybody who can deal with A can also deal with B, because B extends A. But, List of B doesn t drop in where List of A normally goes. Why? fla = fla.add(new A(...)); // fine fla = fla.add(new B(...)); // also fine, because B extends A fla = flb; // error, but let s pretend it s okay fla = fla.add(new A(...)); // you just added an A onto a list of B!
Generics + Class Inheritance For now, just deal with List<A> and List<B> not being interchangeable. Later on, we ll learn about type wildcards that work around this. And, while we re at it, why functional lists give us more flexibility than mutating. If you re bored, go read up on covariance and contravariance. There s a lot of deep theory here that we re totally ignoring today. (Coming in week 6!)
Cool Java Features
Useful Java feature #1: auto-boxing Java has two kinds of values: primitive types and objects. int, float, double, long, char, byte, boolean You can only use object types for generic type parameters Sorry, FList<int> isn t allowed The solution? Java object classes specifically made to wrap the primitives Integer, Float, Double, Long, Char, Byte, Boolean FList<Integer> emptyintlist = FList.Empty.create(); FList<Integer> list = emptyintlist.add(5).add(3).add(7); You pass an int and it s automatically wrapped in an Integer
Useful Java feature #2: exceptions What do you do when you don t have something useful to return? Example: there really is no head() of an empty list. Typical ugly solution: return null But, like we said earlier, null sucks. Better: throw an exception public T head() { throw new RuntimeException("can't take head() of an empty list");
Catching exceptions @Test public void testhead() throws Exception { FList<String> emptylist = List.Empty.create(); FList<String> list = emptylist.add("alice").add("bob").add("charlie"); assertequals("charlie", list.head()); asserttrue(emptylist.empty()); // now, verify that we can't take head of an empty list try { String foo = emptylist.head(); fail("exception should have been thrown"); catch (RuntimeException e) { // good! another JUnit feature: an assertion that always fails
Zen coding rule #2: proper exceptions Exceptions have an inheritance hierarchy Create and throw specific exception types for your problem class EmptyListException extends RuntimeException {... Rules of thumb: Don t catch an exception you re not prepared to deal with Catch the most specific exception(s) that you expect you might see Throw specific exceptions (not just RuntimeException ) Declared vs. undeclared exceptions RuntimeException extends Exception extends Throwable Anybody can throw a RuntimeException or subclass Other exceptions thrown must be declared public T head() throws EmptyListException {... Forces the caller to do a try/catch block and catch the exception
Zen coding rule #2: proper exceptions Exceptions have an inheritance hierarchy Create and throw specific exception types for your problem class EmptyListException extends RuntimeException {... Rules of thumb: Don t catch an exception you re not prepared to deal with Catch the most specific exception(s) that you expect you might see Throw specific exceptions (not just RuntimeException ) Declared vs. undeclared exceptions RuntimeException extends Exception extends Throwable Anybody can throw a RuntimeException or subclass Other exceptions thrown must be declared public T head() throws EmptyListException {... Forces the caller to do a try/catch block and catch the exception Java8 Optional is even better. Stay tuned.
Live coding demo Convert FList from Objects to generics Make sure null is properly forbidden Write unit tests