只包含一种类型的集合



我需要一个集合只包含一个子类型。具体是哪一个并不重要,但重要的是集合的所有元素都属于同一类。子类型本身在编译时是未知的。

我所需要的可以用下面的单元测试来最好地描述:

import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
public class SingleTypeTest {
    public static abstract class AbstractFoo {
        public abstract void someMethod();
    }
    public static class FooA extends AbstractFoo {
        @Override
        public void someMethod() {};
    }
    public static class TestB extends AbstractFoo {
        @Override
        public void someMethod() {};
    }
    public List<? extends AbstractFoo> myList;
    @Test
    public void testFooAOnly() {
        myList = new ArrayList<FooA>();
        myList.add(new FooA()); // This should work!
        myList.add(new FooB()); // this should fail!

    }
    @Test
    public void testFooBOnly() {
        myList = new ArrayList<FooB>();
        myList.add(new FooB()); // This should work!
        myList.add(new FooA()); // this should fail!

    }

}

由于类型擦除,这段代码实际上是不可编译的,但它最好地指定了我想要做的事情。

问题是:还有什么方法可以确保所有元素都是相同类型的?

我唯一能做的就是写一个委托来包装列表,并检查添加到类中的对象是否都是相同的类型,但这看起来相当笨拙。还有其他想法吗?

更新:我已经澄清了问题和单元测试代码。

好的,我想我明白你想要什么,所以让我们看看为什么你的代码不能编译。

首先,你说你需要

只包含一个子类型的集合

这很好,List<? extends AbstractFoo>就是AbstractFoo的某个亚型的集合。问题是,由于它可以通过任何子类型,因此您不能将任何内容添加到集合中(除了null)。例子:

List<? extends AbstractFoo> myList = getListSomewhere();
myList.add(new FooA()); //illegal -> compile-time error

现在编译器不知道这个列表是什么类型,它可能是List<TestB>,因此添加到这样的列表永远不是类型安全的。

可能的解决方案:

不要失去希望,你仍然可以使用一个只包含一个子类型的集合,对它进行读写:

public static <T extends AbstractFoo> void test(List<T> list, T t) {
    if(list.contains(t)) {
        t.someMethod();
    } else {
        list.add(t); //you can add to list
    }
    list.get(0).someMethod(); //you can read from list
}

向上是一个简单而又演示的方法,它适用于AbstractFoo的未知子类型列表。

技巧在于,现在您使用有界类型参数而不是通配符,因此,尽管您仍然不知道确切的类型是什么,但您知道参数t与列表的类型相同,因此可以添加它。

用法:

List<FooA> listA = new ArrayList<>();
test(listA, new FooA()); //OK
test(listA, new TestB()); //Compile error

我已经通过委派/包装器解决了这个问题:

import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Spliterator;
import java.util.function.UnaryOperator;
/**
 *
 * @author fhossfel
 */
public class SingleTypeList<T> implements List<T> {
    private final List<T> wrappedList;
    private final boolean allowSubTypes;
    private Class clazz;
    public SingleTypeList(List<T> list, Class clazz) {
        this(list, clazz, true);
    }
    public SingleTypeList(List<T> list, Class clazz, boolean allowSubTypes) {
        this.wrappedList = list;
        this.allowSubTypes = allowSubTypes;
        this.clazz = clazz;
    }

