第三十四讲 初次认识泛型

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yerenyuan_pku/article/details/84189285

泛型的简单概述

泛型是JDK1.5版本以后出现的新特性。它用于解决安全问题,是一个类型安全机制。

泛型的由来

概念说完之后,我们来看看Java语言是如何引入泛型的。在JDK1.4版本之前,容器什么类型的对象都可以存储,但是在取出时,需要用到对象的特有内容时,这时需要做向下转型。比如下面的程序:

public class MyGenericDemo {
    public static void main(String[] args) {
        List list = new ArrayList();

        list.add("abc");
        list.add(4); // list.add(Integer.valueOf(4)); 由于集合没有做任何限定,任何类型都可以存放在其中

        for (Iterator it = list.iterator(); it.hasNext();) {
            String str = (String) it.next();
            System.out.println(str.length());
        }

    }
} 

程序在运行的时候发生了异常:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String,其主要原因是对象的类型不一致。
为了避免这个问题,所以程序员们只能主观上控制,往集合中存储的对象类型保持一致。JDK1.5以后解决了该问题,即在定义集合时,就直接明确集合中存储元素的具体类型,这样,编译器在编译时,就可以对集合中存储的对象类型进行检查,一旦发现类型不匹配,就编译失败。这个技术就是泛型技术。

泛型的格式

泛型的格式是通过<>来定义要操作的引用数据类型。

泛型的好处

泛型的好处:

  1. 将运行时期出现的问题(例如ClassCastException异常),转移到了编译时期,方便于程序员解决问题,让运行时期问题减少,安全;
  2. 避免了向下转型的麻烦;
  3. 优化了程序设计,解决了黄色警告线。

总结:泛型就是应用在编译时期的一项安全机制。

泛型的擦除

泛型的擦除用一句话来说就是:编译器通过泛型对元素类型进行检查,只要检查通过,就会生成class文件,但在class文件中,就将泛型标识去掉了。

泛型的表现

泛型技术在集合框架中应用的范围很大。那么什么时候需要写泛型呢?只要看到类或接口在描述的时候右边有定义<>,就需要泛型了。其实是容器在不明确操作元素的类型的情况下,对外提供了一个参数<>,使用容器时,只要将具体的类型实参传递给该参数即可。说白了,泛型就是传递类型参数。

使用泛型技术存储整数

例,创建一个List集合,用于存储整数,使用泛型技术。

package cn.liayun.generic.demo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class GenericDemo2 {

	public static void main(String[] args) {
		//创建一个List集合,存储整数。List接口和ArrayList类
		List<Integer> list = new ArrayList<Integer>();
		
		list.add(5);
		list.add(6);
		
		for (Iterator<Integer> it = list.iterator(); it.hasNext();) {
			Integer integer = it.next();
			System.out.println(integer);
		}
	}

}

使用泛型技术存储自定义对象

例,将Person对象存储到TreeSet集合中,同姓名同年龄的视为同一个人,不存;按照学生的姓名进行升序排序,而且当姓名相同时,需要按照学生的年龄进行升序排序。

分析:此题主旨就是复习一下TreeSet集合和Comparator接口,我们定义一个比较器实现Comparator接口,覆盖compare方法,将Comparator接口的实现类作为参数传递给TreeSet集合的构造函数。

先描述Person类,由于Person类的对象有可能存放到HashSet集合中,所以我们最好还是覆盖hashCode()和equals()方法。而且我们还让Person类实现了Comparable接口,强制让Person类具备比较性。

package cn.liayun.domain;

public class Person implements Comparable<Person> {
	private String name;
	private int age;

	public Person() {
		super();
	}

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public int hashCode() {
		final int NUMBER = 38;
		return this.name.hashCode() + age * NUMBER;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (!(obj instanceof Person)) {
			throw new ClassCastException("类型错误");
		}
		Person per = (Person)obj;
		return this.name.equals(per.name) && this.age == per.age;
	}

	@Override
	public int compareTo(Person o) {
		int temp = this.age - o.age; 
		return temp == 0 ? this.name.compareTo(o.name) : temp;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	
}

接着自定义一个比较器。

package cn.liayun.comparator;

import java.util.Comparator;

import cn.liayun.domain.Person;

public class ComparatorByName implements Comparator<Person> {

	@Override
	public int compare(Person o1, Person o2) {
		int temp = o1.getName().compareTo(o2.getName());
		return temp == 0 ? o1.getAge() - o2.getAge() : temp;
	}

}

最后编写一个测试类,进行测试。

package cn.liayun.generic.demo;

import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

import cn.liayun.comparator.ComparatorByName;
import cn.liayun.domain.Person;

public class GenericDemo3 {

