文章目录
什么是泛型 为什么使用泛型?
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参列表,普通方法的形参列表中,每个形参的数据类型是确定的,而变量是一个参数。在调用普通方法时需要传入对应形参数据类型的变量(实参),若传入的实参与形参定义的数据类型不匹配,则会报错。
那
参数化类型
是什么?以方法的定义为例,在方法定义时,将方法签名中的形参的数据类型
也设置为参数(也可称之为类型参数),在调用该方法时再从外部传入一个具体的数据类型和变量。
泛型的本质是为了将类型参数化, 也就是说在泛型使用过程中,数据类型被设置为一个参数,在使用时再从外部传入一个数据类型;而一旦传入了具体的数据类型后,传入变量(实参)的数据类型如果不匹配,编译器就会直接报错。这种参数化类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
例子:实现一个栈
public class Stack {
// 定义数组
private int[] arr;
private int i = -1;
public Stack(int size){
arr = new int[size];
}
// 添加数据
public void add(int value){
i++;
arr[i] = value;
}
// 输出数据
public int get(){
return arr[i--];
}
}
Stack stack = new Stack(10);
stack.add(1);
stack.add(2);
stack.add(3);
stack.add(4);
System.out.println(stack.get());
System.out.println(stack.get());
System.out.println(stack.get());
System.out.println(stack.get());
}
}
但如果在添加 Integer 对象时,不小心添加了一个 String 对象,会发生什么?
- 上述代码在编译时没有报错,但在运行时却抛出了一个
ClassCastException 异常
,其原因是String 对象不能强转为 Integer 类型。
但是我们如果想实现其他类型的栈的话(如字符型,浮点型),并且避免出现以上的报错情况,我们就要创建很多个类,很麻烦,所以我们使用泛型,使得我们可以在Stack栈中放入所有类型的对象:
public class Stack<all> { // 泛型类
// 定义数组
private all[] arr;
private int i = -1;
public Stack(int size){
arr = (all[])new Object[size];
}
// 添加数据
public void add(all value){
i++;
arr[i] = value;
}
// 输出数据
public all get(){
return arr[i--];
}
}
public class Test {
public static void main(String[] args) {
Stack<Integer> stack = new Stack(10); //此时只需要输入想创建的类型就可以,不需要再开辟新的类
stack.add(1);
stack.add(2);
stack.add(3);
stack.add(4);
System.out.println(stack.get());
System.out.println(stack.get());
System.out.println(stack.get());
System.out.println(stack.get());
Stack stack = new Stack(10); // 或者像这样不进行定义,下面就可以存储各种类型的数据,但是这种写法不推荐
stack.add(1);
stack.add(2);
stack.add(3);
stack.add(4);
stack.add("sss");
System.out.println(stack.get());
System.out.println(stack.get());
System.out.println(stack.get());
System.out.println(stack.get());
System.out.println(stack.get());
}
}
- < Integer > 是一个泛型,其限制了 ArrayList 集合中存放对象的数据类型只能是 Integer,当添加一个非 Integer 对象时,编译器会直接报错。这样,我们便解决了上面产生的 ClassCastException 异常的问题(这样体现了泛型的类型安全检测机制)。
泛型类
基本语法如下:
class 类名称 <泛型标识> {
private 泛型标识 /*(成员变量类型)*/ 变量名;
.....
}
}
-
尖括号 <> 中的 泛型标识被称作是
类型参数
,用于指代任何数据类型。 -
泛型标识是任意设置的,Java 常见的泛型标识以及其代表含义如下:
T :代表一般的任何类。
E :代表 Element 元素的意思,或者 Exception 异常的意思。
K :代表 Key 的意思。
V :代表 Value 的意思,通常与 K 一起配合使用。
S :代表 Subtype 的意思 -
在泛型类中,类型参数定义的位置有三处,分别为:
1.非静态的成员属性类型
2.非静态方法的形参类型(包括非静态成员方法和构造器)
3.非静态的成员方法的返回值类型
注意:
-
泛型类中的静态方法和静态变量不可以使用泛型类所声明的类型参数
public class Test<T> { public static T one; // 编译错误 public static T show(T one){ // 编译错误 return null; } }
- 泛型类中的类型参数的确定是在创建泛型类对象的时候(例如 ArrayList< Integer >)。
- 而静态变量和静态方法在类加载时已经初始化,直接使用类名调用;在泛型类的类型参数未确定时,静态成员有可能被调用,因此泛型类的类型参数是不能在静态成员中使用的。
-
静态泛型方法中可以使用自身的方法签名中新定义的类型参数,而不能使用泛型类中定义的类型参数。
public class Test2<T> { // 泛型类定义的类型参数 T 不能在静态方法中使用 public static <E> E show(E one){ // 这是正确的,因为 E 是在静态方法签名中新定义的类型参数 return null; } }
-
泛型类不只接受一个类型参数,它还可以接受多个类型参数。
public class MultiType <E,T> { E value1; T value2; public E getValue1(){ return value1; } public T getValue2(){ return value2; } }
抽奖器案例
public class ProductGetter<T> {
// 奖金或者奖品
private T product;
// 定义奖品,奖金池
ArrayList<T> arrayList = new ArrayList<T>();
// 添加奖品到奖品池
public void addProduct(T t){
arrayList.add(t);
}
// 定义一个随机数,用来抽选奖品
Random random = new Random();
// 抽奖
public T getProduct(){
product = arrayList.get(random.nextInt(arrayList.size()));
return product;
}
}
public class Test {
public static void main(String[] args) {
ProductGetter<String> stringProductGetter = new ProductGetter<>();
String[] strPro = {"苹果手机","华为手机","扫地机器人","咖啡机"};
// 将奖品放入奖池
for (int i = 0;i < strPro.length;i++) {
stringProductGetter.addProduct(strPro[i]);
}
String product = stringProductGetter.getProduct();
System.out.println("恭喜您抽中了:"+product);
System.out.println("************************");
ProductGetter<Integer> IntegerProductGetter = new ProductGetter<>();
Integer[] intPro = {100,1000,10000,20000};
// 将奖金放入奖池
for (int i = 0;i<intPro.length;i++){
IntegerProductGetter.addProduct(i);
}
Integer money = IntegerProductGetter.getProduct();
System.out.println("恭喜你收到了:"+money+"元");
}
}