集合只包含一种类型

时间:2014-06-24 15:45:34

标签: java generics collections

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

下面的单元测试可以最好地描述我需要的东西:

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!


    }


}

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

问题是:有哪些其他apporaches可用于确保所有元素属于同一类型?

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

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

2 个答案:

答案 0 :(得分:2)

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

首先,你说你需要

  

只包含一个子类型的集合

完全没问题,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的未知子类型列表。

诀窍是现在使用bounded type parameter而不是通配符,所以当您还不知道该类型到底是什么时,您知道参数t是同一类型作为列表,所以你可以添加它。

用法:

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

答案 1 :(得分:0)

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

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!

    }

}

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

P.S。:construcotr中的allowSubType标志将确定是否接受传入类的子类型,或者是否应考虑呼叫要求&#34; final&#34;。