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 方法是一个泛型方法,编译器会推断出类型参数 TString,所以该方法返回了第一个字符串元素 "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>被擦除成ListList<Integer>也被擦除成List。意味着,运行时无法获取泛型类型信息,只能得到原生类型的信息。

这种类型擦除的行为对于Java语言的兼容性非常重要,因为它使得新版本的Java语言可以与旧版本的Java语言保持兼容。但是,它也带来了一些限制和挑战,例如无法使用泛型类型作为静态变量、局部变量、方法参数、异常类型等,还需要通过反射来获取泛型信息

泛型数组

泛型数组是指可以存储任意类型元素的数组,它在声明时需要指定元素类型的占位符,例如:

其中,T 是一个类型参数(type parameter),可以被任意类型所替换(例如 StringInteger 等)

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
0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论