PROPOSAL: Static Methods in Interfaces (v1.1)

Reinier Zwitserloot reinier at zwitserloot.com
Tue Mar 17 18:59:38 PDT 2009


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/third_edition/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<E> extends Collection<E> {
             int size();
             // List's other instance methods

             public static <T> List<T> of(final T... items) {
                 return new AbstractList<T>() {
                     public T get(int index) {
                         return items[index];
                     }

                     public int size() {
                         return items.length;
                     }
                 };
             }
         }

         List<String> 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/third_edition/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/third_edition/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/third_edition/html/interfaces.html#9.5 
 > and
         9.6
         <http://java.sun.com/docs/books/jls/third_edition/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 TypeParameters_opt  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/third_edition/html/classes.html#78188 
 >,
         The access modifier |public| is discussed in 6.6
         <http://java.sun.com/docs/books/jls/third_edition/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/third_edition/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/third_edition/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/second_edition/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/view_bug.do?bug_id=4093687  
(Main RFE)
         http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4291381
         (Duplicate)
         http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4491759
         (Duplicate)
         http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4306573
         (Duplicate)


     URL FOR PROTOTYPE (optional):

         None.






More information about the coin-dev mailing list