Java基础API中最具误导性的方法是什么?

36

最近我试图把一个字符串转换成boolean类型,自动完成窗口出现了方法boolean Boolean.getBoolean(String name)。随后,另外一个方法boolean Boolean.parseBoolean(String s)也跟着出现了。这让我搜索了解它们之间的区别,因为它们看起来都是做同样的事情。

事实证明,Boolean.getBoolean(String name)真正做的是检查给定名称的System属性是否存在且其值为true。我认为这很误导人,因为我肯定不希望Boolean的一个方法实际上调用了System.getProperty,而且仅仅通过观察方法签名,它看起来(至少对我来说)应该用于将String解析为boolean。当然,javadoc清楚地说明了这一点,但我仍然认为该方法的名称具有误导性,并且不在正确的位置。其他基本类型包装器,例如Integer也有类似的方法。

此外,我认为像-Darg=true这样的东西并不常见,因此它似乎不是属于基本API中非常有用的方法。也许这是一个适合Java职位面试的好问题:“Boolean.getBoolean(“true”)”的输出是什么?我认为这些方法更适合放在System类中,例如getPropertyAsBoolean;但是,我仍然认为将这些方法放入基本API中是不必要的。将它们放在诸如Properties类之类的地方会更合理,因为在这种情况下进行此类型的转换非常常见。

您对此有何看法?另外,如果您还知道其他“棘手”的方法,请分享出来。

N.B. 我知道可以使用Boolean.valueOfBoolean.parseBoolean将字符串文字转换为boolean,但我只是想讨论API设计。


1
哇,那真是尴尬。我希望他们解雇了想出那个方法的人:P - Thorarin
2
更有经验的Java开发人员不太可能会对此感到困惑,因为他们知道valueOf()是一种用于所有值类的一致惯例。 - Michael Borgwardt
在我看来,你不应该成为一名更有经验的Java开发人员才能避免被这样的方法绊倒... - Steve McLeod
2
请注意,许多看似愚蠢的API方法名称都可以追溯到Java早期,在当今许多惯例确立之前。出于向后兼容性的原因,这些愚蠢的方法必须保留。 - Steve McLeod
1
但是他们可以弃用这种方法,然后在下一个Java版本中将其删除。 - Amir Pashazadeh
17个回答

43

URL的equals()方法用于比较IP地址,使用网络连接,是一个阻塞操作!

从javadocs中得知:

如果两个主机名都能够解析为相同的IP地址,则认为它们等效;否则,如果其中任一主机名无法解析,则不考虑大小写的情况下认为主机名相等;或者两个主机名都等于null。

由于主机比较需要名称解析,因此该操作是一个阻塞操作。

注意: equals的定义行为已知与HTTP中的虚拟托管不一致。

请使用URI代替。


我正要提到这个。这个API函数真是太疯狂了。Joshua Bloch在他的其中一次演讲中强调了这个“特性”。 - Berlin Brown

32

Calendar类的一个众所周知的问题是月份从0到11编号,而不是从1到12。这很容易出现这样的错误:

Calendar cal = Calendar.getInstance();

// Set date to August 18, 2009? WRONG! Sets the date to September 18, 2009!
cal.set(2009, 8, 18);

正确的方法是使用常量来表示月份:

cal.set(2009, Calendar.AUGUST, 18);

但是这种方法使得使用常规月份编号1到12的错误变得太容易了。

我认为这是Calendar类设计中的一个错误。


它是这样设计的,以便您可以在标准的基于0的数组中查找给定数字的月份名称。此外,每个人都使用常量,不是吗?:-P - Jonathan
3
这是在枚举类型出现之前设计的。 - Jason S
5
如果Calendar类提供查找功能(当然要支持国际化),那么一开始就不需要使用基于零的索引。不过,大多数程序员习惯于增加或减少一个来处理索引。但是,归根结底,月份是一个序数值,从1开始计数。这个约定已经存在很长时间了,因此为了这样一个可怜的原因而忽略这个约定是一个非常糟糕的决定,这更加令人恼火的是,DAY_OF_WEEK的值是从1到7! - belugabob
2
我认为这又是一个来自著名的C API的传统 - 包括月份和日期之间的不一致性。 - Michael Borgwardt
它实际上来自Taligent项目。这是Taligent项目中仅有的几件事之一。我认为他们从未完全修复星期几错误。 - Alice Young

27

刚从这里获取了有关 List (当用Integer参数化时)的addremove方法。例如:

List<Integer> l = new ArrayList<Integer>();
l.add(20);
l.remove(20); // throws ArrayIndexOutOfBoundsException, because it will try to access index 20
l.remove(new Integer(20)); // this works   

永远不应该使用魔数的一个好例子。 - Lumpy
2
@Lumpy 如果使用 int 变量,将会产生相同的效果。 - Nikita Rybak
我永远不会遇到这样的情况:你有一个整数列表,然后尝试删除一个int。 - Lumpy
1
这是自动装箱的问题,而不是方法签名的问题。 - Amir Pashazadeh

11
String.getBytes()

由于使用底层平台字符编码,往往是应用程序中许多愚蠢的字符编码问题的原因。


