一. 概述
1. 什么是泛型
泛型,即“参数化类型”,就是将所要操作的数据类型指定为一个参数,然后在使用时传入具体的类型。
泛型可使用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
2. 为什么要使用泛型
(1)引入
- 未使用泛型
import java.util.*;
class GenericDemo{
public static void main(String[] args) {
ArrayList al=new ArrayList();
al.add("abc01");
al.add("abc0991");
al.add(4); //al.add(new Integer(4));
Iterator it=al.iterator();
while (it.hasNext()){
//强制类型转换
String s=(String)it.next();
System.out.println(s+": "+s.length());
}
}
}
运行结果是:
编译成功,但运行失败;在编译时未发现类型转换异常
- 使用泛型
import java.util.*;
class GenericDemo{
public static void main(String[] args) {
//使用泛型
ArrayList<String> al=new ArrayList<String>();
al.add("abc01");
al.add("abc0991");
al.add(4); //al.add(new Integer(4));
//使用泛型
Iterator<String> it=al.iterator();
while (it.hasNext()){
//取消强制转换
String s=it.next();
System.out.println(s+": "+s.length());
}
}
}
运行结果是:
将运行时期出现的问题ClassCastException提前到编译时期,也避免了强制转换的麻烦
(2)使用泛型的意义
- 将运行时错误提前到编译时错误
- 不需要强制进行类型转换
- 当多种数据类型执行相同代码时,可提高代码复用率
二. 泛型的使用
格式:通过<>来定义要操作的数据的类型
1. 定义在集合框架上
当使用集合时,将集合中要存储的元素的数据类型作为参数传递到<>中
(1)泛型写法
在以下这些地方写入泛型标识
- 定义集合时
- 定义迭代器时
- 实现Comparable接口时(TreeSet的自然排序)
- 定义比较器时(TreeSet的比较器排序)
(2)以TreeSet的比较器排序为例
需求:往TreeSet中存入字符串对象,按照字符串对象长度降序排序并迭代输出
import java.util.*;
class GenericDemo{
public static void main(String[] args) {
//1. 定义集合时写入泛型标识
TreeSet<String> ts=new TreeSet<String>(new LenComparator());
ts.add("abcd");
ts.add("cc");
ts.add("cba");
ts.add("aaa");
ts.add("z");
ts.add("hahaha");
//2. 定义迭代器时写入泛型标识
Iterator<String> it=ts.iterator();
while (it.hasNext()){
String s=it.next();//不需要强制转换
System.out.println(s);
}
}
}
//3. 定义比较器写入泛型标识
class LenComparator implements Comparator<String>{
public int compare(String o1, String o2) {
//按照字符串的长度降序排序
int num=new Integer(o2.length()).compareTo(new Integer(o1.length()));
if(num==0){
return o2.compareTo(o1);
}
return num;
}
}
运行结果是:
2. 泛型类
当类中要操作的数据类型不确定的时候,早期定义Object来完成扩展,现在定义泛型来完成扩展。
(1)泛型前的做法(使用Object)
class Worker{
}
class Student{
}
class Tool{
//使用Object类进行扩展
private Object obj;
public void setObj(Object obj){
this.obj=obj;
}
public Object getObj(){
return obj;
}
}
class GenericDemo{
public static void main(String[] args) {
Tool t=new Tool();
//设置对象Student
t.setObj(new Student());
//进行强制转换
Student s=(Student)t.getObj(); //编译、运行都通过
Worker w=(Worker)t.getObj(); //编译通过、运行报错
}
}
运行结果是:
必须进行类型强制转换;
在运行时期才发现类型转换异常
(2)泛型写法
在以下这些地方写入泛型标识
- 定义类时
- 定义类中的成员变量时
- 构造方法的形参
- 获取方法的返回值
//1. 定义类时
public class Generic<T>{
//2. 定义类中的成员变量时
private T key;
//3. 定义构造方法时
public Generic(T key){
this.key=key;
}
//4. 定义获取方法时
public T getKey(){
return key;
}
}
(3)泛型类的例子
class Worker{
}
class Student{
}
//1. 定义类时写入泛型标识
class Tool<QQ>{
//2. 定义成员变量时写入泛型标识
private QQ q;
//3. 定义构造函数时写入泛型标识
public void setObject(QQ q){
this.q=q;
}
//4. 定义获取方法时写入泛型标识
public QQ getObject(){
return q;
}
}
class GenericDemo{
public static void main(String[] args) {
//泛型实例化
Tool<Student> u=new Tool<Student>();
//设置对象Student
u.setObject(new Student());
Student s=u.getObject(); //不需要进行强制转换
Worker w=u.getObject(); //编译时期即报错
}
}
运行结果是:
将运行时期出现的问题ClassCastException提前到编译时期;
不需要强制进行类型转换
3. 泛型接口
(1)泛型写法
//1. 定义一个泛型接口
public interface Inter<T>{
public void show(T t);
}
//2. 实现泛型接口的类,未实例化泛型参数;在新建类对象时才实例化泛型参数
class InterImpl1<T> implements Inter<T>{
public void show(T t){};
}
//3. 实现泛型接口的类,并实例化泛型参数
class InterImpl2 implements Inter<String>{
public void show(String s){};
}
(2)例子:实现泛型接口的类,未实例化泛型参数
//1. 定义一个泛型接口
interface Inter<T>{
void show(T t);
}
//2. 实现泛型接口的类,未实例化泛型参数
class InterImpl1<T> implements Inter<T>{
public void show(T t){
System.out.println("show: "+t);
}
}
//3. 新建类对象时,将泛型参数实例化,传入接口操作的数据类型
class GenericDemo{
public static void main(String[] args) {
//传入接口操作的数据类型
InterImpl1<Integer> i=new InterImpl1<Integer>();
i.show(4);
}
}
运行结果是:
(3)例子:实现泛型接口的类,并实例化泛型参数
//1. 定义一个泛型接口
interface Inter<T>{
void show(T t);
}
//2. 实现泛型接口的类,并实例化泛型参数,传入接口操作的数据类型
class InterImpl2 implements Inter<String>{
public void show(String t){
System.out.println("show: "+t);
}
}
class GenericDemo{
public static void main(String[] args) {
InterImpl2 i=new InterImpl2();
i.show("haha");
}
}
运行结果是:
4. 泛型方法
(1)泛型类的局限性
泛型类定义的泛型,在整个类中有效。
当泛型类的对象明确要操作的数据类型后,泛型类中的方法所能操作的数据类型就已经固定了,同一个泛型类中的不同方法不能操作不同的数据类型。
//泛型类
class Demo<T>{
//这个方法使用了泛型,但并不是泛型方法,它的参数类型是泛型类声明过的泛型T
public void show(T t){
System.out.println("show: "+t);
}
//这个方法使用了泛型,但并不是泛型方法,它的参数类型是泛型类声明过的泛型T
public void print(T t){
System.out.println("print: "+t);
}
}
class GenericDemo{
public static void main(String[] args) {
//类Demo定义为Integer类型
Demo<Integer> d=new Demo<Integer>();
//方法show操作Integer类型
d.show(new Integer(4));
//方法print操作String类型,编译失败
d.print("haha");
}
}
运行结果是:
(2)泛型方法的基本写法
为了让不同方法可以操作不同数据类型,并且类型还不确定,可以声明泛型方法。
泛型方法的基本写法:
在关键字public、static和返回值类型中间加上<泛型标识>
(3)非泛型类中声明泛型方法
class Demo{
//声明了泛型方法show,使用泛型T
public<T> void show(T t){
System.out.println("show: "+t);
}
//声明了泛型方法print,使用泛型T,这个T可以与泛型方法show中声明的T不是同一种类型
public<T> void print(T t){
System.out.println("print: "+t);
}
}
class GenericDemo{
public static void main(String[] args) {
Demo d=new Demo();
//使用泛型方法时不需要指明参数类型,因为编译器会找出具体的类型(参数类型推断)
d.show("haha");
d.show(new Integer(4));
d.print("heihei");
d.print(5);
}
}
运行结果是:
(4)泛型类中声明泛型方法
//泛型类
class Demo<T>{
//这个方法使用了泛型,但并不是泛型方法,它的参数类型是泛型类声明过的泛型T
public void show(T t){
System.out.println("show: "+t);
}
//声明了泛型方法print,使用泛型T,这个T可以与泛型类中声明的T不是同一种类型
public<T> void print(T t){
System.out.println("print: "+t);
}
}
class GenericDemo{
public static void main(String[] args) {
//类Demo定义为String类型
Demo<String> d=new Demo<String>();
//非泛型方法show中操作2种不同的数据类型,编译失败
d.show("haha");
d.show(4);
}
}
运行结果是:
//泛型类
class Demo<T>{
//这个方法使用了泛型,但并不是泛型方法,它的参数类型是泛型类声明过的泛型T
public void show(T t){
System.out.println("show: "+t);
}
//声明了泛型方法print,使用泛型T,这个T可以与泛型类中声明的T不是同一种类型
public<T> void print(T t){
System.out.println("print: "+t);
}
}
class GenericDemo{
public static void main(String[] args) {
//类Demo定义为String类型
Demo<String> d=new Demo<String>();
d.show("haha");
//泛型方法print中操作2种不同的数据类型,运行成功
d.print("heihei");
d.print(4);
}
}
运行结果是:
(5)静态方法无法访问类上定义的泛型
//泛型类
class Demo<T>{
//静态方法method使用泛型类上声明的泛型T
public static void method(T t){
System.out.println("method: "+t);
}
}
class GenericDemo{
public static void main(String[] args) {
Demo<String> d=new Demo<String>();
Demo.method("hehe");
}
}
运行结果是:
编译失败的原因:
静态方法在类加载到内存的时候一起加载,而在这个时候,T的类型还没有确定(T的类型在类对象创建时才确定),所以会报错
(6)静态方法要使用泛型,必须定义成泛型方法
//泛型类
class Demo<T>{
//声明了静态的泛型方法,使用泛型T,这个T可以与泛型类中声明的T不是同一种类型
public static<T> void method(T t){
System.out.println("method: "+t);
}
}
class GenericDemo{
public static void main(String[] args) {
Demo<String> d=new Demo<String>();
Demo.method("hehe");
Demo.method(5);
}
}
运行结果是:
(7)小结
- 方法中使用了泛型,该方法不一定是泛型方法
- 泛型方法解决了泛型类中不同方法只能操作同一种数据类型的局限性
- 使用泛型方法时不需要指明参数类型,因为编译器会找出具体的类型(参数类型推断)
- 静态方法要使用泛型,必须定义成泛型方法,无法访问泛型类上声明的泛型标识
5. 泛型通配符?
(1)引入
需求:定义一个打印方法printColl,可以打印存储String类型元素或者Integer类型元素的ArrayList
- 使用泛型限定T
import java.util.*;
class GenericDemo{
public static void main(String[] args){
ArrayList<String> al=new ArrayList<String>();
al.add("abc1");
al.add("abc2");
al.add("abc3");
ArrayList<Integer> al1=new ArrayList<Integer>();
al1.add(4);
al1.add(7);
al1.add(1);
printColl(al);
printColl(al1);
}
//printColl方法使用泛型限定<T>
public static<T> void printColl(ArrayList<T> al){
Iterator<T> it=al.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
运行结果是:
- 使用泛型通配符?
import java.util.*;
class GenericDemo{
public static void main(String[] args){
ArrayList<String> al=new ArrayList<String>();
al.add("abc1");
al.add("abc2");
al.add("abc3");
ArrayList<Integer> al1=new ArrayList<Integer>();
al1.add(4);
al1.add(7);
al1.add(1);
printColl(al);
printColl(al1);
}
//printColl方法使用泛型通配符?
public static void printColl(ArrayList<?> al) {
Iterator<?> it=al.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
运行结果是:
(2)泛型标识T与通配符?的区别
- T代表的是具体类型,可以被接收并操作
- ?代表的是不明确类型,不可以被接收并操作,也不可以使用类型特定方法
public static<T> void printColl(ArrayList<T> al){
Iterator<T> it=al.iterator();
while(it.hasNext()){
//T可以被接收并操作
T t=it.next();
System.out.println(t);
}
}
public static void printColl(ArrayList<?> al){
Iterator<?> it=al.iterator();
while(it.hasNext()){
//错误操作,?不可以被接收并操作
? t=it.next();
System.out.println(t);
}
}
public static void printColl(ArrayList<?> al){
Iterator<?> it=al.iterator();
while(it.hasNext()){
//错误操作,因为?为不明确类型,而length()是类型特有方法,如Integer类中就无length()
System.out.println(it.next().length());
}
}
public static void printColl(ArrayList<?> al){
Iterator<?> it=al.iterator();
while(it.hasNext()){
//正确操作,因为toString()不是类型特有方法,所有类型都具有该方法
System.out.println(it.next().toString());
}
}
(3)通配符?的应用
引出泛型限定
6. 泛型限定
(1)引入
需求:
一个集合ArrayList中接收Person类型的元素,另一个集合ArrayList中接收Student类型的元素,Student为Person的子类;
要求printColl()能接收Person和Person的子类
分析:
要求printColl()能接收多种类型的参数,用泛型通配符?解决,即printColl()的参数为ArrayList<?>;
限定printColl()只能接收Person和Person的子类,用泛型限定来解决
import java.util.*;
class Person{
private String name;
Person(String name){
this.name=name;
}
public String getName(){
return name;
}
}
class Student extends Person{
Student(String name) {
super(name);
}
}
class GenericDemo{
public static void main(String[] args) {
ArrayList<Student> al1=new ArrayList<Student>();
al1.add(new Student("s1"));
al1.add(new Student("s2"));
al1.add(new Student("s3"));
ArrayList<Person> al2=new ArrayList<Person>();
al2.add(new Person("p1"));
al2.add(new Person("p2"));
al2.add(new Person("p3"));
printColl(al1);
printColl(al2);
}
//不知道打印的是Person类型还是Student类型,用泛型?解决
//且要求只打印Person和Person的子类,用泛型限定解决
private static void printColl(ArrayList<? extends Person> al) {
Iterator<? extends Person>it=al.iterator();
while (it.hasNext()){
System.out.println(it.next().getName());
}
}
}
运行结果是:
(2)泛型限定的定义
? extends E:可以接收E类型或E的子类型(E为接收类型的上限)
? super E:可以接收E类型或E的父类型(E为接收类型的下限)
(3)应用:TreeSet比较器中使用泛型限定
需求:
一个集合TreeSet中接收Student类型的元素,另一个集合TreeSet中接收Worker类型的元素;
Student和Worker都是Person的子类;
使用比较器排序的方法对2个TreeSet进行排序
分析:
2个TreeSet接收的是不同类型的元素,需要用到泛型;
使用到泛型标识的地方包括创建集合TreeSet时、定义迭代器时、定义比较器时;
根据 TreeSet (Comparator<? super E> comparator ) 这个构造方法,当指定了TreeSet所操作元素的类型E,TreeSet使用的可以是E的比较器或是E父类的比较器,因此2个不同的TreeSet可以共用父类Person的比较器
import java.util.*;
class Person{
private String name;
Person(String name){
this.name=name;
}
public String getName(){
return name;
}
}
class Student extends Person{
Student(String name) {
super(name);
}
}
class Worker extends Person{
Worker(String name) {
super(name);
}
}
//定义比较器,用到泛型<Person>
class Comp implements Comparator<Person>{
public int compare(Person o1, Person o2) {
return o1.getName().compareTo(o2.getName());
}
}
class GenericDemo{
public static void main(String[] args) {
/*
此处调用TreeSet集合的构造函数,其中指定E为Student;
则可以使用Student的父类Person的比较器
*/
TreeSet<Student> ts1=new TreeSet<Student>(new Comp());
ts1.add(new Student("abc03"));
ts1.add(new Student("abc02"));
ts1.add(new Student("abc06"));
ts1.add(new Student("abc01"));
/*
此处调用TreeSet集合的构造函数,其中指定E为Worker;
则可以使用Student的父类Person的比较器
*/
TreeSet<Worker> ts2=new TreeSet<Worker>(new Comp());
ts2.add(new Worker("wabc--03"));
ts2.add(new Worker("wabc--02"));
ts2.add(new Worker("wabc--06"));
ts2.add(new Worker("wabc--01"));
Iterator<Student> it1=ts1.iterator();
while (it1.hasNext()){
System.out.println(it1.next().getName());
}
Iterator<Worker> it2=ts2.iterator();
while (it2.hasNext()){
System.out.println(it2.next().getName());
}
}
}
运行结果是: