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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java里的泛型加通配符的用法相关的知识,希望对你有一定的参考价值。

public class Test

public static void main(String[] args)
People p=new People();
List<? extends User> list=new ArrayList();
list.add(p);



class User



class People extends User



这里list.add()的意思不是指添加到list这个集合里面的东西必须是User的子类吗?为什么不能编译,那倒底要怎么用
即使new ArrayList后面加<?>或者<User>或者<People>编译依然失败,结果都是list.add(p)这个方法参数不对
希望回答的人不要肤浅,粘一下代码在你的MyEclipse去试试再说,不会就干脆不要发言的好
其实原题是Iterator<? extends Map.Entry<? extends K, ? extends V>>这行代码究竟是要怎么样

2楼的回答很精辟,很可惜就是不够详细
3楼的详细,希望能总结一下,有点理解有点没理解~想听你的总结我想会更能理解

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

People p=new People();
List<? extends User> list=new ArrayList<People>();
list.add(p);

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

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

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

嗯,所以总结来说,你不能这么写代码,不能把要操作到泛型参数的方法的类声明为带有通配符的模式。还有一些例子看起来好似能通过编译的,实际确是不行的。比如说
class People<K,V> 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的时候,那么两个函数不就冲突了么?
参考技术A 以下是我自己学习通配符部分的学习笔记,希望能对你有帮助:
在上一篇中说到,泛型不具有内建的协变类型,因此,这样是无法通过编译的:

List<Fruit> fruit = new List<Apple>() ;

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

List<? extends Fruit> fruit = new List<Apple>() ;

这样,就可以可以通过编译器的检查了。在这里,List<? extends Fruit>实际上并不是指这个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是<? extends Fruit>的时候,调用add方法时就要通过检查,因此无法通过。如果参数类型是Object,这样无论什么类型都是Object,所以就可以调用了,比如:

boolean contains(Object o) ;

boolean remove(Object o) ;

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

LIst<? extends Fruit> fruit = Arrays.asList(new Apple()) ;

fruit.contains(new Apple()) ;

fruit.indexOf(new Apple()) ;

fruit.remove(new Apple()) ;

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

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

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

public class ExchangeValue

static <T> void f1 (List<T> list) T t = list.get(0) ;

static void f2 ( List<?> list ) f1(list) ;

public static void main (String[] args)

List ls = new List<Integer> (1);

f1(ls) ; //warning!!

f2(ls) ; //no warning!!

参考技术B 泛型可以用"<T>"代表,任意类型的。
解释: “<T>”是泛型的默认值,可以被任意类型所代替,如:
List<String> list = new ArayList<String>();这个就定义了一个String类型的”泛型“集合,那么T的类型就是字符串。
List<T> list = new ArayList<T>();
可以赋值给list:list.add("StringBatch");
可以获取到list的值:list.get(0),结果就是”StringBatch“;
这个时候T的类型也是String。也就是说T是动态的,可以被任意指定类型。
参考技术C 规则:
如果你使用了“? extends T”,你不能往该数据结构中add元素,但可以get元素。

换句话说,如果你要在list中加入User及其子类,用得着通配符么?!
直接List<User> list=new ArrayList<User>();不就得了
参考技术D List<? extends User>你这么写java好像不知道你的list到底是什么类型的东西,所以报错!!!
推荐这样写List<User>
泛型是强类型,用之前应该为它指定类型 省去了转型的烦恼!!!

以上是关于Java里的泛型加通配符的用法的主要内容,如果未能解决你的问题,请参考以下文章

java里的泛型和通配符

JAVA 泛型通配符 ? EXTENDS SUPER 的用法

第65题必学的泛型2-使用通配符增强泛型

Java 泛型泛型用法 ( 泛型类用法 | 泛型方法用法 | 泛型通配符 ? | 泛型安全检查 )

夯实Java基础系列13:深入理解Java中的泛型

Java的泛型和通配符