java数据结构09_泛型Generics

1.泛型的概述

泛型是JDK5.0以后增加的,他可以帮助我们建立类型安全的集合。在使用了泛型的中,不必进行强制类型转换。JDK提供了支持泛型的编译器,将运行时的类型检查提前到了编译时执行,使代码可读性和安全性更高。

l 泛型的引入

在JDK5.0之前,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

先看下面的代码:

ArrayList list = new ArrayList();
list.add("111");
list.add(222);
for (int i = 0; i < list.size(); i++) {
	String str = (String)list.get(i);
	System.out.println(str);
}

以上示例中,向list类型集合中加入了一个String类型的值和一个Integer类型的值。在之后的循环中,由于在list中加入了Integer类型的值,如果直接做强转操作会抛出java.lang.ClassCastException异常。为了解决这个问题,泛型应运而生。

  • 泛型的使用

泛型的本质就是“数据类型的参数化”。 我们可以把“泛型”理解为类型的一个占位符(形式参数),即告诉编译器,在调用泛型时必须传入实际类型。

【示例】泛型的使用案例

// 使用泛型,明确ArrayList存储的数据类型
ArrayList<String> list = new ArrayList<String>();
list.add("111"); // 只能添加String类型数据
list.add("222");
// list.add(333); 存储非String类型数据,编译失败
// 通过循环取出集合中的元素
for (int i = 0; i < list.size(); i++) {
	// 因为list明确了存储的数据类型,所以此处不用做强转
	String str = list.get(i);
	System.out.println(str);
}

泛型的出现避免了强制转换的麻烦,同时把运行时期的ClassCastException异常提前到编译时期。

  • 泛型擦除补偿

java分两个部分,编译和运行,泛型的出现提高了编译的安全性,正因为编译时检查完了,运行时才没事。因此泛型本质上是编译时期的技术,是给编译器用的。

程序在运行时,会将泛型擦除掉(擦除类型参数),生成的class****文件只保留原始类型(不带泛型),这个称为泛型擦除。

为什么要擦除呢?为了兼容运行的类加载器!运行的时候,要靠虚拟机启动类装载器,专门读取类文件解析类文件,并把类文件加载进内存的程序。JDK1.4版本,JDK1.5版本都用的这个加载器。程序只在编译的时候,将类型进行检查。如果检查完是安全的,没有错误的,运行就可以把这个泛型去掉了,因为类型就统一了。我们还想要以前的类加载器进行加载解析,并进内存。

有一个问题,取出来的时候不加强转了,你还知道取出来的是什么吗?编译器只做检查,检查完,最终这个类型被擦掉,所以这个里面和以前一样还是object,(这里应该是指,add方法中加入的元素变成了object类型了),那就意味着它们提升了。同时,下面还不能做强转,取出来就能用,这是为什么呢?

这里又做了另外一种动作:**在运行时,通过获取元素的类型进行转换动作,叫做补偿(不必手动实现强制转换)。**编译结束后,要是完全不告诉类装载器不合适,用了泛型补偿机制告诉类加载器,都检测完了,就编写一个补偿程序。在类装载器已有的基础上,来了以后,我根据你指定的好的元素类型,真正运行的时候就要碰到这个元素了,根据你的元素获取其类型,再对你进行一次转换就行了。

2.泛型的使用

泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中,将数据类型作为参数进行传递。

  • 含有泛型的类

定义格式:修饰符 class 类名<代表泛型的变量> { }

【示例】泛型类的定义

class Box<E> {
	private E obj;
	public void set(E obj) {
		this.obj = obj;
	}
	public E get() {
		return this.obj;
	}
}

使用格式:创建对象时,确定泛型的类型

【示例】泛型类的使用

public static void main(String[] args) {
	// 创建对象时,确定泛型的类型
	Box<Integer> box = new Box<Integer>();
	// 参数只能为Integer类型
	box.set(new Integer(11));
	// 此处返回的类型就是Integer,不用进行强转
	Integer b = box.get();
}

此时,变量 E 的值就是 Integer 类型。

注意:静态方法不能使用类上定义的泛型。

  • 含有泛型的方法

定义格式:修饰符 <代表泛型的变量> 返回值类型 方法名(形参列表){}

【示例】泛型方法的定义

class Box {
	public <T> void method(T obj) {
		System.out.println(obj);
System.out.println("method: " + obj.toString());
		// 注意:泛型对象只能使用Object类中的方法,不能使用其特有的方法
		// System.out.println("method: " + obj.charAt());
	}
}

使用格式:调用方法时,确定泛型的类型

【示例】泛型方法的使用

