改造 void 方法以返回其参数以促进流畅性:打破变化? [英] Retrofitting void methods to return its argument to facilitate fluency: breaking change?

查看:31
本文介绍了改造 void 方法以返回其参数以促进流畅性:打破变化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

API 设计就像性爱:犯一个错误并支持你的余生" (Josh Bloch 在 twitter 上)

Java 库中有很多设计错误.Stack extends Vector(讨论),我们不能在不造成破损的情况下修复它.我们可以尝试弃用 Integer.getInteger (讨论),但它可能会永远存在.

There are many design mistakes in the Java library. Stack extends Vector (discussion), and we can't fix that without causing breakage. We can try to deprecate Integer.getInteger (discussion), but it's probably going to stay around forever.

尽管如此,某些类型的改造可以在不造成损坏的情况下进行.

Nonetheless, certain kinds of retrofitting can be done without causing breakage.

Effective Java 2nd Edition, Item 18: Prefer interfaces to abstract classes:现有的类可以很容易地改造以实现新的接口".

Effective Java 2nd Edition, Item 18: Prefer interfaces to abstract classes: Existing classes can be easily retrofitted to implement a new interface".

示例:String 实现 CharSequenceVector 实现 List

Effective Java 2nd Edition,第 42 条:明智地使用可变参数:您可以改进将数组作为其最终参数的现有方法以采用可变参数而不会对现有客户端产生任何影响.

Effective Java 2nd Edition, Item 42: Use varargs judiciously: You can retrofit an existing method that takes an array as its final parameter to take varags instead with no effect on existing clients.

