The Artima Developer Community
Sponsored Link

Pattern Centric Blog
Inner Classes or Closures
by Howard Lovatt
October 15, 2006
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:

  1. Do nothing — Mitts off my favourite language!
  2. Add Closures - which are like inner classes but have read and write access local variables and 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.
  3. Add spiffy new syntax that makes anonymous inner classes instance creation shorter and let inner classes have write access to local variables (return acts as it currently does for an inner class). E.g. Concise Instance Creation Expressions (CICE), proposed by Bloch, Lea, and Lee.
  4. Shorter Syntax for Common Operations in general but with some bias to make inner class syntax, in particular, shorter and to give inner classes write access to locals (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.

Examples

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.

Java 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;
  }

Java 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 );
    }

    ...

  }

BGGA 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;
  }

BGGA 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; }

CICE 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;
}

CICE 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;
}

SSCO 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
  }

SSCO 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;

Vote

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?

  1. Do nothing — Mitts off my favourite language!
  2. BGGA Closures.
  3. Concise Instance Creation Expressions (CICE).
  4. Shorter Syntax for Common Operations (SSCO) (as described in detail in the Appendix below).

Appendix - New SSCO Proposal

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 Throwables 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.

Talk Back!

Have an opinion? Readers have already posted 38 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Howard Lovatt adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

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.

This weblog entry is Copyright © 2006 Howard Lovatt. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use