Вопрос

This is a bit of a contrived reproducing case, but bear with me.

Suppose you want to create an adder interface for classes capable of adding items to different types of lists with the following behavior:

// 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");

In other words, I want the interface to say:

An adder for an specific list type has a method 'add'. This method takes two arguments. The first is a list of this specific type with items of type T. The second argument is an item of type T. The method adds the item to the list.

I tried with different solutions along the lines of:

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

But the expression L<T> is illegal in its context. The error message I get is "Type 'L' does not have type parameters".

I cannot find a way of leaving the type parameter of the list open until the definition of the add method. Is there any way of specifying this interface, or does that require higher-order generics or something else which Java doesn't have?

Это было полезно?

Решение

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);
    }
}

I don't think binding T is possible at add definition time, since T must be known in order to declare L having a type parameter (it must be given in either bound or unbound form).

I believe you are looking for the equivalent of this C++0x code:

#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;
}

And I'm pretty sure that's not possible in Java.

Другие советы

Unfortunately, Java Generics do not support the functionality you are looking for. The closest you can get is to require a List in the method, and not use type L. Such as:

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

This isn't what you are looking for though, so the next closest thing would be to move your List declaration into the method body, however this is also incorrect:

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

The problem is you are attempting to assign a generic type to an arbitrary type (L), while L may not be genericized, despite forcing L extends List<?>. There is no good way to force the check at compile time or at run time of the list type.

I do not know if this is what you mean but

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

    void add(L list, T t);
}

sounds like an option

This will compile but it does not enforce your rules.

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

You can do this to enforce your rules

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) {
...

to shorten my answer i gave an example using a class:

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));
    }
}

Will this do

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);
    }
}

It compiles and runs fine.

After many rewrites, does this do what you want?

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");
}

Have you considered pulling your collection parameter down? That is, to the method level. So that you do not have separate adders with a common interface, but just one, global adder that handles all cases. Consider this:

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);
    }
}

The Adder is then invoked like this:

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"));

You would no longer need (or be able to employ meaningfully, in fact) an interface for the adder, you would just pass around/use the single object. Or even better, you would make the methods static and use the class as a utility class, or make the class a singleton and always refer to the one instance.

Full example is available here.

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