本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注!
作者| 慕课网精英讲师 ColorfulC
通过本篇文章你将了解到什么是泛型,为什么需要泛型,如何使用泛型,如何自定义泛型,类型通配符等知识。1. 什么是泛型泛型不只是 Java 语言所特有的特性,泛型是程序设计语言的一种特性。允许程序员在强类型的程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须做出声明。Java 中的集合类是支持泛型的,它在代码中是这个样子的:
代码中的
public class GenericsDemo1 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("Hello");
String str = (String) arrayList.get(0);
System.out.println("str=" + str);
}
}
代码块123456789101112运行结果:str=Hello
代码块1虽然运行时没有发生任何异常,但这样做有两个缺点:需要强制类型转换: 由于ArrayList内部就是一个Object[]数组,在get()元素的时候,返回的是Object类型,所以在ArrayList外获取该对象,需要强制类型转换。其它的Collection、Map如果不使用泛型,也存在这个问题;可向集合中添加任意类型的对象,存在类型不安全风险。例如如下代码中,我们向列表中既添加了Integer类型,又添加了String类型:import java.util.ArrayList;
public class GenericsDemo2 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(123);
arrayList.add("Hello");
String str = (String) arrayList.get(0);
System.out.println("element=" + str);
}
}
代码块1234567891011运行结果:Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at GenericsDemo2.main(GenericsDemo2.java:8)
代码块12由于我们的“疏忽”,列表第 1 个元素实际上是整型,但被我们强制转换为字符串类型,这是行不通的,因此会抛出ClassCastException异常。使用泛型可以解决这些问题。泛型有如下优点:可以减少类型转换的次数,代码更加简洁;程序更加健壮:只要编译期没有警告,运行期就不会抛出ClassCastException异常;提高了代码的可读性:编写集合的时候,就限定了集合中能存放的类型。3. 如何使用泛型3.1 泛型使用在代码中,这样使用泛型:List
// Java 7 及以后的版本中,构造方法中可以省略泛型类型:
List
代码块123要注意的是,变量声明的类型必须与传递给实际对象的类型保持一致,下面是错误的例子:List
private T number; // 定义在类上的泛型,在类内部可以使用
public T getNumber() {
return number;
}
public void setNumber(T number) {
this.number = number;
}
public static void main(String[] args) {
// 实例化对象,指定元素类型为整型
NumberGeneric integerNumberGeneric = new NumberGeneric<>();
// 分别调用set、get方法
integerNumberGeneric.setNumber(123);
System.out.println("integerNumber=" + integerNumberGeneric.getNumber());
// 实例化对象,指定元素类型为长整型
NumberGeneric longNumberGeneric = new NumberGeneric<>();
// 分别调用set、get方法
longNumberGeneric.setNumber(20L);
System.out.println("longNumber=" + longNumberGeneric.getNumber());
// 实例化对象,指定元素类型为双精度浮点型
NumberGeneric doubleNumberGeneric = new NumberGeneric<>();
// 分别调用set、get方法
doubleNumberGeneric.setNumber(4000.0);
System.out.println("doubleNumber=" + doubleNumberGeneric.getNumber());
}
}
123456789101112131415161718192021222324252627282930313233运行结果:integerNumber=123
longNumber=20
doubleNumber=4000.0
代码块123我们在类的定义处也定义了泛型:NumberGeneric
参照HashMap
/**
* 类型为K的key属性
*/
private K key;
/**
* 类型为V的value属性
*/
private V value;
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public static void main(String[] args) {
// 实例化对象,分别指定元素类型为整型、长整型
KeyValueGeneric integerLongKeyValueGeneric = new KeyValueGeneric<>();
// 调用setter、getter方法
integerLongKeyValueGeneric.setKey(200);
integerLongKeyValueGeneric.setValue(300L);
System.out.println("key=" + integerLongKeyValueGeneric.getKey());
System.out.println("value=" + integerLongKeyValueGeneric.getValue());
// 实例化对象,分别指定元素类型为浮点型、字符串类型
KeyValueGeneric floatStringKeyValueGeneric = new KeyValueGeneric<>();
// 调用setter、getter方法
floatStringKeyValueGeneric.setKey(0.5f);
floatStringKeyValueGeneric.setValue("零点五");
System.out.println("key=" + floatStringKeyValueGeneric.getKey());
System.out.println("value=" + floatStringKeyValueGeneric.getValue());
}
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546运行结果:key=200
value=300
key=0.5
value=零点五
代码块12343.3 自定义泛型方法前面我们知道了如何定义泛型类,在类上定义的泛型,在方法中也可以使用。下面我们来看一下如何自定义泛型方法。泛型方法不一定写在泛型类当中。当类的调用者总是关心类中的某个泛型方法,不关心其他属性,这个时候就没必要再整个类上定义泛型了。请查看如下实例:实例演示public class GenericMethod {
/**
* 泛型方法show
* @param t 要打印的参数
* @param T
*/
public void show(T t) {
System.out.println(t);
}
public static void main(String[] args) {
// 实例化对象
GenericMethod genericMethod = new GenericMethod();
// 调用泛型方法show,传入不同类型的参数
genericMethod.show("Java");
genericMethod.show(222);
genericMethod.show(222.0);
genericMethod.show(222L);
}
}
123456789101112131415161718192021运行结果:Java
222
222.0
222
代码块1234实例中,使用
void show(T t);
}
代码块123泛型接口的实现类如下:public class GenericInterfaceImpl implements GenericInterface
@Override
public void show(String s) {
System.out.println(s);
}
}
代码块123456子类实现明确了泛型的参数变量为String类型。因此方法show()的重写也将T替换为了String类型。4.2 不明确类型参数变量当实现类不确定泛型类的参数变量时,实现类需要定义类型参数变量,调用者使用子类时,也需要传递类型参数变量。如下是GenericInterface接口的另一个实现类:public class GenericInterfaceImpl1
@Override
public void show(T t) {
System.out.println(t);
}
}
代码块123456在主方法中调用实现类的show()方法: public static void main(String[] args) {
GenericInterfaceImpl1 floatGenericInterfaceImpl1 = new GenericInterfaceImpl1<>();
floatGenericInterfaceImpl1.show(100.1f);
}
代码块12345. 类型通配符我们先来看一个泛型作为方法参数的实例:import java.util.ArrayList;
import java.util.List;
public class GenericDemo3 {
/**
* 遍历并打印集合中的每一个元素
* @param list 要接收的集合
*/
public void printListElement(List
}
代码块1234567891011121314观察上面的代码,参数list的限定的泛型类型为Object, 也就是说,这个方法只能接收元素为Object类型的集合,如果我们想传递其他元素类型的集合,是行不通的。例如,如果传递装载Integer元素的集合,程序在编译阶段就会报错:
Tips: 泛型中的List
public class GenericDemo3 {
/**
* 遍历并打印集合中的每一个元素
* @param list 要接收的集合
*/
public void printListElement(List> list) {
for (Object o : list) {
System.out.println(o);
}
}
public static void main(String[] args) {
// 实例化一个整型的列表
List integers = new ArrayList<>();
// 添加元素
integers.add(1);
integers.add(2);
integers.add(3);
GenericDemo3 genericDemo3 = new GenericDemo3();
// 调用printListElement()方法
genericDemo3.printListElement(integers);
// 实例化一个字符串类型的列表
List strings = new ArrayList<>();
// 添加元素
strings.add("Hello");
strings.add("慕课网");
// 调用printListElement()方法
genericDemo3.printListElement(strings);
}
}
12345678910111213141516171819202122232425262728293031323334运行结果:1
2
3
Hello
慕课网
代码块123455.2 extends 通配符extends通配符用来限定泛型的上限。什么意思呢?依旧以上面的实例为例,我们来看一个新的需求,我们希望方法接收的List 集合限定在数值类型内(float、integer、double、byte 等),不希望其他类型可以传入(比如字符串)。此时,可以改写上面的方法定义,设定上界通配符:public void printListElement(List extends Number> list) {
代码块1这样的写法的含义为:List集合装载的元素只能是Number自身或其子类(Number类型是所有数值类型的父类),完整实例如下:实例演示import java.util.ArrayList;
import java.util.List;
public class GenericDemo4 {
/**
* 遍历并打印集合中的每一个元素
* @param list 要接收的集合
*/
public void printListElement(List extends Number> list) {
for (Object o : list) {
System.out.println(o);
}
}
public static void main(String[] args) {
// 实例化一个整型的列表
List integers = new ArrayList<>();
// 添加元素
integers.add(1);
integers.add(2);
integers.add(3);
GenericDemo4 genericDemo3 = new GenericDemo4();
// 调用printListElement()方法
genericDemo3.printListElement(integers);
}
}
123456789101112131415161718192021222324252627运行结果:1
2
3
代码块1235.3 super 通配符既然已经了解了如何设定通配符上界,也就不难理解通配符的下界了,可以限定传递的参数只能是某个类型的父类。语法如下: super Type>
代码块1
小结
在本篇文章中,我们知道了使用泛型可以避免强制类型转换,也可以避免运行期就抛出的ClassCastException异常。在使用泛型时,要注意变量声明的泛型类型要匹配传递给实际对象的类型, Java 7 及以后的版本中,构造方法中可以省略泛型类型,推荐直接省略。我们也学习了如何自定义泛型类和泛型方法,在实际的开发中,我们想要编写比较通用的代码就避免不了使用泛型,大家可以在以后的开发中慢慢体悟。另外,泛型也是可以继承的。最后,我们还讲解了类型通配符的概念和使用场景。
欢迎关注「慕课网」,发现更多IT圈优质内容,分享干货知识,帮助你成为更好的程序员!