java - 改造 void 方法以返回其参数以促进流畅性 : breaking change?

标签 java jvm api-design binary-compatibility

"API design is like sex: make one mistake and support it for the rest of your life" (Josh Bloch on twitter)

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

不过,某些类型的改造可以在不造成破损的情况下完成。

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, 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,它引起了混淆(discussion),但没有造成破坏。

这个问题是关于一种不同类型的改造:

你能否在不破坏现有代码的情况下改进 void 方法以返回某些内容?

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

  • 返回类型不影响编译时选择哪种方法
  • 即使你使用反射,像Class.getMethod这样的东西不区分返回类型

但是,我想听听其他在 Java/API 设计方面更有经验的人的更透彻的分析。


附录:动机

如标题所示,一个动机是促进 fluent interface风格编程。

考虑这个打印随机名称列表的简单代码段:

    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)被声明为返回输入列表,我们可以这样写:

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

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

// 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)以促进流畅。

// 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 不同,ALL BitSet 的修改器返回 void.

相关主题

最佳答案

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

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

13.2 What Binary Compatibility Is and Is Not

Binary compatibility is not the same as source compatibility.


13.4 Evolution of Classes

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

13.4.15 Method Result Type

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:

  • deleting the old method, and
  • adding a new method with the new result type or newly void result.

13.4.12 Method and Constructor Declarations

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 字节码级别的运行时,此信息很重要。


关于字节码方法描述符

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

8.4.2 Method Signature

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


15.12 Method Invocation Expressions

15.12.2 Compile-Time Step 2: Determine Method Signature

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

15.12.2.12 Example: Compile-Time Resolution

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 对名称改组代码段运行时,我们会看到如下指令:

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

相关问题


关于不间断改造

现在让我们来解释为什么改造新的接口(interface)或可变参数,如Effective Java 2nd Edition中所述,不会破坏二进制兼容性。

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 不会导致类型丢失任何成员,因此这不会破坏二进制兼容性。同样,由于可变参数是使用数组实现的,这种改造也不会破坏二进制兼容性。

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[].

相关问题


绝对没有办法做到这一点吗?

实际上,是的,有一种方法可以改进以前 void 方法的返回值。我们不能在 Java 源代码级别拥有两个具有完全相同签名的方法,但我们可以在 JVM 级别拥有它们,前提是它们具有不同的描述符(由于具有不同的返回类型) .

因此我们可以为例如java.util.BitSet 同时具有 void 和非 void 返回类型的方法。我们只需要将非 void 版本发布为新的 API。事实上,这是我们唯一可以在 API 上发布的内容,因为在 Java 中拥有两个具有完全相同签名的方法是非法的。

这个解决方案是一个可怕的 hack,需要特殊的(和违反规范的)处理来将 BitSet.java 编译为 BitSet.class,因此它可能不值得这样做的努力。

关于java - 改造 void 方法以返回其参数以促进流畅性 : breaking change?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3589946/

相关文章:

java - 如何强制 JBoss 部署使用特定版本的依赖项而不是已安装的模块?

jvm - 什么是共享对象文件?

java.lang.VerifyError - 当类的静态变量包含对类实例的引用时

mysql - Django 休息框架 : Function Based API serializers

c++ - allocator::get_deleter() 应该是 const 合格的吗?

java - 有哪些资源可以教我如何使用 Java 中的 Web 服务?

java - 使用 Spring Session Redis 时身份验证主体为空

java - 最高效的 Java 线程技术?

RESTful API : what to return when updating an entity produces side-effects

java - 文本编辑器分隔符行号重叠和长行标记