Скрытие параметра типа “local” в 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
...
}
Решение
Ваша проблема похожа на ту, которую решает "помощник по захвату", но я не уверен, что это может быть применено к вашему второму примеру, где используются два отдельных метода.Твой первый 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();
}
}
Почему бы не создать трехуровневую иерархию:
abstract class Foo
abstract class FooImplBase<T> extends Foo
class Bar extends FooImplBase<String>
Клиенты знают только о Foo
, который не содержит никаких общих методов.Введите любые общие методы, которые вам нужны в FooImplBase<T>
и затем конкретный класс является производным от него.
Итак, в вашем примере startStuff()
и endStuff()
было бы абстрактно в Foo
и реализован в FooImplBase<T>
.Похоже ли это на то, что это могло бы сработать в вашей реальной ситуации?Я согласен, это немного громоздко.
Вы определяете класс Bar.Верны две вещи...
1) В Bar нет параметрического типа, задействованного.То есть члены foo и t имеют один тип, скажем U, который фиксирован для определения.Если вам передан Foo, который вы ожидаете присвоить foo, это должен быть Foo<U>.Если все это правда, то да, это не часть общедоступного интерфейса - все имеет определенный тип.Тогда я не уверен, что вы подразумеваете под "количественной оценкой", поскольку свободных переменных типа не существует.Если вы имеете в виду универсальную количественную оценку, как вы примиряете тот факт, что Bar не имеет параметрической типизации, поэтому каждому из его членов должен быть присвоен конкретный тип?
2) В Bar задействован параметрический тип.Это может быть неочевидно в общедоступном интерфейсе, но, возможно, вы передаете Foo<T> итак, вы хотите, чтобы класс Bar создавался с более чем одним типом.Затем, как указано, это находится в общедоступном интерфейсе, и вам нужно сделать Bar параметрическим в T с помощью generics.Это дает вам некоторую форму универсальной количественной оценки для определения: "Для всех типов T это определение верно".
Где-то должно быть решено, какой тип вы хотите использовать для 'T' внутри класса Bar.Таким образом, либо вы должны выбрать его в определении Bar (заменив Foo на Foo внутри определения класса), либо вы оставляете это на усмотрение клиента класса 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;
...
}