目录
泛型的基本概念
泛型(Generics) 是 Java 中的一种机制,它允许我们在定义类、接口和方法时使用类型参数,从而使得代码可以适用于多种数据类型。泛型的核心思想是 参数化类型,即在使用时再指定具体的类型,从而提高代码的复用性和类型安全性。
引例:Stack类
手动实现栈操作(数据先进后出)
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--];
}
}
public class Test {
public static void main(String[] args) {
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());
}
}
输出结果为:
现在则出现了问题:我们实现的栈存储1234这样的数值类型的数据是没有问题的,但是如果想要存储字符串或者浮点类型的数据能存储吗?
答案是否定的。
如何解决呢?以字符串类型的数据为例:
public class StringStack {
private String[] arr;
private int i = -1;
public StringStack(int size){
arr = new String[size];
}
//添加数据
public void add(String value){
i++;
arr[i]= value;
}
//输出数据
public String get(){
return arr[i--];
}
}
public class Test {
public static void main(String[] args) {
StringStack stringStack = new StringStack(5);
stringStack.add("北京");
stringStack.add("上海");
stringStack.add("保定");
System.out.println(stringStack.get());
System.out.println(stringStack.get());
System.out.println(stringStack.get());
}
}
输出结果为:
如果我们现在想要存储float或者布尔类型的数据都要重新写一个"**Stack.java",这样效率很低下。
于是乎,引出了泛型,一切类型都可以进行转换。
泛型类的定义
- 泛型类的定义语法
class 类名称 <泛型标识、泛型标识,...> {
private 泛型标识 变量名;
......
}
常用的泛型标识 T、E、K、V
- T:表示任意类型(Type)
- E:表示集合中的元素类型(Element)
- K:表示键类型(Key)
- V:表示值类型(Value)
案例
/**
* 泛型类的定义
* @param <T> 泛型标识-----类型形参
* T 创建对象的时候里面具体制定的数据类型
*/
public class Generic<T> {
// T 是由外部使用类的时候来指定的。
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
@Override
public String toString() {
return "Generic{" +
"key=" + key +
'}';
}
}
泛型类的使用语法
- 使用语法
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
- java 1.7以后,后边的<>中具体的数据类型可以省略不写
类名<具体的数据类型> 对象名 = new 类名<>();
案例
public static void main(String[] args) {
// 泛型类在创建对象的时候来指定操作的具体数据类型
Generic<String> stringGeneric = new Generic<>("a");
String key = stringGeneric.getKey();
System.out.println("key:" + key);
System.out.println("------------------------");
Generic<Integer> integerGeneric = new Generic<>(100);
Integer key1 = integerGeneric.getKey();
System.out.println("key1:"+key1);
// 总结:泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。
System.out.println("----------注意点1:--------------");
// 泛型类在创建对象的时候,没有指定泛型类,将按照Object类型来操作
Generic generic = new Generic("ABC");
Object key3 = generic.getKey();
System.out.println("key3:"+key3);
System.out.println("----------注意点2:--------------");
// 泛型类不支持基本数据类型,原因就是我们在编译期间会将这个 T 编译成 Object
// 基本数据类型无法转化为 object类型
// Generic<int> intGeneric = new Generic<>(100);
System.out.println("----------注意点3:--------------");
// 同一泛型类,根据不同的数据类型创建的对象,本质上是同一类型
System.out.println(integerGeneric.getClass() == stringGeneric.getClass());
}
改造后的Stack类
原来的 Stack 类只能处理特定类型的数据(如 int、String),要处理其他类型的需要重复编写代码。通过使用泛型,可以将 Stack 类改造为能够处理任意类型的数据。
public class Stack<T> {
//定义数组
private T[] arr;
private int i = -1;
public Stack(int size){
arr = (T[])new Object[size]; //object类是所有类型的子类,父类转成子类要强转
}
//添加数据
public void add(T value){
i++;
arr[i]= value;
}
//输出数据
public T get(){
return arr[i--];
}
}
public class Test {
public static void main(String[] args) {
System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
Stack stack = new Stack(10);
stack.add(1);
stack.add("北京");
stack.add(1==1);
stack.add(10.0);
System.out.println(stack.get());
System.out.println(stack.get());
System.out.println(stack.get());
System.out.println(stack.get());
}
}
输出结果为:
小案例:年终抽奖器(可能会是奖金,也可能是奖品)
/**
* 抽奖器
* @param <T>
*/
//ProductGetter<T> 是一个泛型类,其中<T>表示可以接受任意类型的数据,这使得该类可以适用于各种类型的奖品(如String、Integer等)
public class ProductGetter<T> {
//用于存储抽中的奖品
private T product;
// 用于存储所有可能的奖品(即奖池)
ArrayList<T> arrayList = new ArrayList<>();
//将奖品添加到奖池arrayList中
public void addProduct(T t){
arrayList.add(t);
}
// 定义一个随机数,用来抽选奖品
Random random = new Random();
//抽奖
public T getProduct(){
product = arrayList.get(random.nextInt(arrayList.size()));
/**random.nextInt(arrayList.size()) 会生成一个从 0 到 arrayList.size() - 1 之间的随机整数,这个整数作为索引,用于在 arrayList 中随机选择一个元素。
arrayList.get(index) 方法用于获取 arrayList 中指定索引位置的元素。
通过传入 random.nextInt(arrayList.size()) 作为索引,arrayList.get(...) 返回奖品池中对应位置的奖品。
* **/
return product;
}
}
输出结果为:
总结:泛型类注意事项
未指定泛型类型时的默认类型:如果在使用泛型类时没有指定具体的类型参数,那么默认会将 T 视为 Object 类型。
Generic generic = new Generic("ABC"); // T 将被视为 Object 类型 Object key = generic.getKey();
泛型不支持基本数据类型:泛型的类型参数只能是包装类类型,不能是基本数据类型(如 int, char, double)。因为泛型在编译期间会被类型擦除为 Object,而基本数据类型无法直接转换为 Object。
// Generic<int> intGeneric = new Generic<>(100); // 错误 Generic<Integer> intGeneric = new Generic<>(100); // 正确
不同类型的泛型实例在运行时是同一类型:虽然泛型在逻辑上可以看作是多个不同的类型,但在运行时,它们实际上是同一类型。
System.out.println(integerGeneric.getClass() == stringGeneric.getClass()); // true