public static void main(String[] args) {
	Box box = new Box();
	// 此时,变量T的值就是String类型。
	box.method("111");
	// 此时,变量T的值就是Integer类型。
	box.method(222);
}

注意:泛型对象只能使用Object类中的方法,不能使用其特有的方法。

  • 含有泛型的接口

定义格式:修饰符 interface 接口名<代表泛型的变量> { }

【示例】泛型接口的定义

interface impl<E> {
	void method(E e);
}

使用格式:

1、 定义类时确定泛型的类型。

class InterImpl implements impl<String> {
	@Override
	public void method(String e) {
		System.out.println(e);
	}
}
public class Demo {
	public static void main(String[] args) {
		InterImpl imp = new InterImpl();
		imp.method("222");
	}
}

此时,变量 E 的值就是 String 类型。

2、 始终不确定泛型的类型,直到创建对象时,确定泛型的类型。

class InterImpl<E> implements impl<E> {
	@Override
	public void method(E e) {
		System.out.println(e);
	}
}
public class Demo {
	public static void main(String[] args) {
		InterImpl<String> imp = new InterImpl<String>();
		imp.method("111");
	}
}

此时,变量 E 的值就是 String 类型。

  • 泛型通配符

泛型是在限定数据类型,当在集合或者其他地方使用到泛型后,那么这时一旦明确泛型的数据类型,那么在使用的时候只能给其传递和数据类型匹配的类型,否则就会报错。

有的时候,我们在定义方法时,根本无法确定具体集合中的元素类型是什么。为了解决这个“无法确定具体集合中的元素类型”问题,java 中,为我们提供了泛型的通配符。

通配符的几种形式:

1、无限定通配符,<?>。

2、上限通配符,<? extends Number>。表示参数类型只能是Number及其子类。

3、下限通配符,<? supper Number>。表示参数类型只能是Number及其父类。

【示例】无限定通配符案例

class Person<E> {
	private E date;
	public void setDate(E date) {
		this.date = date;
	}
	public E getDate() {
		return this.date;
	}
}
public class GenericDemo {
	public static void main(String[] args) {
		Person<String> p1 = new Person<String>();
		p1.setDate("good date");
		print(p1);
		
		Person<Integer> p2 = new Person<Integer>();
		p2.setDate(1314);
		print(p2);
	}
	// 无限定通配符使用
	public static void print(Person<?> stu) { 
		System.out.println(stu.getDate());
	}
}

上面的示例代码中,print()方法的参数使用了通配符,得以传入泛型类Person 任意类型的参数。如果,将通配符改成具体的一种类型,例如String类型。那print()方法就只能传入泛型类Person 类型的参数。

【示例】上限通配符案例

// 父类
class Person {
	private String name;
	public Person(String name) {
		this.name = name;
	}
	public String getName() {
		return this.name;
	}
}
// 子类
class Student extends Person {
	public Student(String name) {
		super(name);
	}
}
class Worker extends Person {
	public Worker(String name) {
		super(name);
	}
}
// 实现类
public class GenericDemo {
	public static void main(String[] args) {
		ArrayList<Student> list1 = new ArrayList<Student>();
		list1.add(new Student("小明"));
		print(list1);
		
		ArrayList<Worker> list2 = new ArrayList<Worker>();
		list2.add(new Worker("小花"));
		print(list2);
	}
	// 上限通配符使用,表示参数类型只能是Person及其子类
	public static void print(Collection<? extends Person> list) { 
		System.out.println(list);
	}
}

【示例】下限通配符案例

// 父类
class Person {
	private String name;
	public Person(String name) {
		this.name = name;
	}
	public String getName() {
		return this.name;
	}
}
// 子类
class Student extends Person {
	public Student(String name) {
		super(name);
	}
}
class Worker extends Person {
	public Worker(String name) {
		super(name);
	}
}
// 实现类
public class GenericDemo {
	public static void main(String[] args) {
		ArrayList<Student> list1 = new ArrayList<Student>();
		list1.add(new Student("小明"));
		print(list1);
		
		ArrayList<Person> list2 = new ArrayList<Person>();
		list2.add(new Person("小花"));
		print(list2);
	}
	// 上限通配符使用,表示参数只能是Student及其父类
	public static void print(Collection<? super Student> list) { 
		System.out.println(list);
	}
}

ps:如需最新的免费文档资料和教学视频,请添加QQ群(627407545)领取。

发布了35 篇原创文章 · 获赞 0 · 访问量 359

猜你喜欢

转载自blog.csdn.net/zhoujunfeng121/article/details/104535616