【问题标题】:Dynamic Generic Typing in JavaJava中的动态泛型类型
【发布时间】:2011-09-08 00:49:46
【问题描述】:

如果我有一个使用泛型类型的类,例如

public class Record<T> {
    private T value;

    public Record(T value) {
        this.value = value;
    }
}

如果我知道所使用的所有类型(例如本例中的情况),那么在设计时键入所有内容非常简单:

// I type explicitly
String myStr = "A";
Integer myInt = 1;
ArrayList myList = new ArrayList();

Record rec1 = new Record<String>(myStr);
Record rec2 = new Record<Integer>(myInt);
Record rec3 = new Record<ArrayList>(myList);

如果我从不知道类型的“某处”获取对象列表会怎样?如何分配类型:

// now let's assume that my values come from a list where I only know during runtime what type they have

ArrayList<Object> myObjectList = new ArrayList<Object>();
    myObjectList.add(myStr);
    myObjectList.add(myInt);
    myObjectList.add(myList);

    Object object = myObjectList.get(0);

    // this fails - how do I do that?
    new Record<object.getClass()>(object);

【问题讨论】:

  • 泛型用于编译时强类型。如果您不知道要推断的类型,那几乎没有用。您应该以这样一种方式设计它,即至少有一个已知的接口或一个您可以使用的基类。我猜你可以new Record&lt;Object&gt;(object)

标签: java generics


【解决方案1】:

Java 泛型不是 C++ 模板。

Java 泛型是编译时特性,而不是运行时特性。

这是Java generics Tutorial的链接。

这永远无法与 Java 一起使用:

new Record<object.getClass()>(object);

您必须使用多态性(例如,每个对象实现一个已知接口)或 RTTI(instanceof 或 Class.isAssignableFrom())。

你可以这样做:

     class Record
     {
       public Record(String blah) { ... }
       public Record(Integer blah) { ... }
       ... other constructors.
     }

或者您可以使用Builder pattern

【讨论】:

  • 我不明白你的第一句话。您的措辞似乎暗示,C++ 模板是运行时功能,但它们不是。它们的编译时间甚至比 Java 泛型还要多。
  • 这种方法的问题是最终你必须考虑你将创建的所有类型。如果您仅限于几种类型,则可以。如果你使用了很多类型,或者你想输入“任何”类型怎么办?出于这个原因,我认为这是一个糟糕的解决方案。由于泛型的这种限制,最终可能没有很好的解决方案......
  • 您描述的问题是糟糕设计的症状,并不代表泛型的限制,它代表的问题超出了泛型的范围。如果您需要玩“I R Guud 四种类型”的游戏,请使用脚本语言。
  • 感谢您提到重载;在使用 Python 工作后,我现在从事 Java 工作,但我忘记了那些存在。
【解决方案2】:

在运行时从泛型类型创建实例

我不完全确定您要完成什么,但乍一看似乎最简单的解决方案就是最好的解决方案。

这可以通过使用脚本环境(Groovy、JavaScript、JRuby、Jython)来解决,该环境可以动态地评估和执行任意代码来创建对象,但是仅仅为了创建一个对象就变得非常复杂和过于复杂。

但不幸的是,我认为它有一个非常普通的解决方案。

只要有一组预定义的受支持类型,您就可以使用Factory 模式。这里我只是利用javax.inject/com.google.inject 包中的Provider&lt;&gt;T 接口。

Q26289147_ProviderPattern.java

public class Q26289147_ProviderPattern
{
    private static final List<String> CLASS_NAMES = ImmutableList.of("String", "Integer", "Boolean");
    private static final Map<String, Provider<StrawManParameterizedClass>> PROVIDERS;

    static
    {
        final ImmutableMap.Builder<String, Provider<StrawManParameterizedClass>> imb = ImmutableMap.builder();
        for (final String cn : CLASS_NAMES)
        {
            switch (cn)
            {
                case "String":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<String> get() { return new StrawManParameterizedClass<String>() {}; }
                    });
                    break;
                case "Integer":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<Integer> get() { return new StrawManParameterizedClass<Integer>() {}; }
                    });
                    break;
                case "Boolean":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<Integer> get() { return new StrawManParameterizedClass<Integer>() {}; }
                    });
                    break;
                default:
                    throw new IllegalArgumentException(String.format("%s is not a supported type %s", cn, Joiner.on(",").join(CLASS_NAMES)));
            }
        }
        PROVIDERS = imb.build();
    }

    static <T> void read(@Nonnull final StrawManParameterizedClass<T> smpc) { System.out.println(smpc.type.toString()); }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};

        @Override
        public String toString() { return type.getRawType().getCanonicalName(); }
    }

    public static void main(final String[] args)
    {
        for (final String cn : CLASS_NAMES)
        {
            read(PROVIDERS.get(cn).get());
        }
    }
}

免责声明:

这只是一个概念证明示例,我永远不会使用switch 在生产代码中这样的语句我会使用 Strategy PatternChain of Responsibility 模式来封装逻辑 根据ClassName 键创建什么类型。

这最初看起来像是一个泛型问题,其实不是,这是一个创建问题。

也就是说,您不需要传递 Class&lt;?&gt; 的实例,您可以在运行时使用来自 Guava 的 TypeToken 从参数化类中获取 Generic Type 信息。

您甚至可以在运行时使用来自 Guava 库的 TypeToken 创建任何泛型类型的实例。

主要问题是不支持这种语法:Geography&lt;myClass.newInstance()&gt; geo;,除了上面的Provider 实现之外,我无论如何也想不出伪造它。

这是一个稻草人示例,说明如何使用TypeToken,以便您的 参数化的类总是知道它们的类型!

Q26289147.java

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

注意事项:

  1. 非常适合具有默认无参数构造函数的类。
  2. 如果没有默认的 no arg 构造函数,比使用直接反射效果更好。
  3. 应该与 Guice 配合使用,允许您使用 .getRawType() 生成的 Class&lt;T&gt; 传递给注入器的 getInstance()还没有尝试过,我只是想到了!
  4. 您可以使用Class&lt;T&gt;.cast() 进行不需要@SuppressWarning("unchecked") 的强制转换。

【讨论】:

    【解决方案3】:

    如果您不知道类型,则无法使用泛型强制执行编译时检查。

    你可以说只是为了使用它

    new Record<Object>(object);
    

    【讨论】:

      猜你喜欢
      • 2021-07-28
      • 2013-08-04
      • 1970-01-01
      • 2013-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多