First-class methods: Java-style closures
Version 0.5
Stephen Colebourne, UK
Stefan Schulz, Germany
I. Problem
In Java, the method is the principal construct where application logic resides. At present, a method can only exist within a class and cannot be manipulated or referenced in any simple manner. Other languages permit a block of application logic to be referenced outside of a class, and this is often referred to as a function or a closure.
A Java developer can approach the power of functions/closures using two techniques - reflection [5] and inner classes [1:15.9]. However, neither is very developer friendly - reflection is not compile-time safe or refactorable, and inner classes are very verbose.
As a result of the difficulty of writing code using reflection and inner classes the techniques tend to be limited to callbacks between an application and a framework. However, the majority of uses of functions or closures in other languages is for small-scale code re-use. This typically involves refactoring out common sections of code which may have a common beginning and end, but different code in the middle. In this scenario, the extracted code isn't run at a much later time like a callback is, but immediately as the original code would have done.
To enable this power in Java, we propose to change the language to support the referencing and definition of methods as first-class objects. This provides much of the power of functions/closures but in a style familiar to Java developers.
We further propose to enable the referencing of other class members - constructors and fields - as first-class citizens. This is a natural extension to First-class Methods.
Sorting example
The Comparator interface is the standard mechanism in Java to provide the code needed to sort lists. Here is a Comparator that sorts by length of String:
List<String> list = ...
Collections.sort(list, new Comparator<String>() {
public int compare(String str1, String str2) {
return str1.length() - str2.length();
}
});
With the changes in this proposal, the code could be written as follows:
List<String> list = ...
Collections.sort(list, #(String str1, String str2) {
return str1.length() - str2.length();
});
This syntax is termed an anonymous inner method, and is a logical extension to the anonymous inner class concept. The conversion to a Comparator is automatic.
Event listener example
The Swing GUI framework makes widespread use of listeners to inform the application of events. These are frequently implemented as inner classes which delegate their behaviour to a 'normal' method on the class initialising the listener:
public void init() {
JButton button = ...;
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
handleAction(ev);
}
});
}
public void handleAction(ActionEvent ev) {
// handle event
}
With the changes in this proposal, the code could be written as follows:
public void init() {
JButton button = ...;
button.addActionListener(this#handleAction(ActionEvent));
}
public void handleAction(ActionEvent ev) {
// handle event
}
This syntax uses a
bound method reference to refer to a pre-existing method on a specific object. The conversion to an
ActionListener is automatic.
II. Member literals
Member literals represent a literal form, similar to double quoted Strings, for referring to a class's members. There are three kinds of literals each referring to the respective type of member: method literals (1), constructor literals (2), and field literals (3).
MemberLiteral:
MethodLiteral
ConstructorLiteral
FieldLiteral
All kinds of member literals follow the standard rules for visibility. A member literal may refer to a public member from anywhere, to a protected member only from subclasses and within the same package, to a default scope member from within the same package, and to a private member only from within that class.
1. Method literals
A method literal refers to a method definition. The syntax consists of the class name, followed by the # (hash) symbol and the method name with parameter types.
Rationale: The use of the # symbol is deliberate. The symbol is already used within Javadoc to separate the class name from the method name in exactly this manner. The method literal syntax merely brings the Javadoc convention into Java source code.
We go on to use the same # symbol throughout the proposal for method related syntaxes, helping to provide consistency.
Examples
The following examples refer to static methods:
Math#min(int, int)
Collections#sort(List, Comparator)
The following examples refer to instance methods:
Integer#intValue()
Object#equals(Object)
Detail
Method literals are, by definition, assignable to java.lang.reflect.Method. As such, it is possible to call any part of the API of Method directly:
Class<?> returnType = Integer#intValue().getReturnType();
This calls the getReturnType method on the Method object contextually created by the method literal.
Syntax
MethodLiteral:
ClassType # Identifier ( TypeListopt )
2. Constructor literals
A constructor literal refers to a constructor definition. The syntax consists of the class name, followed by the # (hash) symbol and the constructor's parameter types.
Examples
ArrayList#(int)
JButton#(String, Icon)
Detail
Constructor literals are by definition assignable to java.lang.reflect.Constructor<T>, where T must be assignable from the referred class.
It is a compile-time error to reference an abstract class' constructor.
Syntax
ConstructorLiteral:
ClassType # ( TypeListopt )
3. Field literals
A field literal refers to a field definition. The syntax consists of the class name, followed by the # (hash) symbol and the field's name.
Examples
The following examples refer to static fields:
Member#DECLARED
Node#ELEMENT_NODE
The following examples refer to instance fields:
MyBean#name
TaskEntry#priority
Detail
Field literals are by definition assignable to java.lang.reflect.Field.
Syntax
FieldLiteral:
ClassType # Identifier
III. Method types
Method types are a way to define the signature of a method or block of code. As such, they must define the parameters, return type, and thrown exceptions.
The syntax consists of the # (hash) symbol followed by parentheses.
Within the parentheses the method signature is specified following the logical order of defining a method - the return type, followed by the parameter types in parentheses, followed by any exceptions.
Rationale: The use of the # symbol provides a clear visual indication to the developer as to the start of the method type. We feel that using # is considerably clearer in use than the alternatives we examined, including using braces (curly brackets) and a Haskell like notation [2].
The order and style of the method type is very deliberately following that of a method declaration in Java. This is to allow developers to pickup the new syntax easily.
Examples
The following examples show various forms of method types, with various return types, parameters, and thrown exceptions:
#(int(String, String))
#(void(String))
#(String(File) throws IOException)
#(long())
The following example shows how a method type can be assigned to an ActionListener interface:
#(void(ActionEvent)) method = ...
ActionListener lnr = method;
This will compile to code equivalent to:
static class Outer$MethodType$1 implements ActionListener {
private final #(void(ActionEvent)) target;
public Outer$MethodType$1(#(void(ActionEvent)) target) {
this.target = target;
}
public void actionPerformed(ActionEvent arg1) {
target.invoke(arg1);
}
};
#(void(ActionEvent)) method = ...
ActionListener im = new Outer$MethodType$1(method);
Detail
An instance of a method type supports a single method, invoke, that can be used to call the method. Both the arguments and the return type of the invoke method are fully type-safe. For example:
#(boolean(String,String)) nullSafeEqualsMethod = ...
boolean equal = nullSafeEqualsMethod.invoke("Hello", "Hi");
Method types will be implemented as a compiler-generated interface. Considerable investigation and study of the implementation of this concept has already been undertaken as part of the BGGA proposal [4] (function types). As such, we don't attempt to duplicate that work here.
A method type may be assigned to a class or interface provided that the compiler-generated class has a single abstract method with a fully compatible signature. It is a compile error if there is zero or more than one abstract method.
Syntax
Type:
MethodType
MethodType:
# ( ResultType ( TypeListopt ) Throwsopt )
IV. Method references
Method references are a way to refer to a specific method and its environment such that it can be invoked. There are four kinds of method references: static method reference (A1), instance method references (A2), bound method references (A3) and constructor references (A4).
They share a common auto-conversion mechanism for assigning a method reference to a class or interface (B).
MethodReference:
StaticMethodReference
InstanceMethodReference
BoundMethodReference
ConstructorReference
A1. Static method references
A static method reference provides a type-safe way to obtain an object that can be used to invoke a static method.
The syntax consists of the class name, followed by the # (hash) symbol and the static method name with parameter types.
Examples
The following example shows the method type to which a static method reference can be assigned:
#(int(int, int)) ref = Math#min(int, int);
This will compile to code equivalent to:
static class Outer$StaticMethodReference$1 implements #(int(int, int)) {
public int invoke(int arg1, int arg2) {
return Math.min(arg1, arg2);
}
};
#(int(int, int)) ref = new Outer$StaticMethodReference$1();
Detail
A static method reference has the same syntax form as a method literal. It becomes a method reference when it is assigned to, or passed to, a method type.
At compilation time, the static method reference will be implemented by a compiler-generated class. The class will implement the method type that the reference is assigned to
.
Syntax
StaticMethodReference:
ClassType # Identifier ( TypeListopt )
A2. Instance method references
An instance method reference provides a type-safe way to obtain an object that can be used to invoke an instance method. An instance method must be invoked on an object, so the reference is adjusted to pass the object as the first parameter.
The syntax consists of the class name, followed by the # (hash) symbol and the instance method name with parameter types.
Examples
The following example shows the method type to which an instance method reference can be assigned. Note how the method type takes an additional parameter which is the object that will be invoked:
#(int(List, Object)) ref = List#indexOf(Object);
This will compile to code equivalent to:
static class Outer$InstanceMethodReference$1 implements #(int(List, Object)) {
public int invoke(List arg1, Object arg2) {
return arg1.indexOf(arg2);
}
};
#(int(List, Object)) ref = new Outer$InstanceMethodReference$1();
Detail
An instance method reference provides a bridge between object-oriented programming and procedural programming. It is the equivalent of turning an instance method into a static method, by passing the instance to be invoked as the first argument.
An instance method reference has the same syntax form as a method literal. It becomes a method reference when it is assigned to, or passed to, a method type.
At compilation time, the instance method reference will be implemented by a compiler-generated class. The class will implement the method type that the reference is assigned to
.
Syntax
InstanceMethodReference:
ClassType # Identifier ( TypeListopt )
A3. Bound method references
A bound method reference provides a type-safe way to obtain an object that can be used to invoke an instance method. To achieve this, it captures the object that it is to be invoked on.
The syntax consists of an expression yielding an object, followed by the # (hash) symbol and the method name with parameter types.
Examples
The following example shows the method type to which a static method reference can be assigned:
List<String> list = ...
#(int(Object)) ref = list#indexOf(Object);
This will compile to code equivalent to:
static class Outer$BoundMethodReference$1<T> implements #(int(Object)) {
private final List<T> target;
public Outer$BoundMethodReference$1(List<T> target) {
this.target = target;
}
public int invoke(Object arg1) {
return target.indexOf(arg1);
}
};
List<String> list = ...
#(int(Object)) ref = new Outer$BoundMethodReference$1<String>(target);
Detail
A bound method reference is more complex than a static or instance method reference because it captures the object that it is invoked on. This capturing provides a very quick and easy way to pass a callback to a framework.
At compilation time, the bound method reference will be implemented by a compiler-generated class which stores the target object as an instance variable. The class will implement the method type that the reference is assigned to
.
Syntax
BoundMethodReference:
Primary # Identifier ( TypeListopt )
A4. Constructor references
A constructor reference provides a type-safe way to obtain an object that can be used to invoke a constructor.
The syntax consists of the class name, followed by the # (hash) symbol and the parameter types.
Examples
The following example shows the method type to which a constructor reference can be assigned:
#(Integer(int)) ref = Integer#(int);
This will compile to code equivalent to:
static class Outer$ConstructorReference$1 implements #(Integer(int)) {
public Integer invoke(int arg1) {
return new Integer(arg1);
}
};
#(Integer(int)) ref = new Outer$ConstructorReference$1();
Detail
A constructor reference has the same syntax form as a constructor literal. It becomes a constructor reference when it is assigned to, or passed to, a method type.
At compilation time, the constructor reference will be implemented by a compiler-generated class. The class will implement the method type that the reference is assigned to
.
Syntax
ConstructorReference:
ClassType # ( TypeListopt )
B. Common rules and mechanisms
Conversion to class or interface
A method reference of any of the four kinds may be assigned to a class or interface providing that the compiler-generated class has a single abstract method with a fully compatible signature. It is a compile error if there is zero or more than one abstract method.
The following example on a bound method reference creates a compiler-generated class implementing ActionListener where the single abstract method actionPerformed calls the handleAction method on this.
ActionListener lnr = this#handleAction(ActionEvent ev);
This assignment will be compiled to code equivalent to:
static class Outer$InstanceReference$1 implements ActionListener {
private final Outer target;
public Outer$BoundMethodReference$1(Outer target) {
this.target = target;
}
public void actionPerformed(ActionEvent arg1) {
return target.handleAction(arg1);
}
};
ActionListener lnr = new Outer$InstanceReference$1(this);
If the method handleAction is not accessible for any reason, a synthetic method will be generated by the compiler.
The example on a constructor reference below creates a compiler-generated class implementing ViewFactory where the single abstract method create invokes the constructor on IconView.
ViewFactory vf = IconView#(Element);
This assignment will be compiled to code equivalent to:
static class Outer$ConstructorReference$1 implements ViewFactory {
public View create(Element arg1) {
return new IconView(arg1);
}
};
ViewFactory vf = new Outer$ConstructorReference$1();
V. Inner methods
Inner methods are a way of writing a method in-line within the body of another method. This is analogous to an inner class but for methods. There are two kinds of inner method: anonymous (A1) and named (A2). Both of which share some common rules and mechanisms (B).
InnerMethod:
AnonymousInnerMethod
NamedInnerMethod
A1. Anonymous inner methods
An anonymous inner method is a method written within the body of another method. It has no name and may be assigned to a method type or a class/interface with a single abstract method.
Examples
The following example creates an anonymous inner method assigned to a method type:
#(void(ActionEvent)) action = #(ActionEvent ev) {
System.out.println("ActionEvent fired");
}
This will compile to code equivalent to:
static class Outer$InnerMethod$1 implements #(void(ActionEvent)) {
public void invoke(ActionEvent arg1) {
System.out.println("ActionEvent fired");
}
};
#(void(ActionEvent)) im = new Outer$InnerMethod$1();
The following related example creates an anonymous inner method assigned to a single abstract method interface:
ActionListener action = #(ActionEvent ev) {
System.out.println("ActionEvent fired");
}
This will compile to code equivalent to:
static class Outer$InnerMethod$1 implements ActionListener {
public void actionPerformed(ActionEvent arg1) {
System.out.println("ActionEvent fired");
}
};
ActionListener im = new Outer$InnerMethod$1();
Detail
An anonymous inner method may be assigned to a matching method type or a single abstract method type.
At compilation time, the anonymous inner method will be implemented as a method on a compiler-generated class. The class generated depends on the context to which the inner method is assigned:
-
If the context is a method type, then the compiler-generated class implements the method type and provides a type-safe invoke method containing the code from the inner method.
-
If the context is a class or interface with a single abstract method, then the compiler-generated class extends or implements the type and provides an implementation for the abstract method containing the code from the inner method.
It is a compiler error to declare an anonymous inner method without a context type. It is also a compile error, if there is zero or more than one abstract method on the context class/interface.
Syntax
AnonymousInnerMethod:
# InnerMethodParametersopt Block
InnerMethodParameters:
( FormalParameterListopt )
A2. Named inner methods
A named inner method is a method written within the body of another method that has a name associated with it.
Examples
The following example creates a named inner method assigned to a
ThreadLocal with the aim of initialising the object correctly:
private final AtomicInteger nextId = new AtomicInteger(0);
private final ThreadLocal<Integer> threadId = #initialValue() {
return nextId.getAndIncrement();
};
This will compile to code equivalent to:
static class Outer$InnerMethod$1 extends ThreadLocal<Integer> {
public Integer initialValue() {
return nextId.getAndIncrement();
}
};
ThreadLocal<Integer> threadId = new Outer$InnerMethod$1();
Detail
A named inner method may be assigned to a matching method type or the majority of other types. The name is used to determine which method will be overridden.
At compilation time, the named inner method will be implemented as a method on a compiler-generated class. The class generated depends on the context to which the inner method is assigned.
-
If the context is a method type, then the compiler-generated class implements the method type and provides a type-safe invoke method containing the code from the inner method. In this case, the name is ignored.
-
If the context is a class or interface, then the compiler-generated class extends or implements the type and provides an implementation with a method containing the code from the inner method. The name and signature of the inner method must match a method from the context type such that the method is overridden. The resulting class must be concrete.
Rationale: Previous versions of FCM proposed implementing abstract methods that were not overridden with empty stubs. After consideration, it was decided that this is too risky, and has been removed. As a result, the 'method compound' concept from v0.4 has also been removed. Thus you cannot implement MouseListener using FCM - for that you need an inner class.
It is a compiler error to declare a named inner method without a context type.
Syntax
NamedInnerMethod:
# Identifier InnerMethodParametersopt Block
B. Common rules and mechanisms
The content of inner methods has the following rules:
-
The return keyword returns from the method
-
The continue and break keywords are limited to the scope of the method
-
The this keyword refers to the nearest enclosing class in the source code
-
Private methods on the nearest enclosing class in the source code may be accessed
-
There is no ability for the inner method to access member methods or variables on the class that actually implements it
-
Local variables from the enclosing scope may be accessed, both read and write
These rules are similar to those of inner class methods with two exceptions, the
this keyword and local variables.
Rationale: These rules are at the heart of what differentiates one closure proposal from another. The CICE proposal [3] provides a means to simplify the creation of inner classes. The complex semantics of this remain. The BGGA proposal [4] takes the opposite route and tries to remove all restrictions on the content of the block. This results in a different set of issues, such as using exceptions for control flow and the RestrictedFunction interface which considerably changes the allowed content of the block.
The use of the keyword return to return a value from the inner method is driven by a desire to stay close to the existing Java syntax for methods. The BGGA proposal [4] uses the concept of last-line-no semicolon to return a value from a closure. We consider that error-prone, inflexible (as it doesn't allow early return) and unlike any other part of Java syntax.
See the semantic choices section for more detail.
Accessing this
The
this keyword applies to the nearest enclosing class within the source code. (i.e., it does not apply to the compiler-generated class that is created to hold the method when implemented.) The reason for this is simple - as far as the developer is concerned there is no visible class in the source code for
this to refer to.
In the following (contrived) example, the
log method is called using
this. The object that will be passed to the
log method is the instance of
MyClass that surrounds the inner method.
public class MyClass {
public void process() {
#(void(String)) example = #(String message) {
Logger.log(this, message);
};
example.invoke("Logged message");
}
}
Accessing local variables
Local variables in the enclosing scope will be fully accessible to the inner method. If the local variable has been declared
final, then it will be read-only. Otherwise, it will be read-write. The conversion to allow local variables to be accessed from two places will occur at compile-time.
The method that receives an inner method may run concurrently with the method that created the inner method. This might cause a race condition with any shared local variables. We propose an annotation,
@ConcurrentLocalVariableAccess, that applies to the method parameter which receives the inner method. This can be used to act as a warning to the compiler and development environments to help avoid race conditions.
Rationale: These rules for accessing local variables are the least restrictive possible. Any local variable may be accessed by any inner method. To avoid our previous concerns with race conditions we propose an annotation, used to provide warnings and development environment support.
See the semantic choices section for more detail.
Syntax when no parameters
For an inner method not having parameters the parentheses may be omitted:
#{ ... }
#methodName{ ... }
Rationale: The no argument case is fairly common, and the syntax looks considerably more appealing with the parentheses removed.
TransparencyWhen an inner method is invoked immediately (synchronously) by the higher order method that it is passed to, it can be useful to handle exceptions more fully. What is required, is that any checked exception that could be thrown by a statement in the inner method block is transparently made to be an exception of the higher order method.
The BGGA proposal [4] provides mechanisms to achieve this, and we reference that work here.
VI. Further examples
The following sections present some application scenarios for the proposed language enhancements.
Executor
The executor part of the concurrency framework allows a task to be submitted to run in another thread. This is typically written as an inner class at present:
Executor exec = ...;
exec.execute(new Runnable() {
public void run() {
// code to run in another thread
}
});
With the changes in this proposal, the code could be written as an anonymous inner method as follows:
Executor exec = ...;
exec.execute(#{
// code to run in another thread
});
ThreadLocal initialization
The
ThreadLocal class provides a protected method to allow easy initialization. This is intended to be used as an inner class at present:
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
With the changes in this proposal, the code could be written as a named inner method as follows:
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = #initialValue() {
return nextId.getAndIncrement();
};
Inner loop example
Java's collection framework provides the mechanism of iterators to loop
on a collection's entries outside the collection. A common code pattern
to find a matching entry looks like follows:
PersonCache personCache = ...
Person found = null;
for (Person person : personCache.getPersonList()) {
if (person.getAge() >= 18) {
found = person;
break;
}
}
With the changes in this proposal, a method could be provided directly by the domain model, e.g.,
PersonCache allowing the code to be written as follows:
PersonCache personCache = ...
Person found = personCache.find(#(Person person) {
return person.getAge() >= 18;
});
This syntax uses an anonymous inner method to return a boolean
true or
false value. The inner method is called for each
Person until the match is found and the inner method returns
true.
VII. Open issues
-
Is a widening conversion reasonable, either in general or on SAM types, e.g. for eliding unused parameters
-
API to handle method types
-
Is there a shortcut syntax to avoid specifying the types in a method literal or invocable method reference when the method is not overloaded, for example Object#equals(...)
-
Field references (any discussion on property references are out of scope for this document)
-
Is there a way to add support for currying and recursiveness from inner methods
Semantic choices
This section is Rationale about our semantic choices.
In discussing this proposal we identified three basic use-cases - asynchronous (callbacks, like swing event listeners), synchronous (inline code refactoring, like sorting) and control abstraction (keyword based, like withLock or usingFile). The way in which each 'closure' proposal handles these cases is key to understanding their differences.With FCM we have, after much consideration, decided that the asynchronous and synchronous use cases do not truly differ in semantics. The control abstraction use case differs significantly however.With both the asynchronous and synchronous use case, an application developer can create an instance via an inner method and assign it to a local variable. The normal case is that the local variable is then passed to a method as a parameter (either an asynchronous or synchronous use case method). The local variable holding the inner method then falls out of scope at the end of the method it is defined in: public Person findPerson(PersonCache cache) {
#(boolean(Person)) matcher = #(Person person) {...};
return cache.find(matcher);
}In the example above, the matcher is being used synchronously - the matcher is created and used inline within a single method. However, it is possible to refactor the code slightly: public Person findPerson(PersonCache cache) {
#(boolean(Person)) matcher = createMatcher();
return cache.find(matcher);
}
public #(boolean(Person)) createMatcher() {
return #(Person person) {...};
}
Now, the matcher is being created in one method, and used in another. Is this still a synchronous use case? Or an asynchronous one? Somewhere in between? The reality is that both use cases end up operating in the same way. Both are an inner method that can be assigned to a variable and passed around as per any other variable.This implies that using continue or break keywords within the inner method to refer to the method defining the inner method is meaningless. This is because the method defining the inner method and any loop may have been completed before the inner method is invoked. The same logic holds for using the return keyword to return from the enclosing method. As a result, FCM does not permit continue or break from within the inner method, and uses return to return a value from the inner method.We did have a concern that the asynchronous use case might get into a race condition over local variables, so in FCM v0.4 we restricted local variable access to read-only. However, the synchronous use case requires full read-write access to local variables. As a result FCM v0.5 allows full read-write access to local variables, and provides an annotation to warn if there may be concurrent access to local variables, which is where the real danger of race conditions lies.The final point of consideration was the control abstraction syntax. We established that the control abstraction use case is very different from the other use cases.
public void process() {
Lock lock = ...
withLock(lock) {
// code protected by the lock
}
}
This example is at face value similar to the synchronous use case - the block of code is executed immediately and inline. However, there is a key difference - the block of code cannot be assigned to a variable within the process method. As a result, there is no way to pass the block of code to another method in the application - the block is fixed at this point in the code.A result of being fixed at this point in the code is that the semantics of the block are entirely different to blocks which can be assigned to a variable. The continue and break keywords should refer to the nearest loop, while the return keyword should refer to the enclosing method. In other words, it has the full semantics of a normal Java block. Implementing a mechanism to handle control abstraction is thus outside the scope of FCM.
Syntax choices
This section is Rationale about our syntax choices.
When writing a proposal like FCM, there are two aspects to consider - syntax and semantics. Semantics is by far the most important, as it describes what happens in each scenario, and is the guts of the proposed change. However, it is almost always necessary to show examples, and for that you need to use a syntax.
With FCM, we chose to use the # symbol as our key syntactic element. There were a number of reasons. Firstly, # is unused in Java at present. This means that there are no conflicts within the parser, simplifying the proposal. It also means that someone reading the resulting code doesn't have to think about what the symbol means (unlike ?, <, > or : for example). The # has been used by Java developers before however, as it is used in Javadoc.
It has been pointed out that in most, if not all, cases, the parser could work out the meaning of the code without the use of the symbol at all. This is true, but a key feature of Java is readability. We believe that in this case, the extra symbol
adds to the readability by clearly identifying the method syntax. This is important for inner methods which are likely to be running in another thread, hence the # acts as a kind of warning.
Our decision on the method type syntax took quite a bit of thought. We chose a syntax which followed the layout of a Java method declaration - return type, then arguments in brackets, then any exceptions. We did consider a number of alternatives. For example, this is one alternate method type syntax that has some appeal (using the examples from the method types section):
#(String, String return int)
#(String)
#(File return String throws IOException)
#(return long)
The last goal for our syntax was to be unambiguous. We didn't want to introduce any new elements of name shadowing.
Finally, it should be borne in mind that the syntax is relatively unimportant - the semantics of the proposal are what really matter.
Acknowledgments
This proposal is based on the great work of the BGGA and CICE closure proposals.
Our thanks go to Ricky Clarkson for acting as technical reviewer.
In addition, the authors wish to thank all who contribute to the lively Java blog and forum community.
Notes
-
References in this document are denoted by square brackets stating the reference number, optionally followed by a colon introducing a detail section.
-
The notation used for describing the syntax of new language elements corresponds to the one used in [1].
-
Language elements not further described in this proposal are defined in [1].
Changes from v0.4
-
Removed automatic creation of stubs when multiple abstract methods
-
Removed method compounds as a result of removing automatic creation of stubs
-
Changed local variable access to full read-write in all scenarios
-
Introduced a warning annotation to indicate possible concurrent access
- Added section on transparency
- Added semantic choices section
References
-
The Java Language Specification, Third Edition
http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html
-
The Haskell 98 Reports, Expressions, Section: 3.3 Curried Applications and Lambda Abstractions
http://haskell.org/onlinereport/exps.html#sect3.3
-
B. Lee, D. Lea, J. Bloch: "Concise Instance Creation Expressions: Closures without Complexity"
http://docs.google.com/Doc.aspx?id=k73_1ggr36h
-
G. Bracha, N. Gafter, J. Gosling, P. von der Ahé: "Closures for the Java Programming Language (v0.5)"
http://www.javac.info
-
Java™ Platform Standard Ed. 6, Reflection API
http://java.sun.com/javase/6/docs/api/index.html?java/lang/reflect/package-summary.html
-
R. D. Tennent: "Principles of Programming Languages"
Prentice Hall International, Englewood Cliffs, NJ, USA, 1981