共计 6530 个字符,预计需要花费 17 分钟才能阅读完成。
1、泛型概述
1.1、泛型由来
先来看一个案例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class FanXingDemo {public static void main(String[] args) {Collection arrayList = new ArrayList();
arrayList.add("java");
arrayList.add("php");
arrayList.add(100);
Iterator i=arrayList.iterator();
while(i.hasNext()){String s=(String)(i.next());
System.out.println(s);
}
}
}
运行结果:
程序在运行时发生了问题java.lang.ClassCastException。为什么会发生类型转换异常呢?我们来分析下:由于集合中什么类型的元素都可以存储。导致取出时强转引发运行时 ClassCastException。怎么来解决这个问题呢?
Collection 虽然可以存储各种对象,但实际上通常 Collection 只存储同一类型对象。例如都是存储字符串对象。因此在 JDK5 之后,新增了 泛型 (Generic) 语法,让你在设计 API 时可以指定类或方法支持泛型,这样我们使用 API 的时候也变得更为简洁,并得到了编译时期的语法检查。
泛型:可以在类或方法中预支地使用未知的类型。
1.2、使用泛型的好处
泛型带来了哪些好处呢?
将运行时期的 ClassCastException,转移到了编译时期变成了编译失败。
避免了类型强转的麻烦。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class FanXingDemo01 {public static void main(String[] args) {Collection<String> arrayList = new ArrayList<String>();
arrayList.add("java");
arrayList.add("php");
// 当集合明确类型后,存放类型不一致就会编译报错
//arrayList.add(100);
// 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
Iterator<String> i=arrayList.iterator();
while(i.hasNext()){// 当使用 Iterator<String> 控制元素类型后,就不需要强转了。获取到的元素直接就是 String 类型
String s=i.next();
System.out.println(s);
}
}
}
2、泛型定义及使用
我们在集合中会大量使用到泛型,这里来完整地学习泛型知识。
泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。
2.1、泛型类
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
2.1.1、格式
class 类名称 < 泛型标识:可以随便写任意标识号,标识指定的泛型的类型 >{private 泛型标识 /*(成员变量类型)*/ var;
.....
}
}
2.1.2、案例
public class Generic<T>{//key 这个成员变量的类型为 T,T 的类型由外部指定
private T key;
public Generic(T key) {// 泛型构造方法形参 key 的类型也为 T,T 的类型由外部指定
this.key = key;
}
public T getKey(){// 泛型方法 getKey 的返回值类型为 T,T 的类型由外部指定
return key;
}
}
注意:
此处 T 可以随便写为任意标识,常见的如 T、E、K、V 等形式的参数常用于表示泛型
在实例化泛型类时,必须指定 T 的具体类型
public class FanXingDemo02{public static void main(String args[]){// 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
// 传入的实参类型需与泛型的类型参数类型相同,即为 Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);
// 传入的实参类型需与泛型的类型参数类型相同,即为 String.
Generic<String> genericString = new Generic<String>("key_vlaue");
System.out.println("泛型测试 : key is" + genericInteger.getKey());
System.out.println("泛型测试 : key is" + genericString.getKey());
}
}
2.2、泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中
2.2.1、格式
interface 类名称 < 泛型标识:可以随便写任意标识号,标识指定的泛型的类型 >{public T next();
}
2.2.2、案例
// 定义一个泛型接口
public interface Generator<T> {public T next();
}
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{@Override
public T next() {return null;
}
}
/**
* 传入泛型实参时:* 定义一个生产器实现这个接口, 虽然我们只创建了一个泛型接口 Generator<T>
* 但是我们可以为 T 传入无数个实参,形成无数种类型的 Generator 接口。* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator<T>,public T next(); 中的的 T 都要替换成传入的 String 类型。*/
public class FruitGenerator implements Generator<String> {private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
2.3、泛型方法
在 java 中, 泛型类的定义非常简单,但是泛型方法就比较复杂了。
尤其是我们见到的大多数泛型类中的成员方法也都使用了泛型,有的甚至泛型类中也包含着泛型方法,这样在初学者中非常容易将泛型方法理解错了。
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。
2.3.1、格式
修饰符 < 代表泛型的变量 > 返回值类型 方法名(参数){ }
2.3.2、案例
// 这个类是个泛型类,在上面已经介绍过
public class Generic<T> {private T key;
public Generic(T key) {this.key = key;
}
// 我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
// 这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
// 所以在这个方法中才可以继续使用 T 这个泛型。
public T getKey() {return key;
}
}
public class FanXingDemo03 {/**
* 这才是一个真正的泛型方法。* 首先在 public 与返回值之间的 <T> 必不可少,这表明这是一个泛型方法,并且声明了一个泛型 T
* 这个 T 可以出现在这个泛型方法的任意位置.
* 泛型的数量也可以为任意多个
* 如:public <T,K> K showKeyName(Generic<T> container){
* ...
* }
*/
public <T> T showKeyName(Generic<T> container) {System.out.println("container key :" + container.getKey());
// 当然这个例子举的不太合适,只是为了说明泛型方法的特性。
T test = container.getKey();
return test;
}
public static void main(String[] args) {}}
public class FanXingDemo04 {public static <T> void out(T t) {System.out.println(t);
}
public static void main(String[] args) {out("findingsea");
out(123);
out(11.11);
out(true);
}
}
public class FanXingDemo04 {public static <T> void out(T... args) {for (T t : args) {System.out.println(t);
}
}
public static void main(String[] args) {out("findingsea", 123, 11.11, true);
}
}
class Fruit {@Override
public String toString() {return "fruit";
}
}
class Apple extends Fruit {@Override
public String toString() {return "apple";
}
}
class Person {@Override
public String toString() {return "Person";
}
}
class GenerateTest<T> {public void show_1(T t) {System.out.println(t.toString());
}
// 在泛型类中声明了一个泛型方法,使用泛型 E,这种泛型 E 可以为任意类型。可以类型与 T 相同,也可以不同。
// 由于泛型方法在声明的时候会声明泛型 <E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public <E> void show_3(E t) {System.out.println(t.toString());
}
// 在泛型类中声明了一个泛型方法,使用泛型 T,注意这个 T 是一种全新的类型,可以与泛型类中声明的 T 不是同一种类型。
public <T> void show_2(T t) {System.out.println(t.toString());
}
}
public class FanXingDemo05 {public static void main(String[] args) {Apple apple = new Apple();
Person person = new Person();
GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
//apple 是 Fruit 的子类,所以这里可以
generateTest.show_1(apple);
// 编译器会报错,因为泛型类型实参指定的是 Fruit,而传入的实参类是 Person
//generateTest.show_1(person);
// 使用这两个方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
// 使用这两个方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
3、泛型通配符
可以用 <T>、<K,V>、<T extends Number> 等进行泛型的声明。其中,<T extends Number> 的声明方式限定了 T 的范围,T 只能为 Number 的子类。
3.1、通配符
E – Element (在集合中使用,因为集合中存放的是元素)
T – Type(Java 类)K – Key(键)V – Value(值)N – Number(数值类型)?– 表示不确定的 java 类型(无限制通配符类型)Object – 是所有类的根类,任何类的对象都可以设置给该 Object 引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型 T、E 等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换。
3.2、通配符基本使用
泛型的通配符: 不知道使用什么类型来接收的时候, 此时可以使用?,? 表示未知通配符。
此时只能接受数据, 不能往该集合中存储数据。
import java.util.ArrayList;
import java.util.Collection;
public class FanXingDemo06 {public static void main(String[] args) {Collection<Integer> list1 = new ArrayList<Integer>();
getElement(list1);
Collection<String> list2 = new ArrayList<String>();
getElement(list2);
}
public static void getElement(Collection<?> coll){}}
3.3、受限泛型
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在 JAVA 的泛型中可以指定一个泛型的 上限 和下限。
泛型的上限:
格式:类型名称 <? extends 类 > 对象名称
意义:只能接收该类型及其子类
泛型的下限:
格式:类型名称 <? super 类 > 对象名称
意义:只能接收该类型及其父类型
比如:现已知 Object 类,String 类,Number 类,Integer 类,其中 Number 是 Integer 的父类
import java.util.ArrayList;
import java.util.Collection;
public class FanXingDemo07{public static void main(String[] args) {Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement1(list1);
//getElement1(list2);// 报错
getElement1(list3);
//getElement1(list4);// 报错
//getElement2(list1);// 报错
//getElement2(list2);// 报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是 Number 类型或者 Number 类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是 Number 类型或者 Number 类型的父类
public static void getElement2(Collection<? super Number> coll){}}