"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实现CharSequence,Vector实现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 方法以返回某些内容?我最初的预感是肯定的,因为:
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.sort 和 Arrays.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 StringBuffer 和 StringBuilder)以促进流畅。
// 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 replacingvoidwith a result type has the combined effect of:
- deleting the old method, and
- adding a new method with the new result type or newly
voidresult.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
NoSuchMethodErrormay 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 typeT[].
实际上,是的,有一种方法可以改进以前 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/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案
我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere