文章目录
前言
在Java开发中泛型被广泛使用,因此泛型的理解对于编写出高复用性的Java代码有着很重要的作用,本文将对泛型进行简要的描述。一、Java泛型是什么?
Java泛型又被称为参数化类型,其主要是为了建立具有类型安全的集合框架以及提高代码重用性。换句话说Java的泛型机制极大的减少了类型错误,同时在此基础上泛型的使用提高了程序的可读性、复用性。注:Java泛型是在Java1.5版本以后才出现的,在此之前此类编程一般通过继承来实现。
二、为什么要设置泛型
泛型实际上是Java中的一个语法糖,他将所传递的具体参数类型化,通过在编译期间就完成类型转换的工作减少强制类型转换确保类型安全极大的避免了运行错误。下面通过一个简单的案例进行说明设置泛型的好处。
- 不使用泛型
/**
* 一个自定义的Array用于演示不使用泛型设计通用程序的方法
*/
public class ArrayDemo {
private Object[] elements = new Object[0];
public Object get(int index) {
return elements[index];
}
public void add(Object object) {
int length = elements.length;
Object[] newElements = new Object[length + 1];
for(int i = 0; i < length; i ++) {
newElements[i] = elements[i];
}
newElements[length] = object;
elements = newElements;
}
}
/**
* 测试
*/
public class ArrayDemoTest {
@Test
public void testArrayAddString() {
ArrayDemo arrayDemo = new ArrayDemo();
arrayDemo.add("1");
String element1 = (String) arrayDemo.get(0);
System.out.println(element1);
}
@Test
public void testArrayAddInt() {
ArrayDemo arrayDemo = new ArrayDemo();
arrayDemo.add(2);
String element1 = (String) arrayDemo.get(0);
System.out.println(element1);
}
通过上面的案例可以得知使用Object来设计通用程序会存在类型转换的问题,这需要我们能够约束所存储的数据类型,而通过Object所设计的通用方法恰恰不能约束所传入值的数据类型,因为我们既可以向其中添加String类型数据也可以向其中添加Integer型数据,他们都是Object类的子类因此程序会顺利通过编译,直到程序运行时才因为类型不匹配出现异常,而这是我们不想看到的,因此才会设计泛型,将此类异常移到编译期间解决,减少运行时异常。测试方法testArrayAddInt()会发生
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
错误。
- 使用泛型
/**
* 泛型设计通用方法
*/
public class ArrayDemo<T> {
private Object[] elements = new Object[0];
@SuppressWarnings("unchecked")
public T get(int index) {
return (T)elements[index];
}
public void add(T object) {
int length = elements.length;
Object[] newElements = new Object[length + 1];
for(int i = 0; i < length; i ++) {
newElements[i] = elements[i];
}
newElements[length] = object;
elements = newElements;
}
}
/**
* 测试
*/
public class ArrayDemoTest {
@Test
public void testArrayAddString() {
ArrayDemo<String> arrayDemo = new ArrayDemo<String>();
arrayDemo.add("1");
String element1 = (String) arrayDemo.get(0);
System.out.println(element1);
}
@Test
public void testArrayAddInt() {
ArrayDemo<String> arrayDemo = new ArrayDemo<String>();
arrayDemo.add(1);
String element = arrayDemo.get(0);
System.out.println(element);
}
}
通过这样设计testArrayAddInt()就会在编译期间报Error:(19, 23) java: 不兼容的类型: int无法转换为java.lang.String
的错误无法通过编译,提高了程序的安全性。
三、如何使用泛型进行编码
3.1 泛型类
泛型用于类的定义时就被称为泛型类,常见泛型类List、Set、Map等。// 泛型类定义格式如下
public class 类名 <泛型类型1,...> {
}
下面是一个简单的泛型类写法。
public class GenericDemo<T> {
private T element;
public GenericDemo(T element) {
this.element = element;
}
public T getElement() {
return element;
}
}
/**
* 测试
*/
public class GenericDemoTest {
@Test
public void testGerElement() {
GenericDemo<String> stringGenericDemo = new GenericDemo<>("string");
String element = stringGenericDemo.getElement();
System.out.println(element);
}
}
其中的此处T可以是任意标识,常见的如T、E、K、V等。
Java 中泛型标记符如下,这些都是约定的东西不具有唯一性,但正确使用约定的符号可以增加程序的可读性:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
3.2 泛型方法
泛型用于方法的定义时,该方法就为泛型方法。泛型类是在实例化类的时候指明泛型的具体类型;泛型方法是在调用方法的时候指明泛型的具体类型 。
// 泛型方法定义格式如下
public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
}
public class GenericMethodDemo {
public <T> T get(T param) {
return param;
}
}
3.3 泛型接口
泛型接口的定义与使用如下:
public interface GenericInterfaceDemo<T> {
public T get();
}
/**
* 一、实现类指定了泛型类型
*/
public class GenericInterfaceImpl implements GenericInterfaceDemo<String> {
@Override
public String get() {
return null;
}
}
---------------------
/**
* 二、实现类不指定具体的泛型类型,则实现类也是一个泛型类
*/
public class GenericInterfaceImpl<T> implements GenericInterfaceDemo<T> {
@Override
public T get() {
return null;
}
}
四、泛型中需要注意的问题
4.1 泛型通配符
由于Java泛型本身不支持协、逆变,因此需要引入通配符来解决这个问题。
关于Java泛型通配符更加详细的介绍请参考文章:《Java泛型通配符》
4.1.1 无边界通配符 <?>
在使用泛型时我们经常会遇到通配符 ?,其主要作用就是让泛型能够接受未知类型的数据。4.1.2 固定上边界的通配符 <? extends E>
由于泛型(以及泛型集合)不是协变的,所以当你想要放宽对变量的限制时就需要使用通配符来实现。使用固定上边界的通配符就能够接受该类型以及其子类类型。且使用固定上边界的通配符只能够对集合进行读操作,具体原因请参照// 假设woman类和Man两个类继承于Person类
public class GenericDemo {
public void showSex(List<? extends Person> personList) {
for (Person p : personList) {
/* .... */
}
}
}
4.1.3 固定下边界的通配符<? super E>
同上,使用固定下边界的通配符就能够接受该类及其父类类型的数据。且使用固定下边界的通配符只能够对集合进行插入操作,具体原因请参照public class GenericDemo {
public void showSex(List<? super Person> personList) {
for (Object o : personList) {
/* .... */
}
}
}
总结
- 泛型方法不能使用基本数据类型作为参数。
- 使用泛型进行编程可以提高程序安全性以及可读性。
- 泛型只存在于编译期间。
- 通过使用通配符可以放宽对参数的限制增加程序灵活性。
对于上述限制的原因具体请参照《Java泛型(类型擦除)》。
— end