目录
一、泛型概念的提出
当集合中存储的对象类型不同时,那么会导致程序在运行的时候的转型异常
代码程序:
public class Demo {
public static void main(String[] args) {
// 不使用泛型
ArrayList arr = new ArrayList();
arr.add(new Tiger("华南虎"));
arr.add(new Tiger("东北虎"));
arr.add(new Sheep("喜羊羊"));
System.out.println(arr);
Iterator it = arr.iterator();
while (it.hasNext()) {
Object next = it.next();
// 当类型为羊时,类型转换出错——>运行时异常
Tiger t = (Tiger) next;
t.eat();
}
}
}
class Tiger {// 老虎
String name;
public Tiger() {
}
public Tiger(String name) {
this.name = name;
}
public void eat() {
System.out.println(this.name + "吃羊");
}
@Override
public String toString() {
return "Tiger{" +
"name='" + name + '\'' +
'}';
}
}
class Sheep {// 羊
String name;
public Sheep() {
}
public Sheep(String name) {
this.name = name;
}
public void eat() {
System.out.println(this.name + "吃青草");
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
'}';
}
}
原因:
发现虽然集合可以存储任意对象,但是如果需要使用对象的特有方法,那么就需要类型转换,如果集合中存入的对象不同,可能引发类型转换异常。
出现问题:
存入的是特定的对象,取出的时候是Object对象,需要强制类型转换,可能诱发类型转换异常。
无法控制存入的是什么类型的对象,取出对象的时候进行强转时可能诱发异常。而且在编译时期无法发现问题。
虽然可以再类型转换的时候通过if语句进行类型检查(instanceof),但是效率较低。(例如吃饭的时候,还需要判断米饭里有没有沙子,吃饭效率低)。可以通过给容器加限定的形式规定容器只能存储一种类型的对象。
就像给容器贴标签说明该容器中只能存储什么样类型的对象。所以在jdk5.0后出现了泛型。
二、泛型的应用
例如:格式
- 集合类<类类型> 变量名 = new 集合类<类类型>();
1、将运行时的异常提前至编译时发生。
2、获取元素的时候无需强转类型,就避免了类型转换的异常问题。
格式通过<> 来指定容器中元素的类型。
什么时候使用泛型?
当类中操作的引用数据类型不确定的时候,就可以使用泛型类。
JDK5.0之前的Comparable
package java.lang;
public interface Comparable {
public int compareTo(Object o);
}
JDK5.0之后的Comparable
package java.lang;
public interface Comparable<T> {
public int compareTo(T o);
}
这里的<T>表示泛型类型,随后可以传入具体的类型来替换它。
1、声明好泛型类型之后,集合中只能存放特定类型元素
2、泛型类型必须是引用类型
3、使用泛型后取出元素不需要类型转换
三、泛型方法
需求:写一个函数,调用者传递什么类型的变量,该函数就返回什么类型的变量?
实现一
由于无法确定具体传递什么类型的数据,那么方法的形参就定义为Object类型,返回值也就是Object类型。但是使用该函数时需要强制类型转换。
private Object getDate(Object obj) {
return obj;
}
当不进行强制类型转换能否写出该功能.?
—— 使用泛型类解决
泛型: 就是将类型当作变量处理。规范泛型的定义一般是一个大写的任意字母。
函数上的泛型定义
当函数中使用了一个不明确的数据类型,那么在函数上就可以进行泛型的定义。
public <泛型的声明> 返回值类型 函数名( 泛型 变量名 ){}
// 例如
public class Demo {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
System.out.println(new Demo().getData(arr[0]));
}
public <T> T getData(T t) {
return t;
}
}
细节:
使用泛型方法前需要进行泛型声明,使用一对尖括号 <泛型>,声明的位置在static后返回值类型前。
当一个类中有多个函数声明了泛型,那么该泛型的声明可以声明在类上。
四、泛型类
格式
// 类上的泛型声明
修饰符 class 类名<泛型>{}
public class Demo<T> {
public <T> T getData(T t) {
return t;
}
// 反序任意类型数组 两两交换
public void reverse(T[] arr) {
int end = arr.length - 1;
for (int i = 0; i < arr.length; i++) {
if (i < end - i) {
T temp = arr[i];
arr[i] = arr[end - i];
arr[end - i] = temp;
}
}
}
}
1、泛型中的继承
2、在泛型类中定义静态方法
注意:静态方法不可以使用类中定义的泛型
因为类中的泛型需要在对象初始化时指定具体的类型,而静态优先于对象存在。那么类中的静态方法就需要单独进行泛型声明,声明泛型一定要写在static后,返回值类型之前
泛型类细节:
- 创建对象的时候要指定泛型的具体类型
- 创建对象时可以不指定泛型的具体类型(和创建集合对象一样)。默认是Object,例如我们使用集合存储元素的时候没有使用泛型就是那么参数的类型就是Object
- 类上面声明的泛型只能应用于非静态成员函数,如果静态函数需要使用泛型,那么需要在函数上独立声明。
- 如果建立对象后指定了泛型的具体类型,那么该对象操作方法时,这些方法只能操作一种数据类型。
- 既可以在类上的泛型声明,也可以在同时在该类的方法中声明泛型。
总结起来就是:在创建泛型对象时需要指定具体类型,如果没有指明,那就默认是object,指定了具体类型,那该方法就只能使用该指定的类型。
五、泛型接口
六、泛型通配符
需求:定义一个方法,接收一个集合对象(该集合有泛型),并打印出集合中的所有元素。
问题——例如集合对象如下格式:
使用通配符后,编译正常,程序运行正常
使用通配符和使用泛型比较
可以对类型进行范围限定
?extends E: 接收E类型或者E的子类型。(上边界)
?super E: 接收E类型或者E的父类型。(下边界)
(1)限定通配符的上边界
extends :接收Number 类型或者Number的子类型
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
(2)限定通配符的下边界
super:接收Integer 或者Integer的父类型
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
七、总结
JDK5中的泛型允许程序员在编写集合代码时,就限制集合的处理类型,从而把原来程序运行时可能发生问题,转变为编译时的问题,以此提高程序的可读性和稳定性。
注意:
泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。
但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。