Как перебрать универсальный шаблон? - PullRequest
8 голосов
/ 08 июня 2011

Как я могу перебрать универсальный шаблон? В основном я хотел бы указать следующий метод:

private <T extends Fact> void iterateFacts(FactManager<T> factManager) {
    for (T fact : factManager) {
        factManager.doSomething(fact);
    }
}

Если этот код находится в отдельном методе, как показано, он работает, потому что общий контекст метода позволяет определить тип подстановочного знака (здесь T), по которому можно выполнять итерацию. Если кто-то пытается встроить этот метод, контекст метода исчезает, и он больше не может перебирать тип шаблона. Даже сделать это автоматически в Eclipse не удается с помощью следующего (некомпилируемого) кода:

...
for (FactManager<?> factManager : factManagers) {
    ...
    for ( fact : factManager) {
        factManager.doSomething(fact);
    }
    ...
}
...

У меня простой вопрос: есть ли способ поместить какой-нибудь подстановочный знак, который можно перебрать, или это ограничение универсальных выражений (то есть это невозможно)?

Ответы [ 5 ]

5 голосов
/ 08 июня 2011

Нет.В подобной ситуации, обходной путь должен создать вспомогательный метод.

JLS имеет этот пример http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#5.1.10

public static void reverse(List<?> list) { rev(list);}
private static <T> void rev(List<T> list) { ... }

Проблема в том, что у нас есть объект List<?>.Мы знаем, что это должно быть List<X> некоторого X, и мы хотели бы написать код, используя X.Внутренне компилятор преобразует подстановочный знак в переменную типа X, но язык Java не предлагает программистам прямого доступа к нему.Но если есть метод, принимающий List<T>, мы можем передать объект в метод.Компилятор делает вывод, что T=X и вызов хороший.

Если нет стирания типов, X может быть известно во время выполнения, тогда Java определенно даст нам способ доступа к X.Однако на сегодняшний день, поскольку X недоступен во время выполнения, особого смысла нет.Можно предложить чисто синтетический способ, который вряд ли будет проще, чем обходной путь вспомогательного метода.

3 голосов
/ 08 июня 2011

Параметры типа могут быть определены только для

  • типы (т.е. классы / интерфейсы),
  • методов и
  • Конструкторы.

Вам потребуется параметр типа для локального блока, что невозможно.

Да, я тоже иногда что-то пропускал.

Но на самом деле здесь нет проблемы с отсутствием встроенного метода - если он представляет узкое место в производительности, где помогает вставка, Hotspot снова его встроит (не заботясь о типе).

Кроме того, наличие отдельного метода позволяет дать ему описательное имя.


Просто идея, если вам это нужно часто:

interface DoWithFM {
   void <T> run(FactManager<T> t);
}

...
for (FactManager<?> factManager : factManagers) {
    ...
    new DoWithFM() { public <T> run(FactManager<T> factManager) {
        for (T fact : factManager) {
            factManager.doSomething(fact);
        }
    }.run(factManager);
    ...
}
...
2 голосов
/ 08 июня 2011

Ну, я могу придумать способ сделать это, используя внутренние классы, потому что внутренний класс разделяет параметр типа со своим включающим типом. Кроме того, даже с использованием подстановочных знаков вы все равно можете обрабатывать свои коллекции благодаря преобразованию захвата с подстановочными знаками.

Позвольте мне создать пример. Этот код компилируется и работает нормально. Но я не могу быть уверен, что использование внутренних классов будет проблемой для вас.

//as you can see type parameter belongs to the enclosing class
public class FactManager<T> implements Iterable<FactManager<T>.Fact> {

    private Collection<Fact> items = new ArrayList<Fact>();

    public void doSomething(Fact fact) {
        System.out.println(fact.getValue());
    }

    public void addFact(T value) {
        this.items.add(new Fact(value));
    }

    @Override
    public Iterator<Fact> iterator() {
        return items.iterator();
    }


    public class Fact {
        //inner class share its enclosing class type parameter
        private T value;

        public Fact(T value) {
            this.value = value;
        }

        public T getValue() {
            return this.value;
        }

        public void setValue(T value) {
            this.value = value;
        }
    }

    public static void main(String[] args) {
        List<FactManager<String>> factManagers = new ArrayList<FactManager<String>>();

        factManagers.add(new FactManager<String>());
        factManagers.get(0).addFact("Obi-wan");
        factManagers.get(0).addFact("Skywalker");

        for(FactManager<? extends CharSequence> factManager : factManagers){
            //process thanks to wildcard capture conversion
            procesFactManager(factManager);
        }
    }

    //Wildcard capture conversion can be used to process wildcard-based collections
    public static <T> void procesFactManager(FactManager<T> factManager){
        for(FactManager<T>.Fact fact : factManager){
            factManager.doSomething(fact);
        }
    }
}
2 голосов
/ 08 июня 2011

Вы всегда можете вернуться к Object

for (FactManager<?> factManager : factManagers) {
    ...
    for ( Object fact : factManager) {
        factManager.doSomething(fact);
    }
    ...
}

Это, конечно, зависит от того, что является фактическим объявлением doSomething.

Если doSomething объявлено как void doSomething( T fact ), тогда вы можете использовать необработанный тип и проглотить unchecked предупреждения. Если вы можете гарантировать, что в FactManager может быть вставлен только однородный Facts, то это может быть хорошим решением.

for (FactManager factManager : factManagers) { // unchecked warning on this line
    ...
    for ( Object fact : factManager) {
        factManager.doSomething(fact);
    }
    ...
}
0 голосов
/ 08 июня 2011

Это более точно соответствует методу, который вы определили (то есть, если вы можете вызывать iterateFacts () с FactManager в factManager, вы знаете, что FactManager содержит элементы, которые являются некоторым подклассом Fact).

for (FactManager<? extends Fact> factManager : factManagers) {
    for (Fact fact : factManager) {
          factManager.doSomething(fact);
    }
}

Однако я склонен думать, что вы объявите FactManager как универсальный для подтипов Fact (только с указанием имени класса), например,

class FactManager<T extends Fact> implements Iterable<T> {
     ...
}

Рефакторинг Eclipse завершается неудачно, потому что онне может определить тип объекта, содержащийся в FactManager<?>.

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