Подтвердить что ты не робот

Тип псевдонимов для Java-дженериков

У меня довольно сложный набор общих классов в Java. Например, у меня есть интерфейс

interface Doable<X,Y> {
  X doIt(Y y);
}

и реализация

class DoableImpl implements Doable<Foo<Bar<Baz,Qux>>,Foo<Bar<Zot,Qux>>> {
  Foo<Bar<Baz,Qux>> doIt(Foo<Bar<Zot,Qux>> fooBZQ) { ... }
}

В реальной реализации Doable имеет довольно много методов, поэтому Foo<Bar<Baz,Qux>> и т.д. появляются снова и снова. (Верьте или нет, общие типы довольно болезненны, чем это. Я упростил их для примера.)

Я хотел бы упростить их, чтобы сохранить себя набрав и облегчить напряжение на моих глазах. Я хотел бы иметь простой "псевдоним типа" для Foo<Bar<Baz,Qux>> и т.д., Например FooBBQ и FooBZQ.

Моя нынешняя идея - определить классы-оболочки:

class FooBBQ { 
  public static FooBBQ valueOf(Foo<Bar<Baz,Qux>> fooBBQ) { 
    return new FooBBQ(fooBBQ); 
  }
  private Foo<Bar<Baz,Qux>> fooBBQ;
  private FooBBQ(Foo<Bar<Baz,Qux>> fooBBQ) { 
    this.fooBBQ = fooBBQ; 
  }
  public Foo<Bar<Baz,Qux>> toGeneric() {
    return fooBBQ;
  }
}

class FooBZQ { /* pretty much the same... */ }

class DoableImpl implements Doable<FooBBQ,FooBZQ> { 
  FooBBQ doIt(FooBZQ fooBZQ) { ... }
}

Это хорошо работает, но имеет несколько недостатков:

  • Нам нужно определить отдельные обертки для каждого общего экземпляра. Классы-оболочки являются короткими и стилизованными, но я не могу определить способ их макроопределения.
  • У нас есть служебные данные перевода (концептуально, если не оперативно) вызова valueOf и toGeneric для преобразования между FooBBQ и Foo<Bar<Baz,Qux>>. Например, если doIt вызывает некоторую библиотечную процедуру, которая ожидает Foo<Bar<Zot,Qux>> (что делает реальная реализация), мы получаем что-то вроде

    return FooBBQ.valueOf( libraryCall( fooBZQ.toGeneric() ) )
    

    где мы первоначально имели бы

    return libraryCall(fooBZQ);
    

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

[UPDATE] ОК, я запрещаю дальнейшие ответы "не делай этого". Возьмите его как данность, что Foo<Bar<Baz,Qux>> имеет истинную ценность в моей проблемной области (Пит Киркхам может быть прав, что у него достаточно значения, чтобы получить надлежащий класс-оболочку с описательным именем). Но это проблема программирования; не пытайтесь определить проблему.

4b9b3361

Ответ 1

Если вам нужна полная безопасность типов, я не думаю, что вы можете сделать лучше без каких-либо классов-оболочек. Но почему бы не сделать эти классы наследовать/реализовывать исходные общие версии, например:

public class FooBBQ extends Foo<Bar<Baz,Qux>> {
...
}

Это устраняет необходимость в методе toGeneric(), и, на мой взгляд, более понятно, что это просто псевдоним типа. Кроме того, общий тип может быть переведен в FooBBQ без предупреждения компилятора. Мое личное предпочтение заключалось бы в том, чтобы сделать интерфейсы Foo, Bar, Baz..., если это возможно, даже если в реализации произойдет некоторое дублирование кода.

Теперь, не зная конкретную проблемную область, трудно сказать, нужно ли вам сказать FooBBQ, как в вашем примере, или, возможно,

public class FooBar<X, Y> extends Foo<Bar<X, Y>> {
...
}

С другой стороны, подумали ли вы о простой настройке компилятора Java, чтобы не показывать некоторые общие предупреждения, и просто опустить части общего определения? Или используйте стратегически размещенную @SuppressWarnings("unchecked")? Другими словами, вы можете сделать DoableImpl только "частично обобщенным":

class DoableImpl implements Doable<Foo<Bar>>,Foo<Bar>> {
    Foo<Bar> doIt(Foo<Bar> foobar) { ... } 
}

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

Ответ 2

Scala имеет приятную поддержку псевдонимов типов. Например:

type FooBBQ = Foo[Bar[Baz,Qux]]

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

Ответ 3

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

Скорее всего. Нужно ли специализировать "Doit" в 8 измерениях?

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

Ответ 4

Ну, у Java нет псевдонимов типов, поэтому вам не повезло. Однако псевдонимы типов иногда могут быть заменены переменными типа! Таким образом, мы решаем проблему слишком большого числа генериков с еще большим количеством дженериков!

Как вы лишили весь контент из своего примера, я не могу догадаться, где или имеет смысл вводить дополнительные переменные типа, но здесь есть одно возможное разложение:

class DoableImplFoo<A,B> implements Doable<Foo<A>,Foo<B>> {
  public DoableImplFoo(SomePropertyOf<A,B> aAndBAreGoodEnough) { ... }
  Foo<A> doIt(Foo<B> fooB) { ... }
}

Когда вы создаете экземпляр A до Bar<Baz,Qux> и B до Bar<Zot,Qux> позже, вы можете обнаружить, что есть еще один шаблон, но он может быть меньше, чем вы изначально имели.

Ответ 5

Я бы сказал, что да, вам нужно переосмыслить проблему. Объявление

 class DoableImpl implements Doable<Foo<Bar<Baz,Qux>>,Foo<Bar<Zot,Qux>>> {
    Foo<Bar<Baz,Qux>> doIt(Foo<Bar<Zot,Qux>> fooBZQ) { ... } 
 }

- довольно очевидный случай использования дженериков.