Java – Common Gotchas
本着其他平台的精神,跟进这个问题似乎是合乎逻辑的:Java中常见的非显而易见的错误是什么? 似乎他们应该工作的东西,但不要。
我不会给出如何构build答案的指导方针,也不会给出指导方针,因为这就是投票的目的。
也可以看看:
- Perl – 常见的陷阱
- .NET – 共同的陷阱
使用==
而不是.equals()
来比较对象的相等性 – 对于基元,其行为完全不同。
这个gotcha确保当"foo" == "foo"
但是new String("foo") != new String("foo")
时,新手被弄糊涂了。
"a,b,c,d,,,".split(",").length
返回4, 而不是 7你可能(我当然也 )期望。 split
忽略所有返回的空尾string。 这意味着:
",,,a,b,c,d".split(",").length
返回7! 为了得到我所认为的“最不可思议”的行为,你需要做一些相当惊人的事情:
"a,b,c,d,,,".split(",",-1).length
得到7。
重写equals()而不是hashCode()
使用地图,集合或列表时,可能会有非常意想不到的结果。
我认为一个非常偷偷摸摸的是String.substring
方法。 这重新使用与原始string具有不同的offset
和length
的相同的基础char[]
数组。
这可能会导致很难看到的内存问题。 例如,你可能会parsing一些非常大的文件(可能是XML
)。 如果你已经把整个文件转换为一个String
(而不是用Reader
来“遍历”这个文件)并且使用substring
来获取你想要的位,那么你仍然在整个文件大小的char[]
数组中场景。 我已经看到这种情况发生了很多次,可能很难发现。
事实上,这是一个完美的例子,为什么接口永远不能与实现完全分离。 对于为什么你应该怀疑第三方代码的质量,这是一个很好的介绍(对我来说)几年前。
SimpleDateFormat不是线程安全的。
有两个让我烦恼不less。
date/日历
首先,Javadate和日历类严重混乱。 我知道有build议来解决他们,我只是希望他们成功。
Calendar.get(Calendar.DAY_OF_MONTH)从1开始
Calendar.get(Calendar.MONTH)是从0开始的
自动防拳击思维
另一个是Integer vs int(这适用于对象的任何原始版本)。 这是由于不考虑Integer与int不同而造成的一个烦恼(因为你可以通过自动装箱来处理它们)。
int x = 5; int y = 5; Integer z = new Integer(5); Integer t = new Integer(5); System.out.println(5 == x); // Prints true System.out.println(x == y); // Prints true System.out.println(x == z); // Prints true (auto-boxing can be so nice) System.out.println(5 == z); // Prints true System.out.println(z == t); // Prints SOMETHING
由于z和t是对象,即使它们保持相同的值,它们也是(很有可能)不同的对象。 你的意思是:
System.out.println(z.equals(t)); // Prints true
这可能是一个痛苦追查。 你去debugging一些东西,一切都看起来很好,最后你最终发现你的问题是当两个对象都是5!= 5。
可以说
List<Integer> stuff = new ArrayList<Integer>(); stuff.add(5);
太好了。 它让Java变得更加实用,不必把所有这些“新的Integer(5)”和“((Integer)list.get(3))。intValue()”都放在这个地方。 但是这些好处来自这个问题。
尝试阅读充满可怕东西的Java Puzzlers ,即使这些东西大部分都不是你每天碰到的东西。 但是这会破坏你对语言的大部分信心。
List<Integer> list = new java.util.ArrayList<Integer>(); list.add(1); list.remove(1); // throws...
旧的API并没有考虑到拳击的devise,所以过载了基元和对象。
我刚刚遇到的这一个:
double[] aList = new double[400]; List l = Arrays.asList(aList); //do intense stuff with l
任何人都看到问题?
会发生什么事, Arrays.asList()
需要一个对象types的数组(例如Double [])。 如果它只是抛出了以前的错误,这将是很好的。 但是, asList()
也可以像这样使用参数:
Arrays.asList(1, 9, 4, 4, 20);
所以代码是用一个元素创build一个List
– 一个double[]
。
我应该想出了花费0毫秒sorting一个750000元素arrays…
花车
我不知道我见过很多次
floata == floatb
在哪里“正确的”testing应该是
Math.abs(floata - floatb) < 0.001
我真的希望BigDecimal与文字语法是默认的十进制types…
这一个已经超过了我几次,我听说了不less经验丰富的java开发人员浪费了很多时间。
ClassNotFoundException —你知道类是在类path中,但是你不知道为什么类没有被加载。
其实这个类有一个静态块。 在静态块中有一个例外,有人吃了这个例外。 他们不应该。 他们应该抛出ExceptionInInitializerError。 所以,总是寻找静态块来绊倒你。 它还有助于将静态块中的任何代码移动到静态方法中,以便使用debugging器debugging该方法变得更容易。
对于Java来说并不特定,因为许多语言(但不是全部)都以这种方式实现,但%
运算符不是真正的模运算符,因为它使用负数。 这使得它成为余数运算符,如果你不知道它会导致一些惊喜。
下面的代码会显示打印“偶数”或“奇数”,但它不会。
public static void main(String[] args) { String a = null; int n = "number".hashCode(); switch( n % 2 ) { case 0: a = "even"; break; case 1: a = "odd"; break; } System.out.println( a ); }
问题是“number”的散列码是负数,所以交换机中的n % 2
操作也是负数。 由于交换机没有处理负面结果的情况,所以variablesa
永远不会被设置。 该程序打印出null
。
确保你知道%
运算符如何处理负数,不pipe你在用什么语言。
我认为当我是一个年轻的程序员时,我总是会陷入困境,是从一个数组中移除的并发修改exception :
List list = new ArrayList(); Iterator it = list.iterator(); while(it.hasNext()){ //some code that does some stuff list.remove(0); //BOOM! }
从事件调度线程外部操作Swing组件可能会导致难以发现的错误。 即使我们(经验丰富的程序员,拥有3年6年Java经验)经常忘记这件事情! 有时候这些错误会在写完代码之后溜进去,然后不小心重构
看到这个教程为什么你必须 。
整数划分
1/2 == 0 not 0.5
不可变的string,这意味着某些方法不会更改原始对象,而是返回修改的对象副本。 当我用Java开始时,我总是忘记这一点,并想知道为什么replace方法似乎不能在我的string对象上工作。
String text = "foobar"; text.replace("foo", "super"); System.out.print(text); // still prints "foobar" instead of "superbar"
如果你有一个与构造函数同名的方法,那么BUT有一个返回types。 尽pipe这个方法看起来像一个构造函数(对于noob),但它不是。
将主要parameter passing给主要方法 – 新手需要一些时间才能习惯。
传球。 作为在当前目录中执行程序的类path的参数。
意识到一个string数组的名称并不明显
hashCode和equals:很多拥有5年以上经验的java开发人员都不太清楚。
设置vs列表
直到JDK 6,Java没有NavigableSets让你轻松地遍历Set和Map。
(联合)拳击和长期/长期混乱。 与之前的Java 5体验相反,您可以在下面的第二行获得NullPointerException。
Long msec = getSleepMsec(); Thread.sleep(msec);
如果getSleepTime()返回null,则取消装箱操作。
默认散列是非确定性的,因此如果用于HashMap中的对象,则该映射中条目的sorting可能会从运行变为运行。
作为一个简单的演示,下面的程序可以根据运行方式给出不同的结果:
public static void main(String[] args) { System.out.println(new Object().hashCode()); }
多less内存分配给堆,或者是否在debugging器中运行,都可以改变结果。
使用?
generics通配符。
人们看到它,并认为他们必须,例如,当他们想要一个List
他们可以添加任何东西,而不是停下来认为一个List<Object>
已经做到这一点,使用List<?>
。 然后他们想知道为什么编译器不会让它们使用add()
,因为List<?>
真的意味着“我不知道的某种特定types的列表”,所以你可以用List
来做的唯一事情就是获取来自它的Object
实例。
当您创build一个ByteBuffer
的duplicate
或slice
时,它不会从父缓冲区inheritanceorder
属性的值 ,所以像这样的代码不会达到您的预期:
ByteBuffer buffer1 = ByteBuffer.allocate(8); buffer1.order(ByteOrder.LITTLE_ENDIAN); buffer1.putInt(2, 1234); ByteBuffer buffer2 = buffer1.duplicate(); System.out.println(buffer2.getInt(2)); // Output is "-771489792", not "1234" as expected
非统一型制度与客体导向思想相矛盾。 尽pipe一切都不一定是堆分配的对象,程序员仍然应该允许通过调用它们的方法来处理基本types。
types擦除的genericstypes系统实现是可怕的,并且当他们在Java中学习generics时抛弃了大多math生:为什么如果已经提供了types参数,我们仍然需要进行types转换? 是的,他们确保向后兼容,但成本相当低。
在常见的陷阱中,众所周知但偶尔也会咬人程序员,在所有C语言中都有经典的if (a = b)
。
在Java中,当然,只有a和b是布尔值才能工作。 但是我经常看到新手testingif (a == true)
( if (a)
更短,更可读和更安全…),偶尔会写错if (a = true)
,为什么testing不会,工作。
对于那些没有得到它的人:最后的声明首先分配给a
true
a
,然后做testing,总是成功的!
–
一个咬了很多新手,甚至有些分心的更有经验的程序员(在我们的代码中find它), if (str == "foo")
。 请注意,我总是想知道为什么Sun会覆盖string的+号而不是==号,至less对于简单的情况(区分大小写)。
对于新手来说: ==
比较引用,而不是string的内容。 你可以有两个相同内容的string,存储在不同的对象(不同的引用),所以==
将是错误的。
简单的例子:
final String F = "Foo"; String a = F; String b = F; assert a == b; // Works! They refer to the same object String c = "F" + F.substring(1); // Still "Foo" assert c.equals(a); // Works assert c == a; // Fails
–
而且我也看到了if (a == b & c == d)
或类似的东西。 它工作(好奇地),但我们失去了逻辑运算符的快捷方式(不要试图写: if (r != null & r.isSomething())
!)。
对于新手来说:当评估a && b
,如果a是false,Java不会评估b。 在a & b
,Java评估两个部分然后执行操作; 但第二部分可能会失败。
[编辑]来自J库姆斯的好build议,我更新了我的答案。
首先,这是我今天发现的一个。 这与Long
/ long
混乱有关。
public void foo(Object obj) { if (grass.isGreen()) { Long id = grass.getId(); foo(id); } } private void foo(long id) { Lawn lawn = bar.getLawn(id); if (lawn == null) { throw new IllegalStateException("grass should be associated with a lawn"); } }
显然,这些名字已经改变,以保护无辜的:)
另一个我想指出的是(通俗)推动API通用。 使用精心devise的通用代码是很好的。 devise你自己很复杂。 非常复杂!
只要看看新的Swing JTable
中的sorting/筛选function。 这是一个完整的噩梦。 很明显,你可能想要在现实生活中连锁filter,但是我发现如果不使用所提供的原始types版本,就不可能这样做。
System.out.println(Calendar.getInstance(TimeZone.getTimeZone("Asia/Hong_Kong")).getTime()); System.out.println(Calendar.getInstance(TimeZone.getTimeZone("America/Jamaica")).getTime());
输出是一样的。
恕我直言1.使用vector.add(集合),而不是vector.addall(集合)。 第一个将集合对象添加到vector,第二个添加集合的内容。 2.虽然与编程无关,但使用来自多个来源(如xerces,jdom)的xmlparsing器。 依靠不同的parsing器,并在类path中有他们的jar子是一场噩梦。
我曾经有过一次debuggingTreeSet的乐趣,因为我没有意识到API中的这些信息:
请注意,如果要正确实现Set接口,则由集合(不论是否提供显式比较器)维护的sorting必须与equals保持一致。 (请参阅Comparable或Comparator以获得与equals一致的精确定义)。这是因为Set接口是根据equals操作定义的,但TreeSet实例使用compareTo(或compare)方法执行所有关键比较,所以两个从这个方法来看,被这个方法视为相等的密钥是相等的。 即使sorting与等号不一致,集合的行为也是明确的。 它只是不服从Set接口的总体合同。 http://download.oracle.com/javase/1.4.2/docs/api/java/util/TreeSet.html
具有正确的equals / hashcode实现的对象正在被添加,并且再也没有看到compareTo实现与equals不一致的情况。