Передача аргумента, который может быть нескольких типов для метода в Java? - PullRequest
0 голосов
/ 20 ноября 2018

Я использую библиотеку с методом foo с такой подписью:

TypeR foo(TypeA first, int second)
TypeR foo(TypeB first, int second)
TypeR foo(TypeC first, int second)

Я пишу свой собственный метод bar, вызывающий foo, и я хочу напрямую передатьПервый параметр наряду с foo:

bar(??? first, int baz) {
    int second = someCalculation(baz);
    return foo(first, second);
}

Моя проблема в том, что я не знаю, какой тип я должен дать параметру first в bar.Похоже, в Java не было объединений типов.Как я могу решить эту проблему без необходимости писать три почти идентичные версии bar?

Типы TypeA - TypeC не имеют общего интерфейса, и я не контролирую их или foo так как они находятся во внешней библиотеке.Все, что я могу контролировать - это то, как я реализую bar.

Ответы [ 6 ]

0 голосов
/ 21 ноября 2018

Мы можем заменить вызов foo в bar вызовом соответствующего BiFunction.Таким образом, для всех перегруженных foo методов должен быть определен BiFunction.

  private static final Map<Class<?>, BiFunction<?, Integer, TypeR>> FOO = Map.of(
    TypeA.class, (first, second) -> foo((TypeA) first, second),
    TypeB.class, (first, second) -> foo((TypeB) first, second),
    TypeC.class, (first, second) -> foo((TypeC) first, second));

Но для каждого перегруженного foo отображения должна быть написана только одна строка, тогда как для каждого перегруженного bar метода потребуетсячетыре строки, если они написаны как в вопросе.

Теперь мы можем сделать bar родовым.Но следующее не будет работать, так как возвращенный BiFunction имеет несвязанный подстановочный знак в качестве параметра первого типа.

  <T> TypeR bar(T first, int baz) {
    int second = someCalculation(baz);
    return FOO.get(first.getClass()).apply(first, second);
  }

Нам нужно было бы объявить FOO как Map<Class<T>, BiFunction<T, Integer, TypeR>>, но это невозможно.Чтобы обойти это, мы определяем метод с параметром типа T, который должен установить равенство отсутствующего типа.Но это не бесплатно.Это происходит за счет предупреждения, полученного в результате приведения:

  private static <T> BiFunction<T, Integer, TypeR> fooOf(Object o) {
    return (BiFunction<T, Integer, TypeR>) FOO.get(o.getClass());
  }

Теперь мы можем использовать этот метод в bar:

  <T> TypeR bar(T first, int baz) {
    int second = someCalculation(baz);
    return fooOf(first).apply(first, second);
  }

Это похоже на подход instanceof, носмещает различие типов от метода bar к Map FOO.Если есть другие методы, кроме bar, которые также должны вызывать foo аналогичным образом, различие типов больше не кодируется.FOO и fooOf можно использовать повторно.Если библиотека изменяется и вводится дополнительная перегруженная TypeR foo(TypeD first, int second), необходимо обновить только FOO, добавив одну дополнительную строку.

Этот подход требует, чтобы first не было null.

0 голосов
/ 20 ноября 2018

Я бы попытался избежать этой проблемы, разложив вашу функцию bar() на часть, которая занимается вычислением int second, и часть, которая имеет дело с TypeA / B / C first.Таким образом, вы могли бы написать что-то вроде

int second = decomposedPortion(baz);
TypeR result = foo(first, second);
0 голосов
/ 20 ноября 2018

В теории ничто не мешает вам использовать классы типов для специального полиморфизма в Java:

interface Fooable<T> {
    int foo(T t, int second);
}

public int bar<T>(T t, int baz, Fooable<T> fooable) {
    int second = someCalculation(baz);
    return fooable.foo(t, second);
}

Реализации будут выглядеть примерно следующим образом:

public class TypeAFooable implements Fooable<TypeA> {
    public int foo(TypeA t, int second) { return yourLibrary.foo(t, second); }
}

И вызовы будут выглядеть так:

bar(myTypeAThing, 1234, new TypeAFooable());

Если бы bar было бы немного длиннее, это принесло бы несколько преимуществ:

  • , если bar делает много вызововfoo во многих разных местах, тогда вы можете определить каждый экземпляр класса типов один раз , вместо нескольких if-instanceof -ветвлений внутри bar
  • , если позже захотитечтобы заставить его работать с TypeD, TypeE, ..., TypeZ, вам не нужно копаться в коде bar, вы можете просто предоставить еще одну реализацию класса типов
  • moreважно: если кто-то еще хотел бы использовать его с TypeD, TypeE и т. д., то они могли бы предоставить еще одну реализацию класса типов, без необходимости разбирать ваш barреализация.

На практике проблема заключается в том, чтоЭкземпляры классов типов не будут автоматически предоставляться компилятором, поэтому обычно это не стоит хлопот.Если bar не является каким-то гигантским и сверхсложным, просто перегрузите его или используйте instanceof.

0 голосов
/ 20 ноября 2018

Самое близкое, что я могу придумать, это что-то вроде этого (удаление материала, относящегося к second, поскольку он не имеет отношения к делу).

TypeR bar(Object first) {
  TypeR retvalue;
  if (first instanceof TypeA)
    retvalue = foo( (TypeA)first );
  else if (first instanceof TypeB)
    retvalue = foo( (TypeB)first );
  else if (first instanceof TypeC)
    retvalue = foo( (TypeC)first );
  return retvalue;
}

Если есть более конкретный известный супертип типа Aи т. д., очевидно, вы могли бы использовать это вместо Object в качестве параметра.

Это возможно, только если все три версии foo имеют одинаковый тип возвращаемого значения.

К сожалению, это«Решение» на самом деле не лучше, а может и хуже, чем написание трех перегруженных версий bar.

0 голосов
/ 20 ноября 2018

Вы можете определить сначала как универсальный тип тип в баре, как показано ниже.

bar(Object first, int baz) {
int second = someCalculation(baz);
if(first instanceOf TypeA) return foo((TypeA) first, second);
else if(first instanceOf TypeB) return foo((TypeB) first, second);
else if(first instanceOf TypeC) return foo((TypeC) first, second);
else throw new CustomException("Object pass in is not of correct type");
}
0 голосов
/ 20 ноября 2018

Используйте интерфейсы, подобные этому:

interface Type { //...}

class TypeA implements Type { //...}

class TypeB implements Type { //...}

class TypeC implements Type { //...}

, тогда вам потребуется только одна функция foo

foo(Type first, int second)

и одна функция бара

bar(Type first, int baz)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...