【问题标题】:Is there any reason to use generics in Java?有什么理由在 Java 中使用泛型?
【发布时间】:2014-12-29 13:17:30
【问题描述】:

也许我没有抓住重点,但来自 C# 背景,我看不出有任何理由使用 Java 的泛型...

在 C# 中,我有一个方法,它接受一个字符串并将其反序列化为一个对象...

public static Deserialize<T>(string source) {
    // Use reflection to examine T and determine
    // which properties I should be reading from
    // the string. Create an instance of T, 
    // populate the properties and return it
}

我可以这样称呼:x.Deserialize&lt;AnyClass&gt;("String representation of AnyClass");

类型擦除似乎让这在 Java 中变得不可能?

我对泛型的下一个最常见用法是将内容存储在列表中,但由于我不能使用 List&lt;int&gt; 并且必须装箱才能使用 List&lt;Integer&gt;,所以当我可以使用 @987654325 时,它似乎是毫无意义的架空装箱/拆箱@。

所以...是泛型唯一真正的用途,比如

List<MyClass> MyClassList = new List<MyClass>

并进行一些编译时类型检查?如果是这样,考虑到变量名称清楚地表明了类型应该是什么,这似乎是多余的(至少只要我使用合理的命名约定)。

我不禁觉得我错过了什么......

【问题讨论】:

    标签: java generics


    【解决方案1】:

    好吧,如果您不使用泛型,那么您每次都可以强制转换。你必须:

    // if raw List
    MyClass c = (MyClass) myClassList.get(0);
    // if List<MyClass>
    MyClass c = myClassList.get(0);
    

    例如。

    是的,泛型在运行时会被删除,是的,它们仅在编译时强制执行类型安全;但是,当您使用边界时,仍然存在运行时类型擦除。示例:

    public <E extends RuntimeException> E get(final Class<E> c,
        final Throwable t)
    {
        try {
            return (E) getHandle(c).invokeExact(t);
        } catch (Error | RuntimeException e) {
            throw e;
        } catch (Throwable oops) {
            final RuntimeException exception = new IllegalStateException(oops);
            exception.addSuppressed(t);
            throw exception;
        }
    }
    

    这里的E有一个运行时类型擦除;它extends RuntimeException。因此演员表是RuntimeException,而不是Object

    查看javadoc of Collections.max() 了解另一个有趣的运行时类型擦除示例(提示:为什么它定义T extends Object &amp; Comparable&lt;? super T&gt; 而不是T extends Comparable&lt;? super T&gt;?)

    【讨论】:

    • 有趣的一点是:界限,但这意味着你总是以最小的公分母为目标。如果您希望能够比较 (计数)怎么办?可比的作品,因为它被所有可比的班级广泛使用。如果你想拥有 2 个这样的方法,你需要你的类从 MyComparableCountable 继承,这将你限制在你已经实现的类......
    • 好吧,我不知道你说的Count是什么意思;注意你extends E1 &amp; E2 &amp; etc时,第二个参数和进一步必须是接口。如果您的Count 是一个具体的类,那么编写T extends Count &amp; Comparable&lt;? super T&gt; 将起作用,并且您将能够将结果用作CountComparable。同样,此检查是在编译时进行的,编译器将发出适当的代码,这样您就不会得到ClassCastException
    • Count 可能是一个不好的例子。我试图选择另一种可能存在于多个类但不是全部的方法。你是说我可以根据需要将尽可能多的“功能”链接在一起...Count &amp; Comparable &amp; SomethingElse&lt;? super T&gt;
    • “我试图选择另一种可能存在于多个类中的方法”
    • &amp; in type bound 创建一个intersection type
    【解决方案2】:

    来自 Java 文档:

    为什么要使用泛型?

    简而言之,泛型使类型(类和接口)在定义类、接口和方法时成为参数。就像在方法声明中使用的更熟悉的形式参数一样,类型参数为您提供了一种通过不同输入重用相同代码的方法。区别在于形式参数的输入是值,而类型参数的输入是类型。

    使用泛型的代码比非泛型代码有很多好处:

    • 在编译时进行更强的类型检查。 Java 编译器对泛型代码应用强类型检查,并在代码违反类型安全时发出错误。修复编译时错误比修复运行时错误更容易,后者可能很难找到。

    • 消除演员表。 以下没有泛型的代码 sn-p 需要强制转换:

      List list = new ArrayList();
      list.add("hello");
      String s = (String) list.get(0);
      

      当重写为使用泛型时,代码不需要强制转换:

      List<String> list = new ArrayList<String>();
      list.add("hello");
      String s = list.get(0); // no cast
      
    • 使程序员能够实现通用算法。 通过使用泛型,程序员可以实现适用于不同类型集合、可以自定义、类型安全且更易于阅读的泛型算法。

    【讨论】:

    • 我很欣赏你所说的,但这只是编译时的糖。其他语言中泛型的真正强大之处在于它们在运行时的使用。
    【解决方案3】:

    泛型类型仅在编译时检查,但有助于避免错误。

    【讨论】:

      【解决方案4】:
      List<MyClass> MyClassList = new ArrayList<MyClass>();
      

      不等于:

      List MyClassList = new ArrayList();
      

      第二个将适用于 Object 类型,您将被允许做一些愚蠢的事情,例如 MyClassList.add(new Integer(0));

      这是泛型或 C++ 中的模板的主要优势之一。编译时检查,验证您的应用程序不会执行任何未定义的行为 - 就像上面的示例一样。

      【讨论】:

      • 也许我很幸运,但我从未与不称职的开发人员合作过。当然另一个检查很好但是......如果有人犯了这样的错误,我不希望他们在我的代码库上工作
      • @Basic 可能我的示例有点过于激烈,在复杂和大型代码库中类型检查很有帮助 - 这只是帮助开发人员查找错误的另一个工具。这就像是在 java 中使用断言还是总是抛出异常的争论。
      • 我可以根据我在动态语言(主要是 Clojure)方面的经验证明,追踪集合的内容非常痛苦,尤其是当您的代码经常使用相同概念的类似表示时(例如DB ID、完整的实体对象、简化的专用哈希等)。所有此类集合通常使用相同的名称。
      【解决方案5】:

      您的Deserialize 方法在Java 中并非不可能。您只需要显式传递Class 对象,因为它无法在运行时从类型参数T 中确定。

      public static <T> T deserialize(Class<T> clazz, String source) {
          // Use reflection to examine clazz and determine
          // which properties I should be reading from
          // the String. Create an instance of T 
          // (which you can do using clazz.newInstance()), 
          // populate the properties and return it
      }
      

      可以使用deserialize(AnyClass.class, "String representation of AnyClass") 调用它。

      【讨论】:

      • 不错的建议,但如果我要反序列化的类具有通用属性,这不会失败吗?例如,即使从您的clazz 我也无法确定通用参数是什么?支持解决方法
      • 是的,在这种情况下它确实会失败。这是我承认 C# 更好的一个领域。
      【解决方案6】:
      List list = new ArrayList();
      list.add("car");
      list.add("cycle");
      list.add(12);
      

      这里的列表可以包含任何对象,可以是String、Integer或double、Employee等
      这可能会导致类型安全问题并在编译时停止运行时错误。

      List<String> list  = new ArrayList<String>();
      

      这里只能添加字符串值

      【讨论】:

      • 那会是我在原帖中提到的So... Is the only real use for generics to do things like ... and get some compile-time type checking?
      • 是的,它避免了 ClassCastException 并且是类型安全的
      • 我的问题更多的是关于在运行时使用类型信息——例如,使用其类型信息来序列化对象以枚举参数。无论如何,这对我来说是一个有争议的问题,因为我已经设法远离 Java,但无论如何都要留下它,以防其他人以同样的方式解释我的问题并发现它有用。
      猜你喜欢
      • 2012-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-22
      • 1970-01-01
      • 2011-06-14
      • 1970-01-01
      相关资源
      最近更新 更多