泛型规范在JDK1.5版本中被添加,使用泛型机制编写的代码比起随意使用Object变量然后再进行强制类型转换的代码具有更高的安全性和可读性,同时也更为简洁。泛型对于集合类尤其有用,例如 ArrayList
一、泛型
泛型程序设计(Generic programming)意味着编写的代码可以被很多操作不同数据类型的对象所重用。例如
ArrayList array = new ArrayList();
简单声明一个ArrayList类型的集合,集合中维护了一个Object引用的数组。而实际使用中集合往往只需要维护某一种特定的非Obj数据类型并对其进行操作,这意味着这ArrayList集合将面对两个问题:1.对集合中元素进行操作时必须进行强制类型转换。2.可以向集合中添加任意类型的变量,如果对任意类型变量进行操作时(比如强制类型转换),容易产生运行时错误。
泛型提供了一个很好的解决方案:类型参数(type parameters)。比如ArrayList类这样来指定类型参数:
ArrayList<String> array = new ArrayList<String>();
这样生命的ArrayList集合只能接收 String 类型的变量,存储其他类型变量时会在编译时报错。同时不需要进行强制类型转换。
例如这段代码将所有元素转换成大写:
public class Demo1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>(); //<String> 表示该容器只能存储String类型的数据。
list.add("aa");
list.add("bb");
list.add("cc");
list.add(123); //会编译时报错
for(int i = 0 ; i < list.size() ; i++){
String str = list.get(i);
System.out.println("大写:"+ str.toUpperCase());
}
}
}
二、自定义泛型
1.在方法上自定义泛型
自定义泛型:自定义泛型就是一个数据类型的占位符或者是一个数据类型的变量。
方法上自定义泛型的声明格式:(与变量一样需要先声明,再使用)
修饰符 <声明自定义的泛型>返回值类型 函数名(使用自定义泛型 …){函数体}
public class Demo {
public static void main(String[] args) {
String str = getData("abc");
}
public static <E>E getData(E o){ //<E>是声明自定义的泛型 E是返回值类型为E 形参中的E是指传入参数的类型
return o;
}
}
在方法上自定义泛型需要注意的问题:
1.在泛型中不能使用基本数据类型,如果需要使用基本数据类型,那么就使用基本数据类型对应的包装类型。
byte —-> Byte
short —-> Short
int —-> Integer
long —-> Long
double —-> Double
float —-> Float
boolean—-> Boolean
char —-> Character
在方法上自定义泛型,这个自定义泛型的具体数据类型是在调用该方法的时候传入实参时确定具体的数据类型的。
自定义泛型只要符合标识符的命名规则即可, 但是自定义泛型我们一般都习惯使用一个大写字母表示。 T (Type) E (Element).
2.在类上自定义泛型
泛型类的定义格式:
class 类名<声明自定义泛型>{类体}
泛型类在类的声明部分引入类型变量,声明的类型变量可以在整个类中使用。(在类中自定义泛型之后便无需在每个方法中定义)同时泛型类也可以声明多个类型变量,例如
public class Pair<T,U>{}
用具体的类型替换类型变量就可以实例化泛型类,例如:
Pair<String>;
以及文章开头的
ArrayList<E> array = new ArrayList<E>();
实例:重写一个数组类,实现元素翻转
class MyArrays<T>{
public void reverse(T[] arr){ //元素翻转
for(int startIndex = 0, endIndex = arr.length-1 ; startIndex<endIndex ; startIndex++,endIndex--){
T temp = arr[startIndex];
arr[startIndex] = arr[endIndex];
arr[endIndex] = temp;
}
}
public String toString(T[] arr){ //重写toString方法
StringBuilder sb = new StringBuilder();
for(int i = 0 ; i < arr.length ; i++){
if(i==0){
sb.append("["+arr[i]+",");
}else if(i==arr.length-1){
sb.append(arr[i]+"]");
}else{
sb.append(arr[i]+",");
}
}
return sb.toString();
}
public static <T>void print(T[] t){
}
}
public class Demo2 {
public static void main(String[] args) {
Integer[] arr = {10,12,14,19};
MyArrays<Integer> tool = new MyArrays<Integer>(); //使用泛型定义的类既可以对Integer类型进行操作,同时也可以对String数据类型进行操作,提高了复用性
tool.reverse(arr);
System.out.println("数组的元素:"+tool.toString(arr));
MyArrays<String> tool2 = new MyArrays<String>();
String[] arr2 = {"aaa","bbb","ccc"};
tool2.reverse(arr2);
ArrayList<String> list = new ArrayList<String>();
}
}
泛型类要注意的事项:
在类上自定义泛型的具体数据类型是在使用该类的时候创建对象时候确定的。
如果一个类在类上已经声明了自定义泛型,如果使用该类创建对象的时候没有指定泛型的具体数据类型,那么默认为Object类型。
在类上自定义泛型不能作用于静态的方法,如果静态的方法需要使用自定义泛型,那么需要在方法上自己声明使用。
3.在接口上定义泛型
泛型接口的定义格式:
interface 接口名<声明自定义泛型>{类体}
泛型接口要注意的事项:
接口上自定义的泛型的具体数据类型是在实现一个接口的时候指定的。
在接口上自定义的泛型如果在实现接口的时候没有指定具体的数据类型,那么默认为Object类型。
3.延长接口自定义泛型的具体数据类型,那么格式如下:(实现一个接口的时候,还不明确我目前要操作的数据类型,需要等待创建接口实现类的对象的时候才能指定泛型的具体数据类型)
格式: public class Demo
示例代码:
interface Dao<T>{
public void add(T t);
}
public class Demo<T> implements Dao<T> {
public static void main(String[] args) {
Demo<String> d = new Demo<String>();
}
public void add(T t){
}
}
三、泛型的上下限
泛型中通配符: ?
使用格式:
? super Integer : 只能存储Integer或者是Integer父类元素。 泛型的下限
? extends Number : 只能存储Number或者是Number类型的子类数据。 泛型的上限
示例代码:
public class Demo5 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<Integer>();
ArrayList<Number> list2 = new ArrayList<Number>();
HashSet<String> set = new HashSet<String>();
//getData(set);
}
//泛型的上限
//定义一个函数可以接收接收任意类型的集合对象, 要求接收的集合对象只能存储Number或者是Number的子类类型数据。
public static void getData(Collection<? extends Number> c){
}
//泛型的下限
//定义一个函数可以接收接收任意类型的集合对象, 要求接收的集合对象只能存储Integer或者是Integer的父类类型数据。
public static void print(Collection<? super Integer> c){
}
}