Есть ли лучший способ работы с Wildcards в Java? - PullRequest
2 голосов
/ 25 марта 2020

Рассмотрим следующий java заголовок конструктора ...

public FirstPair(Map<Enum, Set<Enum>> f, List<Set<Enum>> fr) 

Теперь я хотел сделать это "безопасным в будущем", добавив немного места для генериков. Например, что если я хочу передать Map<Enum, HashSet<Enum>> для f.

... Итак, я изменил конструктор на этот ...

public FirstPair(Map<? extends Enum, ? extends Set<? extends Enum>> f,
                         List<? extends Set<? extends Enum>> fr) 

Это выглядит правильно? Есть ли легче объяснить различные типы ввода? Или это лучший способ?

**** НЕКОТОРЫЕ БОЛЬШЕ ДЕТАЛЕЙ ****

Рассмотрим следующую функцию.

static void foo(List<Set<Integer>> listOfSetsOfInts)

Следующий код вызовет ошибка ...

List<HashSet<Integer>> diffSetType = new ArrayList<HashSet<Integer>>();
foo(diffSetType);

Причина в том, что список HashSets не соответствует списку наборов. Я хочу справиться с этой ошибкой, изменив заголовок функции, а не оставляя бремя для вызывающего функции ...

Единственный способ решить эту ошибку - изменить заголовок функции на этот. ..

static void foo(List<? extends Set<Integer>> listOfSetsOfInts)
// of even more secure...
static void foo(List<? extends Set<? extends Integer>> listOfSetsOfInts)

Мой вопрос: правильно ли использовать здесь java символы подстановки? Я злоупотребляю ими? Есть ли чёткое исправление, которое я пропускаю?

Ответы [ 2 ]

1 голос
/ 25 марта 2020

Проблема заключается в использовании конструктора, который (естественно) имеет очень специфический тип c, который затем несовместим с типом переменной, в которой вы пытаетесь его сохранить. Чтобы сделать это объяснение немного глубже в веселье и волнение подстановочных знаков типа Java, я сделал параметр типа, а не ваш Enum. (Все это использует Java var, чтобы сделать код немного чище. Если вы используете Java11, этот код будет хорошо компилироваться, за исключением ошибок типа и других предупреждений, которые я обсуждаю ниже. Кроме того, в вашем исходном коде есть проблема с принципом подстановки Лискова. Я пока оставлю это в покое и исправлю это ниже (4).

public interface Foo {
  class A { }
  class B extends A {}
  class C extends B {}

  class FooMap<T> {
    Map<? extends T, ? extends Set<? extends T>> f; // (1)
    List<? extends Set<? extends T>> fr;

    public FooMap(
        Map<? extends T, ? extends Set<? extends T>> f,
        List<? extends Set<? extends T>> fr) {
      this.f = f;
      this.fr = fr;
    }

    static void exercise1() {
      var hm = new HashMap<B, HashSet<B>>();
      var list = new ArrayList<HashSet<B>>(); // (2)
      var fm = new FooMap<A>(hm, list);
    }

    static void foo(
        List<? extends Set<? extends Integer>> listOfSetsOfInts) { }

    static void exercise2() {
      var diffSetType = new ArrayList<HashSet<Integer>>(); // (3)
      foo(diffSetType);
    }
  }
}

Комментарии:

(1) Также возможно избавиться от этих групповых символов. Вот некоторый альтернативный код, который скомпилирует , но вы получите предупреждения о непроверенных типах передач. Предполагая, что вы рассматриваете карту и список как прочитанные только тогда вы могли бы заменить приведенный ниже код. (Примечание: Принцип подстановки Лискова может кое-что сказать по этому поводу; см. (4) ниже.) Как только вы разрешите мутацию, все станет сложнее.

    Map<T, Set<T>> f;
    List<Set<T>> fr;

    public FooMap(
        Map<? extends T, ? extends Set<? extends T>> f,
        List<? extends Set<? extends T>> fr) {
      this.f = (Map<T, Set<T>>) f;
      this.fr = (List<Set<T>>) fr;
    }

(2) Конкретный тип list - это ArrayList<HashSet<B>>, но мы хотим использовать его, как если бы он был List<Set<A>>, что разрешено в подстановочных знаках конструктора FooMap. Обратите внимание, что мы проходишь явный т Введите в конструктор параметр ype, чтобы сказать, что это FooMap<A>.

(3) Конкретный тип diffSetType равен ArrayList<HashSet<Integer>>, что совместимо с типом аргумента foo().

(4) Вам нужно немного подумать о параметре первого типа HashMap, так как мы хотим удовлетворить принцип замены Лискова, что означает удовлетворение «производитель расширяет, потребитель супер» (PECS Правило Первый параметр Map является типом «потребителя», поэтому мы действительно должны написать такой код:

  class FooMap<T> {
    Map<T, Set<T>> f;
    List<Set<T>> fr;

    public FooMap(
        Map<? super T, ? extends Set<? extends T>> f,
        List<? extends Set<? extends T>> fr) {
      this.f = (Map<T, Set<T>>) f;
      this.fr = (List<Set<T>>) fr;
    }

    static void exercise1() {
      var hmA = new HashMap<A, HashSet<B>>();
      var hmB = new HashMap<B, HashSet<B>>();
      var hmC = new HashMap<C, HashSet<B>>();
      var list = new ArrayList<HashSet<B>>();
      var fm1 = new FooMap<A>(hmA, list); // okay
      var fm2 = new FooMap<B>(hmA, list); // okay
      var fm3 = new FooMap<C>(hmA, list); // type error (5)
      var fm4 = new FooMap<B>(hmB, list); // okay
      var fm5 = new FooMap<B>(hmC, list); // type error (6)
      var fm6 = new FooMap<C>(hmC, list); // type error (7)
    }

(5) HashSet<B> из list не соответствует ? extends Set<? extends C>

(6) HashMap<C, HashSet<B>> из hmC не соответствует Map<? super B, ? extends Set<? extends B>>, поскольку C не соответствует ? super B.

(7) hmC is HashMap<C, HashSet<B>>; HashSet<B> не соответствует ? extends Set<? extends C>

0 голосов
/ 25 марта 2020

Для вашего первоначального примера вам пока не нужно go для поддержки того, что вы намереваетесь поддерживать.

У вас есть:

public FirstPair(Map<Enum, Set<Enum>> f, List<Set<Enum>> fr)

Но Map и Set являются интерфейсами, поэтому, если вы намереваетесь использовать это с HashSet или HashMap, вы можете это сделать, потому что это классы, которые реализуют интерфейс Set и Map соответственно, и, следовательно, придерживайтесь и уважайте подпись, которую вы определили для своего конструктора.

Если вы думаете с точки зрения интерфейсов, общих для сущностей, которые вы хотите поддерживать, это должно упростить вашу жизнь, а также подпись конструктора и / или методов / функции, которые вы определяете. Если вы задумаетесь об этом, как правило, если вы хотите разрешить взаимозаменяемое использование других типов, то это потому, что вы можете обращаться с ними аналогичным образом, в контексте функциональности, которую вы делаете доступной, поэтому интерфейсы имеют тенденцию разрешать это легко для вас сразу, и если такого интерфейса нет из того, что вы хотите сделать, создайте его для этой цели. Скорее всего, будет проще документировать, читать и поддерживать в долгосрочной перспективе. Я надеюсь, что это помогает.

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