Получение java.lang.NoSuchMethodException в структурной типизации AnyVal

У меня есть следующий фрагмент, который (я думаю) определяет метод addNumber1(x:T):T для универсального типа T, который является подтипом AnyVal и имеет метод +(s:Int):T.

def addNumber1[T <: AnyVal {def +(s:Int):T}](x:T):T = {x + 1}

addNumber1(31) // compiles but throws exception

java.lang.NoSuchMethodException: java.lang.Integer.$plus(int)
  at java.lang.Class.getMethod(Class.java:1786)
  at .reflMethod$Method1(<console>:8)
  at .addNumber1(<console>:8)
  ... 33 elided

Я попытался добавить import scala.language.reflectiveCalls, чтобы подавить предупреждение о функции, но все равно получаю ошибку.

Я могу использовать это при работе с AnyRef или Any, как показано ниже:

def addNumber1[T <: Any {def +(s:Int):T}](x:T):T = {x + 1}

class Foo(s:String) {def +(i:Int) = new Foo((s+1).toString)} // random code
class Bar(s:Foo) {def +(i:Int) = new Bar(new Foo(i.toString))} // random code

addNumber1(new Foo("1"))  // works
addNumber1(new Bar(new Foo("1")))  // works
addNumber1(1) // compiles but gives exception

person Jus12    schedule 04.09.2016    source источник


Ответы (2)


Вы сталкиваетесь с пересечением довольно многих функций:

  1. Что касается начальных этапов компилятора Scala (включая проверку типов), Int действительно имеет (перегруженный) метод +. Но этот "метод" обрабатывается на более поздних стадиях особым образом (как и все методы на Int, потому что на самом деле это не класс).

  2. Методы с именем +, определенные в Scala, транслируются в методы с именем $plus в байт-коде, поскольку + не является допустимым идентификатором. Поскольку + на Int особенный, как упоминалось выше, это к нему не относится. Поскольку структурные типы реализованы с использованием отражения Java, ваш addNumber1 выглядит примерно так:

    def addNumber1(x: Object) = x.getClass.getMethod("$plus").invoke(x, 1)
    
  3. Чтобы вызвать addNumber1 для int, его нужно сначала упаковать в Integer, потому что int не является объектом. Integer, не будучи типом Scala, не имеет метода $plus. В Scala вы можете написать что-то вроде val x: Integer = ...; x + 1, но при этом используется неявное преобразование, о котором Java-рефлексия не знает.

person Alexey Romanov    schedule 05.09.2016
comment
Я думаю, что структурные типы должны быть реализованы с использованием отражения Scala, которое стало достаточно стабильным. - person Jus12; 05.09.2016

Я думаю, что проблема не имеет ничего общего с AnyVal, AnyRef или Any.

addNumber1(new Foo("1")) 

Это работает, потому что вы действительно определили класс Foo, который обеспечивает реализацию def +(s:Int):T.

addNumber1(1)

Это не работает, потому что класс Integer не предоставляет его, как указано в исключении:

java.lang.NoSuchMethodException: java.lang.Integer.$plus(int)
person pltc325    schedule 05.09.2016
comment
Проблема в том, почему он компилируется. - person Alexey Romanov; 05.09.2016
comment
Он компилируется, потому что компилятор scala считает, что у Int есть оператор +, так и должно быть, потому что, например, i + 1 допустимо, где i имеет тип Int. - person pltc325; 05.09.2016
comment
Я использовал AnyVal, потому что они сопоставляются с примитивными типами Java. Проблема возникает только при использовании примитивных типов Java. - person Jus12; 06.09.2016
comment
число 31 в addNumber1(31) не относится к примитивному типу Java, оно относится к примитивному типу Scala Int и является источником проблемы, компилятор Scala считает, что Int имеет оператор + во время компиляции, это правда, но ложно, если Int упаковывается в Integer во время выполнения. - person pltc325; 06.09.2016
comment
Это то, что я имел в виду. проблема возникает при использовании (типов Scala, которые сопоставляются с) типами примитивов Java. Точно так же я мог бы написать, что проблема возникает только с AnyVals, что является заголовком вопроса. Возможно, компилятор действительно мог бы сделать вывод, используется ли магический метод в структурной типизации (и где-либо еще). - person Jus12; 07.09.2016