Подтвердить что ты не робот

Лямбда ведет себя иначе, чем анонимный внутренний класс

При выполнении некоторых основных упражнений лямбда выход из явно идентичного анонимного внутреннего класса давал мне отличный результат, чем лямбда.

interface Supplier<T> {

    T get(T t);
}

Сценарий # 1

Supplier<Integer> s1 = new Supplier<Integer>() {
    @Override
    public Integer get(Integer t) {
        return t;
    }
};
Supplier<Integer> s2 = t -> t;
System.out.println(s1.get(2));
System.out.println(s2.get(2));

Вывод 2 и 2. Здесь ничего нового.


Но когда я это делаю:

Сценарий №2

Supplier<Integer> s1 = new Supplier<Integer>() {
    @Override
    public Integer get(Integer t) {
        return t++;
    }
};
Supplier<Integer> s2 = t -> t++;
System.out.println(s1.get(2));
System.out.println(s2.get(2));

Выходы 2 и 3

ВОПРОС: Не должны ли оба выхода быть одинаковыми? Я что-то пропустил?


Для полноты: Сценарий № 3

Supplier<Integer> s1 = new Supplier<Integer>() {
    @Override
    public Integer get(Integer t) {
        return ++t;
    }
};
Supplier<Integer> s2 = t -> ++t;
System.out.println(s1.get(2));
System.out.println(s2.get(2));

Вывод 3 и 3. Здесь ничего нового не было.

ОБНОВЛЕНИЕ: по-прежнему получается тот же результат от 1.8.0-b132

ОБНОВЛЕНИЕ # 2: Отчет об ошибке: https://bugs.openjdk.java.net/browse/JDK-8038420

ОБНОВЛЕНИЕ # 3: ошибка была исправлена ​​в javac, теперь вы можете получить тот же результат.

4b9b3361

Ответ 1

В соответствии с созданным байт-кодом:

Java (TM) SE Runtime Environment (сборка 1.8.0-b132)

Lambda:

 private static java.lang.Integer lambda$main$0(java.lang.Integer);
   descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
   flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
   Code:
     stack=2, locals=2, args_size=1
        0: aload_0
        1: invokevirtual #9                  // Method java/lang/Integer.intValue:()I
        4: iconst_1
        5: iadd
        6: invokestatic  #6                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        9: dup
       10: astore_0
       11: astore_1
       12: aload_0
       13: areturn
     LineNumberTable:
       line 20: 0
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      14     0     t   Ljava/lang/Integer;

Анонимный класс:

  public java.lang.Integer get(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=2
         0: aload_1
         1: astore_2
         2: aload_1
         3: invokevirtual #2                  // Method java/lang/Integer.intValue:()I
         6: iconst_1
         7: iadd
         8: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        11: dup
        12: astore_1
        13: astore_3
        14: aload_2
        15: areturn
      LineNumberTable:
        line 16: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   LTest$1;
            0      16     1     t   Ljava/lang/Integer;

Как вы можете видеть, в анонимном классе после загрузки переменной из локальной таблицы переменных (параметр метода t) среда выполнения хранит копию параметра в другой переменной (astore_2), а затем используйте эту копию параметра как возвращаемое значение.

Метод Lambda не делает копию параметра (load → unbox → добавить 1 → box → store → load → return).

UPDATE

Это определенно ошибка javac.

Я получил источник http://hg.openjdk.java.net/jdk8u/jdk8u

Анонимный класс и лямбда преобразуются в следующие промежуточные представления:

@Override()
public Integer get(Integer t) {
    return (let /*synthetic*/ final Integer $112619572 = t in 
       (let /*synthetic*/ final Integer $1295226194 = t = Integer.valueOf((int)(t.intValue() + 1)) in $112619572));
}

/*synthetic*/ private static Integer lambda$main$0(final Integer t) {
    return (let /*synthetic*/ final Integer $1146147158 = t = Integer.valueOf((int)(t.intValue() + 1)) in t);
}

В параметре метода генерируемого лямбда, отмеченном как final, поскольку LambdaToMethod переводчик маркирует все параметры как FINAL (согласно исходному коду LambdaTranslationContext.translate(...): 1899).

Затем пусть выражение builder проверяет флаги переменных и когда, если его окончательное исключает создание временной переменной (в соответствии с исходным кодом Lower.abstractRval(...): 2277), поскольку модификация считается запрещенной.

Возможные решения:

  • Изменение параметра Forbid внутри lambda или
  • Удалить флаг FINAL из локальной переменной (LambdaTranslationContext.translate(...): 1894) и параметр (LambdaTranslationContext.translate(...): 1899) в методе, генерируемом lamda:

     case LOCAL_VAR:
       ret = new VarSymbol(FINAL, name, types.erasure(sym.type), translatedSym);
     ...
    
     case PARAM:
       ret = new VarSymbol(FINAL | PARAMETER, name, types.erasure(sym.type), translatedSym);
     ...
    

Я удалил флаг FINAL и получил ожидаемые результаты тестов: https://bugs.openjdk.java.net/browse/JDK-8038420