就像是在真实的世界中,用于装每种东西的容器都是不一样的,如装水的叫做水杯、水桶,装煤气的叫做罐子。在集合中,用于装每种数据类型的集合也是不一样的,如String类型必须放在String标记过的集合中而不能放在Integer标记的集合中。在集合中学习泛型,易于理解和接收,本文文将以集合为基础讲解Java语言中泛型机制的用法!
泛型的定义: 为一个集合容器添上一种标记,即指定该集合容器有且只能放哪种类型的数据。
泛型的本质:
- 是一个数据类型的占位符或者是一个数据类型的变量(类比于C++中的模板)。
- 一个方法可以接收任意类型的参数,而且返回值类型必须要与实参的类型一致。则是泛型。
泛型的特性:
1,泛型没有多态
的概念,左右两边的数据类型必须要一致,或者只是写一边的泛型类型。推荐两边都写泛型。
2,始于JDK1.5
;
3,将运行时的异常提前至了编译时。
4,避免
了无谓的强制类型转换
。
5,在泛型中不能使用基本数据类型
,如果需要使用基本数据类型,那么就使用
基本数据类型对应的包装类型
。
出于规范的目的,Java 还是建议我们用单个大写字母来代表类型参数。常见的如:
T
代表一般的任何类。E
代表 Element 的意思,或者 Exception 异常的意思。K
代表 Key 的意思。V
代表 Value 的意思,通常与 K 一起配合使用。S
代表 Subtype 的意思
以集合为例,展示泛型的使用方法:
ArrayList<String> list = new ArrayList<String>(); true 推荐使用。
ArrayList<Object> list = new ArrayList<String>(); false
ArrayList<String> list = new ArrayList<Object>(); false
//以下两种写法主要是为了兼顾新老系统的兼用性问题。
ArrayList<String> list = new ArrayList(); true
ArrayList list = new ArrayList<String>(); true
泛型类
泛型类的定义格式:
class 类名<声明自定义泛型>{
。。。
}
泛型类要注意的事项:
- 在类上自定义泛型的
具体数据类型
是在使用该类的时候创建对象时候确定
的。 - 如果一个类在类上已经声明了自定义泛型,如果使用该类创建对象 的时候没有指定 泛型的具体数据类型,那么
默认为Object类型
。 - 在类上自定义类的泛型
不能作用于静态的方法
,如果静态的方法需要使用自定义泛型,那么需要在方法上自己声明使用。(原因:静态的泛型不能引用非静态的泛型)
快速理解泛型类:
class MyArrays<T>{ //定义了泛型: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){
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 Demo3 {
public static void main(String[] args) {
Integer[] arr = {10,12,14,19};
MyArrays<Integer> tool = new MyArrays<Integer>(); //泛型被确定为Int型;
tool.reverse(arr);
System.out.println("数组的元素:"+tool.toString(arr));
MyArrays<String> tool2 = new MyArrays<String>();
String[] arr2 = {"aaa","bbb","ccc"};
tool2.reverse(arr2);
ArrayList list = new ArrayList (); //没有指定泛型类型,那它默认为Object类型;
ArrayList<String> list = new ArrayList<String>();
}
}
泛型接口
定义格式:
interface 接口名<声明自定义泛型>{
...
}
泛型接口要注意的事项:
- 接口上自定义的
泛型的具体数据类型
是在实现一个接口的时候指定
的。 - 在接口上自定义的泛型如果在实现接口的时候没有指定具体的数据类型,那么
默认为Object类型
。
实现一个还不明确、我目前要操作的数据类型接口的时候,要等待创建接口实现类 对象的时候我才能指定泛型的具体数据类型。
interface Dao<T>{ //接口
public void add(T t);
}
class Deo<T> implements Dao<T> {
public void add(T t){
//空实现也算实现
}
}
public class Test {
public static void main(String[] args) {
Deo<String> d = new Deo<String>(); //指定该泛型为String类型,上面代码中的T都表示是String类型了
}
}
泛型方法
方法泛型注意的事项:
- 在方法上自定义泛型,这个自定义泛型的具体数据类型是在调用该方法的时候传入实参时确定具体的数据类型的。
- 自定义泛型只要符合标识符的命名规则即可, 但是自定义泛型我们一般都习惯使用一个大写字母表示。
T Type E Element
快速理解泛型方法:
public class Demo{
public static void main(String[] args) {
String str = getData("abc"); //在调用时依据传入实参才确定泛型的数据类型;
Integer i = getData(123); //使用基本数据类型时是使用的其包装类;
}
public static <T>T getData(T o){ //自定义泛型法则;
return o;
}
}
泛型的上下限
诞生背景:
现有以下两个需求,如何才能实现?
- 需求1: 定义一个函数可以接收任意类型的集合对象,要求接收的集合对象只能存储Integer或者是Integer的父类类型数据。
- 需求2: 定义一个函数可以接收任意类型的集合对象,要求接收的集合对象只能存储Number或者是Number的子类类型数据。
**泛型中通配符: ? **
? super Integer
: 只能存储Integer或者是Integer父类元素。泛型的下限:[Integer, ?]? extends Number
:只能存储Number或者是Number类型的子类数据。泛型上限:[?, Number]
快速理解泛型的上下线:
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);
}
//泛型的上限
public static void getData(Collection<? extends Number> c){ //Number及其子类
}
//泛型的下限
public static void print(Collection<? super Integer> c){ //Integer及其父类
}