Вопрос

Предположим, я использую интерфейс с параметром универсального типа

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;

  ... 
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top