我需要一个集合只包含一个子类型。具体是哪一个并不重要,但重要的是集合的所有元素都属于同一类。子类型本身在编译时是未知的。
我所需要的可以用下面的单元测试来最好地描述:
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"。