7
更何况FileReader甚至没有一个允许你指定编码的构造函数。 - Michael Borgwardt
3
从javadoc中:这个类的构造函数假设默认的字符编码和默认的字节缓冲区大小是合适的。如果你想要指定这些值,请在FileInputStream上构造一个InputStreamReader。 - gnud
2
@gnud: 但是 FileReader 的作用是作为一个方便的类,让您可以在不使用那些类的情况下读取文件。实际上,如果您查看源代码,FileReader 使用 FileInputStreamInputStreamReader,因此,它应该绝对允许通过构造函数设置 Charset,就像其父类一样。 - João Silva
2
Java有很多地方允许或鼓励您省略字符集,这是不幸的,因为它几乎总会导致一个晦涩的小错误,而您的美国测试人员可能不会注意到。平台的“默认编码”比无用还糟。是否有任何Java lint可以标记此使用情况? - bobince
那么获取String.getBytes()的正确方式是什么? - Shervin Asgari
2
传递字符串作为字符集参数:String.getBytes("UTF8") 或使用 Charset 对象。 - Jonathan Holloway

10

刚了解到Thread类的isInterruptedinterrupted方法。来自javadoc:

static boolean interrupted()
// Tests whether the current thread has been interrupted.
boolean isInterrupted()
// Tests whether this thread has been interrupted.
问题在于interrupted除了测试状态之外,会清除中断状态,而isInterrupted只是测试状态。

1
这确实非常令人困惑。在使用静态的interrupted()方法时要小心。另一方面,在执行if (Thread.interrupted()) throw new InterruptedException();时它很方便(通常在抛出此异常时您希望清除中断状态)。 - Peter Štibraný

8

也许这不是最糟糕的方法,但我从来不喜欢这个

假设 x 是一个已知只包含字符串的列表。下面的代码可以用于将列表转储到新分配的字符串数组中:

String[] y = x.toArray(new String[0]);

将大小为0的字符串数组传递给方法对我来说似乎很疯狂和不直观。

4
是的,这真是个令人沮丧的事情,但鉴于类型擦除,这是唯一推断类型的方式。 - João Silva
6
如果你写x.toArray(new String[x.size()]),实际上它的性能会稍微更好一些。这样它就填充了你分配并传递进去的数组,而不是查看传递进来的数组类型、扔掉它,并创建一个新的数组。但我同意,这非常笨拙。我本以为当他们添加模板时会创建一个更简洁的替代方案,但显然没有。 - Jay
@Jay,这个常见的误解是不正确的。你分配的数组需要被清零,而如果你让toArray处理它,它可以采取捷径,请参见此处:https://dev59.com/j3VC5IYBdhLWcg3w0EsD - john16384

6

InputStream.read(byte[])

该方法不会填满数组,而是读取任意数量的字节并返回该数字。您需要循环调用此方法。对于大多数小型数组,它可以正常工作,但仍有些棘手。我认为第一次使用时没有人能够完全正确地理解它。


@Micheal:但它必须这样工作!如果(唯一的)块读取方法分配了自己的字节数组,想象一下J2ME群众的哀嚎声。 - Stephen C
最好的情况是循环应该在方法中实现。由于它无论如何都会阻塞,所以现在的工作方式没有任何可想象的优势。 - Michael Borgwardt
13
它的工作方式与 C 程序员所期望的完全一样。 ;P - KitsuneYMG
1
是的,我也和@kts一样。第一次在C中使用时,因为犯了错误而第一次做对了 :) - Dave Ray
1
@Michael:最后一次读取怎么办?如果缓冲区只填充了一半,但已经到达了EOF,那么如何使读取返回完整的缓冲区呢? - Stephen C
是的,你仍然需要返回读取的字节数,但调用者不必执行循环,在许多情况下(主要是读取文件时)可以预分配正确大小的缓冲区。 - Michael Borgwardt

6

1
好的,因为字符串是不可变的,所以这样做是有意义的,因为它允许共享。当然,如果从一个大字符串中获取一个小子字符串,则该字符串不会被垃圾回收。 - Shimi Bandiel
1
哦,是的...我也遇到过这个问题...从几百个100k-2mb文件中获取单词列表。花了太长时间才发现那个OutOfMemoryException。 - CoderTao

5

我的问题与String类的substring方法有关;每次使用它时,我都必须写出单词"hamburger"和"hamburger".substring(4,8) = "urge",以记住如何正确使用它。


2
大多数子串/切片/子集 API 的工作方式相似,所以我不确定这到底有多令人困惑。我已经习惯了这些范围是 clopen 的 - 底部关闭但顶部开放。这也使迭代更加容易。 - jasonmp85
这样做的主要原因是使用范围,因为整数范围[4,8]的宽度为5;从4开始宽度为4的范围是[4,7]。但是,使用浮点数时,情况就不再如此!这是因为整数范围[4,8]实际上应该写成[4,9)(这也使得计算宽度更容易...)。 - BlueRaja - Danny Pflughoeft

5

System.setOut() 方法将值设置为 System 的一个 final 成员变量!!!


2
在我看来,真正的问题是System.{in,out,err}应该是静态方法而不是静态final成员。 - Stephen C
哇!注意到了。初始化后如何设置一个final变量?setter调用了另一个被标记为native的setter。发生了什么事? - asgs
实际上,System.{in,out,err} 在 JLS 中被定义为最终值,但在线程安全方面不应将其视为最终值。规范中没有类似于异常情况的内容 :( - Shimi Bandiel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接