Скрытие параметра «локального» типа в Java - PullRequest
8 голосов
/ 31 марта 2009

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

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 ]

6 голосов
/ 31 марта 2009

Чтобы быть полезным, в какой-то момент вы собираетесь установить поле 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();
    }
}
4 голосов
/ 31 марта 2009

Ваша проблема похожа на проблему, решаемую «помощником захвата», , но я не уверен, что ее можно применить ко второму примеру, где используются два отдельных метода. Ваш первый 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);
    }
  }
}
1 голос
/ 31 марта 2009

Вы определяете класс Bar. Две вещи верны ...

1) В Bar нет параметрического типа. То есть члены foo и t имеют единственный тип, скажем U, который является фиксированным для определения. Если вы передали Foo, который вы ожидаете назначить для foo, это должен быть Foo . Если это все правда, то да, это не часть публичного интерфейса - все имеет определенный тип. Кроме того, я не уверен, что вы подразумеваете под "количественной оценкой", поскольку нет свободных переменных типа. Если вы имеете в виду универсальное количественное определение, как вы согласуете тот факт, что у Bar нет параметрической типизации, поэтому, должно быть, был задан конкретный тип для каждого из его членов?

2) В Bar есть параметрический тип. Это может быть явно не в общедоступном интерфейсе, но, возможно, вы передаете Foo , поэтому вы хотите, чтобы класс Bar создавался с несколькими типами. Тогда, как уже говорилось, это в общедоступном интерфейсе, и вам нужно сделать параметрический Bar в T с помощью обобщений. Это дает некоторую форму универсального количественного определения для определения «Для всех типов T это определение верно».

1 голос
/ 31 марта 2009

Почему бы не иметь трехуровневую иерархию:

abstract class Foo

abstract class FooImplBase<T> extends Foo

class Bar extends FooImplBase<String>

Клиенты знают только о Foo, который не содержит общих методов. Введите все необходимые вам обобщенные методы в FooImplBase<T>, и тогда конкретный класс будет производным от него.

Так что в вашем примере startStuff() и endStuff() были бы абстрактными в Foo и реализованы в FooImplBase<T>. Похоже, это может работать в вашей реальной ситуации? Я согласен, это немного громоздко.

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

Если вы действительно хотите нарисовать какую-то ярость, вы можете поместить класс Bar в интерфейс Foo и сосать T таким образом. См. эту статью для получения дополнительной информации о классах внутри интерфейсов. Может быть, это тот случай, когда это имеет смысл?

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);
    }
  }
}
0 голосов
/ 31 марта 2009

Где-то должно быть решено, какой тип вы хотите использовать для 'T' внутри класса Bar. Так что либо вы должны выбрать его в определении Bar (заменив Foo на Foo внутри определения класса), либо оставить его клиенту класса Bar: в этом случае Bar должен быть универсальным.

Если вы хотите, чтобы интерфейс Bar не полагался на T и , чтобы можно было выбирать различные типы для T, вам следует использовать неуниверсальный интерфейс или абстрактный базовый класс, как в:

interface Bar {
  void startStuff();
  // ...
}

class BarImplement<T> {
  private Foo<T> foo;
  // ...
}
0 голосов
/ 31 марта 2009

Параметр T в этом случае становится бесполезным для Bar, поскольку он будет удален в Object во время компиляции. Таким образом, вы могли бы также «избавить себя от неприятностей» и выполнить удаление пораньше:

public class Bar {

    private Foo<Object> foo;
    private Object t;

  ... 
}
...