Sponsored Link •
|
Summary
One of the areas receiving a lot of attention for J7 is simplifying and extending the capabilities of inner classes or adding a new construct the closure. This blog examines the options and compares the different proposals using a typical example, inject.
Advertisement
|
I have editted this blog to incorporate some improvements suggested by Neal Gafter - thanks Neal.
Since this is my virgin blog I would like to thank Bill for agreeing to me blogging and say that I will be blogging mostly about Design Patterns in Java. Particularly my own pet project a Pattern Enforcing Compiler™ (PEC™) for Java. However this first blog is about proposed new features for Java 7 — wow we are talking about 7 and 6 isn't done and dusted yet!
One of the areas receiving a lot of attention for J7 is simplifying and extending the capabilities of inner classes or adding a new construct the closure. This blog examines the options and compares the different proposals using a typical example, inject. Amongst the options on the web are:
return
exits from the enclosing method as
well as exiting from the closure (some proposals support break
and continue
as
well as return
). E.g. BGGA
Closures, proposed by Bracha, Gafter, Gosling,
and Ahe. return
acts as it currently does for an inner class). E.g. Concise Instance
Creation Expressions (CICE), proposed by Bloch, Lea, and Lee. return
acts
as it currently does for an inner class). E.g. SSCO Request For Enhancement
6389769, proposed by Yours Truly (note the wild mismatch in standing
between the Authors of the first two proposal and the third :). In the Appendix to this blog an alternative SSCO proposal to RFE 6389769 is given and this proposal is used in the example given below. The example below is straight forward, so you probably don't need to read the details of the three proposals to understand the example. The details of the proposals are however worth a read to understand the thought process behind the proposals.
Taking two examples will illustrate the four different approaches
(my apologies in advance if I don't quite get the syntax exactly correct for
other people's proposals). The examples are taken from some of my code, but
I think they are reasonably typical. I added to my original source code the
throwing of a generic exception (the original didn't throw anything); because
BGGA Closure makes a big deal of exception throwing. The examples are inject
(also
called reduce
, fold
, or accumulate
in
some libraries) and forEach
(a.k.a. each
). inject
applies
a function to each element of a list and when all the elements are processed
the result is returned, typically the result is a scalar. Likewise forEach
processes each element of a list, but does not return anything. The use of
both inject
and
forEach
in the examples is the common example usage of summing
a list of integers, whilst I think inject
and forEach
are typical
methods I am less convinced about the summing of integers example (I have never
actual done this with an inner class and I write scientific software).
However summing of integers is a standard example.
inject
:Use: final Integer sum = inject( input, 0, new Injection< Integer, RuntimeException >() { public void call( final Integer item ) { value += item; } } ); Where: public static < R, Es extends Throwable > R inject( final Iterable< R > collection, final R initial, final Injection< R, Es > injection ) throws Es { injection.value = initial; for ( final R item : collection ) { injection.call( item ); } return injection.value; } public abstract class Injection< R, Es extends Throwable > { public R value; public Injection( final R initial ) { value = initial; } public abstract void call( R item ) throws Es; }
forEach
:Use: final Tuple1< Integer > sum = tuple( 0 ); forEach( input, new Function1< Integer, RuntimeException >() { public void call( final Integer item ) { sum.e1 += item; } } ); Where: public static < A, Es extends Throwable > void forEach( final Iterable< A > collection, final Block1< A, Es > block ) throws Es { for ( final A item : collection ) { block.call( item ); } } public interface Block1< A1, Es extends Throwable > { void call( A1 item ) throws Es; } public class Tuples { public static class Tuple1< E1 > { public E1 e1; public Tuple1( final E1 value ) { this.value = value; } } public static < E1 > T1< E1 > tuple( final E1 value ) { return new Tuple1( value ); } ... }
inject
:Use: final Integer sum = for inject( input, 0, final Integer item ) { value += item; }; Where: public static < R, throws Es > R for inject( final Iterable< R > collection, final R initial, final Injection< R, Es > injection ) throws Es { injection.value = initial; for ( final R item : collection ) { injection.call( item ); } return injection.value; } public abstract class Injection< R, Es extends Throwable > { public R value; public abstract void call( R item ) throws Es; }
forEach
:Use: Integer sum = 0; // cannot be final even if logically it should be for each( input, final Integer item ) { sum += item; } Where: public static < A1, throws Es> void for each( final Iterable< A1 > collection, final Block1< A1, Es > block ) throws Es { for ( final A1 item : collection ) { block.call( item ); } } public interface Block1< A1, throws Es> { void call( A1 a1 ) throws Es; }
inject
:Use: final Integer sum = inject( input, 0, Injection< Integer, RuntimeException >( final Integer item ) { sum += item; } ); Where: public static < R, Es extends Throwable > void inject( final Iterable< R > collection, final R initial, final Injection< R, Es > block ) throws Es { injection.value = initial; for ( final R item : collection ) { injection.call( item ); } return injection.value; } public abstract class Injection< R, Es extends Throwable > { public R value;
public abstract void call( R item ) throws Es;
}
forEach
:Use: Integer sum = 0; // cannot be final even if logically it should be forEach( input, Block1< Integer, RuntimeException >( final Integer item ) { sum += item; } ); Where: public static < A1, Es extends Throwable > void forEach( final Iterable< A1 > collection, final Block1< A1, Es > block ) throws Es { for ( final A1 item : collection ) { block.call( item ); } } public interface Block1< A1, Es extends Throwable > {
void call( A1 a1 ) throws Es;
}
inject
:Use: final Integer sum = inject input, 0, new.{ ( final item ) value += item }; Where: public static < R, Throwable... Es > R inject( final Iterable< R > collection, final R initial, final Injection< R, Es > injection ) throws Es { for ( final R item : collection ) injection.call item; return injection.value } public abstract class Injection< R, Throwable... Es > { public R value; public Injection( final R initial ) value = initial; public abstract void call( R item ) throws Es }
forEach
:Use: Integer sum = 0; // cannot be final even if logically it should be forEach input, new().{ ( final item ) sum += item }; Where: public static < A1, Throwable... Es > void forEach( final Iterable< A1 > collection, final Block1< A1, Es > block ) throws Es for ( final A1 item : collection ) block.call item; public interface Block1< A1, Throwable... Es > void call( A1 item ) throws Es;
I obviously prefer SSCO, otherwise I wouldn't be proposing it! I think the advantages are that it is consistent with current Java (this is also true of CICE) and that it will not only help with instances of inner classes but will also reduce verbosity in many places. E.g. it is the shortest code for both use of the example and declaration of the example. But, which do you prefer?
Acknowledgements: This new SSCO proposal has Rules taken from or inspired by: RFE 6389769, the discussions in the 6389769 forum, Ruby, and BGGA Closures.
Rule 0: For a method call allow brackets, ()
,
to be omitted if there are no arguments and for thelast call in a chain of top
level calls
(method, constructor, or modified new
, see Rules 1 and 2 below);
provided that the call isn't ambiguous with respect to a field. Top Level means
the call isn't an argument to another call. Note braces start a new statement
with a new top level; if a new statement is started without braces then this
does not create a new top level (see E.g. 2 for Rule 5 below). The last call
means that for the top level calls only: it is either the only call (and that
call isn't qualified) or it is the call following the last dot. The last call
rule means it must follow the last dot, even if that dot is a qualifier dot
rather than a call dot; so that an integer argument is distinct from a real
argument (see last example Rule 2). E.g.:
size = list.size; list.set 0, 'A'; list.add( 'A' ).add 'B'; list.add string.charAt( 0 );
Rule 1: Allow new
to be used as a qualifier
with a .
(dot). This rule improves readability, is more consistent
with the rest of Java, and (importantly) enables Rule 2. E.g.:
ArrayList< String > strings = new.ArrayList< String >.add "A"; T[] numbers = (T[])new.Number[ 1 ]; // where T extends Number Which are equivalent to: ArrayList< String > strings = (new ArrayList< String >()).add( "A" ); T[] numbers = (T[])(new Number[ 1 ]); // where T extends Number
Rule 2: Allow new
to infer type and use <>
to
mean a raw type when a .
is used (but not for a space). This rule
considerably shortens generic declarations and also instances of anonymous
inner classes. E.g.:
// Raw Types ArrayList strings = new; // Inferred List strings = new.ArrayList<>; // Explicit // Inferred Generic types List< String > strings = new.ArrayList; ArrayList< String > strings = new; ArrayList< String > strings = new 5; List< String > strings = new.{ // Anonymous List< String > ... }; ArrayList< String > strings = new( 5 ).{ // Anonymous ArrayList< String > of size 5 ... };
Rule 3: If an expression ends in }
then there
is no need for ;
before the brace (like array initializers and
argument lists). This rule helps the readability of instances of anonymous
classes. E.g.:
if ( end ) { break }
Rule 4: Consistently make {}
optional for a
single line (not just if
etc.). This rule helps the readability
of instances of anonymous classes, see last example Rule 5, and shortens many
simple class and method declarations. E.g.:
class IntWrapper public int i; @Override public String toString() return "Hello"; // Also see Rules 9 & 10 below
Rule 5: Infer return type, method name, arguments and exceptions if a single method is abstract (works for interfaces or abstract classes) and it isn't ambiguous. In particular it may be necessary to supply the type of the method arguments, see example at end. The brackets around the argument list, even for an empty argument list cannot be omitted; this is needed to distinguish between a method declaration using the new syntax and an instance initializer. . This rule helps the readability of instances of anonymous classes. E.g.:
// E.g. 1: Using Rule 5 only class MyAction implements ActionListener { ( notUsed ) { textArea.append( textField.getText() ); } } // E.g. 2: Using Rules 0 to 5 textField.addActionListner new.{ ( notUsed ) textArea.append textField.getText() }; // Or textField.addActionListner new.( notUsed ) textArea.append( textField.getText() );;
Rule 6: Allow extends
type generic arguments
to be shortened to < Type name >
(i.e. like normal
variable declarations), e.g.:
public static < K, V > Map< K, V > unmodifiableMap( Map< K ?, V ? > map ) ... public class Enum< Enum< E > E > ... Which are equivalent to public static < K, V > Map< K, V > unmodifiableMap( Map< ? extends K, ? extends K > map ) ... public class Enum< E extends Enum< E > > ...
Rule 7: A throws
clause can be empty, which
means the method doesn't throw anything (equivalent to an absent throws
clause).
This is useful in conjunction with generics, see rule 8 below. E.g.:
Void call() throws;
Rule 8: Generics are extended to allow varargs (only for
use with Throwable
s and derivatives). An empty generics varargs
list is allowed and it is equivalent to an absent throws clause (note Rule
5 above). E.g.:
< R, Es extends Throwable... > R call() throws Es; < R, Throwable... Es > R call() throws Es; // See Rule 6 above
Rule 9: return
may be omitted for last line
of a method and the returned value is the value of the last expression, e.g.:
@Override public String toString() "Hello"; // Also see rule 10
Rule 10: @Override name
infers modifiers,
return type, argument types, and exceptions (like Rule 5 above), e.g.:
@Override toString() "Hello"; @Override equals( other ) ...
Rule 11: Non-final locals that are referenced by an inner class are automatically wrapped in a final-tuple instance, like C# 3.0 does. Note: special treatment, name mangelling temporary required, of non-final arguments is needed (not shown in example below). E.g.:
Given: public class Tuple1< E1 > { public E1 e1; public Tuple1( final E1 e1 ) this.e1 = e1 } public class Tuple2< E1, E2 > extends Tuple1 ... ... public interface Function1< R, A1, Throwable... Es > R call( A1 a1 ) throws Es; public interface Predicate1< A1, Throwable... Es > extends Function1< Boolean, A1, Es >; ... public static < T, Throwable... Es > List< T > select( final Iterable< T > c, final Predicate1< T ?, Es > f ) throws Es ... Then: String beginning = "Fred"; ArrayList< String > names = new.add "Frederick"; ArrayList< String > filtered = select names, new.{ ( name ) name.startsWith beginning }; Which is equivalent to the following verbose version: final Tuple1< String > beginning = new Tuple1< String >( "Fred" ); ArrayList< String > names = (new ArrayList< String >()).add( "Frederick" ); ArrayList< String > filtered = select( names, new Predicate1< String >() { public Boolean call( final String name ) { return name.startsWith( beginning.e1 ); } } );
Rule 12: If a method returns a Void
, then make
the end of a method without a return
and a return
without
an argument synonymous with return null
. E.g. the following are
identical.
Given: interface Function0< R, Throwable... Es > R call() throws Es; Then: Function0< Void > block1 = new.{ () }; Function0< Void > block2 = new.{ () return }; Function0< Void > block3 = new.{ () return null }; Function0< Void > block4 = new Function0< Void >() { Void call() { return null; } }; Are all the same.
Have an opinion? Readers have already posted 38 comments about this weblog entry. Why not add yours?
If you'd like to be notified whenever Howard Lovatt adds a new entry to his weblog, subscribe to his RSS feed.
Dr. Howard Lovatt is a senior scientist with CSIRO, an Australian government owned research organization, and is the creator of the Pattern Enforcing Compiler (PEC) for Java. PEC is an extended Java compiler that allows Software Design Patterns to be declared and hence checked by the compiler. PEC forms the basis of Howard's 2nd PhD, his first concerned the design of Switched Reluctance Motors. |
Sponsored Links
|