Я столкнулся со следующей проблемой в моем проекте с выводом типа Java generics. Это пример кода, похожий на мой исходный:
public class BuildableObject<R, S> {
public static class OneParameter<R> { }
public static class TwoParameters<R, S> { }
interface TwoParamInterface<R, S> { }
public static class Implementer<T> implements TwoParamInterface<T, T> {}
private final OneParameter<R> first;
private final OneParameter<S> second;
private final TwoParameters<R, S> third;
private final TwoParamInterface<R, S> fourth;
private BuildableObject(OneParameter<R> first, OneParameter<S> second, TwoParameters<R, S> third, TwoParamInterface<R, S> fourth) {
this.first = first;
this.second = second;
this.third = third;
this.fourth = fourth;
}
public static class Builder<R, S> {
private OneParameter<R> first = null;
private OneParameter<S> second = null;
private TwoParameters<R, S> third = null;
private TwoParamInterface<R, S> fourth = null;
public Builder() {}
public Builder<R, S> first(OneParameter<R> first) {
this.first = first; return this;
}
public Builder<R, S> second(OneParameter<S> second) {
this.second = second; return this;
}
public Builder<R, S> third(TwoParameters<R, S> third) {
this.third = third; return this;
}
public Builder<R, S> fourth(TwoParamInterface<R, S> fourth) {
this.fourth = fourth; return this;
}
public BuildableObject<R, S> build() {
return new BuildableObject<>(first, second, third, fourth);
}
}
public static void main(String... args) {
new Builder<>()
.first(new OneParameter<>())
.second(new OneParameter<>())
.third(new TwoParameters<>())
.fourth(new Implementer<String>())
.build();
}
}
Этот код не работает на new Implementer<String>, но работает, если я использую new Builder<String, String> вместо new Builder<>.
Почему Java не может сделать вывод, что тип Builder - это Builder<String, String>, если типы R и S указаны в new Implementer<String>?
Каковы ограничения вывода универсальных типов Java? Разрешает ли он только типы, указанные в конструкторах или статических методах? Я не нашел никакой документации по этому поводу.
Означает ли это каким-либо образом, что этот класс может быть небезопасным по типу, если мы не можем использовать вывод типа?




Это подробно описано в https://docs.oracle.com/javase/specs/jls/se9/html/jls-18.html. Но проблема в том, что это задокументировано в деталях: там много жаргона, с которым вы вряд ли будете знакомы, если не прочитаете статьи по этой теме.
В этом случае вам просто нужно понимать, что для вывода типа не имеет значения, какие методы вы вызываете после new Builder<>(); используются только параметры самого конструктора (и целевой тип, например, в Builder<String, String> b = new Builder<>();, но в этом случае у вас его нет).
Does it only resolve types provided in constructors or static methods?
Нет.
Does this mean in any way that this class might not be type safe if we can't use type inference?
Они совершенно не связаны.
Я имею в виду, что он работает для методов экземпляра так же, как и в других местах, но это не имеет отношения к этому случаю, потому что у ваших методов экземпляра нет параметров типа, и для них нечего делать.
Вы хотите, чтобы он использовал аргументы методов экземпляра для определения типа приемник. Насколько я понимаю (что может быть неполным; как вы можете видеть, спецификация очень сложная), это запрещено.
Вывод универсального типа может завершиться ошибкой, если код слишком сложен, но обычно вы можете явно указать типы, чтобы в выводе не было необходимости. Иногда это может быть вызвано связанными вызовами методов. Это просто означает, что вы теряете возможность не указывать тип самостоятельно.
Универсальный тип проверка - это отдельное понятие. Как правило, средство проверки не видит разницы между предполагаемым типом (<> выводится как <String, String>) и явным типом (<String, String>, записанным в коде), в любом случае он проверяет, что с этим объектом используются только строки. Пока компилятор не жалуется, ваш класс должен быть типобезопасным.
«но вы всегда можете явно указать типы, так что вывод не нужен» Не всегда предполагаемые типы могут быть безымянными (например, анонимный класс или захват подстановочных знаков).
Вы сказали, что вывод использует только параметры, переданные конструкторам, но ответили отрицательно на второй вопрос. Вы имеете в виду, что это не работает для статических методов или что есть некоторые другие способы, которыми вывод типа может разрешить типы, которые не имеют отношения к этому случаю?