泛型
泛型就是指在类定义时不会设置类中的属性或方法参数的具体类型,而是在类使用时(创建对象)再进行类型的定义。会在编译期检查类型是否错误
- 类型安全
- 消除强制类型转换
泛型类
- 语法:
class 类名称 <泛型标识,泛型标识,……>{ private 泛型标识 变量名; …… }
常用的 泛型标识:T、E、K、V
- 使用语法:
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
Java1.7以后,后面的<>中的具体的数据类型可以省略不写
类名<具体的数据类型> 对象名 = new 类名<>();
- MainClass类
package itiheima_09; //泛型类 public class MainClass<T> { //定义成员变量,T:是由外部使用类指定 private T key; //get、set方法 public T getKey() { return key; } public void setKey(T key) { this.key = key; } //构造方法 public MainClass(T key) { this.key = key; } public MainClass() { } //toString方法 @Override public String toString() { return "MainClass{" + "key=" + key + '}'; } }
- MainDemo类
package itiheima_09; public class MainDemo { public static void main(String[] args) { MainClass<String> stringMainDemo = new MainClass<>("hello"); String key1 = stringMainDemo.getKey(); System.out.println(key1); //hello MainClass<Integer> intMainDemo = new MainClass<>(100); int key2 = intMainDemo.getKey(); //包装类类型装换为基本数据类型:拆箱 System.out.println(key2); //100 //泛型类在创建对象的时候,没有指定类型,将按照Object类型操作 MainClass mainClass = new MainClass(100); Object key3 = mainClass.getKey(); System.out.println(key3); //100 //泛型类不支持基本数据类型,只支持包装类类型 //MainClass<int> objectMainClass = new MainClass<int>(); //同一泛型类,根据不同的数据类型创建的对象,本质是同一类型 System.out.println(stringMainDemo.getClass()); //class itiheima_09.MainClass System.out.println(intMainDemo.getClass()); //class itiheima_09.MainClass System.out.println(stringMainDemo.getClass() == intMainDemo.getClass()); //true } }
总结
泛型类,如果没有指定具体的数据类型,此时,操作类型是Object
泛型的类型参数只能是包装类类型,不能是具体数据类型
泛型类型在逻辑上可以看成是多个不同的类型,实际上都是相同类型
案例
抽奖实现
- ProductGetter
package itiheima_10; import java.util.ArrayList; import java.util.Random; public class ProductGetter<T> { Random random = new Random(); //奖品 private T product; //奖品池 ArrayList<T> list = new ArrayList<>(); //添加奖品 public void addProduct(T t){ list.add(t); } //抽奖 public T getProduct() { product = list.get(random.nextInt(list.size())); return product; } }
- MainClass
package itiheima_10; public class MainClass { public static void main(String[] args) { //创建抽奖类对象,指定数据类型 ProductGetter<String> stringProductGetter = new ProductGetter<>(); String[] strProducts = {"苹果手机","华为手机","扫地机器人","咖啡机"}; //遍历数组 for (int i = 0; i < strProducts.length; i++) { //添加到奖池 stringProductGetter.addProduct(strProducts[i]); } //抽奖 String product1 = stringProductGetter.getProduct(); System.out.println("恭喜您,你抽中了:" + product1); System.out.println("================================"); ProductGetter<Integer> integerProductGetter = new ProductGetter<>(); int[] intProducts = {10000,5000,3000,500,300000}; for (int i = 0; i < intProducts.length; i++) { integerProductGetter.addProduct(intProducts[i]); } Integer product2 = integerProductGetter.getProduct(); System.out.println(product2); } }
equals()和hashCode()方法
hashCode()方法和equals()方法是在Object类中就已经定义了的,所以在java中定义的任何类都会有这两个方法。原始的equals()方法用来比较两个对象的地址值,而原始的hashCode()方法用来返回其所在对象的物理地址
泛型类派生子类
- 子类是泛型类,子类和父类泛型类型要一致
class ChildGeneric<T> extends Generic<T>
- 子类不是泛型类,父类要明确泛型的数据类型
class ChildGeneric extends Generic<String>
泛型接口
接口中定义了一个泛型方法
doSomething
,该方法接受一个类型为T
的参数并返回一个类型为T
的结果。使用该接口时,可以根据需要指定具体的类型参数
public interface MyInterface<T> { T doSomething(T t); }
泛型方法
方法使用了一个类型参数
T
,并返回了一个T
类型的元素。这个方法可以用于任何类型的数组
public static <T> T getFirst(T[] array) { if (array.length == 0) { return null; } return array[0]; }
调用泛型方法,你需要在方法名之前指定它的类型参数
String[] words = {"hello", "world"}; String firstWord = getFirst(words);
调用了
getFirst
方法,并将字符串类型的数组传递给它。因为getFirst
方法是一个泛型方法,编译器会推断出类型参数T
是String
,所以该方法返回了第一个字符串元素"hello"
类型通配符
类型通配符是Java中的一种特殊语法,用于表示未知类型。它使用问号(?)作为通配符,可以出现在泛型类、泛型方法、变量声明等位置,在泛型类或方法中,类型通配符可以用来表示任何类型
public class Box<T> { public void setValue(T value) { ... } public T getValue() { ... } // 使用类型通配符定义一个方法,可以接受任何类型的Box对象 public void copyValue(Box<?> box) { T value = box.getValue(); // 读取box中的值 setValue(value); // 将值设置到当前对象中 } }
类型通配符的上限
类型通配符的上限指定了通配符所代表的类型的最大边界。在 Java 中,可以使用 extends 关键字指定类型通配符的上限。例如,如果要创建一个泛型方法,该方法只能接受 Number 类型及其子类的参数,则可以使用以下语法:
public <T extends Number> void methodName(T parameterName) { // 方法实现 }
在这个例子中,类型通配符
<T extends Number>
的上限是 Number 类型,这意味着方法只能接受 Number 类型及其子类的参数
泛型通配符的下限
泛型通配符的下限指定了通配符所代表的类型的最小边界。在 Java 中,可以使用 super 关键字指定泛型通配符的下限。例如,如果要创建一个泛型方法,该方法只能接受 Integer 类型及其父类的参数,则可以使用以下语法:
public void methodName(List<? super Integer> parameterName) { // 方法实现 }
在这个例子中,泛型通配符
<? super Integer>
的下限是 Integer 类型的父类,这意味着该方法可以接受类型为 Integer、Number、Object 等超类的 List 参数
类型擦除
Java类型擦除是一种编译时的行为,它指在编译Java泛型代码时,将泛型类型信息擦除,转换成对应的原生类型,以保持与旧版Java语言代码的兼容性。具体解释如下:
Java泛型是在JDK1.5引入的,它允许程序员在定义类、接口或方法时使用一个或多个类型参数,用来限定该类、接口或方法中的某些数据类型。例如,我们可以定义一个泛型类
List<T>
,其中T表示元素的类型。在实例化该类时,我们需要提供具体的类型参数,例如List<String>
或List<Integer>
。然而,在编译过程中,Java编译器会将泛型类型信息擦除掉,转换成对应的原生类型。例如,
List<String>
被擦除成List
,List<Integer>
也被擦除成List
。意味着,运行时无法获取泛型类型信息,只能得到原生类型的信息。这种类型擦除的行为对于Java语言的兼容性非常重要,因为它使得新版本的Java语言可以与旧版本的Java语言保持兼容。但是,它也带来了一些限制和挑战,例如无法使用泛型类型作为静态变量、局部变量、方法参数、异常类型等,还需要通过反射来获取泛型信息
泛型数组
泛型数组是指可以存储任意类型元素的数组,它在声明时需要指定元素类型的占位符,例如:
其中,
T
是一个类型参数(type parameter),可以被任意类型所替换(例如String
、Integer
等)
T[] arr = new T[10]; // 使用占位符 T 声明一个长度为 10 的泛型数组
运行时,Java 虚拟机是无法获知
T
的实际类型的,因此无法创建一个真正的泛型数组。因此,上面的代码会导致编译错误。要想创建一个泛型数组,可以通过类型擦除和强制类型转换来实现,例如:
Object[] arr = new Object[10]; // 创建 Object 类型的数组 T t = (T) arr[0]; // 强制类型转换为 T 类型
这种方式虽然可以创建一个泛型数组,但不建议使用,因为强制类型转换可能会导致运行时错误。通常情况下,可以使用集合类(例如
ArrayList
)来代替泛型数组,以避免这个问题
ArrayList<T> list = new ArrayList<>(); T t = list.get(0); // 直接获取 T 类型元素
这样就不需要使用强制类型转换来将 Object 类型转换为 T 类型了,同时也可以通过 ArrayList 的动态扩容特性方便地添加或删除元素
泛型反射
泛型是一种编程语言的特性,允许程序员可以在编译时不指定类型,而在运行时再确定类型,从而提高代码的灵活性和重用性。反射是Java中一个非常强大的特性,它使得程序可以在运行时获取类、方法、属性等的信息,并且可以动态地调用这些对象
泛型反射的实现就是对泛型类型进行反射操作。通过泛型反射可以获取泛型类型的参数类型、父类、接口等信息,还可以创建泛型对象、调用泛型方法等。为了实现泛型反射,需要使用到Java中的Type、ParameterizedType、GenericArrayType等相关的API
下面是一个简单的泛型反射示例:
public class Generic<T> { private T value; public Generic(T value) { this.value = value; } public T getValue() { return value; } } public class Main { public static void main(String[] args) throws Exception { Generic<Integer> generic = new Generic<>(123); // 获取泛型参数类型 Type type = generic.getClass().getGenericSuperclass(); if (type instanceof ParameterizedType) { ParameterizedType paramType = (ParameterizedType) type; Type[] argTypes = paramType.getActualTypeArguments(); System.out.println(argTypes[0]); } // 创建泛型对象 Class<?> clazz = Class.forName("Generic"); Constructor<?> constructor = clazz.getDeclaredConstructor(Object.class); constructor.setAccessible(true); Generic<String> generic2 = (Generic<String>) constructor.newInstance("hello"); System.out.println(generic2.getValue()); } }
这个示例中定义了一个泛型类
Generic<T>
,并在其中获取泛型参数类型、创建泛型对象。运行结果为:
class java.lang.Integer hello