Почему классы неявных значений имеют дополнительный вызов метода?

Я проверял байт-код, генерируемый неявными классами, и хотел сравнить с тем, что генерируется при расширении AnyVal,

Без неявного:

object Example1 {
  class Wrapper(val self: Int) extends AnyVal {
    def add(n: Int): Int = self + n
  }
  def foo(w: Wrapper): Wrapper = new Wrapper(w.add(42))
}

(Соответствующая часть) байт-код:

scala>:javap Example1

[...]

public int foo(int);
  descriptor: (I)I
  flags: ACC_PUBLIC
  Code:
    stack=3, locals=2, args_size=2
       0: getstatic     #19                 // Field Example1$Wrapper$.MODULE$:LExample1$Wrapper$;
       3: iload_1
       4: bipush        42
       6: invokevirtual #23                 // Method Example1$Wrapper$.add$extension:(II)I
       9: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      10     0  this   LExample1$;
          0      10     1     w   I
    LineNumberTable:
      line 11: 3

[...]

С implicit:

object Example2 {
  implicit class Wrapper(val self: Int) extends AnyVal {
    def add(n: Int): Int = self + n
  }
  def foo(w: Wrapper): Wrapper = w.add(42)
}

(Соответствующая часть) байт-код:

scala>:javap Example2

[...]

public int Wrapper(int);
  descriptor: (I)I
  flags: ACC_PUBLIC
  Code:
    stack=1, locals=2, args_size=2
       0: iload_1
       1: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       2     0  this   LExample2$;
          0       2     1  self   I
    LineNumberTable:
      line 9: 0

public int foo(int);
  descriptor: (I)I
  flags: ACC_PUBLIC
  Code:
    stack=4, locals=2, args_size=2
       0: aload_0
       1: getstatic     #23                 // Field Example2$Wrapper$.MODULE$:LExample2$Wrapper$;
       4: iload_1
       5: bipush        42
       7: invokevirtual #27                 // Method Example2$Wrapper$.add$extension:(II)I
      10: invokevirtual #29                 // Method Wrapper:(I)I
      13: ireturn
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      14     0  this   LExample2$;
          0      14     1     w   I
    LineNumberTable:
      line 12: 0

[...]

В результате расширения AnyVal призыв к add вызывается на объекте-компаньоне, а тип Wrapper не отображается в подписи типа foo (public int foo(int);) в обеих версиях.

Hovewer, во второй версии, перед возвратом происходит вызов: 10: invokevirtual #29, Это вызывает public int Wrapper(int); который, похоже, ничего не делает. (Хотя я могу ошибаться, так как у меня нет большого опыта чтения байт-кода)

Итак, вопрос в том, каково значение этого призыва? Разве это не может быть опущено?

1 ответ

Решение

Проблема в том, что ваши фрагменты кода не эквивалентны. Неявный класс Foo компилируется / десагерируется в класс Foo и метод неявного преобразования Foo, Это также причина того, что неявные классы (в настоящее время) не могут быть верхнего уровня.

Итак, ваш первый фрагмент должен быть:

object Example1 {
  class Wrapper(val self: Int) extends AnyVal {
    def add(n: Int): Int = self + n
  }
  def Wrapper(self: Int): Wrapper = new Wrapper(self)
  def foo(w: Wrapper): Wrapper = Wrapper(w.add(42))
}

Компилятор стирает вызовы конструкторов классов значений, если это возможно. Но это не стирает призыв к Wrapper метод, неявный или нет.

Я предполагаю, что JIT-компилятор в JVM в конечном итоге удалит вызов этого метода.

Другие вопросы по тегам