PROPOSAL: Method and Field Literals (original) (raw)

Rémi Forax forax at univ-mlv.fr
Tue Mar 10 22:54:32 PDT 2009


Neal Gafter a écrit :

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

The major pain point of this proposal is, in my opinion, to have a concensus on the type of field#fieldame and type#methodName().

The proposal uses java.lang.reflect.Field and java.lang.reflect.Method, I would prefer java.lang.reflect.Property and java.dyn.MethodHandle. (with java.lang.reflect.Property a pair of method handles (getter/setter)).

Rémi

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=dhfm3hw262dxg677jn 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/EasyMock24Documentation.html EXISTING BUGS:



More information about the coin-dev mailing list