Введите псевдонимы для обобщений Java - PullRequest
29 голосов
/ 26 марта 2009

У меня довольно сложный набор универсальных классов в 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) { ... }
}

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

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

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

    где бы мы изначально имели

    return libraryCall(fooBZQ);
    

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

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

Ответы [ 5 ]

10 голосов
/ 26 марта 2009

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

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) { ... } 
}

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

5 голосов
/ 12 октября 2011

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

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

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

2 голосов
/ 26 марта 2009

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

Очень вероятно. Нужно ли специализировать «Doit» в 8 измерениях?

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

1 голос
/ 30 мая 2013

Ну, у 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> позже, вы можете обнаружить, что снова есть какой-то шаблон, но он может быть меньше, чем у вас изначально.

0 голосов
/ 26 марта 2009

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

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

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

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