Как перейти от подстановочного типа к параметризованному типу? - PullRequest
0 голосов
/ 21 мая 2018

Скажем, есть тип фреймворка Row с несколькими параметрами типа и метод, который работает с экземплярами типа Row и использует все эти параметры типа.

У меня есть метод, который работает слюбой тип Row, даже разные типы одновременно, поэтому, очевидно, я использую подстановочный тип Row<?,?>.Вопрос в том, как мне вызвать метод, который принимает Row<R,K> с Row<?,?>?

Моя точка зрения: я точно не знаю, что такое тип Row<?,?>, но это, безусловно, какой-товроде Row хорошо.И когда универсальный метод принимает Row<R,K>, это означает, что он хочет что-то сделать с R и K, но в противном случае он может иметь дело с любым типом Row.Так что мой «любой» тип должен работать с методом, который принимает «любой» тип, верно?

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

package foo;

public class Experiment {
  // Defined by a framework.
  interface Key<K extends Key<K>> {}

  interface Row<R extends Row<R, K>, K extends Key<K>> {}

  static <R extends Row<R, K>, K extends Key<K>> R copyRow(R row) {
    return row;
  }

  // My experiments below.
  static class Wrapper<R extends Row<R, K>, K extends Key<K>> {
    public final R row = null; // fixme
    public final Class<R> clazz = null; // fixme
  }

  static <R extends Row<R, K>, K extends Key<K>> R upcast(Row<?, ?> row) {
    return (R) row;
  }

  static <R extends Row<R, K>, K extends Key<K>> R upcast(Row<?, ?> row, Class<R> clazz) {
    assert row.getClass().equals(clazz);
    return (R) row;
  }

  public static void main(String[] args) {
    Wrapper<?, ?> wr = null; // fixme
    copyRow(wr.row); // Compilation error
    copyRow(upcast(wr.row)); // Compilation error
    copyRow(upcast(wr.row, wr.clazz)); // This works, why?
  }
}

(Вы можете отправить этот пример прямо в javac, чтобы посмотреть, что произойдет. С Java 1.8: https://pastebin.com/LB10ySsD)

1 Ответ

0 голосов
/ 21 мая 2018

Вот одна возможность:

public class WildcardsExperiment {
  // Defined by a framework.  <begin unmodifiable>
  interface Key<K extends Key<K>> {}
  interface Row<R extends Row<R, K>, K extends Key<K>> {}

  static <R extends Row<R, K>, K extends Key<K>> R copyRow(R row) {
    return row;
  }
  // <end unmodifiable>

  interface NaturalRowTransformer {
    <R extends Row<R, K>, K extends Key<K>> R apply(R r);
  }

  class Wrapper<R extends Row<R, K>, K extends Key<K>> {
    private final R row = null; // fixme (constructor etc.)
    public final Class<R> clazz = null; // fixme

    R invokeNat(NaturalRowTransformer nat) {
      return nat.apply(row);
    }
  }

  static final NaturalRowTransformer CopyRow = new NaturalRowTransformer() {
    public <R extends Row<R, K>, K extends Key<K>> R apply(R row) {
      return copyRow(row);
    }
  };

  public static void main(String[] args) {
    Wrapper<?, ?> wr = null; // fixme
    wr.invokeNat(CopyRow); // compiles
  }
}

По сути, метод copyRow заключен в посетителя NaturalRowTransformer, что гарантирует, что он может обрабатывать все возможные допустимые комбинации пар F-ограниченного типа R и K.Затем Wrapper предоставляет метод принятия invokeNat, который принимает посетителя и выполняет операцию copyRow в области, где R и K являются конкретными (не подстановочными знаками).

Хитростьвдохновлен теорией категорий (отсюда и название «естественное» в названии) и импортирован из Scala, даже несмотря на то, что современные реализации сопоставления с образцами в Scala для типов допускают более сжатые решения.Известно, что это решение работает при немного более сложных ограничениях .

...