PROPOSAL: Method and Field Literals

Neal Gafter neal at gafter.com
Tue Mar 10 20:48:57 PDT 2009


This seems like a fairly simple change for a longstanding pain point.
Besides moving errors from runtime to compile-time, I expect IDEs will start
offering autocompletion.

I have one small concern regarding the lookup rules for the name before the
"#".  I would expect the name before the "#" to be resolved the same way as
the name before a "." in a normal method invocation: first look for a
variable and then a type.  But this proposal resolves it only as a type.
Therefore, one could construct a puzzler where A#f() finds one method but
invoking A.f() invokes another.  The same puzzler could turn into a language
wart if we ever generalize this to method references compatible with
closures (see Stephen Colebourne's FCM proposal).  Addressing this is easy:
do the lookup the same way as it is done for normal overload resolution with
".", but require an error if the qualifier is not a type.  As a practical
matter, naming conventions would make it hard to tell the difference, but
the result is one less puzzler in the language.

-Neal

On Tue, Mar 10, 2009 at 7:49 PM, Jesse Wilson <jesse at swank.ca> wrote:

> 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