本文内容基于jdk 1.8。
什么是泛型:
泛型,即“参数化类型”,处理的数据类型不是固定的。这么说肯定不明白,因为我一开始看到这个解释也完全不明白。
百度的解释:泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。
怎么理解?举个java例子,假设现在有一个集合,本来打算是用来存字符串的,结果一不小心存了个数字进去了,新建类Test1,如下:
import java.util.ArrayList;
import java.util.List;
public class Test1 {
public static void main(String[] args) {
List arrayList = new ArrayList();
arrayList.add("Hello sir");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
System.out.println("item:"+item);
}
}
}
编译的时候不会报错,因为集合本身可以存放多种不同类型的元素,但运行的时候报错了,如下:
编译时期不能检测到,运行时会出现类转化异常。这个时候就要用到了泛型了。
可以创建集合时先声明,只能存放String类型的数据,如果不小心存了别的类型进去,编译期就会报错。如下:
import java.util.ArrayList;
import java.util.List;
public class Test1 {
public static void main(String[] args) {
List arrayList = new ArrayList<>();
arrayList.add("Hello sir");
arrayList.add(100); //此行在编译期便会报错
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
System.out.println("item:"+item);
}
}
}
将运行时期会发生的异常提前到编译时期了。这就是泛型的作用
语言和程序设计的一个重要目标是将bug尽量消灭在摇篮里,能消灭在写代码的时候,就不要等到代码写完程序运行的时候
所以泛型的作用是一种安全机制,是一种书写规范,它和接口的作用有着一定的类似,都是在制定规则。
泛型的好处
- 更好的安全性。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中
- 更好的可读性,消除强制类型转换。
- 实现更好的代码可重用性,例如通用算法的实现,在面向对象编程及各种设计模式中有非常广泛的应用。
泛型只在编译阶段有效。
泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
泛型有三种使用方式,分别为:泛型方法、泛型类、泛型接口
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
泛型的使用:
泛型方法:
泛型方法,是在调用方法的时候指明泛型的具体类型 。
新建类Test2,创建泛型方法getClzInstance:
public class Test2 {
/**
* 泛型方法的基本介绍
*
* @param clz 传入的泛型实参
* @return T 返回值为T类型
* 说明:
* 1)public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
*/
public T getClzInstance(Class clz) {
T instance = null;
try {
instance = clz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return instance;
}
//泛型方法showE
public void showE(E e) {
System.out.println(e.toString());
}
//可变参数泛型方法printT
public void printT(T... args) {
for (T t : args) {
System.out.println("print T :" + t.toString());
}
}
//泛型的静态方法,
public static void print(T t) {
System.out.println(t.toString());
}
public static void main(String[] args) {
Test2 t1 = new Test2().getClzInstance(Test2.class);
t1.showE(452345);
t1.printT("hello! come here",168168, 69.0);
print("泛型的静态方法!!!");
}
}
Class
运行结果:
泛型方法能使方法独立于类而产生变化,相对于泛型接口和泛型类,尽量优先使用泛型方法去处理问题
泛型类:
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
基本写法:
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
private 泛型标识 /*(成员变量类型)*/ var;
.....
}
}
例子:
public class Person {
private T key; //声明key这个成员变量的类型为T,T的具体类型在实例化泛型类时指定
public Person(T key){
this.key = key;
}
//泛型方法getKey的返回值类型为T,T的具体类型也是实例化泛型类时指定的类型
public T getKey(){
return key;
}
}
实例化泛型类:
public static void main(String[] args) {
Person p1 = new Person<>(8888);
Person p2 = new Person<>("Hello sir");
System.out.println("p1 key:"+p1.getKey());
System.out.println("p2 key:"+p2.getKey());
}
运行结果:
泛型类可以有多个类型变量。其中第一个域和第二个域使用不同的类型,如下:
public class Person { }
泛型接口:
定义一个泛型接口:
public interface IGenerator {
public T first();
public T end();
}
实现泛型接口的类,未传入泛型实参:
public class MailGenerator implements IGenerator {
@Override public T first() {
return null;
}
@Override public T end() {
return null;
}
}
当实现泛型接口的类,传入泛型实参时:
//指定泛型实参,参数类型为Person
public class PersonGenerator implements IGenerator{
@Override public Person first() {
return null;
}
@Override public Person end() {
return null;
}
}
泛型通配符:
在Person类中加一个showKey方法:
//尖括号中的T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
public class Person {
private T key; //声明key这个成员变量的类型为T,T的具体类型在实例化泛型类时指定
public Person(T key){
this.key = key;
}
//泛型方法getKey的返回值类型为T,T的具体类型也是实例化泛型类时指定的类型
public T getKey(){
return key;
}
public static void main(String[] args) {
Person p1 = new Person<>(8888);
Person p2 = new Person<>("Hello sir");
showKey(p1); //此处编译报错
showKey(p2);
}
public static void showKey(Person p){
System.out.println("p key:"+p.getKey());
}
}
showKey(p1); 这行会报错,Person
要让showKey支持两种类型都输出key值。我们可以使用通配符,将代码修改如下:
public class Person {
private T key; //声明key这个成员变量的类型为T,T的具体类型在实例化泛型类时指定
public Person(T key){
this.key = key;
}
//泛型方法getKey的返回值类型为T,T的具体类型也是实例化泛型类时指定的类型
public T getKey(){
return key;
}
public static void main(String[] args) {
Person p1 = new Person<>(8888);
Person p2 = new Person<>("Hello sir");
showKey(p1);
showKey(p2);
}
public static void showKey(Person> p){
System.out.println("p key:"+p.getKey());
}
}
用类型通配符?代替具体的类型实参。注意此处?是类型实参。
泛型上下边界:
将Person修改如下:
//尖括号中的T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
public class Person {
private T key; //声明key这个成员变量的类型为T,T的具体类型在实例化泛型类时指定
public Person(T key){
this.key = key;
}
//泛型方法getKey的返回值类型为T,T的具体类型也是实例化泛型类时指定的类型
public T getKey(){
return key;
}
public static void main(String[] args) {
Person p1 = new Person<>(8888);
Person p2 = new Person<>("Hello sir");
Person p3 = new Person<>(88.9);
showKey(p1);
showKey(p2);//此处报错
showKey(p3);
}
public static void showKey(Person extends Number> p){
System.out.println("p key:"+p.getKey());
}
}
通过 extends Number> 方式限制泛型实参类型必须是Number的子类。
showKey(p2);这一行代码会报错,因为String不是Number的子类
同样也可以用 super Integer>方式限制泛型实参类型必须是Integer或Integer的父类。
类型变量的限定
有些情况,类或方法需要对类型变量加以约束:
比如,我们有如下一个方法用来求最小元素:
public static T min(T[] array){}
假设我们要求T所属的类都有compareTo方法,方便比较元素大小。可以将代码改成如下:
public static T min(T[] array){}
注意这里用的extends并不是继承的意思,而是表示T应该是绑定类型(Comparable)的子类型。限定T所属的类实现了Comparable接口。T和绑定类型可以是类,也可以是接口
一个类型变量或通配符可以有多个限定,例:
在有些情况下,比如遵循接口隔离原则,有些类可能实现了很多个接口,用在泛型类上会非常方便。
public class Person {
}