泛型类和泛型方法
泛型类:具有一个或多个类型变量的类,称之为泛型类
比如:classA<T> {
}
Class A<k,v>{
}
泛型方法:具有一个或多个类型变量的方法,称之为泛型方法
比如:public<T> T fun(T t1) {}
但是请注意:public Tfun(T t1) {},不是泛型方法。
使用泛型的规则
1. 泛型可以使用在返回类型和参数类型上面
比如:public T fun(T t) {}
2. 泛型可以使用在成员变量上面和类型上面
比如:classA<T> {
private T bean;
}
3. 泛型可以使用在局部变量上面
比如:publicvoid fun2() {
T b = …..;
}
4. 泛型不可以new
比如:new T();//不行的!
5. 在创建泛型类实例时,需要为其类型变量赋值
比如:A<String>a = new A<String>();
* 如果创建实例时,不给类型变量赋值,那么会有一个警告!
6.泛型方法与泛型类没什么关系,泛型方法不一定非要在泛型类中
泛型的继承和实现
1.规则:
*子类不是泛型类:需要给父类传递类型常量
比如:class AA extends A<String> {},这个时候AA不是泛型类,而A是泛型类,这个时候需要指定子类中泛型的类型常量,这个时候父类中所有的T都会被String替换
* 子类是泛型类:可以给父类传递类型常量,也可以传递类型变量
比如:class AA3<E> extends A<E> {}
class AA3<E> extends A<String> {}
泛型的局限性
就拿集合来说:
List<String>list = new ArrayList<String>();
//List<Object>list2 = new ArrayList<String>();
局限性1:
集合等号两边所传递的值必须相同否则会报错,原因就是Java中的泛型有一个擦除的机制,就是所编译器期间编译器认识泛型,但是在运行期间Java虚拟机就不认识泛型了,有兴趣的可以通过反编译来看一下,那么运行期间就会变成Listlist = new ArrayList ();如果最终变成这个样子了,那么传入泛型还有什么意思,所以在程序编译期间就报错,这样泛型就得以应用了(这个实际上是引用c++中的模板没有用好才导致的,Java中用泛型的场景就是写一个通用的方法)。
局限性2:
现在要写一个比较通用的方法。
publicvoid fun1(List<Object> list){
System.out.println("泛型方法");
}
但是在调用的时候传入的String类型变量就会报错
publicvoid test2(){
List<String>list = new ArrayList<String>();
//fun1(list);报错
}
原因无他,就是泛型擦除,理由同局限性1,那么怎么办呢?
重载吧!类型变量为String的一个,Integer的一个。
public void fun1(List<String> list){
System.out.println("泛型方法1");
}
public void fun1(List<Integer>list){
System.out.println("泛型方法2");
}
这个时候编译器又报错了,为啥呢?还是泛型擦除,当运行的时候会导致,参数都变成List list,那么这两个方法都变成一个了。,这,我晕,会不会觉得,泛型好垃圾。别急,接下来人家Java才不傻,给你提供了另一种方法,就是通配符。
泛型的通配符
泛型的通配符就是:?,
?表示的意思:
它表示一个不确定的类型,它的值会在调用时确定下来
那么这个时候通用的方法就能做了。
public void fun1(List<? extends Object>list){
System.out.println("泛型方法");
}
这个时候用
public void test2(){
List<String>list = new ArrayList<String>();
fun1(list);
}
就不报错了。
也可以这样写:List<?> list = newArrayList<String>();
通配符注意事项
通配符不能使用在new的后面,否则会报错,为什么呢?因为如果写在new的后面不就相当于直接确定类型变量了吗,那么这就不是通配符了。
通配符的局限性
1.当只是用?的时候,参数类型为和返回值为通配符的方法是不能使用的。
public void print(List<?> list) {
// list.add("hello");
// list.get(0);
}
原因是什么呢?因为list.add("hello");添加的时候,添加的类型可能是String的,也可能是Integer还可能是其他类型,这个时候还会出现泛型擦除的现象。list.get(0);的时候,可能返回String的,也可能是Integer还可能是其他类型,同样会出现泛型擦除。但是可以用Object接收,因为object是所有类的父类,比如:Object s = list.get(0);
2.通配符可以继承:
比如:? extends Number,这个时候通配符就被限制为了Number和” Number的子类型
但是这种情况也有一个特点,就是参数类型为通配符的泛型不能用,但是返回值类型为泛型的可以用。
public void print(List<? extends Number > list) {
// list.add(1);
Number num = list.get(0);
}
因为参数类型为泛型的时候,参数可以为long的,也可能是Integer的还可能是其他类型,而返回值为泛型的,就可以用Number来接收了。
3.通配符可以有上界。
比如:? superInteger,这个时候通配符就被限制为了Integer和” Integer的父类型
但是这种情况也有一个特点,就是返回值类型为泛型的不能用,但是参数类型为通配符的泛型的可以用。
public void print(List<? super Integer > list) {
list.add(5);
//Number num = list.get(0);
}
因为返回值为泛型的时候,返回值可以为Number的,也可能是Object的还可能是其他类型就是Integer的父类型,会发生泛型擦除。而参数为泛型的,就可以放入Integer或者Integer的父类就可以了。
通配符的应用场景
集合中的添加集合的方法是这样定义的。
boolean addAll(Collection<? extendsE> c)
List<Number> numList = new ArrayList<Number>();
List<Integer> intList = newArrayList<Integer>();
numList.addAll(intList);//addAll(Collection<?extends Number> c), 传递的是List<Integer>
当numList调用addAll方法的时候,Collection<? extends E> c 中的E变成了Number类型,然后就变成了Collection<? extends Number> c,然后就可以往这个方法中传递Integer类型
numList并不是<?extends Number>的引用,所以可以使用参数类型含有通配符的泛型。