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