PROPOSAL: Method and Field Literals
Jesse Wilson
jesse at swank.ca
Tue Mar 10 19:49:47 PDT 2009
Rich text (preferred) here:
http://docs.google.com/View?docID=dhfm3hw2_62dxg677jn
Proposal: Method and Field Literals
AUTHOR(S):
Jesse Wilson
OVERVIEW
FEATURE SUMMARY:
Add syntax for method and field literals. Enables compile-time
checking of member references and avoids an impossible checked
exception.
MAJOR ADVANTAGE:
The Java language neatly balances dynamic behaviour with type safety.
Many Java frameworks are successful because they leverage this
balance. Class literals like ArrayList.class are popular because of
the powerful APIs they enable.
Unfortunately methods and fields are missing literal syntax. As a
consequence, code that reflects on a specific method or field is
significantly more clumsy. The member must be referenced using a
String, which hinders refactoring. Code that looks up specific members
must cope with a checked exception that will never be thrown.
Adding literals for method and fields allows the compiler to catch
typos earlier. IDEs will be able to find and fix member references
that were previously obscured by Strings. Finally, it reduces the
amount of boilerplate code to create and use frameworks.
MAJOR DISADVANTAGE:
Like annotations and generics, member literals can be abused.
Excessive use of reflection makes applications more difficult to
maintain, and this proposal encourages reflection.
The member literal syntax is easily confused with regular
dereferencing syntax, which can be a source of confusion.
ALTERNATIVES:
Frameworks like EasyMock[1] obtain method references via invocation.
Users invoke a factory method to construct a dynamic proxy, and invoke
methods on that proxy to reference them. This approach is full of
compromises and doesn't support fields, static methods, or final
classes.
One can lookup methods using an identifying annotation. This requires
a possibly-unwanted layer of indirection, and it cannot be used with
third-party code.
One can obtain a method reference via its String name.
Instead of method and field literals it may be desirable to add
property support to the JDK. This proposal does not preclude that.
EXAMPLES:
BEFORE:
Method get;
Method set;
try {
get = List.class.getMethod("get");
set = List.class.getMethod("set", Object.class);
} catch (NoSuchMethodException e) {
throw new AssertionError();
}
AFTER:
Method get = List#get();
Method set = List#set(Object);
DETAILS
SPECIFICATION:
The following new grammar rules are added:
Expression:
MethodLiteral
FieldLiteral
...
MethodLiteral:
Typeopt # MethodName (TypeList)
FieldLiteral:
Typeopt # FieldName
TypeList:
Type [, TypeList]
If the type is omitted, the literal refers to a visible member in the
current scope. This may be a member of the current type, of an
enclosing type, or a statically imported member. The rules for
resolution are the same as those for invocation.
COMPILATION:
Each member reference would be replaced with a call to an internal
desugaring API method. The helper method will handle runtime
inheritance and visibility issues:
public class A {
public void main(String[] args) {
Method driveMethod = Corvette#drive(Direction,Speed);
Field brakeField = Corvette#brake;
}
}
would be desugared to :
public class A {
public void main(String[] args) {
Method driveMethod = $Internal.methodLiteral(
A.class, Corvette.class, "drive", Direction.class, Speed.class);
Field brakeField = $Internal.fieldLiteral(A.class, Corvette.class, "brake");
}
}
VISIBILITY:
Unlike the java.lang.reflect() API, only visible methods may be
referenced in literals. To illustrate:
// file A.java
public class A {
public String visible;
private String secret;
}
// file B.java {
public class B {
private String mine;
private Field myField = B#mine; // ok
private Field visibleField = A#visible; // ok
private Field secretField = A#secret; // compile error, accessing a
private method
private Field nonexistentField = A#nonexistent; // compile error, no
such field
}
The standard rules of visibility will apply: If you can invoke a
method, you can reference it. We will leverage the compiler's existing
visibility behaviour to implement this.
INHERITANCE:
When a literal references a member inherited from a supertype, the
specific supertype method will be resolved at runtime. Although this
adds additional runtime cost, it ensures that literals and invocations
always have the same behaviour. For example, consider:
// file Car.java
public class Car {
public void honk() {
System.out.println("honk");
}
}
// file Corvette.java
public class Corvette extends Car {
}
// file Main.java
public class Main {
public static void main(String[] args) {
Method honk = Corvette#honk();
System.out.println(honk.getDeclaringClass()); // prints "Car"
}
}
Now change the source code for Corvette.java to override the honk()
method. Recompile only Corvette.java:
// file Corvette.java
public class Corvette extends Car {
public void honk() {
System.out.println("beep");
}
}
Now when we run Main.main(), it should print "Corvette" to illustrate
that method resolution occurs at runtime.
STATIC DISPATCH:
Java method overloading uses the compile-time type of method arguments
to determine which method is invoked. During desugaring, the argument
method's types are replaced with the compile-time types of the target
method. This ensures that a reference and invocation always refer to
the same overload, even if the target method's class is recompiled.
For example:
Method removeMethod = List#remove(String)
is desugared to:
Method removeMethod = $Internal.methodLiteral(
A.class, List.class, "remove", Object.class);
STATIC RESOLUTION:
Members must be specified fully using constant types. The following is
not supported:
Class<?> argument = ...
Method remove = List#remove(argument);
MODIFIERS:
This syntax applies equally to members with any modifier. Synthetic
members are not supported.
RAW TYPES:
The Method and Field classes are not parameterized types. This
proposal does not preclude adding type parameters to these classes.
TESTING:
It is necessary to test all combinations of visibility and inheritance
on both fields and methods. Both success and failure cases need to be
tested.
LIBRARY SUPPORT:
It's necessary to add internal-use static methods to do runtime method
resolution. The existing APIs Class.getMethod() and
Class.getDeclaredMethod() are insufficient because they do not take
the caller's visibility into account.
REFLECTIVE APIS:
No changes to java.lang.reflect. The unreleased javac tree API will
need expressions for member literals.
OTHER CHANGES:
None.
COMPATIBILITY
BREAKING CHANGES:
None.
EXISTING PROGRAMS:
Classes that use this feature cannot be targeted to earlier JVMs.
DESIGN ALTERNATIVES:
It is tempting to also support constructor literals, but coming up
with an intuitive syntax is difficult. Some options:
#ArrayList(Collection)
ArrayList#(Collection)
ArrayList#new(Collection)
REFERENCES
[1] EasyMock: http://www.easymock.org/EasyMock2_4_Documentation.html
EXISTING BUGS:
More information about the coin-dev
mailing list