	public static void main(String[] args) {
		Set<Person> set = new TreeSet<Person>(new ComparatorByName());
		
//		set = new HashSet<Person>();
		
		set.add(new Person("abcd", 20));
		set.add(new Person("abcd", 20));
		set.add(new Person("aa", 26));
		set.add(new Person("nba", 22));
		set.add(new Person("nba", 22));
		set.add(new Person("cba", 24));
		
		for (Person person : set) {
			System.out.println(person);
		}
	}

}

自定义泛型类

现在有两个类,分别为Student和Worker,如下:

  • Student.java

    package cn.liayun.domain;
    
    public class Student extends Person {
    
    	public Student() {
    		super();
    	}
    
    	public Student(String name, int age) {
    		super(name, age);
    	}
    
    	@Override
    	public String toString() {
    		return "Student [name = " + getName() +  ", age = " + getAge() + "]";
    	}
    	
    }
    
  • Worker.java

    package cn.liayun.domain;
    
    public class Worker extends Person {
    
    	public Worker() {
    		super();
    	}
    
    	public Worker(String name, int age) {
    		super(name, age);
    	}
    
    	@Override
    	public String toString() {
    		return "Worker [name=" + getName() + ", age=" + getAge() + "]";
    	}
    	
    }
    

现在我们需要创建一个用于操作Student对象的工具类,对对象进行设置和获取。其实想都不会想,我们就会这么做:

class Tool {
	private Student stu;

	public Student getStu() {
		return stu;
	}

	public void setStu(Student stu) {
		this.stu = stu;
	}
}

可是发现太有局限性了,这时我们就要想可不可以定义一个可以操作所有对象的工具呢?答案是可以的,当要操作的对象类型不确定的时候,为了扩展,可以使用Object类型来完成,但是这种方式有一些小弊端,会出现向下转型,向下转型容易在运行时期发生java.lang.ClassCastException。下面就是JDK1.4版本时(此时泛型技术还没出现)的代码:

//JDK1.4
class Tool {
	private Object obj;

	public Object getObj() {
		return obj;
	}

	public void setObj(Object obj) {
		this.obj = obj;
	}
	
}

JDK1.5以后,新的解决方案出现了。当类型不确定时,可以对外提供参数,由使用者通过传递参数的形式完成类型的确定。这样工具类的代码就可这样写为:

//在类定义时就明确参数,由使用该类的调用者来传递具体的类型。
class Util<W> {//泛型类
	private W obj;

	public W getObj() {
		return obj;
	}

	public void setObj(W obj) {
		this.obj = obj;
	}
}

以上就是一个自定义的泛型类。

小结

什么时候定义泛型类呢?当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展,现在定义泛型来完成扩展。

泛型方法

泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了,为了让不同方法可以操作不同类型,而且类型还不确定,那么可以将泛型定义在方法上。特殊之处:静态方法不可以访问类上定义的泛型。如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。

package cn.liayun.generic.demo;

public class GenericDemo {

	public static void main(String[] args) {
		Demo<String> d = new Demo<String>();
		d.show("abc");
		d.print("abc");
		d.print(6);
	}

}

class Demo<W> {
	public /*static*/ void show(W w) {//静态方法是无法访问类上定义的泛型的。如果静态方法需要定义泛型,泛型只能定义在方法上。
		System.out.println("show: " + w);
	}
	
	public static <A> void staticShow(A a) {
		System.out.println("static show: " + a);
	}
	
	public <Q> void print(Q w) {//泛型方法
		System.out.println("print: " + w);
	}
}

泛型接口

泛型可定义在接口上。如:

interface Inter<T> {//泛型接口
	public void show(T t);
}

泛型接口有两种实现方式,其中一种实现方式为:

package cn.liayun.generic.demo;

public class GenericDemo {

	public static void main(String[] args) {
		InterImpl in = new InterImpl();
		in.show("hello generic type");
	}

}

interface Inter<T> {//泛型接口
	public void show(T t);
}

class InterImpl implements Inter<String> {

	@Override
	public void show(String t) {
		System.out.println(t);
	}
	
}

第二种实现方式为:

package cn.liayun.generic.demo;

public class GenericDemo6 {

	public static void main(String[] args) {
		SubDemo d = new SubDemo();
		d.show("abc");
	}

}

interface Inter<T> {//泛型接口
	public void show(T t);
}

class InterImpl<W> implements Inter<W> {

