Задержка привязки параметра типа второго уровня - PullRequest
3 голосов
/ 18 февраля 2012

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

Предположим, вы хотите создать интерфейс сумматора для классов, способных добавлять элементы в списки различных типов со следующим поведением:

// Can add items to any type of array list.
Adder<ArrayList> arrayListAdder = ...;
// Ok. Right list type and item types match.
arrayListAdder.add(new ArrayList<String>(), "test");
// Ok. Right list type and item types match.
arrayListAdder.add(new ArrayList<Integer>(), 3);
// Compile error. Item types do not match.
arrayListAdder.add(new ArrayList<Integer>(), "test");
// Compile error. Wrong list type although item types match.
arrayListAdder.add(new LinkedList<String>(), "test");

Другими словами, я хочу, чтобы интерфейс сказал:

В сумматоре для определенного типа списка есть метод add.Этот метод принимает два аргумента.Первый - это список этого конкретного типа с элементами типа T. Второй аргумент - это элемент типа T. Метод добавляет элемент в список.

Я пытался с разными решениями по линии:

interface Adder<L extends List<?>> {
    <T> void add(L<T> list, T t);
}

Но выражение L<T> недопустимо в своем контексте.Я получаю сообщение об ошибке «Тип« L »не имеет параметров типа».

Я не могу найти способ оставить параметр типа списка открытым до определения метода add.Есть ли способ указать этот интерфейс, или для этого требуются генерики высшего порядка или что-то еще, чего нет в Java?

Ответы [ 8 ]

4 голосов
/ 18 февраля 2012

К сожалению, Java Generics не поддерживает функциональность, которую вы ищете.Самое близкое, что вы можете получить, это запросить List в методе, а не использовать тип L. Например:

interface Adder
{
    <T> void add(List<T> list, T t);
}

Это не то, что вы ищете, поэтому следующая ближайшая вещь будетпереместить объявление List в тело метода, однако это также неверно:

interface Adder
{
    <T, L extends List<T>> void add(List<T> list, T t);
}

Проблема в том, что вы пытаетесь назначить универсальный тип произвольному типу (L), в то время как L может не генерироватьсянесмотря на принуждение L extends List<?>.Нет хорошего способа форсировать проверку во время компиляции или во время выполнения типа списка.

3 голосов
/ 18 февраля 2012
interface Adder<T, L extends List<T>> {
    void add(L list, T t);
}

class ArrayListAdder implements Adder<String, ArrayList<String>> {
    @Override
    public void add(ArrayList<String> list, String t) {
        list.add(t);
    }
}

Я не думаю, что связывание T возможно в add время определения, поскольку T должно быть известно, чтобы объявить L с параметром типа (оно должно быть задано либо в связанной, либо в несвязанной форме ).

Я полагаю, вы ищете эквивалент этого кода C ++ 0x:

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

template <template <typename _ElementT, typename _AllocatorT> class CollectionT>
struct Adder {
    template <typename ElementT, typename AllocatorT>
    void add(CollectionT<ElementT,  AllocatorT> &collection, ElementT element);
};

struct VectorAdder : public Adder<std::vector> {
    template <typename ElementT, typename _Alloc>
    void add(std::vector<ElementT,  _Alloc> &vector, ElementT element) {
        vector.push_back(element);
    }
};

int main() {
    std::vector<int> vi;
    vi.push_back(1);

    std::vector<double> vd;
    vd.push_back(1.1);

    VectorAdder va;
    va.add(vi, 2); // instantiates VectorAdder::add<int, ...>
    va.add(vd, 2.2);  // instantiates VectorAdder::add<double, ...>

    for_each(vi.begin(), vi.end(), [](int x) { std::cout << x << ' '; });
    for_each(vd.begin(), vd.end(), [](double x) { std::cout << x << ' '; });
    return 0;
}

И я почти уверен, что в Java это невозможно.

1 голос
/ 18 февраля 2012

Будет ли это делать

import java.util.List;
import java.util.ArrayList;
interface Adder<K> {
    void add(List<K> l, K k1);
};

class IntegerListAdder implements Adder<Integer> {
    public void add(List<Integer> l, Integer i) {
        l.add(i);
    }
}

class StringListAdder implements Adder<String> {
    public void add(List<String> l, String i) {
        l.add(i);
    }
}

public class AdderTest {
    public static void main(String... argv) {
        IntegerListAdder ila = new IntegerListAdder();
        List<Integer> l = new ArrayList<Integer>();
        ila.add(l,1);
        ila.add(l,2);
        System.out.println(l);
        StringListAdder sla = new StringListAdder();
        List<String> s = new ArrayList<String>();
        sla.add(s,"One");
        sla.add(s,"Two");
        System.out.println(s);
    }
}

Он компилируется и работает нормально.

1 голос
/ 18 февраля 2012

, чтобы сократить мой ответ, я привел пример, используя класс:

class Adder<T, L extends List<T>> {
    void add(L list, T t) { /* your logic */}
    void test() {
         new Adder<Integer, ArrayList<Integer>>().add(new ArrayList<Integer>(), new Integer(1));
    }
}
1 голос
/ 18 февраля 2012

После многих переписываний это делает то, что вы хотите?

interface Adder<L extends Collection<S>, S> {
    public S add(L c, S s);
}

class AdderImpl <L extends Collection<S>, S> implements Adder<L, S> {
    public S add(L c, S s) {
        c.add(s);
        return s;
    }
}


public void test() {
    Adder<List<String>, String> listAdder = new AdderImpl<List<String>, String>();
    Adder<Set<String>, String> setAdder = new AdderImpl<Set<String>, String>();

    listAdder.add(new ArrayList<String>(), "Hello");
    // setAdder.add(new ArrayList<String>(), "Hello");  Complier error - can't use List on SetAdder
    setAdder.add(new HashSet<String>(), "Hello");
}
1 голос
/ 18 февраля 2012

Это скомпилируется, но не соблюдает ваши правила.

interface Adder<L extends List<?>> {
    <T> void add(L list, T t);
}

Вы можете сделать это для обеспечения соблюдения ваших правил

interface  Adder<L extends List<T>, T> {
    void add(L list, T t);
}

class foo implements Adder<List<Integer>, Integer> {
public void add(List<Integer> list, Integer t) {
...
1 голос
/ 18 февраля 2012

Я не знаю, если вы это имеете в виду, но

public interface Adder<T, L extends List<T>> {

    void add(L list, T t);
}

звучит как опция

0 голосов
/ 18 февраля 2012

Рассматривали ли вы , потянув параметр сбора вниз на ?То есть до уровня метода.Таким образом, у вас нет отдельных сумматоров с общим интерфейсом, а только один глобальный сумматор, который обрабатывает все случаи.Подумайте об этом:

class Adder {
    public <T, W extends Wrapped<T>> void add(ArrayList<W> list, W w) {
        System.out.println("list.add " + w);
        w.commonWrappedMethod();
        list.add(w);
    }

    public <T, W extends Wrapped<T>> void add(HashSet<W> set, W w) {
        System.out.println("set.add " + w);
        w.commonWrappedMethod();
        set.add(w);
    }

    // For all other collections
    public <T, W extends Wrapped<T>> void add(Collection<W> col, W w) {
        System.out.println("col.add " + w);
        w.commonWrappedMethod();
        col.add(w);
    }
}

Затем вызывается Adder следующим образом:

ArrayList<Wrapped1<Integer>> w1il = new ArrayList<Wrapped1<Integer>>();
HashSet<Wrapped1<String>> w1ss = new HashSet<Wrapped1<String>>();
Vector<Wrapped2<String>> w2sv = new Vector<Wrapped2<String>>();

Adder adder = new Adder();

adder.add(w1il, new Wrapped1<Integer>(1));
adder.add(w1ss, new Wrapped1<String>("six"));
adder.add(w2sv, new Wrapped2<String>("twelve"));

Вам больше не понадобится (или, по сути, вы не сможете использовать смысл) интерфейс длясумматор, вы бы просто обойти / использовать один объект.Или, что еще лучше, вы должны сделать методы статичными и использовать класс в качестве служебного класса, или сделать класс одиночным и всегда ссылаться на один экземпляр.

Полный пример доступен здесь .

...