泛型是jdk5引入的类型机制,将类型参数化。泛型机制将类型转换时的类型检查从运行时提前到了编译时,使用泛型编写的代码比杂乱的使用object并在需要时再强制类型转换的机制具有更好的可读性和安全性。在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
一、举例
// ArrayList存放任意类型
public static void main(String[] args) {
List lists= new ArrayList<>();
lists.add("AA");
lists.add(111);
for (Object object:lists) {
System.out.println("测试结果:"+(String)object);
}
}
运行结果:
测试结果:aaaa
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
上面例子中分别添加了String类型和Integer类型,获取数据时都用String类型来获取,所以程序会出现类型错误的异常。为了能在编译器就解决类似这样的问题,所以需要使用泛型。
上面list重新初始化:
List<String> lists= new ArrayList<String>();
//在编译期就会报错
lists.add(111);
二、特性
泛型只在编译阶段有效,在编译之后程序会采取去泛型化的措施也就是泛型擦除,泛型信息不会进入到运行时阶段。泛型类型在逻辑上可以看成是多个不同的类型,实际上都是相同的基本类型。也就是说jvm中没有泛型对象,只有普通对象。
//擦除方式 定义部分,即尖括号中间的部分直接擦除。
public class GenericClass<T extends Comparable>{}
//擦除后:
public class GenericClass{}
public static void main(String[] args) {
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
if(classStringArrayList.equals(classIntegerArrayList)){
System.out.println("泛型测试类型相同");
}
}
// 运行结果:
泛型测试类型相同
三、使用方式
使用方式分为泛型类、泛型接口、泛型方法、类型通配符。
1.泛型类
用于类的定义中被称为泛型类。泛型类是在类名后面添加类型参数的声明,在实例化类的时候指明泛型的具体类型。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
//类名称<泛型标识:随意写,常用的有T、E、K、V>
public class GenericVO<T> {
//T为外部调用的时候定义的类型
public T key;
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
// 泛型类
public static void main(String[] args) {
GenericVO<Integer> genericVOInteger = new GenericVO<>();
genericVOInteger.setKey(111);
System.out.println("int类型取值:"+genericVOInteger.getKey());
GenericVO<String> genericVOString = new GenericVO<>();
genericVOString.setKey("QQQ");
System.out.println("string类型取值:"+genericVOString.getKey());
GenericVO genericVO1 = new GenericVO();
genericVO1.setKey(11.44);
System.out.println("1数据为:"+ genericVO1.getKey());
GenericVO genericVO2 = new GenericVO();
genericVO2.setKey("aaa");
System.out.println("2数据为:"+ genericVO2.getKey());
GenericVO<Double> genericVO3 = new GenericVO();
genericVO3.setKey(11.5555);
System.out.println("3数据为:"+ genericVO3.getKey());
}
}
运行结果:
int类型取值:111
string类型取值:QQQ
1数据为:11.44
2数据为:aaa
3数据为:11.5555
2.泛型接口
泛型接口与泛型类的使用类似。
/**
* 定义一个泛型接口
* @param <T>
*/
public interface GeneratorService<T>{
T test();
}
/**
* 实现泛型的类没有传入实参时,在声明类的时候把泛型声明一起加到类中。
* @param <T>
*/
public class GeneratorServiceImpl<T> implements GeneratorService<T> {
@Override
public T test() {
return null;
}
}
/**
* 实现类传入泛型实参字符串类型
*/
public class GeneratorStringServiceImpl implements GeneratorService<String>{
@Override
public String test() {
return "hello";
}
}
3.类型通配符
类型通配符一般使用 ? 代表的是具体的类型实参。如果定义的类型不确定时可以使用 ? 。
public static void main(String[] args) {
GenericVO<Integer> genericVOInteger = new GenericVO<>();
getKeyValue(genericVOInteger);
}
//定义泛型通配符 ?
private static void getKeyValue(GenericVO<?> obj){
System.out.println("key value:"+ obj.getKey());
}
4.泛型方法
泛型方法,在调用方法的时候指明泛型的具体类型。 泛型方法定义规则:
- 1.所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),声明部分在方法返回类型之前。
- 2.参数声明部分多个类型参数,使用逗号隔开。一个泛型参数(一个类型变量),是用于指定一个泛型类型名称的标识符。
- 3.类型参数可用来声明返回值类型。
- 4.泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型(包装类),不能是原始类型(int,double,char等)。
4.1.泛型方法的基本用法
/**
* 简单的泛型方法示例
*/
public class GenericMethodTest {
/**
* 泛型方法 printArray
* @param genericVO 传入的泛型实参
* @param <T> 返回值为T类型
*/
public static <T> T getKey(GenericVO<T> genericVO) {
// 输出数组元素
System.out.println("输出key value:"+ genericVO.getKey());
T test = genericVO.getKey();
return test;
}
/**
* 普通的方法,使用了泛型类做参数
* @param genericVO 传入的泛型实参
*/
public static void getKeyMethod(GenericVO<String> genericVO) {
// 输出数组元素
System.out.println("输出key value:"+ genericVO.getKey());
}
public static void main(String args[]) {
GenericVO genericVO = new GenericVO();
genericVO.setKey("hello");
getKey(genericVO);
getKeyMethod(genericVO);
}
}
4.2.泛型类中的泛型方法
泛型方法可以出现杂任何地方和任何场景中使用。泛型类中的泛型方法属于一种特殊情况。
/**
* 泛型类中的泛型方法
*/
public class GenericHello {
static class GenericVO {
@Override
public String toString() {
return "aa";
}
}
static class Aa extends GenericVO {
@Override
public String toString() {
return "bb";
}
}
static class Bb {
@Override
public String toString() {
return "cc";
}
}
static class GenerateTest<T> {
public void show_1(T t) {
System.out.println(t.toString());
}
//泛型类中声明泛型方法,泛型T可以任意类型
public <T> void show_2(T t) {
System.out.println(t.toString());
}
//泛型类中声明泛型方法,泛型E可以任意类型
//泛型类中没有声明泛型<E>,由于泛型方法在声明的时候会声明泛型,编译器可以正确识别泛型方法中识别的泛型.
public <E> void show_3(E t) {
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Aa aa = new Aa();
Bb bb = new Bb();
GenerateTest<GenericVO> generateTest = new GenerateTest<>();
//aa是GenericVO的子类
generateTest.show_1(aa);
//编译器会报错
//generateTest.show_1(bb);
generateTest.show_2(aa);
generateTest.show_2(bb);
generateTest.show_3(aa);
generateTest.show_3(bb);
}
}
4.3.泛型方法与可变参数
//泛型方法与可变参数
public static <T> void printKeyValue(T... args){
System.out.println("泛型方法与可变参数key value:");
for(T t : args){
System.out.print(t+" ");
}
}
//演示结果:
泛型方法与可变参数key value:
111 QQQ aaa 11.5555
4.4.有界的参数类型
使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制。要声明一个有界的类型参数,列出类型参数的名称,后跟extends关键字,后面跟着它的上界。
/**
* 泛型方法 有界的参数类型
*/
public class MaximumTest {
// 比较三个值并返回最大值
//<T extends Comparable>,表示的是Comparable及其子类型。
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
// 假设x是初始最大值
T max = x;
if (y.compareTo(max) > 0) {
//y 更大
max = y;
}
if (z.compareTo(max) > 0) {
// 现在 z 更大
max = z;
}
// 返回最大对象
return max;
}
public static void main(String args[]) {
//以下类型都属于Comparable的实现类
//Integer
System.out.printf("%d, %d 和 %d 中最大的数为 %d\n", 3, 4, 5, maximum(3, 4, 5));
// Float
System.out.printf("%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n", 6.6, 8.8, 7.7, maximum(6.6, 8.8, 7.7));
}
}
4.5.泛型与继承
继承的原则:继承泛型类时,必须对父类中的类型参数进行初始化。或者说父类中的泛型参数必须在子类中可以确定具体类型。
初始化方式:
- 具体类型初始化
/**
* 继承泛型类
*/
public class Son extends GenericVO<String>{
}
- 用子类中的泛型类型初始化父类
public class Son<T> extends GenericVO<T>{
}