Пределы вывода универсального типа в Java

Я столкнулся со следующей проблемой в моем проекте с выводом типа 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? Разрешает ли он только типы, указанные в конструкторах или статических методах? Я не нашел никакой документации по этому поводу.

Означает ли это каким-либо образом, что этот класс может быть небезопасным по типу, если мы не можем использовать вывод типа?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
7
0
142
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Это подробно описано в 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?

Они совершенно не связаны.

Вы сказали, что вывод использует только параметры, переданные конструкторам, но ответили отрицательно на второй вопрос. Вы имеете в виду, что это не работает для статических методов или что есть некоторые другие способы, которыми вывод типа может разрешить типы, которые не имеют отношения к этому случаю?

user9834167 23.05.2018 16:28

Я имею в виду, что он работает для методов экземпляра так же, как и в других местах, но это не имеет отношения к этому случаю, потому что у ваших методов экземпляра нет параметров типа, и для них нечего делать.

Alexey Romanov 23.05.2018 16:36

Вы хотите, чтобы он использовал аргументы методов экземпляра для определения типа приемник. Насколько я понимаю (что может быть неполным; как вы можете видеть, спецификация очень сложная), это запрещено.

Alexey Romanov 23.05.2018 16:44

Вывод универсального типа может завершиться ошибкой, если код слишком сложен, но обычно вы можете явно указать типы, чтобы в выводе не было необходимости. Иногда это может быть вызвано связанными вызовами методов. Это просто означает, что вы теряете возможность не указывать тип самостоятельно.

Универсальный тип проверка - это отдельное понятие. Как правило, средство проверки не видит разницы между предполагаемым типом (<> выводится как <String, String>) и явным типом (<String, String>, записанным в коде), в любом случае он проверяет, что с этим объектом используются только строки. Пока компилятор не жалуется, ваш класс должен быть типобезопасным.

«но вы всегда можете явно указать типы, так что вывод не нужен» Не всегда предполагаемые типы могут быть безымянными (например, анонимный класс или захват подстановочных знаков).

Alexey Romanov 23.05.2018 21:12

Другие вопросы по теме