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