Примечание : я не работаю на Amazon или Sun / Oracle, поэтому часть ответа - это предположение.
Я думаю, что существует фундаментальный конфликт между стиранием типа JVM, тем, как AWS пытается обойти это, и тем, что вы пытаетесь сделать. Я также не думаю, что ошибка, на которую вы ссылались, актуальна. Я думаю, что поведение для Java такое же.
AFAIU с точки зрения AWS проблема выглядит следующим образом: есть поток событий разных типов и куча обработчиков. Вам необходимо решить, какие события может обработать данный обработчик. Очевидное решение - посмотреть на сигнатуру метода handleRequest
и использовать тип аргумента. К сожалению, система типов JVM на самом деле не поддерживает дженерики, поэтому вам нужно искать наиболее конкретный метод (см. Далее) и предполагать, что этот метод является реальной сделкой.
Теперь предположим, что вы разрабатываете компилятор, предназначенный для JVM (Scala или Java, дальнейшие примеры будут на Java, чтобы показать, что это не специфичная для Scala проблема). Поскольку JVM не поддерживает дженерики, вы должны удалить свои типы. И вы хотите стереть их до самого узкого типа, который охватывает все возможные аргументы, чтобы вы все еще были в безопасности с типом на уровне JVM.
Для RequestHandler.handleRequest
public O handleRequest(I input, Context context);
единственное допустимое удаление типа
public Object handleRequest(Object input, Context context);
потому что I
и O
не связаны.
Теперь предположим, что вы делаете
public class PojoTest1 implements RequestHandler<SNSEvent, Void> {
@Override
public Void handleRequest(SNSEvent input, Context context) {
// whatever
return null;
}
}
В этот момент вы говорите, что у вас есть метод handleRequest
с этой неуниверсальной сигнатурой, и компилятор должен его соблюдать. Но в то же время он должен уважать и вашу implements RequestHandler
. Поэтому компилятор должен добавить «метод моста», то есть создать код, логически эквивалентный
public class PojoTest1 implements RequestHandler {
// bridge-method
@Override
public Object handleRequest(Object input, Context context) {
// call the real method casting the argument
return handleRequest((SNSEvent)input, context);
}
// your original method
public Void handleRequest(SNSEvent input, Context context) {
// whatever
return null;
}
}
Обратите внимание, что ваш handleRequest
не является переопределением RequestHandler.handleRequest
. Тот факт, что у вас также есть Handler1
, ничего не меняет. Что действительно важно, так это то, что у вас есть override
в вашем неуниверсальном классе, поэтому компилятор должен генерировать неуниверсальный метод (т.е. метод с не стертыми типами) в вашем конечном классе. Теперь у вас есть два метода, и AWS может понять, что тот, который принимает SNSEvent
, является наиболее конкретным, поэтому он представляет вашу реальную границу.
Теперь предположим, что вы добавили свой общий промежуточный класс Handler2
:
public abstract class Handler2<E> implements RequestHandler<E, Void> {
protected abstract void act(E input);
@Override
public Void handleRequest(E input, Context context) {
act(input);
return null;
}
}
На данный момент тип возвращаемого значения фиксирован, но аргумент все еще является не связанным родовым. Поэтому компилятор должен создать что-то вроде этого:
public abstract class Handler2 implements RequestHandler {
protected abstract void act(Object input);
// bridge-method
@Override
public Object handleRequest(Object input, Context context) {
// In Java or Scala you can't distinguish between methods basing
// only on return type but JVM can easily do it. This is again
// call of the other ("your") handleRequest method
return handleRequest(input, context);
}
public Void handleRequest(Object input, Context context) {
act(input);
return null;
}
}
Так что теперь, когда мы подходим к
public class PojoTest2 extends Handler2<SNSEvent> {
@Override
protected void act(SNSEvent input) {
// whatever
}
}
вы переопределили act
, но не handleRequest
. Таким образом, компилятору не нужно генерировать определенный метод handleRequest
, и это не так. Он генерирует только определенный act
. Итак, сгенерированный код выглядит так:
public class PojoTest2 extends Handler2 {
// Bridge-method
@Override
protected void act(Object input) {
act((SNSEvent)input); // call the "real" method
}
protected void act(SNSEvent input) {
// whatever
}
}
Или, если вы расправляете дерево и показываете все (соответствующие) методы в PojoTest2
, это выглядит так:
public class PojoTest2 extends Handler2 {
// bridge-method
@Override
public Object handleRequest(Object input, Context context) {
// In Java or Scala you can't distinguish between methods basing
// only on return type but JVM can easily do it. This is again
// call of the other ("your") handleRequest method
return handleRequest(input, context);
}
public Void handleRequest(Object input, Context context) {
act(input);
return null;
}
// Bridge-method
@Override
protected void act(Object input) {
act((SNSEvent)input); // call the "real" method
}
protected void act(SNSEvent input) {
// whatever
}
}
Оба метода handleRequest
принимают только Object
в качестве параметра, и это то, что AWS должен принять. Поскольку вы не переопределили метод handleRequest
в PojoTest2
(а не необходимость в этом - весь смысл вашей иерархии наследования), компилятор не создал для него более специфический метод.
К сожалению, я не вижу хорошего решения этой проблемы. Если вы хотите, чтобы AWS распознал границу универсального параметра I
, вам необходимо переопределить handleRequest
в том месте иерархии, где эта граница становится действительно известной.
Вы можете попробовать сделать что-то вроде этого:
// Your _non-generic_ sub-class has to have the following implementation of handleRequest:
// def handleRequestImpl(input: EventType, context: Context): Unit = handleRequestImpl(input, context)
trait UnitHandler[Event] extends RequestHandler[Event, Unit]{
def act(input: Event): Unit
protected def handleRequestImpl(input: Event, context: Context): Unit = act(input)
}
Преимущество этого подхода состоит в том, что вы все равно можете добавить некоторую дополнительную логику обертывания (такую как ведение журнала) в handleRequestImpl
. Но все же это будет работать только по соглашению. Я не вижу способа заставить разработчиков правильно использовать этот код.
Если весь смысл вашего Handler2
просто привязать тип вывода O
к Unit
без добавления логики переноса, вы можете просто сделать это без переименования метода в act
:
trait UnitHandler[Event] extends RequestHandler[Event, Unit]{
override def handleRequest(input: Event, context: Context): Unit
}
Таким образом, ваши подклассы по-прежнему должны будут реализовывать handleRequest
с конкретными типами, привязанными к Event
, и компилятор должен будет создавать специальные методы там, чтобы проблема не возникала.