PROPOSAL: Static Methods in Interfaces

Reinier Zwitserloot reinier at zwitserloot.com
Tue Mar 3 16:09:40 PST 2009


Apparently the previous version's attachment didn't come through  
properly, so here's an inline HTML version.

Static Methods in Interfaces

VERSION

This is version 1.0.
The latest version can be found at http://tinyurl.com/static-methods-in-interfaces
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) 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.

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:
original:

InterfaceMemberDeclaration:
	ConstantDeclaration
	AbstractMethodDeclaration
	ClassDeclaration
	InterfaceDeclaration
	;
replacement:

InterfaceMemberDeclaration:
	ConstantDeclaration
	AbstractMethodDeclaration
	StaticMethodDeclaration
	ClassDeclaration
	InterfaceDeclaration
	;

JLS Chapter 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 and 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 Throwsopt ;

StaticMethodModifiers:
  static
  MethodModifiers static
  static MethodModifiers
The MethodModifiers are described in §8.4.3, The access modifier  
public is discussed in §6.6. 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). 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:

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 toInterfaceName. 
$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 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. However, if transparency of the static method  
in interfaces proposal is required for the reflection API, the  
following 4 changes need to be made:

There are 3 methods in java.lang.Class which need minor changes:  
getMethod(), getDeclaredMethods(), and getMethods(). These method  
finders will need to presume all static methods in a member type  
called $Methods are considered part of the type itself, and thus need  
to be returned as well, if the class object represents an interface.  
Because getDeclaredMethods() doesn't return methods in supertypes, and  
getMethod()/getMethods() only return accessible members of supertypes,  
none of these methods need to look in $Methods inner types of  
supertypes. These methods just need to look in the actual interface  
represented by the class object for a $Methods.

There is one method in java.lang.Method that needs a minor change: the  
getDeclaringClass() method needs to return the Class object  
representing the interface, and not the $Methodssynthetic class, when  
invoked on a static method of an interface.

OTHER CHANGES:

No changes required.

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 injava.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:

None.

URL FOR PROTOTYPE (optional):

None.




More information about the coin-dev mailing list