如何在Java中确保运行时参数化类型的正确性?

时间:2014-06-01 20:15:35

标签: java generics casting

我有一个简单的通用列表类。我试图实现这一点:给定一个列表实例,我们只知道它包含类的特定子类的实例,并给定该类的实例,如果它是实例,则将此实例添加到列表中包含(子类)类型,否则抛出异常(例如,ClassCastException)。我尝试了以下方法:

class MyList<T>
{
    private Class<T> genericClass;
    private List<T> list = new ArrayList<>();

    public MyList(Class<T> genericClass)
    {
        this.genericClass = genericClass;
    }

    public void add(T elem)
    {
        list.add(elem);
    }

    //...

    public Class<T> getGenericParamClass()
    {
        return genericClass;
    }
}

class A{}
class B extends A{}
class C extends A{}

class Program
{
    public static void main(String... args)
    {
        MyList<B> list1 = new MyList<>(B.class);
        MyList<C> list2 = new MyList<>(C.class);

        MyList<? extends A> ls = checkStuff() ? list1 : list2;

        ls.add(ls.getGenericParamClass().cast(lotsOfStuff())); //ERROR ?!!
    }

    static boolean checkStuff()
    {
        Random random = new Random();
        return random.nextBoolean();
    }

    static A lotsOfStuff()
    {
        return new B();
    }
}

我认为给定一个Class对象,其类型参数与方法的参数类型相同,我可以使用前者来转换,以便能够将它传递给后者。唉,似乎我不能:我得到一个编译时错误!

我可以把仿制药扔出窗外,完全取消选中,然后说:

A val = lotsOfStuff();
if (myList.getGenericParamClass().isInstance(val))
    ls.add(val)
else
    throw new SomeException();

但是,这可能会产生比它解决的问题更多的问题,而且它也会让我感到烦恼。

我在这里遗漏了什么,或者我想出来的方式根本不可能?

修改: 我完全理解为什么这样的事情不起作用:

List<? extends Number> abc=new ArrayList<Integer>();
abc.add(new Integer(10));

但在我看来,以下传递性成立:add()参数的类型Is-The-Same-作为MyList的类型参数Is-The-Same-作为getGenericParamClass返回的Class的类型参数()Is-The-Same-作为该类的cast()方法的返回类型。我(作为人类)可以知道那些未知类型是相同的,因为我从同一个对象中获取它们。

我的逻辑是否存在错误,或者这是Java的限制?

1 个答案:

答案 0 :(得分:2)

编译错误是:

  

在MyList类型中添加(捕获#2-of?extends A)的方法不适用于参数(捕获#3-of?extends A)

要理解该消息,请回想一下? extends A代表未知类型,即A的子类型。该编译器无法知道? extends A返回的lotsOfStuff()? extends A方法所期望的MyList.add相同(或子类型),事实上,您的程序并不确定是这种情况(因为lotsOfStuff()总是会返回B,即使它应该添加到C列表中。)

为了表示两者属于同一类型,我们必须使用类型参数。获得一个的最简单方法是移动代码进行转换并抛入类MyList<T>(已经有一个合适的类型参数),例如通过添加以下方法:

void addOrThrow(Object o) {
    add(genericClass.cast(o));
}