	@Override
	public void show(W t) {
		System.out.println("show: " + t);
	}
	
}

class SubDemo extends InterImpl<String> {
	
}

泛型通配符

泛型是在限定数据类型,当在集合或者其他地方使用到泛型后,那么这时一旦明确泛型的数据类型,那么在使用的时候只能给其传递与数据类型相匹配的类型,否则就会报错。
在这里插入图片描述
上面调用方法语句属于语法错误,因为泛型限定不一致。方法要的是Collection<Object>类型,但传入的是List<Student>或者Set<String>,二者类型不匹配。
上述定义的printCollection方法中,由于定义的是打印集合的功能,应该是可以打印任意集合中的元素的。但定义方法时,根本无法确定具体集合中的元素类型是什么。为了解决这个”无法确定具体集合中的元素类型”问题,Java中为我们提供了泛型的通配符<?>。对上面的方法,进行修改后,实现了可迭代任意元素类型集合的方法。

private static void printCollection(Collection<?> coll) {//在不明确具体类型的情况下,可以使用通配符来表示。
	for (Iterator<?> it = coll.iterator(); it.hasNext();) {
		Object obj = it.next();
		System.out.println(obj);
	}
}

总结一下:当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

泛型的上限

泛型的限定有两种表现形式,上限和下限。其中上限的格式为:

? extends E:可以接收E类型或者E类型的子类型,泛型的上限

现在我们来演示泛型的上限在API中的体现,以TreeSet的构造函数——TreeSet(Collection<? extends E> c)为例来详细讲解泛型的上限。
TreeSet集合中有一个这样的构造方法:

public TreeSet(Collection<? extends E> c) { }

根据该构造方法可以构造一个带有内容的TreeSet集合,该例中所涉及到的3个类——Person、Student、Worker前文都有描述,继承体系为:
在这里插入图片描述

package cn.liayun.generic.demo;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeSet;

import cn.liayun.domain.Person;
import cn.liayun.domain.Student;

public class GenericDemo9 {

	public static void main(String[] args) {
		/*
		 * 演示泛型限定在api中的体现。
		 * TreeSet的构造函数。
		 * TreeSet(Collection<? extends E> coll);
		 * 
		 * 什么时候会用到上限呢?
		 * 一般往集合中存储元素时,如果集合中定义了E类型,通常情况下应该存储E类型的对象。
		 * 对于E的子类型的对象,E类型也可以接受,所以这时可以将泛型从E改为? extends E。
		 */
		Collection<Student> coll = new ArrayList<Student>();
		coll.add(new Student("abc1", 21));
		coll.add(new Student("abc2", 22));
		coll.add(new Student("abc3", 23));
		coll.add(new Student("abc4", 24));
		
		
		TreeSet<Person> ts = new TreeSet<Person>(coll);
		ts.add(new Person("abc8", 21));
		for (Iterator<Person> it = ts.iterator(); it.hasNext();) {
			Person person = it.next();
			System.out.println(person.getName());
		}
		
	}

}

class MyTreeSet<E> {
	MyTreeSet() {
		
	}
	MyTreeSet(Collection<? extends E> c) {
		
	}
}

小结

什么时候会用到泛型上限呢?一般往集合中存储元素时,如果集合定义了E类型,通常情况下应该存储E类型的对象。对E的子类型的对象也可以接收,所以这时可以将泛型从E改为? extends E。

泛型的下限

下限的格式为:

? super E:可以接收E类型或者E的父类型,泛型的下限

现在我们来演示泛型的下限在API中的体现,以TreeSet的构造函数——TreeSet(Comparator<? super E> comparator)为例来详细讲解泛型的下限。
TreeSet集合中有一个这样的构造方法:

publicTreeSet(Comparator<? super E> comparator) { }

该例中所涉及到的3个类——Person、Student、Worker前文都有描述,继承体系同上。

package cn.liayun.generic.demo;

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

import cn.liayun.domain.Person;
import cn.liayun.domain.Student;
import cn.liayun.domain.Worker;

public class GenericDemo10 {

	public static void main(String[] args) {
		/*
		 * 演示泛型限定在api中的体现。
		 * TreeSet的构造函数。
		 * TreeSet(Comparator<? super E> comparator);
		 * 
		 * 什么时候会用到下限呢?
		 * 当从容器中取出元素进行操作时,可以用E类型接收,也可以用E的父类型接收。
		 */
		//创建一个比较器。
		Comparator<Person> comp = new Comparator<Person>() {

			@Override
			public int compare(Person o1, Person o2) {
				int temp = o1.getAge() - o2.getAge();
				return temp == 0 ? o1.getName().compareTo(o2.getName()) : temp;
			}
			
		};
		
		TreeSet<Student> ts = new TreeSet<Student>(comp);
		ts.add(new Student("abc1", 21));
		ts.add(new Student("abc2", 28));
		ts.add(new Student("abc3", 23));
		ts.add(new Student("abc4", 25));
		
		TreeSet<Worker> ts1 = new TreeSet<Worker>(comp);
		ts1.add(new Worker("abc11", 21));
		ts1.add(new Worker("abc22", 27));
		ts1.add(new Worker("abc33", 22));
		ts1.add(new Worker("abc44", 29));
		
		for (Iterator<Student> it = ts.iterator(); it.hasNext();) {
			Student student = it.next();
			System.out.println(student);
		}
		
	}

}

class YouTreeSet<E> {
	YouTreeSet(Comparator<? super E> comparator) {
		
	}
}

小结

什么时候会用到泛型下限呢?当从容器中取出元素操作时,可以用E类型接收,也可以用E的父类型接收。

猜你喜欢

转载自blog.csdn.net/yerenyuan_pku/article/details/84189285