Скрытие параметра "локального" типа в Java
Предположим, я использую интерфейс с параметром универсального типа
interface Foo<T> {
T getOne();
void useOne(T t);
}
Предполагается, что тип T
является абстрактным: он обеспечивает ограничение типа для реализаций Foo
, но клиентский код не волнует, что именно T
является.
Это не проблема в контексте универсального метода:
public <T> void doStuff(Foo<T> foo) {
T t = foo.getOne();
/* do stuff */
foo.useOne(t);
}
Но предположим, что я хочу прекратить работу doStuff
, сохраняя состояние в классе Bar
, В этом случае мне, кажется, нужно добавить параметр типа Foo
в Bar
,
public class Bar<T> {
private Foo<T> foo;
private T t;
/* ... */
public void startStuff() {
t = foo.getOne();
}
public void finishStuff() {
foo.useOne(t);
}
}
Это немного странно, так как параметр типа T
не отображается в общедоступном интерфейсе Bar
(т.е. он не включен ни в один параметр метода или тип возвращаемого значения). Есть ли способ "количественно T
прочь "? Т.е. могу ли я договориться о параметре T
быть скрытым в интерфейсе Bar
как в следующем?
public class Bar {
<T> { // foo and t have to use the same T
private Foo<T> foo;
private T t;
} // T is out of scope
...
}
7 ответов
Ваша проблема похожа на проблему, решаемую "помощником захвата", но я не уверен, что ее можно применить ко второму примеру, где используются два отдельных метода. Ты первый doStuff
метод определенно может быть лучше написан как public void doStuff(Foo<?> foo)
, поскольку он работает независимо от Foo
параметр типа. Тогда будет полезен шаблон "помощник захвата".
Обновление: немного поработав, расширив идею помощника Гетца по захвату, я придумал это. Внутри это выглядит немного грязно; снаружи ты ничего не заподозришь.
public class Bar {
private final Helper<?> helper;
public Bar(Foo<?> foo) {
this.helper = Helper.create(foo);
}
public void startStuff() {
helper.startStuff();
}
public void finishStuff() {
helper.finishStuff();
}
private static class Helper<T> {
private final Foo<T> foo;
private T t;
private Helper(Foo<T> foo) {
this.foo = foo;
}
static <T> Helper<T> create(Foo<T> foo) {
return new Helper<T>(foo);
}
void startStuff() {
t = foo.getOne();
}
void finishStuff() {
foo.useOne(t);
}
}
}
Чтобы быть полезным, в какой-то момент вы собираетесь установить foo
поле. В этот момент вы должны знать (или быть в состоянии захватить) T
, Я бы предложил сделать это в конструкторе, и тогда это будет иметь смысл для Bar
иметь общий параметр. Вы даже можете использовать интерфейс, чтобы клиентский код не видел тип. Тем не менее, я предполагаю, что вы не примете мой совет и действительно хотите setFoo
, Так что просто добавьте точку к переключаемой реализации:
/* pp */ class final BarImpl<T> {
private final Foo<T> foo;
private T t;
BarImpl(Foo<T> foo) {
this.foo = foo;
}
public void startStuff() {
t = foo.getOne();
}
public void finishStuff() {
foo.useOne(t);
}
}
public final class Bar {
private BarImpl<?> impl;
/* ... */
// Need to capture this wildcard, because constructors suck (pre-JDK7?).
public void setFoo(Foo<?> foo) {
setFooImpl(foo);
}
private <T> void setFooImpl(Foo<T> foo) {
impl = new BarImpl<T>(foo);
}
public void startStuff() {
impl.startStuff();
}
public void finishStuff() {
impl.finishStuff();
}
}
Вы определяете класс Бар. Две вещи верны...
1) В Bar нет параметрического типа. То есть члены foo и t имеют единственный тип, скажем U, который является фиксированным для определения. Если вы передали Foo, который вы ожидаете назначить для foo, это должен быть Foo. Если это все правда, то да, это не часть публичного интерфейса - все имеет определенный тип. Кроме того, я не уверен, что вы подразумеваете под "количественной оценкой", поскольку нет свободных переменных типа. Если вы имеете в виду универсальное количественное определение, как вы согласуете тот факт, что у Bar нет параметрической типизации, поэтому должен быть задан конкретный тип для каждого из его членов?
2) В Bar есть параметрический тип. Это может быть явно не в общедоступном интерфейсе, но, возможно, вы передаете Foo
Почему бы не иметь трехуровневую иерархию:
abstract class Foo
abstract class FooImplBase<T> extends Foo
class Bar extends FooImplBase<String>
Клиенты знают только о Foo
, который не содержит общих методов. Введите любые общие методы, которые вам нужны FooImplBase<T>
и тогда конкретный класс проистекает из него.
Так в вашем примере startStuff()
а также endStuff()
будет абстрактным в Foo
и реализовано в FooImplBase<T>
, Похоже ли это, что это может работать в вашей реальной ситуации? Я согласен, это немного громоздко.
Где-то должно быть решено, какой тип вы хотите использовать для "T" внутри класса Bar. Так что либо вы должны выбрать его в определении Bar (заменив Foo на Foo внутри определения класса), либо оставить его клиенту класса Bar: в этом случае Bar должен быть универсальным.
Если вы хотите иметь интерфейс с Bar, не зависящий от T, и иметь возможность выбирать различные типы для T, вы должны использовать неуниверсальный интерфейс или абстрактный базовый класс, как в:
interface Bar {
void startStuff();
// ...
}
class BarImplement<T> {
private Foo<T> foo;
// ...
}
Если вы действительно хотите нарисовать какую-то ярость, вы можете поместить класс Bar в интерфейс Foo и использовать его таким образом. См. Эту статью для получения дополнительной информации о классах внутри интерфейсов. Может быть, это тот случай, когда это имеет смысл?
interface Foo<T> {
T getOne();
void useOne(T t);
public class Bar<T> {
private Foo<T> foo;
private T t;
public void startStuff() {
t = foo.getOne();
}
public void finishStuff() {
foo.useOne(t);
}
}
}
Параметр T в этом случае становится бесполезным для Bar, поскольку он будет удален в Object во время компиляции. Таким образом, вы могли бы также "избавить себя от неприятностей" и сделать удаление пораньше:
public class Bar {
private Foo<Object> foo;
private Object t;
...
}