Friday, December 28, 2007

Exceptions

Exceptions

Closures are allowed to throw exceptions. We use the throws keyword to declare which exceptions are thrown by a closure:

  { long => void throws InterruptedException } wait = 
    { long millis => Thread.sleep(millis); };
  try {
    wait.invoke(2000);
    System.out.println("hi");
  } catch (InterruptedException e) { e.printStackTrace(); }
  

A closure may throw more than one exception:

  try {
    FileOutputStream fos = new FileOutputStream("primes");
    try {
      { int, long => 
        void throws IOException, InterruptedException } writeAndWait = 
        { int value, long millis => 
          fos.write(value); Thread.sleep(millis); };
      writeAndWait.invoke(17, 2000);
      System.out.println("first");
      writeAndWait.invoke(19, 2000);
      System.out.println("second");
    } finally { fos.close(); }
  } catch (IOException | InterruptedException e) { e.printStackTrace(); }
  

When we assign a closure to a variable of function type, the closure must not throw more than is declared in the function type.

  // constructor FileWriter throws IOException which is a subclass of 
  // Exception
  { String => void throws Exception } truncate = 
    { String name => new FileWriter(name).close(); };
  { => double throws Exception } next = 
    { => Math.random() };  // Math.random() does not throw any exception
  

Exception Transparency

If we pass a closure that throws exceptions to some method, we might want these exceptions are propagated from the method. If we used ordinary type parameter for exception, we could have passed in only a block that throws one exception. But what if we want to accept a block that throws any number of exceptions and we want exceptions are transparently propagated to the caller? We have to use exception type parameters. The exception type parameter is declared using the throws keyword and stands for any number of exceptions.

  // method performTwice throws the same exceptions as the block passed in
  static <throws E> void performTwice({ => void throws E } block) throws E {
    block.invoke();
    block.invoke();
  }
  
  static void passBlockThrowingNoExceptions() {
    performTwice({ => System.out.println("no exception..."); });
  }
  
  static void passBlockThrowingOneException() {
    try {
      // the block passed to performTwice throws one exception 
      // (InterruptedException)
      // the compiler checks that we catch this exception
      performTwice({ => 
        System.out.println("waiting..."); Thread.sleep(2000); });
    } catch (InterruptedException e) { e.printStackTrace(); }
  }
  
  static void passBlockThrowingMoreExceptions() {
    try {
      // the block passed to performTwice throws two exceptions 
      // (InterruptedException and IOException)
      // the compiler checks that we catch these exceptions
      performTwice({ => Thread.sleep(2000); new FileWriter("x").close(); });
    } catch (InterruptedException | IOException e) { e.printStackTrace(); }
  }
  

If we do not want to propagate exceptions, we can invoke a closure in the try block:

  static <throws E> void logAndPerform(
    String desc, { => void throws E } block) {
    System.out.printf("start: %s...%n", desc);
    try {
      block.invoke();
    } catch (Exception e) { e.printStackTrace(); }
    System.out.printf("end: %s%n", desc);
  }
  
  public static void main(String[] args) {
    logAndPerform("doing nothing", { => System.out.println("hi"); });
    logAndPerform("sleeping", { => Thread.sleep(2000); });
    logAndPerform("sleeping and truncating", 
      { => Thread.sleep(2000); new FileWriter("x").close(); });
  }
  

Exercises

  1. Fill in the type of p.
      static void m() throws FileNotFoundException, InterruptedException {
        //...
      }
      
      public static void main(String[] args) {
                                                     p = { => m(); };
        try {
          p.invoke();
        } catch (FileNotFoundException | InterruptedException e) { 
          e.printStackTrace();
        }
      }
      
  2. Declare method forEach that performs an operation on each item of a collection. The operation is described by a closure and may throw an exception (one or more).

Solutions

  { => void throws FileNotFoundException, InterruptedException } p = 
    { => m(); };
  
  static <T, throws E> void forEach(
    Iterable<T> items, { T => void throws E } block) throws E {
    for (Iterator<T> it = items.iterator(); it.hasNext(); ) {
      block.invoke(it.next());
    }
  }
  public static void main(String[] args) {
    List<String> nums = Arrays.asList("first", "second", "third");
    forEach(nums, { String s => System.out.println(s); });
  }
  

No comments: