Java基础知识_泛型
一、什么是泛型?
Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClasCastException异常
泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
参数化类型
把类型当作是参数一样传递
<数据类型>只能是引用类型
相关术语
ArrayList<E>中的E称为类型参数变量
ArrayList<Integer>中的Integer称为实际类型参数
整个称为ArrayList<E>泛型类型
整个ArrayList<Integer>称为参数化的类型ParameterizedType
二、为什么需要泛型
早期Java是使用Object来代替任意类型的,但是向下转型有强转的问题,这样程序就不太安全。
首先,我们来试想一下:没有泛型,集合会怎么样
Collection、Map集合对元素类型是没有任何限制。本来我的Collection集合装的是全部的Dog对象,但是外边把Cat对象存储到集合中,是没有任何语法错误的。
把对象扔进集合中,集合是不知道元素的类型是什么,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换。
有了泛型以后:
代码更加简洁(不用强制转换)
程序更加健壮(只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常)
可读性和稳定性(在编写集合的时候就限定了类型)
2.1 有了泛型后可以使用增强for遍历集合
在创建集合的时候,我们明确了集合的类型了,所以我们可以用增强for来遍历集合
1 //创建集合对象 2 ArrayList<String> list = new ArrayList<>(); 3 list.add("hello"); 4 list.add("world"); 5 list.add("java"); 6 //遍历,由于明确了类型,我们可以增强for 7 for(String s : list){ 8 System.out.println(s); 9 }
三、泛型基础
3.1 泛型类
泛型类就是把类型定义在类上,用户使用该类的时候,才把类型明确下来,这样的话,用户明确了什么类型,该类就代表着什么类型,用户在使用的时候就不用但是强转的问题,运行时转换异常的问题了。
在类上定义的泛型,在类的方法也可以使用!
1 /*1、把泛型定义在类上 2 *2、类型变量定义在类上,方法中也可以使用 3 **/ 4 public class ObjectTool<T>{ 5 private T obj; 6 public T getObj(){ 7 return obj 8 } 9 public void setObj(T obj){ 10 this.obj=obj; 11 } 12 }
测试代码
用户想要使用哪种类型,就在创建的时候指定类型。使用的时候,该类就自动转换成用户想要使用的类型了
1 public static void main(String[] args){ 2 ObjectTool<String> tool = new ObjectTool<>(); 3 tools.setObj(new String("hhh")); 4 String s = tool.getObj(); 5 System.out.println(s); 6 //创建对象并指定元素类型 7 ObjectTool<Integer> objectTool = new ObjectTool<>(); 8 //如果我在这里输入的是String类型,编译期就通过不了。 9 objectTool.setObj(10); 10 int i = objectTool.getObj(); 11 System.out.println(i); 12 }
3.2 泛型方法
前面已经介绍了泛型类了,在类上定义的泛型,在方法中也可以使用
现在呢,我们可能就仅仅在某个方法上需要使用泛型,外界仅仅是关心该方法,不关心类其他的属性,这样的话,我们在整个类型上定义泛型未免就有些大题小作了。
定义泛型方法...泛型是先定义后使用的
1 //定义泛型方法 2 public <T> void show(T t){ 3 System.out.println(t); 4 }
测试代码
用户传进来的是什么类型,返回值就是什么类型了
1 public static void main(String[] args){ 2 //创建对象 3 ObjectTool tool = new ObjectTool(); 4 //调用方法,传入的参数是什么类型,返回值就是什么类型 5 tool.show("hello"); 6 tool.show(12); 7 tool.show(12.5); 8 }
3.3 泛型类派生出的子类
前面我们已经定义了泛型类,泛型类是拥有泛型这个特性的类,它本质上还是一个Java类,那么它就可以被继承。
那么它是怎么被继承的呢??这里分两种情况
1、子类明确泛型类的类型参数变量
2、子类不明确泛型类的类型参数变量
3.3.1 子类明确泛型类的类型参数变量
泛型接口
1 //把泛型定义在接口上 2 public interface Inter<T>{ 3 public abstract void show(T t); 4 }
实现泛型接口的类
1 //子类明确泛型类的类型参数变量 2 public class InterImpl implements Inter<String>{ 3 @Override 4 public void show(String s){ 5 System.out.println(s); 6 } 7 }
3.3.2子类不明确泛型类的类型参数变量
当子类不明确泛型类的类型参数变量时,外界使用子类的时候也需要传递类型参数变量进来,在实现类上需要定义出类型参数变量。
1 //子类不明确泛型类的类型参数变量,实现类也要定义出<T>类型的 2 public class InterImpl<T> implements Inter<T>{ 3 @Override 4 public void show(T t){ 5 System.out.println(t); 6 } 7 }
测试代码
1 public static void main(String [] args){ 2 Inter<String> i = new InterImpl<>(); 3 i.show("100"); 4 }
值得注意的是:
实现类要是重写父类方法,返回值的类型要和父类一样
类上声明的泛型只对非静态成员有效
3.4 类型通配符
为什么需要类型通配符,举个栗子
现在有一个需求,方法接受一个集合参数,遍历集合并把集合元素打印出来,怎么办?
按照我们没有学习泛型之前,我们可能会这样做;
1 public void test(List list){ 2 for(int i=0;i<list.size();i++) 3 System.out.println(list.get(i)); 4 }
上面的代码是正确的,只不过会在编译时期出现警告,说没有确定集合元素的类型,这样不优雅
那我们学习了泛型了,现在要怎么做呢??有的人可能会这样做:
1 public void test(List<Object> list){ 2 for(int i=0;i<list.size();i++) 3 System.out.println(list.get(i)); 4 }
这样做语法是没毛病的,但是这里十分值得注意的是:该test()方法只能遍历装着Object的集合。
强调
泛型中的<Object>并不是像以前那样有继承关系的,也就是说List<Object>和List<String>是毫无联系的。
那咋办嘛,我们又不清楚元素类型,于是Java提供了类型通配符?
所以代码应该改成这样
1 public void test(List<?> list){ 2 for(int i =0;i<list.size();i++) 3 System.out.println(list.get(i)); 4 }
?通配符表示可以匹配任意类型,任意的Java类都可以匹配
现在非常值得注意的是,当我们使用?时候,就只能调用对象与类型无关的方法,不能调用对象与类型有关的方法。
也就是说上面的List集合,我们是不能使用add方法的,因为add方法是把对象丢进集合中,而现在我是不知道对象的类型是什么。
3.4.1设定通配符上限
首先,我们来看一下设定通配符上限用在哪里
现在我想接受一个List集合,它只能操作数字类型的元素(Float Integer Double Byte等数字类型都行),怎么做???
我们学习了通配符,但是如果直接使用通配符的话,该集合就不是只能操作数字了,因此我们需要用到设定通配符上限
List<? extends Number>
上面的代码表示的是List集合装载的元素只能是Number的子类或自身
1 public static void main(String [] args){ 2 //List集合装载的是Integer,可以调用该方法 3 List <Integer> integer = new ArrayList<>(); 4 test(integer); 5 //List集合装载的是String,在编译期就报错了 6 List <String> strings = new ArrayList<>(); 7 test(strings); 8 } 9 10 public static void test(List<? extends Number> list){ 11 12 }
3.4.2设定通配符下限
既然上面我们已经说了如何设定通配符上限,那么设定通配符下限也不是陌生的事了,直接看语法吧
//传递进来的只能是Type或Type的父类 <? super Type>
设定通配符的下限并不少见,在TreeSet集合中就有,我们来看一下
1 public TreeSet(Comparator<? super E> comparator) { 2 this(new TreeMap<>(comparator)); 3 }
那它有什么用呢??我们想一下,当我们要创建一个TreeSet<String>类型的变量的时候,并传入一个可以比较String大小的Comparator。
那么这个Comparator的选择就有很多了,它可以是Comparator<String>还可以是类型参数是String的父类比如说Comparator<Object>
这样做就比较灵活了,也就是说,只要它能比较字符串的大小就行了
值得注意的是:无论是设定通配符的上限还是下限都不能操作与对象有关的方法,只要涉及到了通配符,它的类型都是不确定的!!!
3.5 通配符和泛型方法
大多时候,我们都可以使用泛型方法来代替通配符的
//使用通配符 public static void test(List<?> list){ } //使用泛型方法 public <T> void test2(List <T> t){ }
上面两个方法都可以用,那么我们是使用泛型方法还是通配符呢
原则
如果参数之间类型有依赖关系,或者返回值与参数之间有依赖关系的,那么就使用泛型方法
如果没有依赖关系的,就使用通配符,通配符会灵活一点
3.6 泛型擦除
泛型是给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住想集合中插入方法数据,但编译器编译完带有泛型的Java程序后,生成的class文件中将不会再带有泛型信息,因此使程序运行效率不受影响,这个过程称之为擦除
3.6.1 兼容性
JDK5提出了泛型这个概念,但jdk5之前的是没有泛型的
当把带有泛型特征的集合赋值给老版本的集合的时候,会把泛型擦除了
值得注意的是,它保留的类型参数的上限
1 List<String> list = new ArrayList<>(); 2 //类型被擦除了,保留的是类型的上限,String的上限就是Object 3 List list1 = list;
如果我们把没有类型的集合赋值给有类型参数的集合怎么样?
1 List list = new ArrayList(); 2 List<String> list2 = list;
它也不会报错,仅仅是提示“未经检查的转换”
四、泛型的应用
当我们写网页的时候,常常会有多个DAO,我们需要写好几个DAO会很麻烦
我们可以试试写一个抽象DAO,别的DAO只要继承该抽象DAO,就有对应的方法了。
要实现这样的效果肯定是要用到泛型的。因为在抽象DAO中,是不可能知道哪个DAO会继承它自己,所以是不知道其具体的类型的。而泛型就是在创建的时候才指定其具体的类型。

浙公网安备 33010602011771号