添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
小眼睛的香菜  ·  VCPKG ...·  1 年前    · 
  • 在 Java 8 之后:使用 IoC 技术创建
  • 创建泛型数组
  • 完整源代码
  • 总结
  • 很多人应该很清楚,在 Java 中,是不能直接创建泛型对象和泛型数组的。原因是 Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型,因此创建泛型对象就相当于创建了一个 Object 类型的对象。创建 Object 类型的对象通常没有任何意义,所以直接创建泛型对象的行为被编译器禁止。泛型数组也是一样。

    不能创建泛型对象,意味着下面的代码是非法的。

    // 设 T 为已被“赋值”的泛型变量
    T targetObject = new T(...); // 因为类型擦除,所以 new T(...) 会退化为 new Object(...)
    

      从某种意义上说,Java 实际上并没有真正实现泛型。Java 中的泛型只是基于面向对象语言共有的允许“向上引用”的语法而已。因为 Java 中所有的对象都直接或间接继承自 Object 类,所以基于这种技术的泛型变得可行。也因为这样,在 Java 中的很多情况下,可以使用 Object 类来代替使用泛型。

      真正的泛型并不是无法实现的,例如 C++ 就已经实现了真正的泛型,它将其称为 模板。为什么 Java 不实现真正的泛型技术呢?第一,Java 与 C 家族的语言相比,Java 相对而言不是一个更注重算法的语言,因此使用泛型的场景不是太多。例如,Java 的语法默认支持的是浅拷贝。与动不动就调用 复制构造函数=运算符重载函数C++ 相比,Java 更喜欢直接复制对象的引用(指针)值,来重用之前的对象,因此,真正需要创建对象的情况不多。第二,真正实现泛型之后,之前涉及泛型的代码将不能用。对于市场占有率岌岌可危的 Java 的来说,将会极大地损失用户,带来灾难性的后果。

      不过,由于 Java 还有其它丰富语法,因此这并不是没有办法。

    创建泛型对象

    Java 8 之前:使用反射创建

      在 Java 8 之前,使用反射就可以创建泛型对象。操作模板如下:

      设 T 的某个构造器的参数分别为类 A、类 B 的对象。类 A、类 B 均有公有的无参数构造器,且 A、B 均不是泛型(变量):

    // classT 为已有 Class 对象,函数 getClassT 代表一个返回 Class 对象的函数
    Class<T> classT = getClassT(); 
    // aObject 为已有对象,函数 getFirstParameter 代表一个返回 A 对象的函数
    A aObject = getFirstParameter();
    // bObject 为已有对象,函数 getSecondParameter 代表一个返回 B 对象的函数
    B bObject = getSecondParameter();
    // “通过类来获得类对象”:使用 T 的有参数构造器来创建对象
    Object targetObj = classT
            .getConstructor(A.class, B.class)
            .newInstance(aObject, bObject);
    

    这种办法为什么可以创建泛型对象

      为什么这样做可行呢?原因是,擦除的类在虚拟机中仍然保留有原先泛型的微弱记忆,因此如果使用反射技术就可以找到原先的类,并调用其构造函数来创建该类对象。这就相当于说,多年失散的父子可能第一眼不能相认,但仍然可以通过亲子鉴定来确定他们的关系。

    在 Java 8 之后:使用 IoC 技术创建

      在 Java 8 之后,Java 终于有了久违的 lambda 表达式。使用 lambda 表达式及其变体也可以用于间接创建泛型对象。

      有些读者可能对此不太理解,这里从一个简单的示例开始,最后来讲它的原理。

      首先先定义一个示例类:

    package org.wangpai.genericcreator.model;
    public class DemoObject {
        public DemoObject() {
            this.msg = "本对象是使用无参构造器创建的";
        public DemoObject(String para1, String para2) {
            this.msg = para1 + para2;
        private String msg = "这是 DemoObject";
        public void show() {
            System.out.println(this.msg);
    

      注意:本 DemoObject 类有一个无参构造器。Java 8 提供了一个名为 Supplier<T> 的泛型接口,它同时也是一个 函数式接口。它有一个无参方法 get 可用于返回一个泛型对象,条件是需要实现这个接口。这很简单,由于支持 lamdba 的语法,可以提供一种 构造器引用。就像这样。

    public static <T> T createGenericObject(Supplier<T> genericObjectCreator) {
        return genericObjectCreator.get();
    
    DemoObject demoObject1 = GenericCreator.createGenericObject(DemoObject::new);
    

      前面有言,DemoObject 支持一个无参构造器,因此 Supplier<T> 接口将使用这个构造器来创建 DemoObject 对象。

      请注意,Supplier<T> 接口本身没有任何魅力,它只是一个没有任何实现的接口,它也不是一个 native 方法。是我们提供了创建 DemoObject 对象的方法实现。因此,并一定非要使用 Supplier<T> 接口,我们可以自己定义一个接口。

      Supplier<T> 接口的方法是无参的,所以它一般说来只能调用无参构造器,我们可以设计一个有参接口。

    package org.wangpai.genericcreator.model;
    @FunctionalInterface
    public interface DemoObjectConstructor<T> {
        T constructor(String firstPara, String secondPara);
    

      然后再提供它的 接口调用方法,以及 接口实现方法

    • 接口调用方法
    public static <T> T createGenericObject(DemoObjectConstructor<T> genericObjectCreator,
                                            String firstPara, String secondPara) {
        return genericObjectCreator.constructor(firstPara, secondPara);
    
    • 接口实现方法
    DemoObject demoObject2 = GenericCreator.createGenericObject(
            (firstPara, secondPara) -> new DemoObject(firstPara, secondPara),
            "本对象是使用两个参数的构造器创建的。",
            "编号 002");
    

      当然,也可以直接提供 构造器引用

    DemoObject demoObject3 = GenericCreator.createGenericObject(DemoObject::new,
            "本对象是使用两个参数的构造器创建的。", "编号 003");
    

      综合代码如下:

    package org.wangpai.genericcreator.test;
    import org.wangpai.genericcreator.model.DemoObject;
    import org.wangpai.genericcreator.model.GenericCreator;
    public class GenericObjectTest {
        public static void main(String[] args) {
            DemoObject demoObject1 = GenericCreator.createGenericObject(DemoObject::new);
            demoObject1.show();
            DemoObject demoObject2 = GenericCreator.createGenericObject(
                    (firstPara, secondPara) -> new DemoObject(firstPara, secondPara),
                    "本对象是使用两个参数的构造器创建的。",
                    "编号 002");
            demoObject2.show();
            DemoObject demoObject3 = GenericCreator.createGenericObject(DemoObject::new,
                    "本对象是使用两个参数的构造器创建的。", "编号 003");
            demoObject3.show();
    

      运行结果如下:

    本对象是使用无参构造器创建的
    本对象是使用两个参数的构造器创建的。编号 002
    本对象是使用两个参数的构造器创建的。编号 003
    

    这种办法为什么可以创建泛型对象

      不是有类型擦除,为什么还可以创建泛型对象呢?注意,这并不与“不能直接创建泛型对象”所矛盾,这实际上是基于一种 IoC 技术。上面的泛型方法实际上自身并没有直接创建泛型对象,它只是调用了一个方法,而这个方法是我们在使用这个泛型方法时才实现,且这个方法也没有创建泛型对象,它创建的是具体类型的对象,所以这不矛盾。

      IoC 需要函数式编程的支持。这种办法的原理其实就是构造这样的一个方法 A,这个方法 A 假定一个方法 B 会返回一个泛型对象,因此它可以直接使用方法 B 的返回值,也就是那个泛型对象,而不需要创建它。创建这个泛型对象是由方法 B 完成的,而方法 B 的实现是在要调用方法 A 时临时完成的。因此,这个办法实际上就是在调用方法 A 的这时,提供方法 B 的实现,而这个方法 B 会创建一个具体的非泛型对象并返回,从而表现得好像方法 A 可以创建泛型对象一样。

      关于函数式编程,可见笔者的另一篇博客:

      Java 函数式编程入门:
    https://blog.csdn.net/wangpaiblog/article/details/122762637

      能不能事先提供方法 B 的实现呢?不能。还是因为不能直接创建泛型对象,而又因为是泛型,所以无法知道需要创建的具体是哪个具体类的对象,所以只能在使用方法 A 时提供。因为在那时,方法 A 的使用者就知道了自己具体需要使用的类型是什么,因此就可以为其提供构造方法,这叫做泛型的实例化。

    创建泛型数组

      有了前面的铺垫,现在就变得很简单。因为数组本质上也是一种特殊的对象类型,所以从本质上来讲,此处的办法与前面的原理是一样的,只是使用的 API 有所不同。

    Java 8 之前:使用反射创建

      操作模板如下:

    // classArrayT 为已有 Class 对象,函数 getArrayClassT 代表一个返回目标数组所属的 Class 对象的函数
    Class<T> classArrayT = getArrayClassT(); 
    // arrayLength 为已有 int 类型变量,函数 getArrayLength 代表一个返回所需数组长度的函数
    int arrayLength = getArrayLength();
    // 通过数组类型来创建空数组
    T[] targetArray = (T[]) Array.newInstance(classArrayT.getComponentType(), arrayLength);
    

      示例如下:

    public static <T> T[] createGenericArray(Class<T[]> classArrayT, int arrayLength) {
        return (T[]) Array.newInstance(classArrayT.getComponentType(), arrayLength);
    
    int length = 100;
    // demoArray 为空数组,不能直接使用,还需初始化其内各元素
    Demo[] demoArray = GenericCreator.createGenericArray(Demo[].class, length);
    

      注意:这样创建之后,得到的是一个空有长度无内容的空数组,因此,后续使用该数组,还需要依次初始化该数组的各个元素。

    在 Java 8 之后:使用 IoC 技术创建

      因为创建空数组不需要参数,所以创建泛型数组将会比前面创建泛型变量简单。此处不再需要自定义接口了,因为 JDK 已经提供了一个 IntFunction<T[]> 接口,这个接口正好有一个 int 类型的参数。因此只需要提供它的 接口调用方法,然后直接传入一个数组的构造器即可。

    • 接口调用方法
    public static <T> T[] createGenericArray(IntFunction<T[]> genericArrayCreator, int arrayLength) {
        return genericArrayCreator.apply(arrayLength);
    
    • 接口实现方法
    int length = 10;
    DemoObject[] demoArray1 = GenericCreator.createGenericArray(DemoObject[]::new, length);
    

      但是要注意,这样创建之后,得到的是一个空有长度无内容的空数组,因此,后续使用该数组,还需要依次初始化该数组的各个元素。

      不是不能实现让数组在创建时初始化。为此,需要自行编写一个返回数组的 lambda 表达式。

    int length = 10;
    DemoObject[] demoArray2 = GenericCreator.createGenericArray(
            arrayLength -> {
                DemoObject[] demoArray = new DemoObject[arrayLength];
                for (int order = 0; order < demoArray.length; ++order) {
                    demoArray[order] = new DemoObject("本对象是使用两个参数的构造器创建的。", "编号 " + order);
                return demoArray;
            }, length);
    

      综合代码如下:

    package org.wangpai.genericcreator.test;
    import org.wangpai.genericcreator.model.DemoObject;
    import org.wangpai.genericcreator.model.GenericCreator;
    public class GenericArrayTest {
        public static void main(String[] args) {
            int length = 10;
            // demoArray1 为空数组,不能直接使用,还需初始化其内各元素
            DemoObject[] demoArray1 = GenericCreator.createGenericArray(DemoObject[]::new, length);
            DemoObject[] demoArray2 = GenericCreator.createGenericArray(
                    arrayLength -> {
                        DemoObject[] demoArray = new DemoObject[arrayLength];
                        for (int order = 0; order < demoArray.length; ++order) {
                            demoArray[order] = new DemoObject("本对象是使用两个参数的构造器创建的。", "编号 " + order);
                        return demoArray;
                    }, length);
            for (var demoObject : demoArray2) {
                demoObject.show();
    

      运行结果如下:

    本对象是使用两个参数的构造器创建的。编号 0
    本对象是使用两个参数的构造器创建的。编号 1
    本对象是使用两个参数的构造器创建的。编号 2
    本对象是使用两个参数的构造器创建的。编号 3
    本对象是使用两个参数的构造器创建的。编号 4
    本对象是使用两个参数的构造器创建的。编号 5
    本对象是使用两个参数的构造器创建的。编号 6
    本对象是使用两个参数的构造器创建的。编号 7
    本对象是使用两个参数的构造器创建的。编号 8
    本对象是使用两个参数的构造器创建的。编号 9
    

      那么,究竟是使用反射的方案,程序运行速度快,还是使用 IoC 方案呢?很多人可能认为反射会拖慢速度,所以是使用反射更慢。不过实践是检验真理的唯一标准。

      这里以创建数组为例进行了测试。由于运行结果会因机器性能、运行环境等而异,运行结果仅供参考。

    @Test
    void test() {
        long maxTime = 10000000000L;
        int length = 10000;
            final long START_TIME = System.currentTimeMillis();
            for (long time = 0; time < maxTime; ++time) {
                GenericCreator.createGenericArray(Demo[]::new, length);
            final long interval = System.currentTimeMillis() - START_TIME;
            System.out.println(String.format("测试 1 运行用时:%dms", interval));
            final long START_TIME = System.currentTimeMillis();
            for (long time = 0; time < maxTime; ++time) {
                GenericCreator.createGenericArray(Demo[].class, length);
            final long interval = System.currentTimeMillis() - START_TIME;
            System.out.println(String.format("测试 2 运行用时:%dms", interval));
        // ----------- 以上是预测试,结果不计最终测试结果。实验结果表明,方法在第一次运行时,时间会偏大很多,所以先进行一次预测试 ----------
            final long START_TIME = System.currentTimeMillis();
            for (long time = 0; time < maxTime; ++time) {
                GenericCreator.createGenericArray(Demo[]::new, length);
            final long interval = System.currentTimeMillis() - START_TIME;
            System.out.println(String.format("测试 1 运行用时:%dms", interval));
            final long START_TIME = System.currentTimeMillis();
            for (long time = 0; time < maxTime; ++time) {
                GenericCreator.createGenericArray(Demo[].class, length);
            final long interval = System.currentTimeMillis() - START_TIME;
            System.out.println(String.format("测试 2 运行用时:%dms", interval));
    

      测试结果表明,在数组长度为 1 万、连续测试 100 亿次时,这两种方案的运行结果均大致为 3.4s,因此就运行效率而言,无论使用哪种方案都是可以的。

    完整源代码

      已上传至 GitHub 中,可免费下载:https://github.com/wangpaiblog/20220916_create-generic-object

      使用 IoC 技术间接实现创建泛型对象,总结起来,就是需要完成以下三个部分:

    • 接口

    • 接口调用方法

    • 接口实现方法

    很多人应该很清楚,在 Java 中,是不能直接创建泛型对象和泛型数组的。原因是 Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型,因此创建泛型对象就相当于创建了一个 Object 类型的对象。创建 Object 类型的对象通常没有任何意义,所以直接创建泛型对象的行为被编译器禁止。泛型数组也是一样。
    JDataStructures库 JDataStructures是使用Java泛型的本地数据结构库。 当我在大学学习数据结构时,泛型不存在。 当泛型最终可用时,我涉足了一些个人项目,但没有深入探讨。 使用泛型重新访问常见的数据结构是一种有趣的审查和学习方法。 该项目以TDD样式完成,仅使用Ant构建工具即可完成。 鉴于此,要使用此库,您将需要熟悉命令行/终端的用法。 可用的数据结构类 进行的作品 N-Ary树 动态调整数组大小(例如yo的Vector类) 堆栈的性能改进 JDK 1.8 JUnit的 以下内容在Ant构建系统被称为“目标”(不包括标有黑点的对象。它们只是命令)。 有关目标的更多信息,请查看build.xml或(是的,我知道它很糟糕,就像真正的代码战士一样!) 克隆项目(其$>是您的命令行提示符):
    目录一、泛型概述二、泛型定义(1)泛型类(2)泛型方法2.1 泛型可变参数(3)泛型接口(4)类型限定三、泛型使用(1)类型通配符1.1 上限1.2 下限四、泛型擦除五、泛型数组 一、泛型概述 泛型,即是参数化类型。在没有泛型前,构建Object类型的集合可能出现ClassCastException。而在jdk5引入泛型后,便可以在编译器实现类型检查(实际为编译器语法糖,虚拟机没有泛型对象,所有对象都属于普通类) 泛型标识符一般为 T,E,K,V 二、泛型定义 (1)泛型类 代码示例: public class Generic { private T num; public T
    可以声明带泛型数组引用,但是不能直接创建泛型数组对象,可以通过java.lang.reflect.Array的newInstance(Class<T>, int )创建T[]数组 import java.lang.reflect.Array; public class Fruit<T> { private T[] array; public Fruit(Class<T> clz, int length) { 尝试创建一个泛型数组 如果你使用Java语言,并且尝试创建一个泛型数组;好吧,其实你会发现根本无法创建一个泛型数组,编译器在编译阶段就制止了你的这一行为。 数组泛型不能很好的结合,也不能创建具有泛型类型的数组。其的原因与泛型的实现机制有关。 泛型与类型擦除 Java...
    (4)注意必须要引用类型 (5)泛型类使用可以省略类型实参的填写 擦除机制就是,在编译的过程,将泛型T替换为Object,并且擦除机制就是编译时期的一种机制,运行期间没有泛型这个概念 通配符是用来解决泛型无法协变的问题的 通配符的上界,不能进行写入数据,只能进行读取数据。
    2. 创建泛型对象——自动类型推断的菱形语法: 1) 首先,定义泛型引用一定要使用尖括号指定类型参数,例如:List<String> list、Map<String, Integer>等,其的String、Integer之类的就是类型参数; 2) 其次,使用构造器构造泛型对象的时候可以指定类型参数也可以不指定,例如: i. List...
    ### 回答1: 在Java,利用泛型返回类型不同的对象方法可以非常方便地处理不同类型的对象泛型允许我们在定义类、接口或方法时使用类型形参,从而可以使得这些类、接口或方法可以处理多种不同类型的对象。 在定义方法时,可以使用泛型来指定该方法的返回类型,同时也可以允许该方法的参数可以是不同类型的对象。例如,可以定义一个泛型方法来返回不同类型的对象: ```java public static <T> T getObject(T[] array, int index) { if (index < array.length) { return array[index]; return null; 在这个方法,使用了泛型类型形参T来指定返回类型,同时也使用了泛型数组参数T[]来允许传入不同类型的对象数组。调用该方法时,可以通过传入不同类型的数组和索引来获取不同类型的对象: ```java String[] strArray = {"Hello", "World"}; Integer[] intArray = {1, 2, 3}; String str = getObject(strArray, 1); Integer i = getObject(intArray, 2); 通过这种方法,可以避免在代码使用不必要的if语句或者switch语句来处理不同类型的对象,同时也可以使代码更加简洁、清晰、易于维护。 ### 回答2: Java泛型是一种提高代码复用性和安全性的方法。Java泛型允许定义类、接口和方法在声明时不指定类型,而是在实例化或调用方法时指定类型,这样可以提高程序的灵活性。 Java泛型返回类型不同的对象方法就是指方法的返回值类型是可变的,可以根据不同的实例化对象返回不同类型的对象。在Java泛型可以使用通配符和泛型限定来实现。 使用通配符”?”实现泛型返回类型不同的对象方法 在Java泛型,“?”是一种通配符,表示任意类型。使用通配符“?”可以在方法返回值和参数使用任意类型。通配符一般应用于参数的只读操作,不能用于写入新数据。以下是使用通配符“?”实现泛型返回类型不同的对象方法的示例: public class GenericReturnType { public static <T> T getObject(Class<T> clazz) throw Exception{ T obj = clazz.getConstructor().newInstance(); return obj; public static void main(String[] args) throw Exception{ String str = getObject(String.class); Integer intObj = getObject(Integer.class); 在这个例子,构造方法获取一个泛型类型对象,并返回该对象。通过使用通配符“?”,这个方法能够返回不同类型的对象。 使用泛型限定实现泛型返回类型不同的对象方法 Java泛型限定是指使用“extends”或“super”关键字限制类型的范围,使得泛型类型只能是某个类或其子类,或某个超类或其父类。使用泛型限定可以在泛型返回类型不同的对象方法灵活地对类型进行限制。以下是使用泛型限定实现泛型返回类型不同的对象方法的示例: public static <T extends Number> T getObject(Class<T> clazz) throws Exception { T obj = clazz.getConstructor().newInstance(); return obj; 在这个例子,使用了“ extends Number”的泛型限定,表示“T”只能是Number或其子类类型。这样,该方法只能返回一个Number或其子类对象Java泛型能够提高代码的可读性、重用性和安全性。通过使用通配符“?”和泛型限定,“泛型返回类型不同的对象方法”可以根据不同的实例化对象返回不同类型的对象。 ### 回答3: Java泛型是一种强类型检查机制,它可以确保集合只存储特定类型的数据。Java利用泛型可以方便地创建不同类型的集合、类、接口和方法。在Java,可以将泛型作为方法的返回类型,用来返回不同类型的对象泛型的返回类型可以使用通配符来表示,例如: ```java public class Data<T> { private T data; public Data(T data) { this.data = data; public T getData() { return data; public static void main(String[] args) { Data<String> stringData = new Data<String>("Hello World"); Data<Integer> intData = new Data<Integer>(100); System.out.println(stringData.getData()); System.out.println(intData.getData()); 在上面的例子,Data类使用泛型T作为类型参数,并实现了一个方法来返回T类型的数据。在main方法,我们创建了Data<String>和Data<Integer>的实例,并调用getData方法来获得各自类型的数据。 此外,Java还提供了通配符表达式和上限通配符来进一步扩展泛型的使用。 使用通配符表达式时,可以使用问号(?)代替泛型类型参数,表示能够接受任何类型的数据。例如: ```java public static void printList(List<?> list) { for (Object obj : list) { System.out.print(obj + " "); System.out.println(); 在上面的例子,printList方法的参数是一个可以接受任何类型的数据的List集合。 使用上限通配符时,可以对泛型类型参数进行限制,只允许方法返回指定的对象类型或其子类型。例如: ```java public static <T extends Number> T add(T a, T b) { return (T) (a.intValue() + b.intValue()); 在上面的例子,add方法使用了一个泛型类型参数T,并使用extends关键字将其限制为Number的子类型。这样,我们就可以保证方法只会返回Number类型或其子类型的数据。 因此,Java利用泛型返回类型不同的对象的方法可以通过泛型的继承特性、通配符表达式和上限通配符来实现。通过正确使用泛型,可以增加代码的可读性和可维护性,避免出现类型转换错误和其他常见的程序错误。
    解决IntelliJ IDEA报错Error:Cannot determine path to ‘tools.jar‘ library for 17 (C:\Program Files\Java\jd 90210 解决IntelliJ IDEA下Maven报错Unknown lifecycle phase “.test.skip=true“. You must specify a valid lifecycle weixin_37520685: 加引号正常了