Friday, December 28, 2007

Closure Conversion

Closure Conversion

A closure can be assigned to a variable of compatible interface type. A compatible interface type must have a single method with a compatible return type and compatible argument types. For example:

  interface IntPlus {
    int add(int x, int y);
  }
  IntPlus plus = { int x, int y => x + y };
  

In such case, a closure is converted to an instance of anonymous class that implements the given interface:

  IntPlus plus = new IntPlus() {
    public int add(int x, int y) { return x + y; }
  };
  

Then, we call the closure by the interface method:

  int sum = plus.add(5, 6);
  

This allows us to use a closure instead of anonymous subclass of interface with a single method. For example, the closure conversion can create an instance of Runnable:

  new Thread({ => System.out.println("hi"); }).start();
  

Sometimes we can use the closure conversion even if the interface has more than one method. It is in case when all but one method are implemented in Object. The closure is then converted to a single remaining method. For example, interface Comparator contains two methods: compare() and equals(). Method equals() is in Object and so the closure is converted to method compare().

  String[] girls = { "Jane", "Eva", "Sarah", "Alice" };
  Arrays.sort(girls,
    { String s1, String s2 =>
      int r = s1.length() - s2.length(); r == 0 ? s1.compareTo(s2) : r 
    });
  

Exercises

  1. Declare interface HashCalculator.
      HashCalculator hc = 
        { String s1, String s2 => s1.hashCode() ^ s2.hashCode() };
      System.out.println(hc.hash("elephant", "giraffe"));
      
  2. Use a closure to define Comparator which sort numbers descendingly.
      Integer[] wages = { 1200, 4500, 850, 1500, 990 };
      Arrays.sort(wages,                                      );
      
  3. Sort an array of strings and count the number of comparisons.

Solutions

  interface HashCalculator {
    int hash(String s1, String s2);
  }
  
  Integer[] wages = { 1200, 4500, 850, 1500, 990 };
  Arrays.sort(wages, { Integer i, Integer j => j.compareTo(i) });
  
  String[] countries = { 
    "France", "Czech Republic", "Poland", "Hungary", "Germany" 
  };
  int c = 0;
  Arrays.sort(countries, { String s1, String s2 => c++; s1.compareTo(s2) });
  System.out.println("number of comparisons: " + c);
  

6 comments:

Anonymous said...

Wow, i think your examples are easier to follow than Neal Grafter's own. Thanks.

Vyas, Anirudh

Anonymous said...

I'm trying to rewrite:
EventQueue.invokeLater(new Runnable()
{
public void run()
{
//set some text.
}
});

As:
EventQueue.invokeLater(new Thread(
{ =>
//set some text
}
).start();
);

Running into compile time errors(after I downloaded the new prototype from javac.info), and I tried to follow your example from above. Can you spot the error please?

Zdeněk Troníček said...

EventQueue.invokeLater() takes a Runnable as argument:
EventQueue.invokeLater({ => System.out.println("hi"); });

Anonymous said...

Thanks a lot! It works :).

lwesterhoff said...

Normally adding a method to an interface is backwards compatible. With closure conversion, adding a method to an interface with a single method is no longer backwards compatible since it can break code when its used with closures.

Zdeněk Troníček said...

Perhaps I do not understand what you want to say because adding a method to an interface is NOT backward compatible.