一个著名的例子是 Arrays.asList,它引起了混淆(

An (in)famous example is Arrays.asList, which caused confusions (discussion), but not breakage.

这个问题是关于另一种改造的:

This question is about a different kind of retrofitting:

我最初的预感是肯定的,因为:

My initial hunch points to yes, because:

  • 返回类型不影响编译时选择的方法
    • Return type doesn't affect which method is chosen at compile time
      • See: JLS 15.12.2.11 - Return Type Not Considered
      • Thus, changing return type doesn't change which method is chosen by the compiler
      • Retrofitting from void to return something is legal (but not the other way around!)

      不过,我想听听其他在 Java/API 设计方面更有经验的人的更全面的分析.

      However, I'd like to hear a more thorough analysis by others more experienced in Java/API design.

      正如标题所暗示的,一个动机是促进流畅的界面风格的编程.

      As suggested in the title, one motivation is to facilitate fluent interface style programming.

      考虑这个打印无序名称列表的简单片段:

      Consider this simple snippet that prints a shuffled list of names:

          List<String> names = Arrays.asList("Eenie", "Meenie", "Miny", "Moe");
          Collections.shuffle(names);
          System.out.println(names);
          // prints e.g. [Miny, Moe, Meenie, Eenie]
      

      Collections.shuffle(List) 被声明为返回输入列表,我们可以这样写:

      Had Collections.shuffle(List) been declared to return the input list, we could have written:

          System.out.println(
              Collections.shuffle(Arrays.asList("Eenie", "Meenie", "Miny", "Moe"))
          );
      

      Collections 中还有其他方法,如果它们返回输入列表而不是 void,使用起来会更愉快,例如reverse(List), sort(List) 等等.其实有 Collections.sortArrays.sort return void 尤其不幸,因为它剥夺了我们编写表达性代码的能力,例如:

      There are other methods in Collections that would've been much more pleasant to use if they were to return the input list instead of void, e.g. reverse(List), sort(List), etc. In fact, having Collections.sort and Arrays.sort return void is especially unfortunate, because it deprives us from writing expressive code such as this:

      // DOES NOT COMPILE!!!
      //     unless Arrays.sort is retrofitted to return the input array
      
      static boolean isAnagram(String s1, String s2) {
          return Arrays.equals(
              Arrays.sort(s1.toCharArray()),
              Arrays.sort(s2.toCharArray())
          );
      }
      

      当然,这个 void 返回类型阻止流畅性不仅限于这些实用程序方法.java.util.BitSet 方法也可以被编写为返回 this(ala StringBufferStringBuilder)以提高流畅性.

      This void return type preventing fluency isn't just restricted to these utility methods, of course. The java.util.BitSet methods could've also been written to return this (ala StringBuffer and StringBuilder) to facilitate fluency.

      // we can write this:
          StringBuilder sb = new StringBuilder();
          sb.append("this");
          sb.append("that");
          sb.insert(4, " & ");
          System.out.println(sb); // this & that
      
      // but we also have the option to write this:
          System.out.println(
              new StringBuilder()
                  .append("this")
                  .append("that")
                  .insert(4, " & ")
          ); // this & that
      
      // we can write this:
          BitSet bs1 = new BitSet();
          bs1.set(1);
          bs1.set(3);
          BitSet bs2 = new BitSet();
          bs2.flip(5, 8);
          bs1.or(bs2);
          System.out.println(bs1); // {1, 3, 5, 6, 7}
      
      // but we can't write like this!
      //  System.out.println(
      //      new BitSet().set(1).set(3).or(
      //          new BitSet().flip(5, 8)
      //      )
      //  );
      

      不幸的是,与 StringBuilder/StringBuffer 不同,BitSetALL 的修改器返回 void.

      Unfortunately, unlike StringBuilder/StringBuffer, ALL of BitSet's mutators return void.

      推荐答案

      不幸的是,是的,更改 void 方法以返回某些内容是一项重大更改.此更改不会影响源代码兼容性(即,相同的 Java 源代码仍会像以前一样编译,绝对没有明显影响),但会破坏二进制兼容性(即,先前针对旧 API 编译的字节码将不再运行).

      Unfortunately, yes, changing a void method to return something is a breaking change. This change will not affect source code compatibility (i.e. the same Java source code would still compile just like it did before, with absolutely no discernible effect) but it breaks binary compatibility (i.e. bytecodes that was previously compiled against the old API will no longer run).

      以下是 Java 语言规范第 3 版的相关摘录:

      二进制兼容性与源代码兼容性不同.

      13.2 What Binary Compatibility Is and Is Not

      Binary compatibility is not the same as source compatibility.

      本节描述对类及其成员和构造函数的声明进行更改对预先存在的二进制文件的影响.

      This section describes the effects of changes to the declaration of a class and its members and constructors on pre-existing binaries.

      改变方法的结果类型,用void替换结果类型,或用结果类型替换void具有以下综合效果:

      Changing the result type of a method, replacing a result type with void, or replacing void with a result type has the combined effect of:

      • 删除旧方法,以及
      • 添加具有新结果类型或新 void 结果的新方法.
      • deleting the old method, and
      • adding a new method with the new result type or newly void result.

      从类中删除方法或构造函数可能会破坏与引用此方法或构造函数的任何预先存在的二进制文件的兼容性;当链接来自预先存在的二进制文件的此类引用时,可能会抛出 NoSuchMethodError.只有在超类中没有声明具有匹配签名和返回类型的方法时才会发生这样的错误.

      Deleting a method or constructor from a class may break compatibility with any pre-existing binary that referenced this method or constructor; a NoSuchMethodError may be thrown when such a reference from a pre-existing binary is linked. Such an error will occur only if no method with a matching signature and return type is declared in a superclass.

      也就是说,虽然在方法解析过程中 Java 编译器在编译时会忽略方法的返回类型,但此信息在 JVM 字节码级别的运行时很重要.

      That is, while the return type of a method is ignored by the Java compiler at compile-time during the method resolution process, this information is significant at run-time at the JVM bytecode level.

      方法的签名不包括返回类型,但它的字节码描述符包括.

      A method's signature does not include the return type, but its bytecode descriptor does.

      如果两个方法具有相同的名称和参数类型,则它们具有相同的签名.

      8.4.2 Method Signature

      Two methods have the same signature if they have the same name and argument types.

      最具体方法的描述符(签名加返回类型)是在运行时用于执行方法分派的描述符.

      The descriptor (signature plus return type) of the most specific method is one used at run time to perform the method dispatch.

      在编译时选择最适用的方法;它的描述符决定了在运行时实际执行的方法.

      The most applicable method is chosen at compile time; its descriptor determines what method is actually executed at run time.

      如果将新方法添加到类中,则使用该类的旧定义编译的源代码可能不会使用新方法,即使重新编译会导致选择此方法.

      If a new method is added to a class, then source code that was compiled with the old definition of the class might not use the new method, even if a recompilation would cause this method to be chosen.

      理想情况下,只要更改了源代码所依赖的代码,就应该重新编译源代码.但是,在不同组织维护不同类的环境中,这并不总是可行的.

      Ideally, source code should be recompiled whenever code that it depends on is changed. However, in an environment where different classes are maintained by different organizations, this is not always feasible.

      稍微检查一下字节码将有助于澄清这一点.当 javap -c 在名称改组片段上运行时,我们会看到如下指令:

      A little inspection of the bytecode will help clarify this. When javap -c is run on the name shuffling snippet, we see instructions like this:

      invokestatic java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
                   \______________/ \____/ \___________________/\______________/
                      type name     method      parameters        return type
      
      invokestatic java/util/Collections.shuffle:(Ljava/util/List;)V
                   \___________________/ \_____/ \________________/|
                         type name        method     parameters    return type
      

      相关问题

      • java:这是什么:[Ljava.lang.对象;?
      • 现在让我们解决为什么改造新的 interface 或可变参数(如 Effective Java 2nd Edition 中所述)不会破坏二进制兼容性.

        Let's now address why retrofitting a new interface or a vararg, as explained in Effective Java 2nd Edition, does not break binary compatibility.

        更改类类型的直接超类或直接超接口集不会破坏与预先存在的二进制文件的兼容性,前提是该类类型的超类或超接口的总集分别不丢失任何成员.

        13.4.4 Superclasses and Superinterfaces

        Changing the direct superclass or the set of direct superinterfaces of a class type will not break compatibility with pre-existing binaries, provided that the total set of superclasses or superinterfaces, respectively, of the class type loses no members.

        改造新的 interface 不会导致类型丢失任何成员,因此这不会破坏二进制兼容性.同样,由于 varargs 是使用数组实现的,这种改造也不会破坏二进制兼容性.

        Retrofitting a new interface does not cause the type to lose any member, hence this does not break binary compatibility. Similarly, due to the fact that varargs is implemented using arrays, this kind of retrofitting also does not break binary compatibility.

        如果最后一个形参是T类型的可变实参,则认为定义了一个T[]类型的形参.

        8.4.1 Formal Parameters

        If the last formal parameter is a variable arity parameter of type T, it is considered to define a formal parameter of type T[].

        相关问题

        • double...<形参类型声明中的/code> 和 double[]
        • 实际上,是的,有一种方法可以改进以前 void 方法的返回值.我们不能在 Java 源代码级别拥有两个具有完全相同签名的方法,但是我们可以在 JVM 级别拥有它,前提是它们具有不同的描述符(由于具有不同的返回类型).

          Actually, yes, there is a way to retrofit a return value on previously void methods. We can't have two methods with the same exact signatures at the Java source code level, but we CAN have that at the JVM level, provided that they have different descriptors (due to having different return types).

          因此我们可以提供一个二进制文件,例如java.util.BitSet 具有同时具有 void 和非 void 返回类型的方法.我们只需要发布非void 版本作为新的 API.事实上,这是我们唯一可以在 API 上发布的东西,因为在 Java 中拥有两个具有完全相同签名的方法是非法的.

          Thus we can provide a binary for e.g. java.util.BitSet that has methods with both the void and non-void return types simultaneously. We only need to publish the non-void version as the new API. In fact, that's the only thing we can publish at the API, since having two methods with the exact same signature is illegal in Java.

          这个解决方案是一个可怕的黑客,需要特殊的(和规范的)处理来编译 BitSet.javaBitSet.class,因此它可能不值得努力做到这一点.

          This solution is a terrible hack, requiring special (and spec-defying) processing to compile BitSet.java to BitSet.class, and thus it may not be worth the effort to do so.

          这篇关于改造 void 方法以返回其参数以促进流畅性:打破变化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