Java里的泛型加通配符的用法

2024-12-14 07:39:32
推荐回答(5个)
回答1:

感觉是有点问题,像是设计上的问题。

People p=new People();
List list=new ArrayList();
list.add(p);

像你这个代码,如果list.add(p);加的不是一个People,那会怎么样呢?假设另一个类Man继承于User,且Man与People没从属关系。那么然后按照List list来理解,将Man加到list里面应该是没问题的吧。可是另一方面我们定义的list是一个new ArrayList(),这样看的话把Man加到list里面貌似就呃掉了吧。

而按照freish的说法也是不那么对的样子。因为如果存在这么一个函数f(List LN),在这里函数里,我们限制了输入的参数LN必须只能存放数字类Number,这样一个限制应该是很有用的吧,可以避免你把一个List或者其他东西传给函数f()。如果按照freish的方法,我们把? extends Number直接写为Number的话,那么你f(new ArrayList())这样写的话编译器会华丽丽的报错,所以这么改又是不对的。

在你这个问题里面的话,把list写出 List list 是没什么实际意义的,但是如果像是在上面函数f(List LN)里的话确是有意义了。也就是说理论上来说,通配符是被设计成一个有用的东西的,也就是用来限定传入函数的参数的(或者赋值时来限定等号右边的)。而通过通配符来限定是某某某的子类或者超类在list这种容器应用的时候却产生了矛盾,也就是上面说的你本来只是想把People给加到list的,结果却一不小心把人家Man给加进来了。这样看来的话,java应该是考虑到这种情况了,所以就? extends User 成什么乱七八糟的 capture#105 of ? extends User类型了。泛型产生的原因就是当时的list这些容器能把任何类型的东西存进去又再拿出来(因为是Object),这样容易产生混乱。所以就产生了泛型来限制和检查类型。而这种限制却在这些特殊的情况下面产生了矛盾。

嗯,所以总结来说,你不能这么写代码,不能把要操作到泛型参数的方法的类声明为带有通配符的模式。还有一些例子看起来好似能通过编译的,实际确是不行的。比如说
class People extends User{
void f(K k){System.out.println("f(K k)");}
void f(V v){System.out.println("f(V v)");}
}
在这里f有着两个不同类型参数K和V,看起来好像没事儿的样子。但是这是不能编译成功的。因为如果K是String而V也是String的时候,那么两个函数不就冲突了么?

回答2:

以下是我自己学习通配符部分的学习笔记,希望能对你有帮助:
在上一篇中说到,泛型不具有内建的协变类型,因此,这样是无法通过编译的:

List fruit = new List() ;

这是因为List是持有Fruit或其导出类的容器,而List是持有Apple的容器,它们之间并没有类型上的相关性。所以如果真的想达到这种目的,可以借用通配符的作用:

List fruit = new List() ;

这样,就可以可以通过编译器的检查了。在这里,List实际上并不是指这个List可以持有任何类型的

Fruit,通配符引用的是明确的类型,所以它的意思是某种指定的具体类型,所以编译器只是知道它可能是持有Apple的

List,也可能是持有Orange的List,或者其他持有其他具体类型的Fruit的List,所以上面的可以通过,然而正是这种不确定性导致了一些不愉快的情况发生:

fruit.add(new Apple()) ; //compile-time error

fruit.add(new Fruit()) ; //compile-time error

奇怪啊!怎么把它们放进去都是出错呢?我想可能是这样,既然fruit是持有某种具体类型的List,但是编译器并不知道是哪种,你随便放一个进去,是正确的吗?这一点连编译器都无法判断,怎么能随意就通过编译器的检查呢?于是就出错了。但是如果你真的用一种神奇的方法(事实证明这种神奇的方法是存在的)将某个对象加进去,试图想将它取出时,编译器知道它至少是一个Fruit,所以你可以这样是行得通的:

Fruit f = fruit.get(0) ;

其实这个无法通过编译的本质原因是Java设计者在捣鬼,查看List的源代码,会发现add的方法是这样的:

boolean add(E e) ;

在这里(以上代码是我从源代码中复制过来的)E是代表泛型参数,所以当E是的时候,调用add方法时就要通过检查,因此无法通过。如果参数类型是Object,这样无论什么类型都是Object,所以就可以调用了,比如:

boolean contains(Object o) ;

boolean remove(Object o) ;

以下代码是个简单的应用举例:

LIst fruit = Arrays.asList(new Apple()) ;

fruit.contains(new Apple()) ;

fruit.indexOf(new Apple()) ;

fruit.remove(new Apple()) ;

通配符的另一个应用是与super结合:List 。顾名思义,这是持有任何T的基类的容器。那样是不是所有T的基类都可以放进去呢?同样编译器是无法知道是哪个具体的T的基类,所以无法检查,所以编译器就不能确保安全性,所以就无法通过。但是无论是哪一个具体的基类,但是如果是放入T或者是T的基类都是可以的,因为它们可以安全的向上转型而不会出现任何差错。

还有一种通配符的用法是“?”的孤军奋战:List。这个我也可以来一次顾名思义,就是持有任何类型的容器,但是限于某种具体的。然而你会发现,如果没有使用泛型,只有List,它也是可以持有任何类型的,所以编译器一般不会太在意这点区别。但是还是有另一种特殊情形:List,这就相当有趣,它表示持有Object或从Object继承下来的类的容器,但是在Java中,所有的类都是从Object中继承的,所以它的范围和上面两种情况一概一样的啊!可是在

List和List的相互赋值是会出现警告,但是List和List的相互赋值是不会的。这样一种有趣的情形就会出现,如果将List和List相互赋值,再将List和List相互赋值,这样就实现了 List和List的相互赋值,且不会出现警告,看以下应用:

public class ExchangeValue{

static void f1 (List list) { T t = list.get(0) ; }

static void f2 ( List list ) { f1(list) ;}

public static void main (String[] args){

List ls = new List (1);

f1(ls) ; //warning!!

f2(ls) ; //no warning!!

}

回答3:

泛型可以用""代表,任意类型的。
解释: “”是泛型的默认值,可以被任意类型所代替,如:
List list = new ArayList();这个就定义了一个String类型的”泛型“集合,那么T的类型就是字符串。
List list = new ArayList();
可以赋值给list:list.add("StringBatch");
可以获取到list的值:list.get(0),结果就是”StringBatch“;
这个时候T的类型也是String。也就是说T是动态的,可以被任意指定类型。

回答4:

规则:
如果你使用了“? extends T”,你不能往该数据结构中add元素,但可以get元素。

换句话说,如果你要在list中加入User及其子类,用得着通配符么?!
直接List list=new ArrayList();不就得了

回答5:

List你这么写java好像不知道你的list到底是什么类型的东西,所以报错!!!
推荐这样写List
泛型是强类型,用之前应该为它指定类型 省去了转型的烦恼!!!