前言
在 JDK1.5 中,提出了一种新的概念:泛型,那么什么是泛型呢?
它其实就是一种参数化的集合,限制了你添加进集合的类型。泛型的本质是为了参数化类型,也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型的意义
要看出泛型的好处,那就举例子说明没有泛型的坏处。
强制转换类型
public class TestGeneric {
public static void main(String[] args) {
// 1.没有泛型,可向集合中添加任意类型的对象,存在类型不安全风险。
ArrayList arrayList = new ArrayList();
arrayList.add("Hello");
arrayList.add(2021);
arrayList.add(false);
for (int i = 0; i < arrayList.size(); i++) {
// 强制类型转换会抛出 java.lang.ClassCastException 异常
System.out.println((String)arrayList.get(i));
}
}
}
由于 ArrayList 内部就是一个 Object[]数组,所以添加字符串类型、数值类型、布尔类型都可以,但是在获取元素使用时都以 String类型使用,就会抛出错误。所以,在获取元素时要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常。
所以,泛型可以在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的。程序更加健壮:只要编译期没有警告,运行期就不会抛出 ClassCastException
异常;提高了代码的可读性:编写集合的时候,就限定了集合中能存放的类型。
代码复用
// 2.没有泛型,每次都要重复新增一个代码相似的
private static int add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
// float
private static float add(float a, float b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
// double
private static double add(double a, double b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
如果没有泛型的话,处理相同逻辑不同类型的需求会非常麻烦。如上所示,要实现不同类型的加法,每种类型都需要重载一个 add()方法;当然我们也可以声明传入参数为 Object,并在比较两个元素大小时,判断元素类型,并使用对应的方法比较。这样,代码就会恶心在类型判断上了,虽然不优雅的范围小了一点,并不能解决问题。
通过泛型,我们可以复用为一个方法:
// 使用泛型,提高代码复用
private static <T extends Number> double add(T a, T b) {
System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
return a.doubleValue() + b.doubleValue();
}
那么,可以总结一下泛型的应用场景了,当遇到以下场景时,我们可以考虑使用泛型:
- 当参数类型不明确,可能会扩展为多种时;
- 想声明参数类型为 Object,并在使用时用 instanceof 判断时。
需要注意,泛型只能替代 Object 的子类型,如果需要替代基本类型,可以使用包装类。
泛型的基本使用
泛型指代一种参数类型,可以声明在类、方法和接口上。
声明
泛型的声明使用 <占位符 [, 另一个占位符]> 的形式,需要在一个地方同时声明多个占位符时,使用逗号 , 隔开。占位符的格式并无限制,不过一般约定使用单个大写字母,如 T 代表类型(Type),E 代表元素(Element)等。
虽然没有严格规定,我们可以换成 A-Z 之间的任何一个字母都可以,并不会影响程序的正常运行。不过为了代码的易读性,最好使用前检查一下约定用法。
通常情况下,T、E、K、V、?是这样约定的:
- ?表示不确定的 java 类型;
- T(type) 表示具体的一个 java 类型;
- K V(key value) 分别代表 java 键值中的 Key 与 Value;
- E(element) 代表 Element。
泛型类
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。
最典型的就是各种容器类,如:List、Set、Map。
// JDK7 提供了一个可以略微减少代码量的泛型简写方式,后面可以只用<>
List<Integer> list = new ArrayList<>();
Set<Integer> set = new HashSet<>();
Map<String, Integer> map = new HashMap<>();
在实例化泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值。
一个最普通的自定义泛型类:
/**
* 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
* 在实例化泛型类时,必须指定T的具体类型
*/
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;
}
}
public class TestGeneric {
public static void main(String[] args) {
// 泛型的类型参数只能是对象类型(包括自定义类),不能是基本数据类型
// 传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic<Integer> genericInteger = new Generic<Integer>(20210815);
// 传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic<String> genericString = new Generic<String>("value");
System.out.println(genericInteger.getKey());
System.out.println(genericString.getKey());
}
}
参照 HashMap<K,V> 类的定义,多元泛型的写法如下:
/**
* 参照 HashMap<K,V>类的定义,此处指定了两个泛型类型
*/
class Notepad<K, V> {
private K key;
private V value;
public K getKey() {
return this.key;
}
public V getValue() {
return this.value;
}
public void setKey(K key) {
this.key = key;
}
public void setValue(V value) {
this.value = value;
}
}
public class TestGeneric {
public static void main(String[] args) {
// 定义两个泛型类型的对象
Notepad<String, Integer> t = null ;
// 里面的key为String,value为Integer
t = new Notepad<String,Integer>() ;
t.setKey("杰克") ;
t.setValue(18) ;
System.out.print("姓名;" + t.getKey()) ;
System.out.print(",年龄;" + t.getValue()) ;
}
}
泛型接口
泛型接口与泛型类的定义及使用基本相同。
/**
* 泛型接口
*/
interface Info<T> {
// 定义抽象方法,抽象方法的返回值就是泛型类型
public T getVar();
}
/** 实现类 */
class InfoImpl<T> implements Info<T> {
// 定义属性
private T var;
public InfoImpl(T var) {
// 通过构造方法设置属性内容
this.setVar(var);
}
public void setVar(T var) {
this.var = var;
}
@Override
public T getVar() {
return this.var;
}
}
public class TestGeneric {
public static void main(String[] args) {
// 声明接口对象
Info<String> i = null;
// 通过子类实例化对象
i = new InfoImpl<String>("杰斯");
System.out.println("输出:" + i.getVar()) ;
}
}
泛型方法
泛型同样可以在类中包含参数化的方法,而方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是不是泛型没有关系
。
泛型方法使得该方法能够独立于类而产生变化。如果使用泛型方法可以取代类泛型化,那么就应该只使用泛型方法。另外,对一个 static 的方法而言,无法访问泛型类的类型参数。因此,如果 static 方法需要使用泛型能力,就必须使其成为泛型方法。
定义泛型方法的语法格式如下:
[访问权限修饰符] [static] [final] <类型参数列表> 返回值类型 方法名([形式参数列表])
为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新 new 一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。
public class TestGeneric {
// 定义泛型方法
public static <T> void Show(T obj) {
if (obj != null) {
System.out.println(obj);
}
}
public static void main(String[] args) {
Show(Integer.valueOf("666"));
}
}
参考
CSDN
Java全栈知识体系