Почему
public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) {...}
более строгим, чем
public <R> Builder<T> with(Function<T, R> getter, R returnValue) {...}
Это продолжение того, почему лямбда-тип возврата не проверяется во время компиляции . Я нашел с помощью метода, withX()
как
.withX(MyInterface::getLength, "I am not a Long")
выдает искомую ошибку времени компиляции:
Тип getLength () из типа BuilderExample.MyInterface является длинным, это несовместимо с возвращаемым типом дескриптора: String
при использовании метода with()
нет.
полный пример:
import java.util.function.Function;
public class SO58376589 {
public static class Builder<T> {
public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) {
return this;
}
public <R> Builder<T> with(Function<T, R> getter, R returnValue) {
return this;
}
}
static interface MyInterface {
public Long getLength();
}
public static void main(String[] args) {
Builder<MyInterface> b = new Builder<MyInterface>();
Function<MyInterface, Long> getter = MyInterface::getLength;
b.with(getter, 2L);
b.with(MyInterface::getLength, 2L);
b.withX(getter, 2L);
b.withX(MyInterface::getLength, 2L);
b.with(getter, "No NUMBER"); // error
b.with(MyInterface::getLength, "No NUMBER"); // NO ERROR !!
b.withX(getter, "No NUMBER"); // error
b.withX(MyInterface::getLength, "No NUMBER"); // error !!!
}
}
javac SO58376589.java
SO58376589.java:32: error: method with in class Builder<T> cannot be applied to given types;
b.with(getter, "No NUMBER"); // error
^
required: Function<MyInterface,R>,R
found: Function<MyInterface,Long>,String
reason: inference variable R has incompatible bounds
equality constraints: Long
lower bounds: String
where R,T are type-variables:
R extends Object declared in method <R>with(Function<T,R>,R)
T extends Object declared in class Builder
SO58376589.java:34: error: method withX in class Builder<T> cannot be applied to given types;
b.withX(getter, "No NUMBER"); // error
^
required: F,R
found: Function<MyInterface,Long>,String
reason: inference variable R has incompatible bounds
equality constraints: Long
lower bounds: String
where F,R,T are type-variables:
F extends Function<MyInterface,R> declared in method <R,F>withX(F,R)
R extends Object declared in method <R,F>withX(F,R)
T extends Object declared in class Builder
SO58376589.java:35: error: incompatible types: cannot infer type-variable(s) R,F
b.withX(MyInterface::getLength, "No NUMBER"); // error
^
(argument mismatch; bad return type in method reference
Long cannot be converted to String)
where R,F,T are type-variables:
R extends Object declared in method <R,F>withX(F,R)
F extends Function<T,R> declared in method <R,F>withX(F,R)
T extends Object declared in class Builder
3 errors
Расширенный пример
В следующем примере показано другое поведение метода и параметра типа, сводящихся к поставщику. Кроме того, он показывает разницу с поведением потребителя для параметра типа. И это показывает, что не имеет значения, является ли он Потребителем или Поставщиком для параметра метода.
import java.util.function.Consumer;
import java.util.function.Supplier;
interface TypeInference {
Number getNumber();
void setNumber(Number n);
@FunctionalInterface
interface Method<R> {
TypeInference be(R r);
}
//Supplier:
<R> R letBe(Supplier<R> supplier, R value);
<R, F extends Supplier<R>> R letBeX(F supplier, R value);
<R> Method<R> let(Supplier<R> supplier); // return (x) -> this;
//Consumer:
<R> R lettBe(Consumer<R> supplier, R value);
<R, F extends Consumer<R>> R lettBeX(F supplier, R value);
<R> Method<R> lett(Consumer<R> consumer);
public static void main(TypeInference t) {
t.letBe(t::getNumber, (Number) 2); // Compiles :-)
t.lettBe(t::setNumber, (Number) 2); // Compiles :-)
t.letBe(t::getNumber, 2); // Compiles :-)
t.lettBe(t::setNumber, 2); // Compiles :-)
t.letBe(t::getNumber, "NaN"); // !!!! Compiles :-(
t.lettBe(t::setNumber, "NaN"); // Does not compile :-)
t.letBeX(t::getNumber, (Number) 2); // Compiles :-)
t.lettBeX(t::setNumber, (Number) 2); // Compiles :-)
t.letBeX(t::getNumber, 2); // !!! Does not compile :-(
t.lettBeX(t::setNumber, 2); // Compiles :-)
t.letBeX(t::getNumber, "NaN"); // Does not compile :-)
t.lettBeX(t::setNumber, "NaN"); // Does not compile :-)
t.let(t::getNumber).be(2); // Compiles :-)
t.lett(t::setNumber).be(2); // Compiles :-)
t.let(t::getNumber).be("NaN"); // Does not compile :-)
t.lett(t::setNumber).be("NaN"); // Does not compile :-)
}
}
java
generics
lambda
type-inference
jukzi
источник
источник
javac
или инструмента сборки, такого как Gradle или Maven?Ответы:
Это действительно интересный вопрос. Боюсь, ответ сложный.
ТЛ; др
Выяснение различий включает в себя довольно глубокое чтение спецификации логического вывода типов Java , но в основном сводится к следующему:
with
есть (по-видимому, расплывчатая) замена, которая удовлетворяет всем требованиямR
:Serializable
withX
, введение дополнительного параметра типаF
вынуждает компиляторR
сначала разрешать , не принимая во внимание ограничениеF extends Function<T,R>
.R
разрешается до (гораздо более конкретно),String
что означает, что выводF
неудач.Эта последняя точка пули является самой важной, но также и самой волнистой. Я не могу придумать лучшего и краткого формулировки, поэтому, если вам нужны более подробные сведения, я предлагаю вам прочитать полное объяснение ниже.
Это намеренное поведение?
Я собираюсь выйти на конечности здесь и сказать нет .
Я не предполагаю, что в спецификации есть ошибка, более того, что (в случае
withX
) разработчики языка подняли руки и сказали: «В некоторых ситуациях вывод типов становится слишком сложным, поэтому мы просто потерпим неудачу» . Несмотря на то, что поведение компилятора по отношению кwithX
тому, что вам нужно, я бы посчитал, что это побочный побочный эффект текущей спецификации, а не положительно задуманное дизайнерское решение.Это важно, потому что это дает вопрос: следует ли мне полагаться на такое поведение в дизайне моего приложения? Я бы сказал, что вы не должны этого делать, потому что вы не можете гарантировать, что будущие версии языка будут продолжать вести себя таким образом.
Хотя разработчики языка очень стараются не ломать существующие приложения при обновлении своих спецификаций / дизайна / компилятора, проблема заключается в том, что поведение, на которое вы хотите положиться, - это то, где компилятор в настоящее время терпит неудачу (то есть не существующее приложение ). Обновления Langauge постоянно превращают некомпилируемый код в компилируемый. Например, следующий код может быть гарантировано не компилировать в Java 7, но будет компилировать в Java 8:
Ваш вариант использования не отличается.
Еще одна причина, по которой я буду осторожен при использовании вашего
withX
метода, - этоF
сам параметр. Как правило, параметр универсального типа в методе (который не указан в возвращаемом типе) существует для связывания типов нескольких частей подписи вместе. Это говорит:Мне все равно, что
T
есть, но хочу быть уверенным, что везде, где я использую,T
это один и тот же тип.Таким образом, логично, что мы ожидаем, что каждый параметр типа появится как минимум дважды в сигнатуре метода, в противном случае «он ничего не делает».
F
в вашейwithX
подписи появляется только один раз, что подсказывает мне использование параметра типа, не соответствующего цели этой функции языка.Альтернативная реализация
Один из способов реализовать это немного более «намеченным образом» - разделить ваш
with
метод на цепочку из 2:Это может быть использовано следующим образом:
Это не включает посторонний параметр типа, как у вас
withX
. Разбивая метод на две подписи, он также лучше выражает намерение того, что вы пытаетесь сделать, с точки зрения безопасности типов:With
), который определяет тип на основе ссылки на метод.of
) ограничивает типvalue
совместимости с тем, что вы ранее настроили.Единственный способ, которым будущая версия языка сможет скомпилировать это, - это реализовать полную типизацию утки, что кажется маловероятным.
И последнее замечание, чтобы сделать всю эту вещь неактуальной: я думаю, что Mockito (и, в частности, его функциональность заглушки ), в принципе, может уже делать то, что вы пытаетесь достичь с помощью вашего «универсального конструктора безопасных типов». Может быть, вы могли бы просто использовать это вместо этого?
Полное (ish) объяснение
Я собираюсь проработать процедуру вывода типа для обоих
with
иwithX
. Это довольно долго, поэтому принимайте это медленно. Несмотря на то, что я долго, я все еще оставил довольно много деталей. Вы можете обратиться к спецификации для получения более подробной информации (перейдите по ссылкам), чтобы убедиться, что я прав (возможно, я допустил ошибку).Также, чтобы немного упростить ситуацию, я собираюсь использовать более минимальный пример кода. Основное отличие заключается в том , что она меняет местами вне
Function
дляSupplier
, так что есть меньше типов и параметров в игре. Вот полный фрагмент, который воспроизводит поведение, которое вы описали:Работа Давайте через тип вывод применимости и логический вывод типа процедуру для каждого вызова метода , в своей очереди:
with
У нас есть:
Начальное связанное множество, B 0 , равно:
R <: Object
Все выражения параметров имеют отношение к применимости .
Следовательно, исходный набор ограничений для вывода применимости , С , является:
TypeInference::getLong
совместим сSupplier<R>
"Not a long"
совместим сR
Это сводит к ограниченному набору B 2 из:
R <: Object
(от B 0 )Long <: R
(из первого ограничения)String <: R
(из второго ограничения)Так как это не содержит связанный « ЛОЖЬ », и (я предполагаю) разрешение на
R
преуспевает (предоставлениеSerializable
), то вызов применим.Итак, мы переходим к выводу типа вызова .
Новый набор ограничений C со связанными входными и выходными переменными:
TypeInference::getLong
совместим сSupplier<R>
R
Это не содержит взаимозависимостей между входными и выходными переменными, поэтому может быть уменьшено за один шаг, и окончательный набор границ, B 4 , такой же, как B 2 . Следовательно, разрешение преуспевает как прежде, и компилятор вздыхает с облегчением!
withX
У нас есть:
Начальное связанное множество, B 0 , равно:
R <: Object
F <: Supplier<R>
Только выражение второго параметра имеет отношение к применимости . Первый (
TypeInference::getLong
) нет, потому что он соответствует следующему условию:Следовательно, исходный набор ограничений для вывода применимости , С , является:
"Also not a long"
совместим сR
Это сводит к ограниченному набору B 2 из:
R <: Object
(от B 0 )F <: Supplier<R>
(от B 0 )String <: R
(из ограничения)Опять же , так как это не содержит связанную « ЛОЖЬ », и разрешение на
R
преуспевает (предоставлениеString
), то вызов применим.Вывод типа вызова еще раз ...
На этот раз новый набор ограничений C с соответствующими входными и выходными переменными:
TypeInference::getLong
совместим сF
F
Опять же, у нас нет взаимозависимостей между входными и выходными переменными. Однако на этот раз, то есть входной переменный (
F
), поэтому мы должны решить это , прежде чем снижение . Итак, начнем с нашего связанного множества B 2 .Мы определяем подмножество
V
следующим образом:По второй оценке в B 2 разрешение
F
зависит отR
, тV := {F, R}
.Мы выбираем подмножество в
V
соответствии с правилом:Единственное подмножество,
V
которое удовлетворяет этому свойству, это{R}
.Используя третий bound (
String <: R
), мы создаем егоR = String
и включаем в наш ограниченный набор.R
теперь решен, и вторая граница эффективно становитсяF <: Supplier<String>
.Используя (пересмотренную) вторую оценку, мы создаем экземпляр
F = Supplier<String>
.F
сейчас решено.Теперь, когда
F
это решено, мы можем приступить к сокращению , используя новое ограничение:TypeInference::getLong
совместим сSupplier<String>
Long
совместим сString
... и мы получаем ошибку компилятора!
Дополнительные примечания к «Расширенному примеру»
Расширенный Пример в вопросе выглядит на несколько интересных случаях, которые непосредственно не охвачены выше выработками:
Integer <: Number
)Consumer
а неSupplier
)В частности, 3 из данных вызовов выделяются как потенциально предполагающие «отличное» поведение компилятора от описанного в пояснениях:
Второй из этих 3 будет проходить точно такой же процесс вывода, как
withX
указано выше (просто заменитьLong
наNumber
иString
сInteger
). Это иллюстрирует еще одну причину , почему вы не должны полагаться на это не смогло поведения типа логического вывода для вашего дизайна класса, как неспособность собрать здесь, скорее всего , не желательное поведение.Для других 2 (и, действительно, для любых других вызовов, связанных с тем, через что
Consumer
вы хотите работать), поведение должно быть очевидным, если вы работаете с процедурой вывода типа, изложенной для одного из методов выше (то естьwith
для первого,withX
для в третьих). Есть только одно маленькое изменение, на которое нужно обратить внимание:t::setNumber
совместимо сConsumer<R>
) будет уменьшено доR <: Number
того,Number <: R
что было сделано дляSupplier<R>
. Это описано в связанной документации по сокращению.Я оставляю читателю в качестве упражнения упражнение для выполнения одной из вышеуказанных процедур, вооруженных этим дополнительным знанием, чтобы продемонстрировать себе, почему конкретный вызов компилируется или не компилируется.
источник
TypeInference::getLong
может реализовыватьSupplier<Long>
илиSupplier<Serializable>
или иSupplier<Number>
т. Д., Но , главное , он может реализовать только один из них (как и любой другой класс)! Это отличается от всех других выражений, где все реализованные типы известны заранее, и компилятору нужно только определить, соответствует ли одно из них требованиям ограничения.