Java泛型

简介: 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

🌼 结语:创作不易,如果觉得博主的文章赏心悦目,还请——点赞👍收藏⭐️评论📝


目录
相关文章
|
4月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
96 2
|
2月前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
51 0
[Java]泛型
|
2月前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
21 1
|
3月前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
47 9
Java——包装类和泛型
|
2月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
29 5
|
2月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
21 1
|
2月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
23 2
|
3月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
|
3月前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。
|
2月前
|
安全 Java 编译器
Java基础-泛型机制
Java基础-泛型机制
21 0