Java泛型
泛型
泛型就是指在类定义时不会设置类中的属性或方法参数的具体类型,而是在类使用时(创建对象)再进行类型的定义。会在编译期检查类型是否错误
- 类型安全
- 消除强制类型转换
泛型类
- 语法:
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
相关文章
- Java基础知识总结--ArrayList
- Java多线程详解_java支持多线程
- java指定长度数组长度_Java声明数组时不能指定其长度[通俗易懂]
- java 取余 小数_Java小数取余问题求助「建议收藏」
- java 调用.asmx_Java调用asmx的一个例子
- java控制台输入数组_Java控制台输入数组并逆序输出的方法实例
- 1600页!卷S人的 Java《八股文》PDF手册
- 【说站】java接口中静态方法的继承
- 【说站】java全栈是什么意思
- 【测开技能】Java系列(三十)静态方法和静态字段
- Java JDK1.5: 泛型 新特性的讲解说明
- 编写高性能 Java 代码的最佳实践详解编程语言
- java JNI: C 语言调用 Java 方法示例详解编程语言
- 轻松学习MySQL与Java(mysqljava教程)
- Linux 卸载Java:简单步骤完成(linux卸载java)
- 深入浅出Java配置MySQL数据库(java配置mysql)
- 清理Redis Java中过期Key自动清理机制(redisjava过期)
- 时间设置Redis使用Java设置过期时间(redisjava过期)
- 机制研究Redis中Java实现的过期机制(redisjava过期)
- 数据处理实现基于Redis和Java的数据过期处理(redisjava过期)
- 存储过程使用Java语言执行Oracle存储过程(java执行oracle)
- Redis中使用Java快速实现自增(redis自增 java)
- java编写简单的ATM存取系统