Ошибка Java при объединении предложений lambdas и multi-catch?

import java.io.*;             
import java.net.*;            

public class Test {           
    public static void main(String[] arguments) throws Exception {
        Runnable runnable = () -> {
            try {
                throwException();
            }
            catch (SocketException|EOFException exception) {
                System.err.println("wrong");
            }
            catch (IOException exception) {
                System.err.println("right");
            }
        };

        runnable.run();
    }                         

    private static void throwException() throws IOException {
        throw new NotSerializableException();
    }                         
}

Почему эта программа печатает "неправильно"? Если я удалю лямбда, или если я сломаю предложение с несколькими улочками, тогда он напечатает "справа".

$ javac -version
javac 1.8.0_11
$ java -version
java version "1.8.0_11"
Java(TM) SE Runtime Environment (build 1.8.0_11-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.11-b03, mixed mode)

Ответ 1

Это была исправленная ошибка в 1.8.0_20, от 1.8.0_11:

Область: инструменты /javac
Синопсис: javac генерирует неверную таблицу исключений для операторов с несколькими catch в lambda

Исправлена ​​обработка try-catch с несколькими уловами внутри лямбда.

Фактический отчет об ошибке - JDK-8036942

Что на самом деле пошло не так, это предполагаемая потеря информации типа в компиляторе:

LTM сильно использует стирание() во время переводов и отображения переменных. Эти операции стирания могут быть правильными в большинстве случаев, но это может привести к потере информации, как показывает этот случай. Также возможно, что такое интенсивное использование erasure() необходимо здесь, поскольку LTM применяется после TransTypes, который должен стирать большинство/все типы, поэтому я задаюсь вопросом, может ли это быть ошибкой в ​​TransTypes. Я думаю, что это должно быть оценено Робертом Филдом, которое является текущим поддерживающим LTM, что является лучшим подходом здесь, поэтому я переназначу его ему.

Что я вижу на 8u20 (я забыл указать параметр командной строки и больше не должен иметь 8u20):

wlan1-loopback% /usr/lib/jvm/java-8-oracle/bin/javap -c Test
Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
       5: astore_1
       6: aload_1
       7: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
      12: return
}
wlan1-loopback% java Test 
right
wlan1-loopback% java -version
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)
wlan1-loopback% 

Правильно:

public class Test {
  public Test();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    Code:
       0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
       5: astore_1
       6: aload_1
       7: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
      12: return

  private static void throwException() throws java.io.IOException;
    descriptor: ()V
    Code:
       0: new           #4                  // class java/io/NotSerializableException
       3: dup
       4: invokespecial #5                  // Method java/io/NotSerializableException."<init>":()V
       7: athrow

  private static void lambda$main$0();
    descriptor: ()V
    Code:
       0: invokestatic  #6                  // Method throwException:()V
       3: goto          27
       6: astore_0
       7: getstatic     #9                  // Field java/lang/System.err:Ljava/io/PrintStream;
      10: ldc           #10                 // String wrong
      12: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      15: goto          27
      18: astore_0
      19: getstatic     #9                  // Field java/lang/System.err:Ljava/io/PrintStream;
      22: ldc           #13                 // String right
      24: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: return
    Exception table:
       from    to  target type
           0     3     6   Class java/net/SocketException
           0     3     6   Class java/io/EOFException
           0     3    18   Class java/io/IOException
}

Ответ 2

Эта ошибка исправлена ​​с 1.8.0_20 https://bugs.openjdk.java.net/browse/JDK-8036942

Я могу реплицировать его 1.8.0_11, и он исправлен с 1.8.0_20

$ javac -version
javac 1.8.0_11
$ java -version
java version "1.8.0_11"
Java(TM) SE Runtime Environment (build 1.8.0_11-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.11-b03, mixed mode)
$ javac Test.java
$ java Test
wrong

отлично работает

~$ javac -version
javac 1.8.0_20
$ javac Test.java 
$ java -version
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)
$ java Test
right