Почему Java выбирает параметризованный тип Object при использовании обобщений с лямбда-выражениями?

1

Скажем, у меня есть метод, который принимает java.util.function.Predicate и return CompletableFuture:

public <R> CompletableFuture<R> call(Predicate<R> request) {
    return new CompletableFuture<>();
}

Если я вызову этот метод, используя анонимный класс, например:

Integer a = call(new Predicate<Integer>() {
    @Override
    public boolean test(Integer o) {
        return false;
    }
}).join();

Это работает, потому что я должен явно объявить тип Предиката. Однако, если я использую выражение лямбда вместо анонимного класса следующим образом:

Integer a = call(o -> false).join();

Он не компилируется, потому что Java думает, что это Predicate<Object> и возвращает такое сообщение:

Error:(126, 42) java: incompatible types: java.lang.Object cannot be converted to java.lang.Integer

Есть несколько обходных решений, которые я нашел. Я могу создать переменную CompletableFuture явно вместо цепочки или добавить лишний дополнительный аргумент Class<R> который сообщает Java, какой тип мы хотим получить или заставить тип в лямбда-выражении.

Однако я удивляюсь, почему Java выбирает Object вместо Integer в первом примере лямбда, он уже знает, какой тип я хочу получить, поэтому компилятор может использовать наиболее специфический тип вместо Object потому что все три обходных пути кажутся мне уродливыми.

Теги:
generics
java-8
lambda

2 ответа

4

Существуют ограничения на вывод типа Java 8, и он не смотрит на тип переменной, к которой вы назначили результат. Вместо этого он указывает тип Object для параметра type.

Вы можете исправить это, явно указав тип аргумента o в лямбда:

Integer a = call((Integer o) -> false).join();
  • 0
    Да, это один из обходных путей, которые я предложил, но я думаю, что это также ужасно, потому что, если у меня есть несколько аргументов в лямбда-выражении, я должен объявить типы для всех аргументов. Давайте рассмотрим этот пример: ConsistentHashRing remoteRing = ctx.ask(oneMemberOfCluster, (RingMap service, OperationContext<ConsistentHashRing> ctx1) -> ctx1.reply(service.getRing())).join();
  • 2
    @burakemre Там нет бесплатного обеда. Вывод типа требует некоторой информации о типе для работы. Он довольно хорош в поиске некоторых из большинства программ, но если вы не предоставите ему ничего для работы, он не сможет творить чудеса. Добавление дополнительной информации о типе не является «обходным путем»; он обеспечивает вывод типа с достаточным количеством информации для решения проблемы, которую вы задали. Время от времени добавление типов манифеста к лямбда-параметру не является навязчивым. Вы также можете добавить явные свидетели типа в метод вызова: receiver.<R>call(...) в тех случаях, когда это будет менее навязчивым.
Показать ещё 3 комментария
3

Другие люди ответили, как заставить лямбду правильного типа. Тем не менее, я бы сказал, что Predicate<Object> не является "неправильным" для этого предиката, и вам должно быть разрешено использовать его - у вас есть предикат для всех объектов - он должен работать на всех объектах, включая Integer s; почему вам нужно сделать более ограниченный предикат для Integer? Вы не должны.

Я бы сказал, что настоящая проблема - это подпись вашего метода call. Это слишком строго. Predicate - это потребитель своего параметра типа (у него есть только метод, который принимает тип типа своего типа и возвращает boolean); поэтому, согласно правилу PECS (продюсер extends, потребитель super), вы всегда должны использовать его с super ограниченным шаблоном.

Поэтому вы должны объявить свой метод следующим образом:

public <R> CompletableFuture<R> call(Predicate<? super R> request)

Независимо от того, какой код вы использовали в этом методе, он все равно будет компилироваться после изменения привязки к super, потому что Predicate является потребителем. И теперь вам не нужно заставлять его быть Predicate<Integer> - вы можете использовать Predicate<Object> и по-прежнему возвращать CompletableFuture<Integer>.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню