Chapter 15 Generics
General classes and methods can only use specific types: either basic types or custom types. If you want to write code that can be applied to many types of code, this rigid restriction will impose great constraints on the code.
In object-oriented programming languages, polymorphism is a generalization mechanism.
Generics implement the concept of parameterized types, so that code can be applied to a variety of types.
15.2 Simple Generics
Creating container classes facilitates the emergence of generics.
The following are classes that can only hold a single object:
class Automobile {} public class Holder1 { private Automobile a; public Holder1(Automobile a) { this.a = a; } Automobile get() { return a; } }
Objects of type Object are held directly, which can store any type of object:
public class Holder2 { private Object a; public Holder2(Object a) { this.a = a; } public void set(Object a) { this.a = a; } public Object get() { return a; } public static void main(String[] args) { Holder2 h2 = new Holder2(new Automobile()); Automobile a = (Automobile)h2.get(); h2.set("Not an Automobile"); String s = (String)h2.get(); h2.set(1); // Autoboxes to Integer Integer x = (Integer)h2.get(); } }
Using generic definitions:
public class Holder3<T> { private T a; public Holder3(T a) { this.a = a; } public void set(T a) { this.a = a; } public T get() { return a; } public static void main(String[] args) { Holder3<Automobile> h3 = new Holder3<Automobile>(new Automobile()); Automobile a = h3.get(); // No cast needed // h3.set("Not an Automobile"); // Error // h3.set(1); // Error } }
15.2.1 A tuple class library
Tuple is a group of objects directly packaged and stored in one of the single objects. This container object allows you to read elements in it, but not store new objects in it.
Tuples can be of any length and objects in tuples can be of any different type.
//: generics/TupleTest.java import net.mindview.util.*; class Amphibian {} class Vehicle {} public class TupleTest { static TwoTuple<String,Integer> f() { // Autoboxing converts the int to Integer: return new TwoTuple<String,Integer>("hi", 47); } static ThreeTuple<Amphibian,String,Integer> g() { return new ThreeTuple<Amphibian, String, Integer>( new Amphibian(), "hi", 47); } static FourTuple<Vehicle,Amphibian,String,Integer> h() { return new FourTuple<Vehicle,Amphibian,String,Integer>( new Vehicle(), new Amphibian(), "hi", 47); } static FiveTuple<Vehicle,Amphibian,String,Integer,Double> k() { return new FiveTuple<Vehicle,Amphibian,String,Integer,Double>( new Vehicle(), new Amphibian(), "hi", 47, 11.1); } public static void main(String[] args) { TwoTuple<String,Integer> ttsi = f(); System.out.println(ttsi); // ttsi.first = "there"; // Compile error: final System.out.println(g()); System.out.println(h()); System.out.println(k()); } } /* Output: (80% match) (hi, 47) (Amphibian@1f6a7b9, hi, 47) (Vehicle@35ce36, Amphibian@757aef, hi, 47) (Vehicle@9cab16, Amphibian@1a46e30, hi, 47, 11.1) *///:~
15.2.2 A stack class
public class LinkedStack<T> { private static class Node<U> { U item; Node<U> next; Node() { item = null; next = null; } Node(U item, Node<U> next) { this.item = item; this.next = next; } boolean end() { return item == null && next == null; } } private Node<T> top = new Node<T>(); // End sentinel public void push(T item) { top = new Node<T>(item, top); } public T pop() { T result = top.item; if(!top.end()) top = top.next; return result; } public static void main(String[] args) { LinkedStack<String> lss = new LinkedStack<String>(); for(String s : "Phasers on stun!".split(" ")) lss.push(s); String s; while((s = lss.pop()) != null) System.out.println(s); } } /* Output: stun! on Phasers *///:~
15.2.3 RandomList
//: generics/RandomList.java import java.util.*; public class RandomList<T> { private ArrayList<T> storage = new ArrayList<T>(); private Random rand = new Random(47); public void add(T item) { storage.add(item); } public T select() { return storage.get(rand.nextInt(storage.size())); } public static void main(String[] args) { RandomList<String> rs = new RandomList<String>(); for(String s: ("The quick brown fox jumped over " + "the lazy brown dog").split(" ")) rs.add(s); for(int i = 0; i < 11; i++) System.out.print(rs.select() + " "); } } /* Output: brown over fox quick quick dog brown The brown lazy brown *///:~
15.3 Generic Interface
Generics can be applied to interfaces. For example, a generator, which is a class specifically responsible for creating objects. This is an application of factory method design pattern. However, when a generator is used to create a new object, it does not require any parameters, whereas factory methods generally require parameters.
package coffee; public class Americano extends Coffee {} . . .
Implement Generator < Coffee > interface and generate different types of objects randomly:
package coffee; import java.util.*; import net.mindview.util.*; public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> { private Class[] types = { Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class, }; private static Random rand = new Random(47); public CoffeeGenerator() {} // For iteration: private int size = 0; public CoffeeGenerator(int sz) { size = sz; } public Coffee next() { try { return (Coffee) types[rand.nextInt(types.length)].newInstance(); // Report programmer errors at run time: } catch(Exception e) { throw new RuntimeException(e); } } class CoffeeIterator implements Iterator<Coffee> { int count = size; public boolean hasNext() { return count > 0; } public Coffee next() { count--; return CoffeeGenerator.this.next(); } public void remove() { // Not implemented throw new UnsupportedOperationException(); } } public Iterator<Coffee> iterator() { return new CoffeeIterator(); } public static void main(String[] args) { CoffeeGenerator gen = new CoffeeGenerator(); for(int i = 0; i < 5; i++) System.out.println(gen.next()); for(Coffee c : new CoffeeGenerator(5)) System.out.println(c); } }
Use it to generate Fibonacci sequence:
import net.mindview.util.*; public class Fibonacci implements Generator<Integer> { private int count = 0; public Integer next() { return fib(count++); } private int fib(int n) { if(n < 2) return 1; return fib(n-2) + fib(n-1); } public static void main(String[] args) { Fibonacci gen = new Fibonacci(); for(int i = 0; i < 18; i++) System.out.print(gen.next() + " "); } }
One limitation of generics is that basic types cannot be used as type parameters. Java SE5 has the functions of automatic packaging and unpacking.
Create adapters through inheritance:
import java.util.*; public class IterableFibonacci extends Fibonacci implements Iterable<Integer> { private int n; public IterableFibonacci(int count) { n = count; } public Iterator<Integer> iterator() { return new Iterator<Integer>() { public boolean hasNext() { return n > 0; } public Integer next() { n--; return IterableFibonacci.this.next(); } public void remove() { // Not implemented throw new UnsupportedOperationException(); } }; } public static void main(String[] args) { for(int i : new IterableFibonacci(18)) System.out.print(i + " "); } }
15.4 Generic Method
Basic Guidelines: Whenever you can, you should try to use generic methods. For a static method, it is impossible to access the type parameters of generic classes. If the static method needs to use generic capabilities, it must become a generic method over time.
Define generic methods by placing the list of generic parameters before the return value:
public class GenericMethods { public <T> void f(T x) { System.out.println(x.getClass().getName()); } public static void main(String[] args) { GenericMethods gm = new GenericMethods(); gm.f(""); gm.f(1); gm.f(1.0); gm.f(1.0F); gm.f('c'); gm.f(gm); } } /* Output: java.lang.String java.lang.Integer java.lang.Double java.lang.Float java.lang.Character GenericMethods *///:~
When using generic methods, it is usually not necessary to specify parameter types. The compiler will find specific types, which is called type parameter inference.
If basic types are introduced to generic methods, the automatic packaging mechanism intervenes, wrapping the values of basic types as corresponding objects.
15.4.1 Lever Utilization Type Parameter Inference
Write a tool class that contains a variety of static methods for creating various container objects:
public class New { public static <K,V> Map<K,V> map(){ return new HashMap<K,V>(); } }
import pets.*; import java.util.*; import net.mindview.util.*; public class SimplerPets { public static void main(String[] args) { Map<Person, List<? extends Pet>> petPeople = New.map(); // Rest of the code is the same... } } ///:~
Type inference is only valid for assignment operations and does not work at other times. If the result of a generic method call is passed as a parameter to another method, it is that the compiler will not perform type inference. The compiler considers that the return value of the generic method is assigned to a variable of type Object.
Explicit Type Description
To explicitly specify a type, you must insert an angle bracket between the operator and the method name, and then place the type in the angle bracket:
import pets.*; import java.util.*; import net.mindview.util.*; public class ExplicitTypeSpecification { static void f(Map<Person, List<Pet>> petPeople) {} public static void main(String[] args) { f(New.<Person, List<Pet>>map()); } } ///:~
15.4.2 Variable parameters and generic methods
Generic methods and variable parameter lists can coexist well:
import java.util.*; public class GenericVarargs { public static <T> List<T> makeList(T... args) { List<T> result = new ArrayList<T>(); for(T item : args) result.add(item); return result; } public static void main(String[] args) { List<String> ls = makeList("A"); System.out.println(ls); ls = makeList("A", "B", "C"); System.out.println(ls); ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split("")); System.out.println(ls); } } /* Output: [A] [A, B, C] [, A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z] *///:~
15.4.3 Generator Generic Method
import coffee.*; import java.util.*; import net.mindview.util.*; public class Generators { public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen, int n) { for(int i = 0; i < n; i++) coll.add(gen.next()); return coll; } public static void main(String[] args) { Collection<Coffee> coffee = fill( new ArrayList<Coffee>(), new CoffeeGenerator(), 4); for(Coffee c : coffee) System.out.println(c); Collection<Integer> fnumbers = fill( new ArrayList<Integer>(), new Fibonacci(), 12); for(int i : fnumbers) System.out.print(i + ", "); } } /* Output: Americano 0 Latte 1 Americano 2 Mocha 3 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, *///:~
15.4.4 A generic Generator
public class CountedObject { private static long counter = 0; private final long id = counter++; public long id() { return id; } public String toString() { return "CountedObject " + id;} } ///:~
import net.mindview.util.*; public class BasicGeneratorDemo { public static void main(String[] args) { Generator<CountedObject> gen = BasicGenerator.create(CountedObject.class); for(int i = 0; i < 5; i++) System.out.println(gen.next()); } } /* Output: CountedObject 0 CountedObject 1 CountedObject 2 CountedObject 3 CountedObject 4 *///:~
15.4.5 Simplify the use of tuples
//: generics/TupleTest2.java import net.mindview.util.*; import static net.mindview.util.Tuple.*; public class TupleTest2 { static TwoTuple<String,Integer> f() { return tuple("hi", 47); } static TwoTuple f2() { return tuple("hi", 47); } static ThreeTuple<Amphibian,String,Integer> g() { return tuple(new Amphibian(), "hi", 47); } static FourTuple<Vehicle,Amphibian,String,Integer> h() { return tuple(new Vehicle(), new Amphibian(), "hi", 47); } static FiveTuple<Vehicle,Amphibian,String,Integer,Double> k() { return tuple(new Vehicle(), new Amphibian(), "hi", 47, 11.1); } public static void main(String[] args) { TwoTuple<String,Integer> ttsi = f(); System.out.println(ttsi); System.out.println(f2()); System.out.println(g()); System.out.println(h()); System.out.println(k()); } } /* Output: (80% match) (hi, 47) (hi, 47) (Amphibian@7d772e, hi, 47) (Vehicle@757aef, Amphibian@d9f9c3, hi, 47) (Vehicle@1a46e30, Amphibian@3e25a5, hi, 47, 11.1) *///:~
15.4.6 A Set Utility
15.5 Anonymous Inner Class
Generics can also be applied to internal classes and anonymous internal classes, implemented using anonymous internal classes Generator Interface:
//: generics/BankTeller.java // A very simple bank teller simulation. import java.util.*; import net.mindview.util.*; class Customer { private static long counter = 1; private final long id = counter++; private Customer() {} public String toString() { return "Customer " + id; } // A method to produce Generator objects: public static Generator<Customer> generator() { return new Generator<Customer>() { public Customer next() { return new Customer(); } }; } } class Teller { private static long counter = 1; private final long id = counter++; private Teller() {} public String toString() { return "Teller " + id; } // A single Generator object: public static Generator<Teller> generator = new Generator<Teller>() { public Teller next() { return new Teller(); } }; } public class BankTeller { public static void serve(Teller t, Customer c) { System.out.println(t + " serves " + c); } public static void main(String[] args) { Random rand = new Random(47); Queue<Customer> line = new LinkedList<Customer>(); Generators.fill(line, Customer.generator(), 15); List<Teller> tellers = new ArrayList<Teller>(); Generators.fill(tellers, Teller.generator, 4); for(Customer c : line) serve(tellers.get(rand.nextInt(tellers.size())), c); } } /* Output: Teller 3 serves Customer 1 Teller 2 serves Customer 2 Teller 3 serves Customer 3 Teller 1 serves Customer 4 Teller 1 serves Customer 5 Teller 3 serves Customer 6 Teller 1 serves Customer 7 Teller 2 serves Customer 8 Teller 3 serves Customer 9 Teller 3 serves Customer 10 Teller 2 serves Customer 11 Teller 4 serves Customer 12 Teller 2 serves Customer 13 Teller 1 serves Customer 14 Teller 1 serves Customer 15 *///:~
15.6 Building Complex Models
An important benefit of generics is the ability to create complex models simply and safely.
Create List tuples:
//: generics/TupleList.java // Combining generic types to make complex generic types. import java.util.*; import net.mindview.util.*; public class TupleList<A,B,C,D> extends ArrayList<FourTuple<A,B,C,D>> { public static void main(String[] args) { TupleList<Vehicle, Amphibian, String, Integer> tl = new TupleList<Vehicle, Amphibian, String, Integer>(); tl.add(TupleTest.h()); tl.add(TupleTest.h()); for(FourTuple<Vehicle,Amphibian,String,Integer> i: tl) System.out.println(i); } } /* Output: (75% match) (Vehicle@11b86e7, Amphibian@35ce36, hi, 47) (Vehicle@757aef, Amphibian@d9f9c3, hi, 47) *///:~
Build a store that includes corridors, shelves and goods:
//: generics/Store.java // Building up a complex model using generic containers. import java.util.*; import net.mindview.util.*; //commodity class Product { private final int id; private String description; private double price; public Product(int IDnumber, String descr, double price){ id = IDnumber; description = descr; this.price = price; System.out.println(toString()); } public String toString() { return id + ": " + description + ", price: $" + price; } public void priceChange(double change) { price += change; } public static Generator<Product> generator = new Generator<Product>() { private Random rand = new Random(47); public Product next() { return new Product(rand.nextInt(1000), "Test", Math.round(rand.nextDouble() * 1000.0) + 0.99); } }; } //Goods shelves class Shelf extends ArrayList<Product> { public Shelf(int nProducts) { Generators.fill(this, Product.generator, nProducts); } } //corridor class Aisle extends ArrayList<Shelf> { public Aisle(int nShelves, int nProducts) { for(int i = 0; i < nShelves; i++) add(new Shelf(nProducts)); } } class CheckoutStand {} class Office {} //Shop public class Store extends ArrayList<Aisle> { private ArrayList<CheckoutStand> checkouts = new ArrayList<CheckoutStand>(); private Office office = new Office(); public Store(int nAisles, int nShelves, int nProducts) { for(int i = 0; i < nAisles; i++) add(new Aisle(nShelves, nProducts)); } public String toString() { StringBuilder result = new StringBuilder(); for(Aisle a : this) for(Shelf s : a) for(Product p : s) { result.append(p); result.append("\n"); } return result.toString(); } public static void main(String[] args) { System.out.println(new Store(14, 5, 10)); } } /* Output: 258: Test, price: $400.99 861: Test, price: $160.99 868: Test, price: $417.99 207: Test, price: $268.99 551: Test, price: $114.99 278: Test, price: $804.99 520: Test, price: $554.99 140: Test, price: $530.99 ... *///:~
15.7 Elimination of Mysteries
We can declare ArrayList.class, but not ArrayList < Integer >.class.
import java.util.*; public class ErasedTypeEquivalence { public static void main(String[] args) { Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2); } } /* Output: true *///:~
//: generics/LostInformation.java import java.util.*; class Frob {} class Fnorkle {} class Quark<Q> {} class Particle<POSITION,MOMENTUM> {} public class LostInformation { public static void main(String[] args) { List<Frob> list = new ArrayList<Frob>(); Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>(); Quark<Fnorkle> quark = new Quark<Fnorkle>(); Particle<Long,Double> p = new Particle<Long,Double>(); System.out.println(Arrays.toString( list.getClass().getTypeParameters())); System.out.println(Arrays.toString( map.getClass().getTypeParameters())); System.out.println(Arrays.toString( quark.getClass().getTypeParameters())); System.out.println(Arrays.toString( p.getClass().getTypeParameters())); } } /* Output: [E] [K, V] [Q] [POSITION, MOMENTUM] *///:~
Within generic code, no information about generic parameter types can be obtained.
Java generics are implemented using erasure, which means that when you use generics, any specific type information is erased, and all you know is that you are using an object.
15.7.1 C++ Approach
public class HasF { public void f() { System.out.println("HasF.f()"); } } ///:~
//: generics/Manipulation.java // {CompileTimeError} (Won't compile) class Manipulator<T> { private T obj; public Manipulator(T x) { obj = x; } // Error: cannot find symbol: method f(): public void manipulate() {/* obj.f();Unable to find type*/ } } public class Manipulation { public static void main(String[] args) { HasF hf = new HasF(); Manipulator<HasF> manipulator = new Manipulator<HasF>(hf); manipulator.manipulate(); } } ///:~
] Because of erasure, the Java compiler cannot map manipulate() to the fact that it must be able to call f() on obj with a requirement that HasF owns f().
To call f(), we reuse the extends keyword:
class Manipulator2<T extends HasF> { private T obj; public Manipulator2(T x) { obj = x; } public void manipulate() { obj.f(); } } ///:~
We said that generic type parameters will be erased to its first boundary, and we also mentioned erasing of type parameters. The compiler actually replaces the type parameter with its erasure, as above. T erases HasF, just like replacing T with HasF in class declarations.
Only when you want to use type parameters that are more generalized than a specific type -- that is, when you want code to work across multiple classes.
class ReturnGenericType<T extends HasF> { private T obj; public ReturnGenericType(T x) { obj = x; } public T get() { return obj; } } ///:~
15.7.2 Migration Compatibility
In order to reduce the potential confusion about erasure, you must clearly recognize that this is not a linguistic feature. It's a compromise in Java's generic implementation, because generics are not part of the Java language when it comes into being.
In erasure-based implementations, generic types are treated as second types, i.e. types that cannot be used in some important context. Generic types occur only during static type checking, after which all generic types in the program will be erased and replaced with their non-generic upper bounds. For example, type annotations such as List < T > will be erased as List, while ordinary type variables will be erased without specifying boundaries.
The core motivation of erasure is that it enables generic clients to use non-generic libraries. This is called migration compatibility.
15.7.3 Erase Problem
The main justification for erasure is the transition from non-generic code to generic code, and the integration of generics into the Java language without breaking existing types.
Create an instance:
class Foo<T>{ T var; } Foo<Cat> f=new Foo<Cat>();
Type T is not replaced everywhere in the whole class. When you write code for this class, you must know that it is only an Object.
//: generics/ErasureAndInheritance.java class GenericBase<T> { private T element; public void set(T arg) { arg = element; } public T get() { return element; } } class Derived1<T> extends GenericBase<T> {} class Derived2 extends GenericBase {} // No warning // class Derived3 extends GenericBase<?> {} // Strange error: // unexpected type found : ? // required: class or interface without bounds public class ErasureAndInheritance { @SuppressWarnings("unchecked") public static void main(String[] args) { Derived2 d2 = new Derived2(); Object obj = d2.get(); d2.set(obj); // Warning here! } } ///:~
@SuppressWarnings("unchecked")
This annotation is placed on the method IQ that can generate such warnings, not on the whole class. When you want to turn off warnings, it's best to focus as much as possible so that you don't accidentally obscure the real problem by too broad a turn-off warning.
15.7.4 Boundary Action
Because of erasure, the most confusing thing about generics is that they can mean things that don't make any sense:
//: generics/ArrayMaker.java import java.lang.reflect.*; import java.util.*; public class ArrayMaker<T> { private Class<T> kind; public ArrayMaker(Class<T> kind) { this.kind = kind; } @SuppressWarnings("unchecked") T[] create(int size) { return (T[])Array.newInstance(kind, size); } public static void main(String[] args) { ArrayMaker<String> stringMaker = new ArrayMaker<String>(String.class); String[] stringArray = stringMaker.create(9); System.out.println(Arrays.toString(stringArray)); } } /* Output: [null, null, null, null, null, null, null, null, null] *///:~
For creating arrays, using Array.newInstance() is recommended.
import java.util.*; public class ListMaker<T> { List<T> create() { //The < T > was removed, and at run time, there was no < T > inside. return new ArrayList<T>(); } public static void main(String[] args) { ListMaker<String> stringMaker= new ListMaker<String>(); List<String> stringList = stringMaker.create(); } } ///:~
Before returning to the list, put some objects in it:
import java.util.*; public class FilledListMaker<T> { List<T> create(T t, int n) { List<T> result = new ArrayList<T>(); for(int i = 0; i < n; i++) result.add(t); return result; } public static void main(String[] args) { FilledListMaker<String> stringMaker = new FilledListMaker<String>(); List<String> list = stringMaker.create("Hello", 4); System.out.println(list); } } /* Output: [Hello, Hello, Hello, Hello] *///:~
Here, even if the compiler cannot know anything about T in create(), it can still ensure that the object placed in result has a T type at compile time, making it suitable for ArrayList < T >. Even if erasing removes information about actual types within or within methods, the compiler can still ensure internal consistency of types used in methods or classes.
Erase removes type information from the method body, and all problems at run time are boundaries: where the object enters and leaves the method.
All actions in generics occur at boundaries -- additional compile-time checks are performed on the passed values and transitions to the passed values are inserted. The boundary is where the action takes place.
15.8 Erased Compensation
Erasion loses the ability to perform certain operations in generic code. Any operation that needs to know the exact type of information at runtime will not work:
public class Erased<T> { private final int SIZE = 100; public static void f(Object arg) {//Here the type information is erased. if(arg instanceof T) {} // Error T var = new T(); // Error new T cannot be implemented, partly because it is erased, and partly because the compiler cannot verify that T has a default constructor T[] array = new T[SIZE]; // Error T[] array = (T)new Object[100]; // Unchecked warning } } ///:~
If type labels are introduced, dynamic isInstance():
class Building {} class House extends Building {} public class ClassTypeCapture<T> { Class<T> kind; public ClassTypeCapture(Class<T> kind) { this.kind = kind; } public boolean f(Object arg) { return kind.isInstance(arg); } public static void main(String[] args) { ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class); System.out.println(ctt1.f(new Building())); System.out.println(ctt1.f(new House())); ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class); System.out.println(ctt2.f(new Building())); System.out.println(ctt2.f(new House())); } } /* Output: true true false true *///:~
15.8.1 Create Type Instances
Pass a factory object and use it to create new instances:
import static net.mindview.util.Print.*; class ClassAsFactory<T> { T x; public ClassAsFactory(Class<T> kind) { try { x = kind.newInstance(); } catch(Exception e) { throw new RuntimeException(e); } } } class Employee {} public class InstantiateGenericType { public static void main(String[] args) { ClassAsFactory<Employee> fe = new ClassAsFactory<Employee>(Employee.class); print("ClassAsFactory<Employee> succeeded"); try { ClassAsFactory<Integer> fi = new ClassAsFactory<Integer>(Integer.class);//It will fail because Integer does not have default construction parameters } catch(Exception e) { print("ClassAsFactory<Integer> failed"); } } } /* Output: ClassAsFactory<Employee> succeeded ClassAsFactory<Integer> failed *///:~
Use the factory shown and limit the type:
interface FactoryI<T> { T create(); } class Foo2<T> { private T x; public <F extends FactoryI<T>> Foo2(F factory) { x = factory.create(); } // ... } class IntegerFactory implements FactoryI<Integer> { public Integer create() { return new Integer(0); } } class Widget { public static class Factory implements FactoryI<Widget> { public Widget create() { return new Widget(); } } } public class FactoryConstraint { public static void main(String[] args) { new Foo2<Integer>(new IntegerFactory()); new Foo2<Widget>(new Widget.Factory()); } } ///:~
Template method design pattern:
abstract class GenericWithCreate<T> { final T element; GenericWithCreate() { element = create(); } abstract T create(); } class X {} class Creator extends GenericWithCreate<X> { X create() { return new X(); } void f() { System.out.println(element.getClass().getSimpleName()); } } public class CreatorGeneric { public static void main(String[] args) { Creator c = new Creator(); c.f(); } } /* Output: X *///:~
15.8.2 Generic Array
Cannot create generic arrays. The general solution is to use ArrayList anywhere you want to create generic arrays:
import java.util.*; public class ListOfGenerics<T> { private List<T> array = new ArrayList<T>(); public void add(T item) { array.add(item); } public T get(int index) { return array.get(index); } } ///:~
A reference can be defined according to the compiler's preferred method:
class Generic<T> {} public class ArrayOfGenericReference { static Generic<Integer>[] gia; } ///:~
The compiler can never create this exact array.
Create an Object array and convert it to the desired array type. This is compilable, but not usable.
public class ArrayOfGeneric { static final int SIZE = 100; static Generic<Integer>[] gia; @SuppressWarnings("unchecked") public static void main(String[] args) { // Compiles; produces ClassCastException: // GIA = (Generic < Integer >[]) New Object [SIZE]; // Runtime Error Reporting // Runtime type is the raw (erased) type: gia = (Generic<Integer>[])new Generic[SIZE]; System.out.println(gia.getClass().getSimpleName()); gia[0] = new Generic<Integer>(); //! gia[1] = new Object(); // Compile-time error // Discovers type mismatch at compile time: //! gia[2] = new Generic<Double>(); } } /* Output: Generic[] *///:~
The only way to create a generic array successfully is to create a new array of erased types and then transform it:
public class GenericArray<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArray(int sz) { array = (T[])new Object[sz]; }//Creating Object to Transition to T public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Method that exposes the underlying representation: public T[] rep() { return array; } public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(10); // This causes a ClassCastException: //! Integer[] ia = gai.rep(); // This is OK: Object[] oa = gai.rep(); } } ///:~
T[] array=new T[sz] cannot be declared, so we create an array of objects and then transform it.
Because of erasure, the runtime type of the array can only be Object []. If we immediately convert it to T [], the recompiler will lose the actual type of the array, and the compiler may miss some potential error checks. Because of this, it's better to use Object [] inside the collection, and then add a transition to T when you use array elements.
public class GenericArray2<T> { private Object[] array; public GenericArray2(int sz) { array = new Object[sz]; } public void put(int index, T item) { array[index] = item; } @SuppressWarnings("unchecked") public T get(int index) { return (T)array[index]; } @SuppressWarnings("unchecked") public T[] rep() { return (T[])array; // Warning: unchecked cast } public static void main(String[] args) { GenericArray2<Integer> gai = new GenericArray2<Integer>(10); for(int i = 0; i < 10; i ++) gai.put(i, i); for(int i = 0; i < 10; i ++) System.out.print(gai.get(i) + " "); System.out.println(); try { Integer[] ia = gai.rep(); } catch(Exception e) { System.out.println(e); } } } /* Output: (Sample) 0 1 2 3 4 5 6 7 8 9 java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; *///:~
Internally, the advantage of treating array as Object [] rather than T [] is that we are unlikely to forget the runtime type of this array and accidentally introduce defects.
For new code, a type identifier should be passed:
import java.lang.reflect.*; public class GenericArrayWithTypeToken<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArrayWithTypeToken(Class<T> type, int sz) { array = (T[])Array.newInstance(type, sz); } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Expose the underlying representation: public T[] rep() { return array; } public static void main(String[] args) { GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>( Integer.class, 10); // This now works: for(int i=0;i<10;i++) gai.put(i,i); Integer[] ia = gai.rep(); for (int i:ia ) System.out.println(i); } } ///:~
The type tag Class < T > is passed to the constructor to recover from erasure so that we can create arrays of the actual type we need.
15.9 Border
Boundaries allow you to set constraints on the parameter types used for generics. Although this allows you to force generics to apply types, a potentially more important effect is that you can call methods according to your boundary type.
Because erasure removes type information, methods that can be invoked with unbounded generic parameters are only those that can be invoked with Object. If you can restrict this parameter to a subset of types, you can use these subsets to call methods. To enforce this limitation, Java generics reuse the extends keyword.
interface HasColor { java.awt.Color getColor(); } class Colored<T extends HasColor> { T item; Colored(T item) { this.item = item; } T getItem() { return item; } // The bound allows you to call a method: java.awt.Color color() { return item.getColor(); } } class Dimension { public int x, y, z; } // This won't work -- class must be first, then interfaces: // class ColoredDimension<T extends HasColor & Dimension> { // Multiple bounds: class ColoredDimension<T extends Dimension & HasColor> { T item; ColoredDimension(T item) { this.item = item; } T getItem() { return item; } java.awt.Color color() { return item.getColor(); } int getX() { return item.x; } int getY() { return item.y; } int getZ() { return item.z; } } interface Weight { int weight(); } // As with inheritance, you can have only one // concrete class but multiple interfaces: class Solid<T extends Dimension & HasColor & Weight> { T item; Solid(T item) { this.item = item; } T getItem() { return item; } java.awt.Color color() { return item.getColor(); } int getX() { return item.x; } int getY() { return item.y; } int getZ() { return item.z; } int weight() { return item.weight(); } } class Bounded extends Dimension implements HasColor, Weight { public java.awt.Color getColor() { return null; } public int weight() { return 0; } } public class BasicBounds { public static void main(String[] args) { Solid<Bounded> solid = new Solid<Bounded>(new Bounded()); solid.color(); solid.getY(); solid.weight(); } } ///:~
Through inheritance, the boundary is passed on:
class HoldItem<T> { T item; HoldItem(T item) { this.item = item; } T getItem() { return item; } } class Colored2<T extends HasColor> extends HoldItem<T> { Colored2(T item) { super(item); } java.awt.Color color() { return item.getColor(); } } class ColoredDimension2<T extends Dimension & HasColor> extends Colored2<T> { ColoredDimension2(T item) { super(item); } int getX() { return item.x; } int getY() { return item.y; } int getZ() { return item.z; } } class Solid2<T extends Dimension & HasColor & Weight> extends ColoredDimension2<T> { Solid2(T item) { super(item); } int weight() { return item.weight(); } } public class InheritBounds { public static void main(String[] args) { Solid2<Bounded> solid2 = new Solid2<Bounded>(new Bounded()); solid2.color(); solid2.getY(); solid2.weight(); } } ///:~
More levels of inheritance:
// Demonstrating bounds in Java generics. import java.util.*; interface SuperPower {} interface XRayVision extends SuperPower { void seeThroughWalls(); } interface SuperHearing extends SuperPower { void hearSubtleNoises(); } interface SuperSmell extends SuperPower { void trackBySmell(); } class SuperHero<POWER extends SuperPower> { POWER power; SuperHero(POWER power) { this.power = power; } POWER getPower() { return power; } } class SuperSleuth<POWER extends XRayVision>//POWER boundaries are derived from the parent class of XRayVision extends SuperHero<POWER> { SuperSleuth(POWER power) { super(power); } void see() { power.seeThroughWalls(); } } class CanineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER> { CanineHero(POWER power) { super(power); } void hear() { power.hearSubtleNoises(); } void smell() { power.trackBySmell(); } } class SuperHearSmell implements SuperHearing, SuperSmell { public void hearSubtleNoises() {} public void trackBySmell() {} } class DogBoy extends CanineHero<SuperHearSmell> { DogBoy() { super(new SuperHearSmell()); } } public class EpicBattle { // Bounds in generic methods: static <POWER extends SuperHearing> void useSuperHearing(SuperHero<POWER> hero) { hero.getPower().hearSubtleNoises(); } static <POWER extends SuperHearing & SuperSmell> void superFind(SuperHero<POWER> hero) { hero.getPower().hearSubtleNoises(); hero.getPower().trackBySmell(); } public static void main(String[] args) { DogBoy dogBoy = new DogBoy(); useSuperHearing(dogBoy); superFind(dogBoy); // You can do this: List<? extends SuperHearing> audioBoys; // But you can't do this: // List<? extends SuperHearing & SuperSmell> dogBoys; } } ///:~
15.10 wildcards
Array references to cardinal types can be derived from an array of types:
class Fruit {} class Apple extends Fruit {} class Jonathan extends Apple {} class Orange extends Fruit {} public class CovariantArrays { public static void main(String[] args) { Fruit[] fruit = new Apple[10]; fruit[0] = new Apple(); // OK fruit[1] = new Jonathan(); // OK // Runtime type is Apple[], not Fruit[] or Orange[]: try { // Compiler allows you to add Fruit: fruit[2] = new Fruit(); // ArrayStoreException can't be transformed upwards } catch(Exception e) { System.out.println(e); } try { // Compiler allows you to add Oranges: fruit[3] = new Orange(); // ArrayStoreException } catch(Exception e) { System.out.println(e); } } } /* Output: java.lang.ArrayStoreException: Fruit java.lang.ArrayStoreException: Orange *///:~
Array objects can retain rules about the types of objects they contain.
import java.util.*; public class NonCovariantGenerics { // Compile Error: incompatible types: List<Fruit> flist = new ArrayList<Apple>();//You cannot assign an Apple container to a Fruit container } ///:~
Unlike arrays, generics have no built-in covariant types. Sometimes you want to establish some kind of upward transition between the two types, which is what wildcards allow:
import java.util.*; public class GenericsAndCovariance { public static void main(String[] args) { // Wildcards allow covariance: List<? extends Fruit> flist = new ArrayList<Apple>();//The upward transition here will lose the ability to pass any object in, so that no type of object can be passed in, nor can Object be passed in. // Compile Error: can't add any type of object: // flist.add(new Apple()); // flist.add(new Fruit()); // flist.add(new Object()); flist.add(null); // Legal but uninteresting // We know that it returns at least Fruit: Fruit f = flist.get(0); } } ///:~
How smart is the 15.10.1 compiler
import java.util.*; public class CompilerIntelligence { public static void main(String[] args) { List<? extends Fruit> flist = Arrays.asList(new Apple()); Apple a = (Apple)flist.get(0); // No warning flist.contains(new Apple()); // Object parameter is Object flist.indexOf(new Apple()); // Argument is 'Object' //flist.add(new Apple()); what parameters do Add need here? extends Fruit, but it doesn't know which specific type is needed, so it won't accept any parameters. } } ///:~
add() accepts a parameter with a generic parameter type, but contains() and indexOf() accepts an Object type parameter, so when you specify an ArrayList <? extends Fruit >, the parameter of add() becomes? extends Fruit.
public class Holder<T> { private T value; public Holder() {} public Holder(T val) { value = val; } public void set(T val) { value = val; } public T get() { return value; } public boolean equals(Object obj) { return value.equals(obj); } public static void main(String[] args) { Holder<Apple> Apple = new Holder<Apple>(new Apple()); Apple d = Apple.get(); Apple.set(d); // Holder<Fruit> Fruit = Apple; // Cannot upcast Holder<? extends Fruit> fruit = Apple; // OK Fruit p = fruit.get(); d = (Apple)fruit.get(); // Returns 'Object' try { Orange c = (Orange)fruit.get(); // No warning } catch(Exception e) { System.out.println(e); } // fruit.set(new Apple()); // Cannot call set() // fruit.set(new Fruit()); // Cannot call set() System.out.println(fruit.equals(d)); // OK } } /* Output: (Sample) java.lang.ClassCastException: Apple cannot be cast to Orange true *///:~
15.10.2 Inversion
Super-type wildcards can be declared to be defined by any base class of a particular class by specifying <? Super MyClass> or even using type parameters <? Super T>.
import java.util.*; public class SuperTypeWildcards { static void writeTo(List<? super Apple> apples) { apples.add(new Apple()); apples.add(new Jonathan()); // apples.add(new Fruit()); // Error } } ///:~
Supertype boundaries relax the constraints on parameters that can be passed to the method:
import java.util.*; public class GenericWriting { static <T> void writeExact(List<T > list, T item) { list.add(item); } static List<Apple> apples = new ArrayList<Apple>(); static List<Fruit> fruit = new ArrayList<Fruit>(); static void f1() { writeExact(apples, new Apple()); // writeExact(fruit, new Apple()); // Error: // Incompatible types: found Fruit, required Apple } static <T> void writeWithWildcard(List<? super T> list, T item) { list.add(item); } static void f2() { writeWithWildcard(apples, new Apple()); writeWithWildcard(fruit, new Apple()); } public static void main(String[] args) { f1(); f2(); } } ///:~
15.10.3 unbounded wildcards
Unbounded wildcards <?> seem to mean anything, so using unbounded wildcards seems to be equivalent to using native types.
import java.util.*; public class UnboundedWildcards1 { static List list1; static List<?> list2; static List<? extends Object> list3; static void assign1(List list) { list1 = list; list2 = list; // list3 = list; // Warning: unchecked conversion // Found: List, Required: List<? extends Object> } static void assign2(List<?> list) { list1 = list; list2 = list; list3 = list; } static void assign3(List<? extends Object> list) { list1 = list; list2 = list; list3 = list; } public static void main(String[] args) { assign1(new ArrayList()); assign2(new ArrayList()); // assign3(new ArrayList()); // Warning: // Unchecked conversion. Found: ArrayList // Required: List<? extends Object> assign1(new ArrayList<String>()); assign2(new ArrayList<String>()); assign3(new ArrayList<String>()); // Both forms are acceptable as List<?>: List<?> wildList = new ArrayList(); wildList = new ArrayList<String>(); assign1(wildList); assign2(wildList); assign3(wildList); } } ///:~
<?> Can be considered a decoration, in fact, it is a statement: I want to write this code with Java generics, I am not here to use native types, but in the current situation, generic parameters can hold any type.
import java.util.*; public class UnboundedWildcards2 { static Map map1; static Map<?,?> map2; static Map<String,?> map3; static void assign1(Map map) { map1 = map; } static void assign2(Map<?,?> map) { map2 = map; } static void assign3(Map<String,?> map) { map3 = map; } public static void main(String[] args) { assign1(new HashMap()); assign2(new HashMap()); assign3(new HashMap()); // Warning: // Unchecked conversion. Found: HashMap // Required: Map<String,?> assign1(new HashMap<String,Integer>()); assign2(new HashMap<String,Integer>()); assign3(new HashMap<String,Integer>()); } } ///:~
In fact, because the generic parameter will be erased to its first boundary, LIst <?> appears to be equivalent to LIst < Object >, and List is actually List < Object > unless none of these statements is true. List actually means holding a native List of any Object type, while List<?> means having a non-native List of a particular type.
//: generics/Wildcards.java // Exploring the meaning of wildcards. public class Wildcards { // Raw argument: static void rawArgs(Holder holder, Object arg) { holder.set(arg); // Warning: The compiler knows that Holder is generic, so it's not safe to pass Object to set // Unchecked call to set(T) as a // member of the raw type Holder // holder.set(new Wildcards()); // Same warning // Can't do this; don't have any 'T': // T t = holder.get(); // OK, but type information has been lost: Object obj = holder.get(); } // Similar to rawArgs(), but errors instead of warnings: static void unboundedArg(Holder<?> holder, Object arg) { // holder.set(arg); // Error: The native type will hold any type of combination, but Holder <?> will hold an isomorphic set with a specific type, so Object cannot be passed to it. // set(capture of ?) in Holder<capture of ?> // cannot be applied to (Object) // holder.set(new Wildcards()); // Same error // Can't do this; don't have any 'T': // T t = holder.get(); // OK, but type information has been lost: Object obj = holder.get(); } static <T> T exact1(Holder<T> holder) { T t = holder.get(); return t; } static <T> T exact2(Holder<T> holder, T arg) { holder.set(arg); T t = holder.get(); return t; } static <T> T wildSubtype(Holder<? extends T> holder, T arg) { // holder.set(arg); // Error: No type of incoming is allowed to prevent incoming collections of different types of structures. // set(capture of ? extends T) in // Holder<capture of ? extends T> // cannot be applied to (T) T t = holder.get(); return t; } static <T> void wildSupertype(Holder<? super T> holder, T arg) { holder.set(arg);//Super class, any object inherited from T type can be passed in // T t = holder.get(); // Error: // Incompatible types: found Object, required T // OK, but type information has been lost: Object obj = holder.get();//The type passed out here can be any supertype, so it's safe to use Object here. } public static void main(String[] args) { Holder raw = new Holder<Long>(); // Or: raw = new Holder(); Holder<Long> qualified = new Holder<Long>(); Holder<?> unbounded = new Holder<Long>(); Holder<? extends Long> bounded = new Holder<Long>(); Long lng = 1L; rawArgs(raw, lng); rawArgs(qualified, lng); rawArgs(unbounded, lng); rawArgs(bounded, lng); unboundedArg(raw, lng); unboundedArg(qualified, lng); unboundedArg(unbounded, lng); unboundedArg(bounded, lng); Object r1 = exact1(raw); // Warnings: // Unchecked conversion from Holder to Holder<T> // Unchecked method invocation: exact1(Holder<T>) // is applied to (Holder) Long r2 = exact1(qualified); Object r3 = exact1(unbounded); // Must return Object Long r4 = exact1(bounded); Long r5 = exact2(raw, lng); // Warnings: // Unchecked conversion from Holder to Holder<Long> // Unchecked method invocation: exact2(Holder<T>,T) // is applied to (Holder,Long) Long r6 = exact2(qualified, lng); // Long r7 = exact2(unbounded, lng); // Error: // exact2(Holder<T>,T) cannot be applied to // (Holder<capture of ?>,Long) // Long r8 = exact2(bounded, lng); // Error: // exact2(Holder<T>,T) cannot be applied // to (Holder<capture of ? extends Long>,Long) // Long r9 = wildSubtype(raw, lng); // Warnings: // Unchecked conversion from Holder // to Holder<? extends Long> // Unchecked method invocation: // wildSubtype(Holder<? extends T>,T) is // applied to (Holder,Long) Long r10 = wildSubtype(qualified, lng); // OK, but can only return Object: Object r11 = wildSubtype(unbounded, lng); Long r12 = wildSubtype(bounded, lng); // wildSupertype(raw, lng); // Warnings: // Unchecked conversion from Holder // to Holder<? super Long> // Unchecked method invocation: // wildSupertype(Holder<? super T>,T) // is applied to (Holder,Long) wildSupertype(qualified, lng); // wildSupertype(unbounded, lng); // Error: // wildSupertype(Holder<? super T>,T) cannot be // applied to (Holder<capture of ?>,Long) // wildSupertype(bounded, lng); // Error: // wildSupertype(Holder<? super T>,T) cannot be // applied to (Holder<capture of ? extends Long>,Long) } } ///:~
The advantage of using exact types instead of wildcard types is that you can do more with generic parameters, but using wildcards makes you have to accept a wider range of parameterized types as parameters.
15.10.4 capture conversion
There is a specific case where <?> is used instead of the native type. If you pass a native type to a method that uses <?>, then for the compiler, you may infer the actual type so that the method can turn around and call another method that uses the exact type, which is called capture conversion. Because the unspecified wildcard type is captured and converted to the exact type.
public class CaptureConversion { static <T> void f1(Holder<T> holder) {//Parameter determination, no wildcards or boundaries T t = holder.get(); System.out.println(t.getClass().getSimpleName()); } static void f2(Holder<?> holder) {//Unbounded wildcards, the type is unknown, but F1 is called here. Here the parameter type is captured in the process of calling f2, so it can be used in the call to f1. f1(holder); // Call with captured type } @SuppressWarnings("unchecked") public static void main(String[] args) { Holder raw = new Holder<Integer>(1); f1(raw); // Produces warnings f2(raw); // No warnings Holder rawBasic = new Holder(); rawBasic.set(new Object()); // Warning f2(rawBasic); // No warnings // Upcast to Holder<?>, still figures it out: Holder<?> wildcarded = new Holder<Double>(1.0); f2(wildcarded); } } /* Output: Integer Object Double *///:~
15.11 Question
15.11.1 No basic type can be used as a type parameter
Use basic types of wrappers:
import java.util.*; public class ListOfInt { public static void main(String[] args) { List<Integer> li = new ArrayList<Integer>(); for(int i = 0; i < 5; i++) li.add(i); for(int i : li) System.out.print(i + " "); } } /* Output: 0 1 2 3 4 *///:~
If performance is required, you can use a dedicated container version that adapts the basic type: Org.apache.commons.collections.primitives.
Create Byte's Set in another way:
import java.util.*; public class ByteSet { Byte[] possibles = { 1,2,3,4,5,6,7,8,9 }; Set<Byte> mySet = new HashSet<Byte>(Arrays.asList(possibles)); // But you can't do this: // Set<Byte> mySet2 = new HashSet<Byte>( // Arrays.<Byte>asList(1,2,3,4,5,6,7,8,9)); } ///:~
Using generic Generator interface:
import net.mindview.util.*; // Fill an array using a generator: class FArray { public static <T> T[] fill(T[] a, Generator<T> gen) { for(int i = 0; i < a.length; i++) a[i] = gen.next(); return a; } } public class PrimitiveGenericTest { public static void main(String[] args) { String[] strings = FArray.fill( new String[8], new RandomGenerator.String()); for(String s : strings) System.out.println(s); Integer[] integers = FArray.fill( new Integer[7], new RandomGenerator.Integer()); for(int i: integers) System.out.println(i); // Autoboxing won't save you here. This won't compile: // int[] b = // FArray.fill(new int[7], new RandIntGenerator()); } } /* Output: YNzbrnyGcF OWZnTcQrGs eGZMmJMRoE suEcUOneOE dLsmwHLGEa hKcxrEqUCB bkInaMesbt 7052 6665 2654 3909 5202 2209 5458 *///:~
15.11.2 Implementation of Parametric Interface
A class cannot implement two variants of the same generic interface. Because of erasure, the two variants become the same interface.
interface Payable<T> {} class Employee1 implements Payable<Employee1> {} class Hourly extends Employee implements Payable<Hourly> {} ///:~This is erased as Object. public class MultipleInterfaceVariants{ public static void main(String[] args){ } }
15.11.3 Transition and Warning
Using transitions or instanceof with generic type parameters will not have any effect.
The following container internally stores the values as Object s and transforms them back to T when they are acquired:
class FixedSizeStack<T> { private int index = 0; private Object[] storage; public FixedSizeStack(int size) { storage = new Object[size]; } public void push(T item) { storage[index++] = item; } @SuppressWarnings("unchecked") public T pop() { return (T)storage[--index]; }//Because of erasure, the compiler cannot know whether the transition is safe, and the pop() method does not actually perform any transition. } public class GenericCast { public static final int SIZE = 10; public static void main(String[] args) { FixedSizeStack<String> strings = new FixedSizeStack<String>(SIZE); for(String s : "A B C D E F G H I J".split(" ")) strings.push(s); for(int i = 0; i < SIZE; i++) { String s = strings.pop(); System.out.print(s + " "); } } } /* Output: J I H G F E D C B A *///:~
Generics do not eliminate the need for transformation, which can be warned by the compiler, which is inappropriate:
import java.io.*; import java.util.*; public class NeedCasting { @SuppressWarnings("unchecked") public static void main(String[] args) throws Exception { ObjectInputStream in = new ObjectInputStream( new FileInputStream("")); List<Widget> shapes = (List<Widget>)in.readObject();//readObject cannot know what it is reading, so it returns objects that must be transformed. } } ///:~
Generic classes to transition:
import java.io.*; import java.util.*; public class ClassCasting { @SuppressWarnings("unchecked") public void f(String[] args) throws Exception { ObjectInputStream in = new ObjectInputStream( new FileInputStream(args[0])); // Won't Compile: // List<Widget> lw1 = // List<Widget>.class.cast(in.readObject()); List<Widget> lw2 = List.class.cast(in.readObject()); } } ///:~
15.11.4 Heavy Duty
The following program cannot be compiled:
import java.util.*; public class UseList<W,T> { void f1(List<T> v) {}//Overloading will produce the same type of signature void f1(List<W> v) {} } ///:~
15.11.5 Base Class Hijacked Interface
Create a comparison class for other objects:
public class ComparablePet implements Comparable<ComparablePet> { public int compareTo(ComparablePet arg) { return 0; } } ///:~
Narrowing its subclasses doesn't make sense: the code can't run
class Cat extends ComparablePet implements Comparable<Cat>{ // Error: Comparable cannot be inherited with // different arguments: <Cat> and <Pet> public int compareTo(Cat arg) { return 0; } } ///:~
The following code does work as long as the exact classes are the same:
class Hamster extends ComparablePet implements Comparable<ComparablePet> { public int compareTo(ComparablePet arg) { return 0; } } // Or just: class Gecko extends ComparablePet { public int compareTo(ComparablePet arg) { return 0; } } ///:~
15.12 Self-limiting Types
In Java generics, there is a common usage that seems to occur regularly:
class SelfBounded<T extends SelfBounded<T>>
It's like two mirrors shining at each other to create a dazzling effect, an infinite reflection. The SelfBounded class accepts the generic parameter T, and T is defined by a boundary class, which is the SelfBounded with T as the parameter.
15.12.1 Strange Cyclic Generics
You cannot directly inherit a generic parameter, but you can inherit classes that use this generic parameter in their own definitions.
class GenericType<T> {} public class CuriouslyRecurringGeneric extends GenericType<CuriouslyRecurringGeneric> {} ///:~
This is called a weird cyclic generic.
Generics in Java are about parameters and return types, so they can generate base classes that use export classes as their parameters and return types. It can also use export types as its domain types, even those that will be erased as Object s.
public class BasicHolder<T> { T element; void set(T arg) { element = arg; } T get() { return element; } void f() { System.out.println(element.getClass().getSimpleName()); } } ///:~
This is a common generic type, and here's another weird circular generic to use it:
class Subtype extends BasicHolder<Subtype> {} public class CRGWithBasicHolder { public static void main(String[] args) { Subtype st1 = new Subtype(), st2 = new Subtype(); st1.set(st2); Subtype st3 = st1.get(); st1.f(); } } /* Output: Subtype *///:~
CRG Essence: Base Classes Replace Their Parameters with Derived Classes
The exact type is used instead of the base type in the generated class.
15.12.2 Self-limiting
BasicHolder can use any type as its generic parameter:
class Other {} class BasicOther extends BasicHolder<Other> {} public class Unconstrained { public static void main(String[] args) { BasicOther b = new BasicOther(), b2 = new BasicOther(); b.set(new Other()); Other other = b.get(); b.f(); } } /* Output: Other *///:~
Self-qualifying will take additional steps to force generics to be used as their own boundary parameters:
class SelfBounded<T extends SelfBounded<T>> { T element; SelfBounded<T> set(T arg) { element = arg; return this; } T get() {System.out.println(this.getClass().getSimpleName()); return element; } } class A extends SelfBounded<A> {}//Forces the class being defined to be passed as a parameter to the base class. class B extends SelfBounded<A> {} // Also OK class C extends SelfBounded<C> { C setAndGet(C arg) { set(arg); return get(); } } class D {} // Can't do this: //class E extends SelfBounded<D> {} // Compile error: Type parameter D is not within its bound // Alas, you can do this, so you can't force the idiom: class F extends SelfBounded {}//Self-limiting idioms are not enforceable public class SelfBounding { public static void main(String[] args) { A a = new A(); a.set(new A()); a = a.set(new A()).get(); a = a.get(); C c = new C(); c = c.setAndGet(new C()); } } ///:~
Self-limiting parameter meaning: It ensures that type parameters must be the same as the classes being defined.
The restriction of self-qualification can be removed so that all classes can still be compiled.
public class NotSelfBounded<T> { T element; NotSelfBounded<T> set(T arg) { element = arg; return this; } T get() { return element; } } class A2 extends NotSelfBounded<A2> {} class B2 extends NotSelfBounded<A2> {} class C2 extends NotSelfBounded<C2> { C2 setAndGet(C2 arg) { set(arg); return get(); } } class D2 {} // Now this is OK: class E2 extends NotSelfBounded<D2> {} ///:~
Obviously, self-limiting restrictions can only force inheritance relationships. If self-limiting is used, it should be understood that the type parameters used by this class will have the same base type as those used by the class.
Self-limiting can also be used for generic methods:
public class SelfBoundingMethods { static <T extends SelfBounded<T>> T f(T arg) { return arg.set(arg).get(); } public static void main(String[] args) { A a = f(new A()); } } ///:~
15.12.3 parameter covariance
The value of self-qualified types is that they can produce covariant parameter types - method parameter types vary with subclasses.
class Base {} class Derived extends Base {} interface OrdinaryGetter { Base get(); } interface DerivedGetter extends OrdinaryGetter { // Return type of overridden method is allowed to vary: Derived get(); } public class CovariantReturnTypes { void test(DerivedGetter d) { Derived d2 = d.get(); } } ///:~
Self-qualified generics actually produce exact export types as their return values:
interface GenericGetter<T extends GenericGetter<T>> { T get(); } interface Getter extends GenericGetter<Getter> {} public class GenericsAndReturnTypes { void test(Getter g) { Getter result = g.get(); GenericGetter gg = g.get(); // Also the base type } } ///:~
When using a self-qualified type, there is only one method for the derived class, and this method receives the derived type instead of the base type as a parameter:
interface SelfBoundSetter<T extends SelfBoundSetter<T>> { void set(T arg); } interface Setter extends SelfBoundSetter<Setter> {} public class SelfBoundingAndCovariantArguments { void testA(Setter s1, Setter s2, SelfBoundSetter sbs) { s1.set(s2); // s1.set(sbs); // Error: The compiler cannot recognize an attempt to pass the base type as a parameter to set(), because no method has such a signature. In fact, this parameter has been overwritten. // set(Setter) in SelfBoundSetter<Setter> // cannot be applied to (SelfBoundSetter) } } ///:~
Without the use of self-limiting types, common inheritance mechanisms will intervene and will be able to overload:
class GenericSetter<T> { // Not self-bounded void set(T arg){ System.out.println("GenericSetter.set(Base)"); } } class DerivedGS extends GenericSetter<Base> { void set(Derived derived){ System.out.println("DerivedGS.set(Derived)"); } } public class PlainGenericInheritance { public static void main(String[] args) { Base base = new Base(); Derived derived = new Derived(); DerivedGS dgs = new DerivedGS(); dgs.set(derived); dgs.set(base); // Compiles: overloaded, not overridden! } } /* Output: DerivedGS.set(Derived) GenericSetter.set(Base) *///:~
If no self-limiting is used, the parameter type will be overloaded. If self-limiting is used, only one version of a method can be obtained, and it will receive the exact parameter type.
15.13 Dynamic Type Security
There are a set of convenience tools in Java SE5's java.util.Collections: static methods checkedCollection(), checkedList(),checkedMap(),checkedSet(),checkedSortedMap() and checkedSortedSet(). Each of these methods accepts the container you want to dynamically check as a parameter and the type you want to force as a second parameter.
import pets.*; import java.util.*; public class CheckedList { @SuppressWarnings("unchecked") static void oldStyleMethod(List probablyDogs) { probablyDogs.add(new Cat()); } public static void main(String[] args) { List<Dog> dogs1 = new ArrayList<Dog>(); oldStyleMethod(dogs1); // Quietly accepts a Cat List<Dog> dogs2 = Collections.checkedList( new ArrayList<Dog>(), Dog.class); try { oldStyleMethod(dogs2); // Throws an exception will throw an exception } catch(Exception e) { System.out.println(e); } // Derived types work fine: List<Pet> pets = Collections.checkedList(//It doesn't matter if the export type is checked in the base type new ArrayList<Pet>(), Pet.class); pets.add(new Dog()); pets.add(new Cat()); } } /* Output: java.lang.ClassCastException: Attempt to insert class typeinfo.pets.Cat element into collection with element type class typeinfo.pets.Dog *///:~
15.14 anomaly
Because of erasure, the application of generics to exceptions is very limited. The catch statement cannot catch exceptions of generic types, because the exact type of exceptions must be known at compile time and run time, and generic classes cannot directly or indirectly inherit from Throwable.
import java.util.*; interface Processor<T,E extends Exception> { void process(List<T> resultCollector) throws E; } class ProcessRunner<T,E extends Exception> extends ArrayList<Processor<T,E>> { List<T> processAll() throws E { List<T> resultCollector = new ArrayList<T>(); for(Processor<T,E> processor : this) processor.process(resultCollector); return resultCollector; } } class Failure1 extends Exception {} class Processor1 implements Processor<String,Failure1> { static int count = 3; public void process(List<String> resultCollector) throws Failure1 { if(count-- > 1) resultCollector.add("Hep!"); else resultCollector.add("Ho!"); if(count < 0) throw new Failure1(); } } class Failure2 extends Exception {} class Processor2 implements Processor<Integer,Failure2> { static int count = 2; public void process(List<Integer> resultCollector) throws Failure2 { if(count-- == 0) resultCollector.add(47); else { resultCollector.add(11); } if(count < 0) throw new Failure2(); } } public class ThrowGenericException { public static void main(String[] args) { ProcessRunner<String,Failure1> runner = new ProcessRunner<String,Failure1>(); for(int i = 0; i < 3; i++) runner.add(new Processor1()); try { System.out.println(runner.processAll()); } catch(Failure1 e) { System.out.println(e); } ProcessRunner<Integer,Failure2> runner2 = new ProcessRunner<Integer,Failure2>(); for(int i = 0; i < 3; i++) runner2.add(new Processor2()); try { System.out.println(runner2.processAll()); } catch(Failure2 e) { System.out.println(e); } } } ///:~
15.15 Mixed Type
Basic Concept: The ability to mix multiple classes to produce a class that represents all types of mixtures.
One of the value of mixtures is that they can apply features and behaviors consistently to multiple classes. If you want to modify something in a mix, as an unexpected benefit, these modifications will apply to all types of mix applications.
15.15.2 Mixed with Interface
import java.util.*; interface TimeStamped { long getStamp(); } class TimeStampedImp implements TimeStamped { private final long timeStamp; public TimeStampedImp() { timeStamp = new Date().getTime(); } public long getStamp() { return timeStamp; } } interface SerialNumbered { long getSerialNumber(); } class SerialNumberedImp implements SerialNumbered { private static long counter = 1; private final long serialNumber = counter++; public long getSerialNumber() { return serialNumber; } } interface Basic { public void set(String val); public String get(); } class BasicImp implements Basic { private String value; public void set(String val) { value = val; } public String get() { return value; } } class Mixin extends BasicImp implements TimeStamped, SerialNumbered {//With proxies, each mixed type requires a corresponding domain in Mixin, and you must write the necessary methods in Mixin to forward method calls to the appropriate objects. private TimeStamped timeStamp = new TimeStampedImp(); private SerialNumbered serialNumber = new SerialNumberedImp(); public long getStamp() { return timeStamp.getStamp(); } public long getSerialNumber() { return serialNumber.getSerialNumber(); } } public class Mixins { public static void main(String[] args) { Mixin mixin1 = new Mixin(), mixin2 = new Mixin(); mixin1.set("test string 1"); mixin2.set("test string 2"); System.out.println(mixin1.get() + " " + mixin1.getStamp() + " " + mixin1.getSerialNumber()); System.out.println(mixin2.get() + " " + mixin2.getStamp() + " " + mixin2.getSerialNumber()); } } /* Output: (Sample) test string 1 1132437151359 1 test string 2 1132437151359 2 *///:~
15.15.3 Use Decorator Mode
When you look at the use of mixing, you will find that the concept of mixing is similar to the design pattern of decoration. Decorators are often used to satisfy a variety of possible combinations, and direct subclassing can produce too many classes, so it is impractical.
Decorator pattern uses hierarchical objects to dynamically and transparently add responsibility to a single object. The decorator specifies that all objects wrapped around the original object have the same basic interface. Some things can be decorated. Functions can be stratified by wrapping other classes around this decorated object.
Decoration is achieved by using combination and formal structure, while mixing is based on inheritance.
Rewrite the previous example to use a decorator:
package decorator; import java.util.*; class Basic { private String value; public void set(String val) { value = val; } public String get() { return value; } } class Decorator extends Basic { protected Basic basic; public Decorator(Basic basic) { this.basic = basic; } public void set(String val) { basic.set(val); } public String get() { return basic.get(); } } class TimeStamped extends Decorator { private final long timeStamp; public TimeStamped(Basic basic) { super(basic); timeStamp = new Date().getTime(); } public long getStamp() { return timeStamp; } } class SerialNumbered extends Decorator { private static long counter = 1; private final long serialNumber = counter++; public SerialNumbered(Basic basic) { super(basic); } public long getSerialNumber() { return serialNumber; } } public class Decoration { public static void main(String[] args) { TimeStamped t = new TimeStamped(new Basic()); TimeStamped t2 = new TimeStamped( new SerialNumbered(new Basic())); //! t2.getSerialNumber(); // Not available SerialNumbered s = new SerialNumbered(new Basic()); SerialNumbered s2 = new SerialNumbered( new TimeStamped(new Basic())); //! s2.getStamp(); // Not available } } ///:~
The object type produced by using the decorator is the last type to be decorated. That is to say, although multiple layers can be added, the last layer is the actual type, so only the method of the last layer is visible, and the mixed type is all the types that are mixed together.
15.15.4 Mixed with Dynamic Agent
Dynamic proxies can be used to create a model mechanism that is closer to mixing than decorators. By using dynamic proxies, the dynamic types of generated classes will be composite types that have been mixed in.
import java.lang.reflect.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Tuple.*; class MixinProxy implements InvocationHandler { Map<String,Object> delegatesByMethod; public MixinProxy(TwoTuple<Object,Class<?>>... pairs) { delegatesByMethod = new HashMap<String,Object>(); for(TwoTuple<Object,Class<?>> pair : pairs) { for(Method method : pair.second.getMethods()) { String methodName = method.getName(); // The first interface in the map // implements the method. if (!delegatesByMethod.containsKey(methodName)) delegatesByMethod.put(methodName, pair.first); } } } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Object delegate = delegatesByMethod.get(methodName); return method.invoke(delegate, args); } @SuppressWarnings("unchecked") public static Object newInstance(TwoTuple... pairs) { Class[] interfaces = new Class[pairs.length]; for(int i = 0; i < pairs.length; i++) { interfaces[i] = (Class)pairs[i].second; } ClassLoader cl = pairs[0].first.getClass().getClassLoader(); return Proxy.newProxyInstance( cl, interfaces, new MixinProxy(pairs)); } } public class DynamicProxyMixin { public static void main(String[] args) { Object mixin = MixinProxy.newInstance( tuple(new BasicImp(), Basic.class), tuple(new TimeStampedImp(), TimeStamped.class), tuple(new SerialNumberedImp(),SerialNumbered.class)); Basic b = (Basic)mixin; TimeStamped t = (TimeStamped)mixin; SerialNumbered s = (SerialNumbered)mixin; b.set("Hello"); System.out.println(b.get()); System.out.println(t.getStamp()); System.out.println(s.getSerialNumber()); } } /* Output: (Sample) Hello 1132519137015 1 *///:~
15.16 Potential Type Mechanisms
Problems arise when operations are performed on generic types, because erasure requires specifying boundaries that may be used on generic types to safely invoke specific methods on generic objects in code. This is an obvious limitation on generalization because you have to limit your generic types to inherit from specific classes or implement specific interfaces. In some cases, you may end up using a generic class or interface, because a generic with boundaries may be no different from a specified class or interface.
Some solutions that become language-provided are called latent type mechanisms or structured type mechanisms.
Potential type mechanisms allow you to call methods that do not belong to a common interface across class inheritance structures. So, in fact, a piece of code can declare: I don't care what type you are, as long as you can speak() and sit(). Since no specific type is required, the code can be more generalized.
Potential mechanism A code organization and reuse mechanism
Potential mechanisms do not require static or dynamic type checking
15.17 Compensation for Lack of Potential Type Mechanisms
Although Java does not support potential type mechanisms, this does not mean that bounded generic code can no longer be applied between different type hierarchies.
15.17.1 reflex
Potential type mechanism through reflection:
//: generics/LatentReflection.java // Using Reflection to produce latent typing. import java.lang.reflect.*; import static net.mindview.util.Print.*; // Does not implement Performs: class Mime { public void walkAgainstTheWind() {} public void sit() { print("Pretending to sit"); } public void pushInvisibleWalls() {} public String toString() { return "Mime"; } } // Does not implement Performs: class SmartDog { public void speak() { print("Woof!"); } public void sit() { print("Sitting"); } public void reproduce() {} } class CommunicateReflectively { public static void perform(Object speaker) { Class<?> spkr = speaker.getClass(); try { try { Method speak = spkr.getMethod("speak"); speak.invoke(speaker); } catch(NoSuchMethodException e) { print(speaker + " cannot speak"); } try { Method sit = spkr.getMethod("sit"); sit.invoke(speaker); } catch(NoSuchMethodException e) { print(speaker + " cannot sit"); } } catch(Exception e) { throw new RuntimeException(speaker.toString(), e); } } } public class LatentReflection { public static void main(String[] args) { CommunicateReflectively.perform(new SmartDog()); CommunicateReflectively.perform(new Robot()); CommunicateReflectively.perform(new Mime()); } } /* Output: Woof! Sitting Click! Clank! Mime cannot speak Pretending to sit *///:~
These classes are completely separate from each other, without any common base classes or interfaces. Reflection dynamically determines whether the required methods are available and invokes them.
15.17.2 Applying a Method to Sequences
Variable parameters are used to solve:
import java.lang.reflect.*; import java.util.*; import static net.mindview.util.Print.*; public class Apply { public static <T, S extends Iterable<? extends T>> void apply(S seq, Method f, Object... args) { try { for(T t: seq) f.invoke(t, args); } catch(Exception e) { // Failures are programmer errors throw new RuntimeException(e); } } } class Shape { public void rotate() { print(this + " rotate"); } public void resize(int newSize) { print(this + " resize " + newSize); } } class Square extends Shape {} class FilledList<T> extends ArrayList<T> { public FilledList(Class<? extends T> type, int size) { try { for(int i = 0; i < size; i++) // Assumes default constructor: add(type.newInstance()); } catch(Exception e) { throw new RuntimeException(e); } } } class ApplyTest { public static void main(String[] args) throws Exception { List<Shape> shapes = new ArrayList<Shape>(); for(int i = 0; i < 10; i++) shapes.add(new Shape()); Apply.apply(shapes, Shape.class.getMethod("rotate")); Apply.apply(shapes, Shape.class.getMethod("resize", int.class), 5); List<Square> squares = new ArrayList<Square>(); for(int i = 0; i < 10; i++) squares.add(new Square()); Apply.apply(squares, Shape.class.getMethod("rotate")); Apply.apply(squares, Shape.class.getMethod("resize", int.class), 5); Apply.apply(new FilledList<Shape>(Shape.class, 10), Shape.class.getMethod("rotate")); Apply.apply(new FilledList<Shape>(Square.class, 10), Shape.class.getMethod("rotate")); SimpleQueue<Shape> shapeQ = new SimpleQueue<Shape>(); for(int i = 0; i < 5; i++) { shapeQ.add(new Shape()); shapeQ.add(new Square()); } Apply.apply(shapeQ, Shape.class.getMethod("rotate")); } } /* (Execute to see output) *///:~
The apple(0 method accepts anything that implements the Iterable interface, including all Collection classes such as List.
For example, the following class:
import java.util.*; public class SimpleQueue<T> implements Iterable<T> { private LinkedList<T> storage = new LinkedList<T>(); public void add(T t) { storage.offer(t); } public T get() { return storage.poll(); } public Iterator<T> iterator() { return storage.iterator(); } } ///:~
15.17.3 When you don't have the right interface
Because the Iterable interface is built-in, and it's exactly what we need, if there's no interface that fits right?
import java.util.*; // Doesn't work with "anything that has an add()." There is // no "Addable" interface so we are narrowed to using a // Collection. We cannot generalize using generics in // this case. public class Fill { public static <T> void fill(Collection<T> collection, Class<? extends T> classToken, int size) { for(int i = 0; i < size; i++) // Assumes default constructor: try { collection.add(classToken.newInstance()); } catch(Exception e) { throw new RuntimeException(e); } } } class Contract { private static long counter = 0; private final long id = counter++; public String toString() { return getClass().getName() + " " + id; } } class TitleTransfer extends Contract {} class FillTest { public static void main(String[] args) { List<Contract> contracts = new ArrayList<Contract>(); Fill.fill(contracts, Contract.class, 3); Fill.fill(contracts, TitleTransfer.class, 2); for(Contract c: contracts) System.out.println(c); SimpleQueue<Contract> contractQueue = new SimpleQueue<Contract>(); // Won't work. fill() is not generic enough: // Fill.fill(contractQueue, Contract.class, 3); } } /* Output: Contract 0 Contract 1 Contract 2 TitleTransfer 3 TitleTransfer 4 *///:~
15.17.4 Emulate Potential Type Mechanisms with Adapters
Potential typing mechanisms mean that you can write code declarations: I don't care about the types I use here, as long as it has these methods. In fact, the latent type mechanism creates a cryptographic interface that contains the required methods. So it follows the rule that if we manually write the necessary interfaces, then it should be able to solve the problem.
We can use the adapter to adapt the existing interface and generate the desired interface:
import coffee.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; interface Addable<T> { void add(T t); } public class Fill2 { // Classtoken version: public static <T> void fill(Addable<T> addable, Class<? extends T> classToken, int size) { for(int i = 0; i < size; i++) try { addable.add(classToken.newInstance()); } catch(Exception e) { throw new RuntimeException(e); } } // Generator version: public static <T> void fill(Addable<T> addable, Generator<T> generator, int size) { for(int i = 0; i < size; i++) addable.add(generator.next()); } } // To adapt a base type, you must use composition. // Make any Collection Addable using composition: class AddableCollectionAdapter<T> implements Addable<T> { private Collection<T> c; public AddableCollectionAdapter(Collection<T> c) { this.c = c; } public void add(T item) { c.add(item); } } // A Helper to capture the type automatically: class Adapter { public static <T> Addable<T> collectionAdapter(Collection<T> c) { return new AddableCollectionAdapter<T>(c); } } // To adapt a specific type, you can use inheritance. // Make a SimpleQueue Addable using inheritance: class AddableSimpleQueue<T> extends SimpleQueue<T> implements Addable<T> { public void add(T item) { super.add(item); } } class Fill2Test { public static void main(String[] args) { // Adapt a Collection: List<Coffee> carrier = new ArrayList<Coffee>(); Fill2.fill( new AddableCollectionAdapter<Coffee>(carrier), Coffee.class, 3); // Helper method captures the type: Fill2.fill(Adapter.collectionAdapter(carrier), Latte.class, 2); for(Coffee c: carrier) print(c); print("----------------------"); // Use an adapted class: AddableSimpleQueue<Coffee> coffeeQueue = new AddableSimpleQueue<Coffee>(); Fill2.fill(coffeeQueue, Mocha.class, 4); Fill2.fill(coffeeQueue, Latte.class, 1); for(Coffee c: coffeeQueue) print(c); } } /* Output: Coffee 0 Coffee 1 Coffee 2 Latte 3 Latte 4 ---------------------- Mocha 5 Mocha 6 Mocha 7 Mocha 8 Latte 9 *///:~
15.18 Use Function Objects as Policies
Policy design patterns, which can produce more elegant code, because they completely isolate things that change into a function object.
The value of function objects is that, unlike ordinary methods, they can be passed on and have a state of persistence between multiple calls.
Function objects are mainly distinguished by their purpose. The goal here is to create something that behaves like a single method that can be passed on.
//: generics/Functional.java import java.math.*; import java.util.concurrent.atomic.*; import java.util.*; import static net.mindview.util.Print.*; // Different types of function objects: interface Combiner<T> { T combine(T x, T y); } interface UnaryFunction<R,T> { R function(T x); } interface Collector<T> extends UnaryFunction<T,T> { T result(); // Extract result of collecting parameter } interface UnaryPredicate<T> { boolean test(T x); } public class Functional { // Calls the Combiner object on each element to combine // it with a running result, which is finally returned: public static <T> T reduce(Iterable<T> seq, Combiner<T> combiner) { Iterator<T> it = seq.iterator(); if(it.hasNext()) { T result = it.next(); while(it.hasNext()) result = combiner.combine(result, it.next()); return result; } // If seq is the empty list: return null; // Or throw exception } // Take a function object and call it on each object in // the list, ignoring the return value. The function // object may act as a collecting parameter, so it is // returned at the end. public static <T> Collector<T> forEach(Iterable<T> seq, Collector<T> func) { for(T t : seq) func.function(t); return func; } // Creates a list of results by calling a // function object for each object in the list: public static <R,T> List<R> transform(Iterable<T> seq, UnaryFunction<R,T> func) { List<R> result = new ArrayList<R>(); for(T t : seq) result.add(func.function(t)); return result; } // Applies a unary predicate to each item in a sequence, // and returns a list of items that produced "true": public static <T> List<T> filter(Iterable<T> seq, UnaryPredicate<T> pred) { List<T> result = new ArrayList<T>(); for(T t : seq) if(pred.test(t)) result.add(t); return result; } // To use the above generic methods, we need to create // function objects to adapt to our particular needs: static class IntegerAdder implements Combiner<Integer> { public Integer combine(Integer x, Integer y) { return x + y; } } static class IntegerSubtracter implements Combiner<Integer> { public Integer combine(Integer x, Integer y) { return x - y; } } static class BigDecimalAdder implements Combiner<BigDecimal> { public BigDecimal combine(BigDecimal x, BigDecimal y) { return x.add(y); } } static class BigIntegerAdder implements Combiner<BigInteger> { public BigInteger combine(BigInteger x, BigInteger y) { return x.add(y); } } static class AtomicLongAdder implements Combiner<AtomicLong> { public AtomicLong combine(AtomicLong x, AtomicLong y) { // Not clear whether this is meaningful: return new AtomicLong(x.addAndGet(y.get())); } } // We can even make a UnaryFunction with an "ulp" // (Units in the last place): static class BigDecimalUlp implements UnaryFunction<BigDecimal,BigDecimal> { public BigDecimal function(BigDecimal x) { return x.ulp(); } } static class GreaterThan<T extends Comparable<T>> implements UnaryPredicate<T> { private T bound; public GreaterThan(T bound) { this.bound = bound; } public boolean test(T x) { return x.compareTo(bound) > 0; } } static class MultiplyingIntegerCollector implements Collector<Integer> { private Integer val = 1; public Integer function(Integer x) { val *= x; return val; } public Integer result() { return val; } } public static void main(String[] args) { // Generics, varargs & boxing working together: List<Integer> li = Arrays.asList(1, 2, 3, 4, 5, 6, 7); Integer result = reduce(li, new IntegerAdder()); print(result); result = reduce(li, new IntegerSubtracter()); print(result); print(filter(li, new GreaterThan<Integer>(4))); print(forEach(li, new MultiplyingIntegerCollector()).result()); print(forEach(filter(li, new GreaterThan<Integer>(4)), new MultiplyingIntegerCollector()).result()); MathContext mc = new MathContext(7); List<BigDecimal> lbd = Arrays.asList( new BigDecimal(1.1, mc), new BigDecimal(2.2, mc), new BigDecimal(3.3, mc), new BigDecimal(4.4, mc)); BigDecimal rbd = reduce(lbd, new BigDecimalAdder()); print(rbd); print(filter(lbd, new GreaterThan<BigDecimal>(new BigDecimal(3)))); // Use the prime-generation facility of BigInteger: List<BigInteger> lbi = new ArrayList<BigInteger>(); BigInteger bi = BigInteger.valueOf(11); for(int i = 0; i < 11; i++) { lbi.add(bi); bi = bi.nextProbablePrime(); } print(lbi); BigInteger rbi = reduce(lbi, new BigIntegerAdder()); print(rbi); // The sum of this list of primes is also prime: print(rbi.isProbablePrime(5)); List<AtomicLong> lal = Arrays.asList( new AtomicLong(11), new AtomicLong(47), new AtomicLong(74), new AtomicLong(133)); AtomicLong ral = reduce(lal, new AtomicLongAdder()); print(ral); print(transform(lbd,new BigDecimalUlp())); } } /* Output: 28 -26 [5, 6, 7] 5040 210 11.000000 [3.300000, 4.400000] [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] 311 true 265 [0.000001, 0.000001, 0.000001, 0.000001] *///:~