470 likes | 619 Views
Type Inference in Java SE 8. Danie l Smith Java Language Designer. Overview. Context & terminology Target typing Variable dependencies Lambda expressions & method references Migration experience.
E N D
Type Inference in Java SE 8 Daniel SmithJava Language Designer
Overview • Context & terminology • Target typing • Variable dependencies • Lambda expressions & method references • Migration experience
The following is intended to outline our general product direction. It is intended for information purposes only, and may not be incorporated into any contract. It is not a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. The development, release, and timing of any features or functionality described for Oracle’s products remains at the sole discretion of Oracle.
Example interface Chooser { int pick(int arg1, int arg2); <T> T pick(T arg1, T arg2); } chooser.pick(23, “hello”).someMethod();
Overload resolution Which method is i) applicable and ii) best? int pick(int arg1, int arg2); <T> T pick(T arg1, T arg2); chooser.pick(23, “hello”) Applicable means arguments are compatible with parameter types. But we don’t know what “T” means…
Invocation typing What is the generic method’s signature/return for this invocation? <T> T pick(T arg1, T arg2); chooser.pick(23, “hello”).someMethod() In other words, what does “T” mean?
Inference variables • Inference variables are meta-variables for types. They allow us to talk generally about types. (Compare someMethod on previous slide…) • Examples: a, b, s, t, a1, a2 • Examples in types: • t[] • List<t> • Pair<? extends a1, a2[]> • An inference variable (t) is not a type variable (T)
Constraint formulas • Is ArrayList<String> <: List<? extends t> true or false? It depends… • Constraint formulas are assertions that constrain the choices for inference variables: • ArrayList<String> → List<? extends t> • a → int • Map<a1, a2> <: Map<String, ?> • These assertions come from, e.g., arguments passed to generic parameters (replacing the type variables with inference variables).
Bounds Bounds are conclusions drawn from constraint formulas: • a = String • a <: Number • Integer <: a • a <: List<b> • a <: b • false
Reduction To get from a constraint formula to bounds, we reduce it: • HashMap<String, Number[]> →Map<a1, ? extends a2[]> • HashMap<String, Number[]> <: Map<a1, ? extends a2[]> • String = a1, Number[] <: a2[] • String = a1, Number <: a2
Resolution • Given a set of bounds, choose types for the inference variables that satisfy their bounds. • { a = String, b <: Object, Integer <: b }resolves to a=String, b=Integer • { Integer <: a, String <: a }resolves to a = Serializable & Comparable<?> • { a <: Runnable, a <: Closeable }resolves to a = Runnable & Closeable • There is often more than one right answer…
When inference fails Map<String, List<String>> m = …; m.put(name, Collections.emptyList()); error: method put in interface Map<K,V> cannot be applied to given types required: String,List<String> found: String,List<Object> m.put(name, Collections.<String>emptyList());
Poly expressions Poly expressions have different types in different contexts, depending on the target type. List<String> strings = Collections.emptyList(); List<Integer> ints = Collections.emptyList(); Map<String, Integer> m1 = new HashMap<>(); Map<Integer, String> m2 = new HashMap<>();
Contexts for target typing • Assignment (including variable declaration, return statement) List<Float> lf = new ArrayList<>(); • Lambda expression body Supplier<List<Float>> lf = () -> new ArrayList<>() • Conditional expression (? :) List<Float> lf = cond ? null : new ArrayList<>(); • Method argument (including constructors) float average(List<Float> lf) { … } average(new ArrayList<>());
Delayed resolution List<Number> ln = Arrays.asList(1, 2, 3); // Java 7: type error // Java 8: compiles • Overload resolution bounds: { Integer <: t } • Invocation typing bounds: { Integer <: t, t = Number }
Nested invocations List<String> ls=Collections.checkedList(new ArrayList<>(), String.class); • Overload resolution of constructor: { t1 <: Object } • checkedList constraints: ArrayList<_____> → List<t2> Class<String> → Class<t2>
Nested invocations Java 7 Strategy List<String> ls =Collections.checkedList(new ArrayList<>(), String.class); • Overload resolution of constructor: { t1 <: Object } • ‘checkedList’ constraints: ArrayList<Object> -> List<t2> Class<String> -> Class<t2>
Nested invocations Java 8 Strategy List<String> ls =Collections.checkedList(new ArrayList<>(), String.class); • Overload resolution of constructor: { t1 <: Object } • ‘checkedList’ constraints: ArrayList<t1> -> List<t2> Class<String> -> Class<t2>
Overload resolution & target typing Overload resolution is always context-independent.
Declared bounds & dependencies interface Widget extends Comparable<Widget> { … } class Sprog implements Widget { … } <T extends C, C extends Comparable<C>> void sort(List<T> list); { t <: c, c <: Comparable<c> } List<Sprog> sprogs = …; sort(sprogs); { t = Sprog, t <: c, t <: Comparable<c> }
Declared bounds & dependencies Java 7 Strategy <T extends C, C extends Comparable<C>> void sort(List<T> list); List<Sprog> sprogs = …; sort(sprogs); { t = Sprog, t <: c, c <: Comparable<c> } Resolution:t = Sprog, c = CAP#1 extends Comparable<CAP#1> error: invalid inferred types for C; inferred type does not conform to declared bound(s) inferred: Sprog bound(s): CAP#1
Declared bounds & dependencies Java 8 Strategy <T extends C, C extends Comparable<C>> void sort(List<T> list); List<Sprog> sprogs = …; sort(sprogs); { t = Sprog, t <: c, c <: Comparable<c> } { t = Sprog, t <: c, Sprog <: c, c <: Comparable<c> } Sprog <: Comparable<c> c = Widget Resolution:t = Sprog, c = Widget
Incorporation • When adding bounds, identify extra new constraints: • a = S and a = Timplies S = T • a = Sanda <: Timplies S <: T • a = SandT <: aimplies T <: S • S <: aanda <: Timplies S <: T • a = UandS = Timplies S[a:=U] = T[a:=U] • a = UandS <: Timplies S[a:=U] <: T[a:=U]
Nesting & dependencies <C extends Collection<String>> C fill(C coll); String s = fill(new ArrayList<>()).get(0); ArrayList<t> -> c { ArrayList<t> <: c, c <: Collection<String> } ArrayList<t> <: Collection<String> { t = String, ArrayList<String> <: c, c <: Collection<String> }
Lambda expression typing Implicitly-typed (x, y) ->x.substring(y) Explicitly-typed (String x, int y) ->x.substring(y)
Implicitly-typed lambda as argument <T, K extends Comparator<T>> comparing(Function<T,K> getKey); Comparator<String> byLength = comparing(s -> s.length());
Implicitly-typed lambda as argument <T, K extends Comparator<T>> comparing(Function<T,K> getKey); <T> comparing(ToIntFunction<T> getKey); Comparator<String> byLength = comparing(s -> s.length()); // Error: ambiguous overloads
Explicitly-typed lambda as argument <T, K extends Comparator<T>> comparing(Function<T,K> getKey); <T> comparing(ToIntFunction<T> getKey); Comparator<String> byLength = comparing((String s) -> s.length());
Method reference typing Inexact String::substring Exact String::getLength
Inexact method reference as argument <T, K extends Comparator<T>> comparing(Function<T,K> getKey); <T> comparing(ToIntFunction<T> getKey); Comparator<String> byLower = comparing(String::toLowerCase); // Error: ambiguous overloads
Exact method reference as argument <T, K extends Comparator<T>> comparing(Function<T,K> getKey); <T> comparing(ToIntFunction<T> getKey); Comparator<String> byLength = comparing(String::length);
Overloading for lambdas & method references • Rule of thumb: don’t declare overloads with functional interface types of the same arity <T, K extends Comparator<T>> comparing(Function<T,K> getKey); <T> comparingInt(ToIntFunction<T> getKey); • Exception: some other argument can be used to disambiguate
Grab bag of other new features • Dependency-based ordering of variable resolution • Dependency-based ordering of lambda typing • Exception variables prefer resolving to RuntimeException • Simulate capture of generic return types • More flexibility when unboxing a generic return type
Get rid of explicit type arguments Map<String, List<String>> m = …; m.put(name, Collections.<String>emptyList()); becomes m.put(name, Collections.emptyList()); (For methods, grep for “.<”; for diamond, look for common generic types)
Risk of overload resolution changes void m(Object o) { System.out.println(“x”); } void m(List<Number> ln) { System.out.println(“y”); } m(Arrays.asList(1, 2, 3)); (Note that it is nearly impossible to improve type checking without tripping over this.)
New freedom • Can use diamond everywhere • Don’t have to be afraid of ? : • Generic methods don’t mind multiple levels of nesting • Can define more sophisticated generic APIs • No more anonymous inner class boilerplate
OpenJDK: Lambda Project JCP: JSR 335 lambda-dev@openjdk.java.net