PROPOSAL: Static Methods in Interfaces (v1.1) (original) (raw)
Neal Gafter neal at gafter.com
Tue Mar 17 19:05:55 PDT 2009
- Previous message: PROPOSAL: Static Methods in Interfaces (v1.1)
- Next message: PROPOSAL: Static Methods in Interfaces (v1.1)
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Experience with extension methods have shown them to be a powerful and more flexible alternative to this. I believe extension methods are also simpler. If there is enough interest I could write it up.
On Tue, Mar 17, 2009 at 6:59 PM, Reinier Zwitserloot <reinier at zwitserloot.com> wrote:
Includes Joe D'Arcy's suggestions. An easier to read copy with markup is available at:
http://tinyurl.com/static-methods-in-interfaces
The text below is identical aside from the formatting: PROPOSAL: Static Methods in Interfaces VERSION This is version 1.1. The latest version can be found at http://tinyurl.com/static-methods-in-interfaces Changes from v1.0 to v1.1: Added links to bug reports on bugs.sun.com, expanded on alternatives, simplified reflective APIs, added note on javadoc. (via Joe D'Arcy). AUTHOR(S): Reinier Zwitserloot Roel Spilker OVERVIEW FEATURE SUMMARY: Static methods are now allowed in interfaces. The static method is defined with a method body in the interface and it exists in the namespace of the interface; it does not imply that implementing classes must implement the method. This feature is especially useful for utility methods that belong with a given interface. For example, many methods in java.util.Collections are specific for a particular interface, such as sort (java.util.List) and unmodifiableMap (java.util.Map). MAJOR ADVANTAGE: Allows maintaining code that 'goes together' in a single file, and helps auto-complete dialogs come up with more relevant operations on any given object. Also improves syntax by using a more specific term (e.g. List) instead of a usually somewhat generic grab bag utility class (e.g. Collections). Also allows interfaces to contain pseudo-constructor, for common tasks and default implementations (e.g. java.io.FileFilter could contain a method to create a new one based on a file extension). This proposal will also offer an easy solution to the current deplorable situation that you need to call on 2 separate utility classes, one of which has no relation to List whatsoever, just to create an immutable List: Collections.unmodifiableList(Arrays.asList(items)) can be replaced with the much more elegant List.of(items) MAJOR BENEFIT: Java (the language) is very strictly namespaced; the default package is discouraged and java does not allow dynamically adding or changing methods at runtime (so called monkey patching). Java also does not support mixins nor multiple inheritance. Therefore, the platform currently lacks a way to meaningfully offer utility methods for interfaces. Instead, kludges such as java.util.Collections exist as a vehicle for these support methods. Furthermore, static methods in interfaces are currently illegal (see JLS Chapter 9.4 <http://java.sun.com/docs/books/jls/thirdedition/html/interfaces.html#9.4_ _>) so this proposed change does not complicate the language very much. MAJOR DISADVANTAGE: Confusion about the notion that static methods in java in java are not 'virtual' (they are not inherited and cannot be overridden) may cause a programmer to erroneously think a static method in an interface implies it is something an implementing class must implement. However, the mandatory method body should help avoid confusion. Slightly more complex language specification. No opportunity to use the static keyword in interfaces for some sort of /factory interface/ concept (an interface for constructors and static methods). The proposed implementation is also somewhat inconsistent in rare cases compared to static methods in classes. While this inconsistency is a disadvantage, the authors do not believe there's a way to avoid this inconsistency without creating more serious disadvantages ALTERNATIVES: The usual solution to this problem right now is to offer a separate utility class (a class that is not instantiable and contains only static methods) that contain the utility methods, along with a reference in the /javadoc/ of the interface to this utility class. For example, java.util.Collections is the utility class that goes with Map, List, Set and other Java Collections API interfaces. The use case of default / common implementations is currently handled by having an implementing class with a constructor. For example, a new class called java.io.ExtensionFileFilter could be made that takes a String and implements FileFilter. The sugar employed by this proposal is itself also an alternative: Creating a member type class that contains the static methods (With just a backwards and migration compatible API addition, you could make List.Utils.of(items) work in java 1.6 notation (The Utils class is an inner member type to the interface, which is legal, and as it is a class, may contain static methods. Another language change that can serve as an alternative is extension methods (the ability to lexically 'monkey patch' methods onto types, such as "import static java.util.Collections.sort into java.util.List;" EXAMPLES SIMPLE EXAMPLE: public interface Foo { public static void printHello() { System.out.println("Hello, World!"); } } Foo.printHello(); //Prints 'Hello, World! ADVANCED EXAMPLE: package java.util; public interface List extends Collection { int size(); // List's other instance methods public static List of(final T... items) { return new AbstractList() { public T get(int index) { return items[index]; } public int size() { return items.length; } }; } } List list = List.of("foo", "bar"); assert list.get(0).equals("foo"); assert list.size() == 2; DETAILS SPECIFICATION: Java Language Specification changes: JLS Chapter 9.1.4 <http://java.sun.com/docs/books/jls/thirdedition/html/interfaces.html#9.1.4_ _>: original: InterfaceMemberDeclaration: ConstantDeclaration AbstractMethodDeclaration ClassDeclaration InterfaceDeclaration ; replacement: InterfaceMemberDeclaration: ConstantDeclaration AbstractMethodDeclaration StaticMethodDeclaration ClassDeclaration InterfaceDeclaration ; JLS Chapter 9.4 <http://java.sun.com/docs/books/jls/thirdedition/html/interfaces.html#9.4_ _>: original: Every method declaration in the body of an interface is implicitly abstract, so its body is always represented by a semicolon, not a block. replacement: Every non-static method declaration in the body of an interface is implicitly abstract, so its body is always represented by a semicolon, not a block. original: Note that a method declarated in an interface must not be declared static, or a compile-time error occurs, because static methods cannot be abstract. replacement: None - this line is removed from the JLS. JLS Chapter 9.5 <http://java.sun.com/docs/books/jls/thirdedition/html/interfaces.html#9.5_ _> and 9.6 <http://java.sun.com/docs/books/jls/thirdedition/html/interfaces.html#9.6_ _> are bumped to 9.6 and 9.7, respectively, and a new 9.5 is added: ------------------------------------------------------------------------ 9.5 Static Method Declarations StaticMethodDeclaration: StaticMethodModifiers TypeParametersopt ResultType MethodDeclarator Throws(opt) ; StaticMethodModifiers: static MethodModifiers static static MethodModifiers The MethodModifiers are described in 8.4.3 <http://java.sun.com/docs/books/jls/thirdedition/html/classes.html#78188_ _>, The access modifier |public| is discussed in 6.6 <http://java.sun.com/docs/books/jls/thirdedition/html/names.html#104285_ _>. A compile-time error occurs if the same modifier appears more than once in an static method declaration. The static keyword is mandatory. Static method declarations in interfaces are either public or private; protected and package private are not allowed. If no access modifier is specified, the static method is implicitly public, to be consistent with the notion that everything else in an interface, be it a field, an abstract method, or a member type, is implicitly public. Private static methods are allowed to accommodate helper methods. Other than being limited to public and private access, a static interface method is identical to a method declaration in a class (8.4) <http://java.sun.com/docs/books/jls/thirdedition/html/classes.html#40420_ _>. During compilation, all static methods are stored in a synthetic inner class called $Methods, which is generated with a private constructor. If that class already exists in the source file, a compile-time error occurs. ------------------------------------------------------------------------ JLS Chapter 15.12.1 <http://java.sun.com/docs/books/jls/thirdedition/html/expressions.html#15.12.1_ _>: The specification of method invocation forms that invoke static methods are updated to refer to 'class or interface' instead of just 'class', and the following line: If /TypeName/ is the name of an interface rather than a class, then a compile-time error occurs, because this form can invoke only |static| methods and interfaces have no |static| methods. is replaced with: If /TypeName/ is the name of an interface rather than a class, then the call is presumed to be for a static method in a synthetic member class of the interface, called $Methods. If the method exists, the class to be searched is denoted by /TypeName.$Methods/ where /$Methods/ is a static inner class of the interface /TypeName/, literally called "$Methods". COMPILATION: Any interface with static methods is sugared by creating a member class called $Methods which will contain the static methods. The generated $Methods class should also have a private constructor, as they aren't supposed to be instantiable. If the $Methods inner class has been explicitly created in the source file, any static methods in the interface are considered a compile-time error (if the class is present in the source file, the assumption is that the programmer wants to keep manual control of the static methods). For method invocations, currently, an invocation of the form InterfaceName.method(), will result in an immediate compile error. The compiler will instead need to search the $Methods class (if any exists) for the method, and rewrite the call to InterfaceName.$Methods.method() if the method does exist in the $Methods inner class. The method is not inherited by any member types. So, the second 'hello()' call in the following example would result in a compile time error (method not found): public interface Foo { public static void hello() { System.out.println("Hello, World!"); } } public class Bar implements Foo {} Foo.hello(); //works Bar.hello(); //does not work This is an unfortunate inconsistency (if Foo was a class, Bar.hello() would work just fine), but there is no way to adequately recreate this inheritance system with syntax sugar. Even with a more thorough solution (that involves changing the JVM), allowing inheritance of the methods would mean allowing diamond relations (where 1 class implements 2 interfaces, that each have the same static method signature with different implementations. Which one is chosen?). This is principally the reason why this proposal suggests not letting the method be inherited. However, if inheritance is deemed important, the alternate solution is to fix the JVM Specification chapter 2.13 <http://java.sun.com/docs/books/jvms/secondedition/html/Concepts.doc.html#16432_ _> in similar ways as the JLS, and make static methods legal in interfaces. The Type.method() invocation would then require a much broader search and should give up with a compile-time error if a diamond relation is found, listing the conflicting implementations and asking the programmer to choose one. As this proposal breaks the norm on inheritance already, the following syntax is not allowed either, which for static methods in classes is currently legal but flagged as a warning in all popular IDEs and code style checkers: Character c = 'f'; c.isWhiteSpace(' '); //if Character was an interface, this would not be legal.
TESTING: This new feature can be tested by applying the existing tests for static method calls to static methods in interfaces. Compilation and class file parsing needs to be expanded to apply tests to read method bodies in interfaces as well as classes. "Finding" the static method inside the $Methods inner class during compilation needs to be tested, which is straight forward. The changes described below to the reflective APIs also need testing, which is also straight forward. LIBRARY SUPPORT: No library support is needed. However, it would be advisable to update various interfaces in the core java APIs with useful static utility methods. Some examples: * java.util.List/Map/Set: All methods in java.util.Collections should also be made available on the appropriate java collections API interface. * java.io.Closeable: should contain a utility method 'closeAndIgnoreException' (arguably better suited on InputStream instead). * java.util.List/Set: Should contain an 'of' method that makes unmodifiable lists and sets via varargs. * java.io.FileFilter: Should contain an 'ofExtension(String)' method that creates a FileFilter for the provided extension. REFLECTIVE APIS: Currently, synthetic members are not treated specially by the reflection API. Therefore, this proposal does not require any reflective API changes. Attempting to use reflection to access these methods requires you to access them via the generated $Methods member. Asking for such a method's parent class would return the $Methods class, not the interface. OTHER CHANGES: The javadoc tool will need a minor change: It will need to produce a new section on static methods for interfaces, if they exist. (Unlike reflection, javadoc should abstract away the inner $Methods class). Fortunately, this code already exists for printing static methods in normal classes. MIGRATION: No migration is needed. However, any java projects that currently employ utility classes (defined as having a private constructor that is not called anywhere in scope) which either return interface types, or take as first parameter an interface type, or both, where all previously mentioned interfaces are in the same package, are likely candidates for moving or copying to the relevant interface. Thus, IDEs can offer refactor advice to perform this task automatically and to find likely candidates. Such a refactor tool would for example identify all methods in java.util.Collections. COMPATIBILITY BREAKING CHANGES: Existing source that already uses an inner type named $Methods in an interface will change semantics when this proposal is implemented, primarily when queried via reflection. Between the vanishingly small odds of both a $Methods already existing and its methods being queried by the reflection API, and the general rule that $ should only be used in type names by compilers, the potential breaking change is hardly worth mentioning. EXISTING PROGRAMS: Existing programs are not affected by this change, other than as described above in the 'breaking changes' section. REFERENCES EXISTING BUGS: http://bugs.sun.com/bugdatabase/viewbug.do?bugid=4093687 (Main RFE) http://bugs.sun.com/bugdatabase/viewbug.do?bugid=4291381 (Duplicate) http://bugs.sun.com/bugdatabase/viewbug.do?bugid=4491759 (Duplicate) http://bugs.sun.com/bugdatabase/viewbug.do?bugid=4306573 (Duplicate) URL FOR PROTOTYPE (optional): None.
- Previous message: PROPOSAL: Static Methods in Interfaces (v1.1)
- Next message: PROPOSAL: Static Methods in Interfaces (v1.1)
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]