泛型
1.什么是泛型?
Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法
ArrayList<String> list = new ArrayList<>();
list.add("呵呵");
// 里面的<String>就是"泛型"
//作用:可以约束这个集合中只能存储某一种固定的类型,能够保证集合使用的安全。
2.定义泛型类:
public class MyList<E> {//指定Mylist集合存储的数据类型是<E>类型
public void add(E e) {
}
public E get(){
return null;
}
}
3.定义具有"泛型"的方法:
public class Demo04 {
public static void main(String[] args) {
ArrayList<String> put = Demo04.<String>put("10", "呵呵", "10", "呵呵", "10", "呵呵", "10", "呵呵", "10", "呵呵");
ArrayList<Integer> list3 = Demo04.<Integer>put(10, 11, 12, 13, 14, 15, 16, 17, 18, 19);
}
//定义一个数据类型是<T>类型的方法,可以接收10个数据,然后封装到一个集合,并返回集合
public static <T> ArrayList put(T s1, T s2, T s3, T s4, T s5,
T s6, T s7, T s8, T s9, T s10) {
ArrayList<T> strList = new ArrayList<>();
strList.add(s1);
strList.add(s2);
strList.add(s3);
strList.add(s4);
strList.add(s5);
strList.add(s6);
strList.add(s7);
strList.add(s8);
strList.add(s9);
strList.add(s10);
return strList;
}
}
4.定义具有"泛型"的接口:跟泛型类一样。
定义父类接口:
public interface Animal<E> {//定义父类接口时,只需在类名后申明数据类型时<E>类型就行
public void eat(E e);
}
定义父类的继承子类时,子类有三种选择:
1)第一种情况:子类可以丢弃泛型
//第一种情况:子类可以丢弃泛型
class Z1 implements Animal {
@Override
public void eat(Object o) {
}
}
2)第二种情况:子类可以指定为某种固定类型
//第二种情况:子类可以指定为某种固定类型
class Z2 implements Animal<String>{
@Override
public void eat(String s) {
}
}
3)第三种情况:子类可以继续继承泛型
//第三种情况:子类可以继续继承泛型
class Z3<E> implements Animal<E>{
@Override
public void eat(E e) {
}
}
5).泛型通配符:
我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V 等等,这些通配符又都是什么意思呢?本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,?是这样约定的:
-
?表示不确定的 java 类型
-
T (type) 表示具体的一个java类型
-
K V (key value) 分别代表java键值中的Key Value
-
E (element) 代表Element
? 无界通配符
引入无界通配符主要是为了程序的拓展性,比如:
我有一个父类 Animal 和几个子类,如狗、猫等,现在我需要一个动物的列表,我的第一个想法是像这样的:
List<Animal> listAnimals
但是在实际的开发中如果引入通配符的话,就可以通过类与类的继承关系来实现代码的复用。
List<? extends Animal> listAnimals
为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。
static int countLegs (List<? extends Animal > animals ) {
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
static int countLegs1 (List< Animal > animals ){
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
// 不会报错
countLegs( dogs );
// 报错
countLegs1(dogs);
}
1).<? extends E>
extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
-
如果传入的类型不是 E 或者 E 的子类,编译不成功
-
泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
static int countLegs (List<? extends Animal > animals ) {
//定义的类的数据类型必须是animal及animal的继承子类
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
2).<? super E>:
用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。
private <T> void test(List<? super T> dst, List<T> src){
for (T t : src) {
dst.add(t);
}
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = new ArrayList<>();
new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子类
class Dog extends Animal {
}
dst 类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src 。
上界通配符主要用于读数据,下界通配符主要用于写数据。
3).<?>:
表示可以是任何类型。
4)?和 T 的区别
?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行,比如如下这种 :
// 可以
T t = operate();
// 不可以
?car = operate();
简单总结下:
T 是一个 确定的类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
区别1:通过T来确保泛型参数的一致性
// 通过 T 来 确保 泛型参数的一致性
public <T extends Number> void
test(List<T> dest, List<T> src)
//通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型
public void
test(List<? extends Number> dest, List<? extends Number> src)
区别2:类型参数可以多重限定而通配符不行
使用 & 符号设定多重边界(Multi Bounds),指定泛型类型 T 必须是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子类型,此时变量 t 就具有了所有限定的方法和属性。对于通配符来说,因为它不是一个确定的类型,所以不能进行多重限定。
区别3:通配符可以使用超类限定而类型参数不行
类型参数 T 只具有 一种 类型限定方式:
T extends A
但是通配符 ? 可以进行 两种限定:
? extends A
? super A