    @Override
    public int size() {
        return wrappedList.size();
    }
    @Override
    public boolean isEmpty() {
        return wrappedList.isEmpty();
    }
    @Override
    public boolean contains(Object o) {
        return wrappedList.contains(o);
    }
    @Override
    public Iterator<T> iterator() {
        return wrappedList.iterator();
    }
    @Override
    public Object[] toArray() {
        return wrappedList.toArray();
    }
    @Override
    public <T> T[] toArray(T[] a) {
        return wrappedList.toArray(a);
    }
    @Override
    public boolean add(T e) {
        if (isAcceptable(e)) {
            return wrappedList.add(e);
        } else {
            throw new IllegalArgumentException("Object " + e.toString() + "is of class " + e.getClass() 
                                                  + " but only elements of type " + clazz.getName()
                                                  + (allowSubTypes ? " or any subtype of it " : "") + " may be added to this collection");           
        }
    }
    @Override
    public boolean remove(Object o) {
        return wrappedList.remove(o);
    }
    @Override
    public boolean containsAll(Collection<?> c) {
        return wrappedList.containsAll(c);
    }
    @Override
    public boolean addAll(Collection<? extends T> c) {
        if (areAllAcceptable(c)) {
            return wrappedList.addAll(c);
        } else {
            throw new IllegalArgumentException("Not all elements are of type " + clazz.getName()
                                                  + (allowSubTypes ? " or any subtype of it " : "") + " and may not be added to this collection");           
        }
    }
    @Override
    public boolean addAll(int index, Collection<? extends T> c) {
        if (areAllAcceptable(c)) {
            return wrappedList.addAll(index, c);
        } else {
            throw new IllegalArgumentException("Not all elements are of type " + clazz.getName()
                                                  + (allowSubTypes ? " or any subtype of it " : "") + " and may not be added to this collection");           
        }
    }
    @Override
    public boolean removeAll(Collection<?> c) {
        return wrappedList.removeAll(c);
    }
    @Override
    public boolean retainAll(Collection<?> c) {
        return wrappedList.retainAll(c);
    }
    @Override
    public void replaceAll(UnaryOperator<T> operator) {
        wrappedList.replaceAll(operator);
    }
    @Override
    public void sort(Comparator<? super T> c) {
        wrappedList.sort(c);
    }
    @Override
    public void clear() {
        wrappedList.clear();
    }
    @Override
    public boolean equals(Object o) {
        return wrappedList.equals(o);
    }
    @Override
    public int hashCode() {
        return wrappedList.hashCode();
    }
    @Override
    public T get(int index) {
        return wrappedList.get(index);
    }
    @Override
    public T set(int index, T element) {
        return wrappedList.set(index, element);
    }
    @Override
    public void add(int index, T element) {
        wrappedList.add(index, element);
    }
    @Override
    public T remove(int index) {
        return wrappedList.remove(index);
    }
    @Override
    public int indexOf(Object o) {
        return wrappedList.indexOf(o);
    }
    @Override
    public int lastIndexOf(Object o) {
        return wrappedList.lastIndexOf(o);
    }
    @Override
    public ListIterator<T> listIterator() {
        return wrappedList.listIterator();
    }
    @Override
    public ListIterator<T> listIterator(int index) {
        return wrappedList.listIterator(index);
    }
    @Override
    public List<T> subList(int fromIndex, int toIndex) {
        return wrappedList.subList(fromIndex, toIndex);
    }
    @Override
    public Spliterator<T> spliterator() {
        return wrappedList.spliterator();
    }
    private boolean isAcceptable(T o) {
        return (o == null                                   // o is null -> then it can be added
                ||  (allowSubTypes  && clazz.isInstance(o)) // sub-types are allowed and o is a sub-type of clazz
                ||  (o.getClass().equals(clazz)));          // or o is actually of the type clazz
    }
    private boolean areAllAcceptable(Collection<? extends T> c) {
        if (c == null || c.isEmpty()) return true;
        for (T o : c) {
            if (! isAcceptable(o)) {
                return false;
            }                        
        }
        return true;
    }
}

显示使用情况的单元测试如下所示:

import biz.direction.punkrockit.snapastyle.snapastyle.servlet.util.SingleTypeList;
import java.util.ArrayList;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class SingleTypeListTest {
    public static abstract class AbstractFoo {
        public abstract void someMethod();
    }
    public static class FooA extends AbstractFoo {
        @Override
        public void someMethod() {};
    }
    public static class BarA extends FooA {
        @Override
        public void someMethod() {};
    }
    public static class FooB extends AbstractFoo {
        @Override
        public void someMethod() {};
    }
    public List<AbstractFoo> myList;
    @Before
    public final void setUp() {
         myList = new SingleTypeList<AbstractFoo>(new ArrayList<AbstractFoo>(), FooA.class);
    }

    @Test
    public void testFooAOnly() {
        myList.add(new FooA()); // this should work 
        Assert.assertFalse("List must not be empty.", myList.isEmpty());

    }
    @Test
    public void testFooAandBarA() {
        myList.add(new FooA()); // this should work 
        myList.add(new BarA()); // this should work 
        Assert.assertTrue("List must contain two elements.", myList.size() == 2);

    }
   @Test(expected=IllegalArgumentException.class)
    public void testAddFooB() {
        myList.add(new FooB());
    }
    @Test(expected=IllegalArgumentException.class)
    public void testNoSubtype() {
        myList = new SingleTypeList<AbstractFoo>(new ArrayList<AbstractFoo>(), FooA.class, false);
        myList.add(new FooA()); // this should work 
        Assert.assertFalse("List must not be empty.", myList.isEmpty());
        myList.add(new BarA()); // This should fail!
    }
}

对代码的影响相当小,但对于我想要做的事情来说,它似乎有很多代码。

注::构造函数中的allowSubType标志将决定是否接受类中传递的子类型,或者是否将调用要求视为"final"。

最新更新