Мы все знаем, что Лонг расширяется Number
. Так почему же это не скомпилировать?
А как определить метод, with
чтобы программа компилировалась без ручного приведения?
import java.util.function.Function;
public class Builder<T> {
static public interface MyInterface {
Number getNumber();
Long getLong();
}
public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue) {
return null;//TODO
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// works:
new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
// works:
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, Long.valueOf(4));
// compiles but also involves typecast (and Casting Number to Long is not even safe):
new Builder<MyInterface>().with( myInterface->(Long) myInterface.getNumber(), 4L);
// compiles but also involves manual conversion:
new Builder<MyInterface>().with(myInterface -> myInterface.getNumber().longValue(), 4L);
// compiles (compiler you are kidding me?):
new Builder<MyInterface>().with(castToFunction(MyInterface::getNumber), 4L);
}
static <X, Y> Function<X, Y> castToFunction(Function<X, Y> f) {
return f;
}
}
- Невозможно определить тип аргумента (ов) для
<F, R> with(F, R)
- Тип getNumber () из типа Builder.MyInterface является Number, это несовместимо с типом возвращаемого дескриптором: Long
Пример использования: см. Почему лямбда-тип возврата не проверяется во время компиляции
java
type-inference
jukzi
источник
источник
MyInterface
?<F extends Function<T, R>, R, S extends R> Builder<T> with(F getter, S returnValue)
но получилjava.lang.Number cannot be converted to java.lang.Long)
, что удивительно, потому что я не понимаю, откуда компилятор получает идею, что возвращаемое значениеgetter
нужно будет преобразовать вreturnValue
.Number getNumber()
на<A extends Number> A getNumber()
заставляет вещи работать. Понятия не имею, хотел ли ты этого. Как уже говорили другие, проблема в том, что этоMyInterface::getNumber
может быть функция, которая возвращает,Double
например, а неLong
. Ваша декларация не позволяет компилятору сузить тип возвращаемого значения на основе другой имеющейся информации. Используя универсальный тип возвращаемого значения, вы позволяете компилятору делать это, поэтому он работает.Ответы:
Это выражение:
можно переписать как:
С учетом подписи метода:
R
будет выведеноLong
F
будетFunction<MyInterface, Long>
и вы передаете ссылку на метод, который будет представлен как
Function<MyInterface, Number>
Это ключ - как компилятор должен предсказать, что вы действительно хотите вернутьсяLong
из функции с такой сигнатурой? Это не сделает уныние для вас.Поскольку он
Number
является суперклассом классаLong
иNumber
не является обязательнымLong
(именно поэтому он не компилируется), вам придется явно приводить его самостоятельно:сделать,
F
чтобы бытьFunction<MyIinterface, Long>
или передать общие аргументы явно во время вызова метода, как вы это сделали:и знать
R
будет видно какNumber
и код скомпилируется.источник
with
написано ваше . У васMJyInterface::getNumber
есть тип ,Function<MyInterface, Number>
такR=Number
и тогда вы такжеR=Long
от другого аргумента (помните , что Java литералы не полиморфные!). На этом этапе компилятор останавливается, потому что не всегда возможно преобразовать aNumber
в aLong
. Единственный способ исправить это - изменить егоMyInterface
на использование в<A extends Number> Number
качестве возвращаемого типа, это дает компилятору время отR=A
времени,R=Long
и так какA extends Number
он может заменитьA=Long
Ключ к вашей ошибке в общей декларации типа
F
:F extends Function<T, R>
. Утверждение, которое не работает:new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
Во-первых, у вас есть новоеBuilder<MyInterface>
. Объявление класса поэтому подразумеваетT = MyInterface
. Согласно вашей декларацииwith
,F
должен бытьFunction<T, R>
, который являетсяFunction<MyInterface, R>
в этой ситуации. Следовательно, параметрgetter
должен приниматьMyInterface
как параметр (удовлетворяемый ссылками методаMyInterface::getNumber
иMyInterface::getLong
) и возвращатьR
, который должен быть того же типа, что и второй параметр функцииwith
. Теперь давайте посмотрим, верно ли это для всех ваших случаев:Вы можете «исправить» эту проблему с помощью следующих параметров:
Помимо этого, в основном это дизайнерское решение, для которого опция уменьшает сложность кода для вашего конкретного приложения, поэтому выбирайте то, что вам больше подходит.
Причина, по которой вы не можете сделать это без приведения, заключается в следующем из спецификации языка Java :
Как вы можете ясно видеть, не существует неявного преобразования бокса из long в Number, и расширяющееся преобразование из Long в Number может происходить только тогда, когда компилятор уверен, что для него требуется Number, а не Long. Поскольку существует конфликт между ссылкой на метод, которая требует Number, и 4L, который предоставляет Long, компилятор (по какой-то причине ???) не может сделать логический скачок, что Long is-a Number и вывести, что
F
этоFunction<MyInterface, Number>
.Вместо этого мне удалось решить проблему, слегка отредактировав сигнатуру функции:
После этого изменения происходит следующее:
Изменить:
Потратив немного больше времени на это, раздражающе трудно обеспечить безопасность типа на основе геттера. Вот рабочий пример, который использует методы установки для обеспечения безопасности типов в сборщике:
Предоставляя возможность создания безопасных типов для создания объекта, мы надеемся, что когда-нибудь в будущем мы сможем вернуть неизменный объект данных от компоновщика (возможно, добавив
toRecord()
метод в интерфейс и указав компоновщик какBuilder<IntermediaryInterfaceType, RecordType>
), так что вам даже не нужно беспокоиться о том, что полученный объект будет изменен. Честно говоря, это просто позор, что требуется так много усилий, чтобы получить безопасный для типов компоновщик с гибкими полями, но это, вероятно, невозможно без некоторых новых функций, генерации кода или раздражающего количества размышлений.источник
Кажется, что компилятор использовал значение 4L, чтобы решить, что R - Long, а getNumber () возвращает Number, который не обязательно является Long.
Но я не уверен, почему значение имеет приоритет над методом ...
источник
Компилятор Java, как правило, не подходит для вывода нескольких / вложенных универсальных типов или подстановочных знаков. Часто я не могу заставить что-то скомпилировать без использования вспомогательной функции для захвата или вывода некоторых типов.
Но вам действительно нужно захватить точный тип
Function
какF
? Если нет, возможно, следующие работы, и, как вы можете видеть, также, кажется, работают с подтипамиFunction
.источник
Самая интересная часть заключается в разнице между этими двумя строками, я думаю:
В первом случае,
T
это явноNumber
, так что4L
это тожеNumber
не проблема. Во втором случае4L
это aLong
, такжеT
как и aLong
, поэтому ваша функция несовместима, и Java не может знать, имели ли вы в видуNumber
илиLong
.источник
Со следующей подписью:
все ваши примеры компилируются, кроме третьего, который явно требует, чтобы метод имел две переменные типа.
Причина, по которой ваша версия не работает, заключается в том, что ссылки на методы Java не имеют одного определенного типа. Вместо этого они имеют тип, который требуется в данном контексте. В вашем случае
R
предполагается,Long
что это происходит из-за4L
, но метод получения не может иметь тип,Function<MyInterface,Long>
потому что в Java универсальные типы являются инвариантными в своих аргументах.источник
with( getNumber,"NO NUMBER")
что нежелательно. Также неверно, что дженерики всегда инвариантны (см. Stackoverflow.com/a/58378661/9549750, чтобы доказать, что дженерики сеттеров ведут себя не так, как дженерики )Thing<Cat>
кThing<? extends Animal>
переменной, но для реальной ковариации я ожидал бы , чтоThing<Cat>
может быть назначенThing<Animal>
. Другие языки, такие как Kotlin, позволяют определять ко-и контравариантные переменные типа.