Java - 常见问题

时间:2008-10-04 05:41:09

标签: java

与其他平台一样,跟进这个问题似乎合乎逻辑:Java中常见的非显而易见的错误是什么?似乎他们应该工作的事情,但不要。

我不会就如何构建答案给出指导,或者什么“太容易”被认为是问题,因为这就是投票的目的。

另见:

28 个答案:

答案 0 :(得分:36)

"a,b,c,d,,,".split(",").length

会尽可能地返回4, 7(而且我当然 )。 split忽略返回的所有尾随空字符串。这意味着:

",,,a,b,c,d".split(",").length

返回7!为了得到我认为的“最不惊人”的行为,你需要做一些非常惊人的事情:

"a,b,c,d,,,".split(",",-1).length

得到7。

答案 1 :(得分:35)

使用==而不是.equals()比较对象的相等性 - 它们对于基元的行为完全不同。

这个问题确保新移民在"foo" == "foo"new String("foo") != new String("foo")时会被迷惑。

答案 2 :(得分:30)

覆盖equals()但不覆盖hashCode()

使用地图,集合或列表时,可能会产生意想不到的结果。

答案 3 :(得分:30)

我认为一个非常狡猾的方法是String.substring方法。这会将相同的基础char[]数组重新用作具有不同offsetlength的原始字符串。

这可能会导致很难看到内存问题。例如,您可能正在解析一些极小的文件(可能是XML)。如果您已将整个文件转换为String(而不是使用Reader来“遍历”文件)并使用substring来抓取您想要的位,那么您仍然需要在幕后完整的文件大小的char[]数组周围。我已经看过很多次这种情况,很难发现。

事实上,这是接口永远无法与实现完全分离的完美示例。几年前,对于为什么你应该怀疑第三方代码的质量,这是一个完美的介绍(对我而言)。

答案 4 :(得分:25)

SimpleDateFormat不是线程安全的。

答案 5 :(得分:21)

尝试阅读充满可怕内容的Java Puzzlers,即使这些东西不是你每天都碰到的东西。但它会破坏你对这种语言的大部分信心。

答案 6 :(得分:21)

有两个让我烦恼不已。

日期/日历

首先,Java Date和Calendar类严重搞砸了。我知道有修复它们的建议,我只希望它们成功。

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

这可能是追踪的痛苦。你去调试一切,一切看起来都很好,你终于发现你的问题是5!= 5,当它们都是对象时。

能够说出

List<Integer> stuff = new ArrayList<Integer>();

stuff.add(5);

所以很好。它使Java变得更加可用,不必将所有那些“new Integer(5)”和“((Integer)list.get(3))。intValue()”放在所有地方。但这些好处伴随着这个问题。

答案 7 :(得分:20)

List<Integer> list = new java.util.ArrayList<Integer>();
list.add(1);
list.remove(1); // throws...

旧的API并没有考虑到拳击的设计,所以用原语和对象重载。

答案 8 :(得分:17)

我刚刚遇到过这个:

double[] aList = new double[400];

List l = Arrays.asList(aList);
//do intense stuff with l

有人看到问题吗?


会发生什么,Arrays.asList()需要一个对象类型数组(例如Double [])。如果它只是为前一个ocde抛出一个错误,那就太好了。但是,asList()也可以采用如下参数:

Arrays.asList(1, 9, 4, 4, 20);

所以代码的作用是创建一个带有一个元素的List - double[]

我应该想一下,当需要0ms来对750000个元素数组进行排序时......

答案 9 :(得分:16)

这个曾经嘲笑我几次,我听说有不少经验丰富的java开发人员浪费了很多时间。

ClassNotFoundException ---你知道这个类在classpath中但是你不确定为什么这个类没有被加载。

实际上,这个类有一个静态块。静态块中有一个异常,有人吃了异常。他们不应该。他们应该抛出ExceptionInInitializerError。所以,总是寻找静止的块来绊倒你。它还有助于移动静态块中的任何代码进入静态方法,以便使用调试器更容易调试方法。

答案 10 :(得分:16)

浮筒

我不知道很多次见过

floata == floatb

“正确”测试应该

Math.abs(floata - floatb) < 0.001

我真的希望BigDecimal的文字语法是默认的十进制类型......

答案 11 :(得分:13)

并不是特定于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 );
}

问题是“数字”的哈希码是负数,因此交换机中的n % 2操作也是负数。由于交换机中没有处理否定结果的情况,变量a永远不会被设置。该程序打印出null

确保您知道%运算符如何处理负数,无论您使用的语言是什么。

答案 12 :(得分:8)

我认为,当我还是一名年轻的程序员时,我总是会感到困惑,当你从正在迭代的数组中删除时,是并发修改异常

  List list = new ArrayList();
    Iterator it = list.iterator();
    while(it.hasNext()){
      //some code that does some stuff
      list.remove(0); //BOOM!
  }

答案 13 :(得分:8)

从事件派发线程外部操作Swing组件可能会导致极难找到的错误。这甚至是我们(经验丰富的程序员,拥有3年6年的java经验)经常忘记!有时这些错误会在编写正确的代码并随后不经意地重构之后潜入......

请参阅tutorial为什么必须

答案 14 :(得分:8)

不可变字符串,这意味着某些方法不会更改原始对象,而是返回修改后的对象副本。从Java开始我曾经一直忘记这一点,并想知道为什么替换方法似乎不适用于我的字符串对象。

String text = "foobar";
text.replace("foo", "super");
System.out.print(text); // still prints "foobar" instead of "superbar"

答案 15 :(得分:7)

如果你有一个与构造函数同名的方法,那么BUT有一个返回类型。虽然这个方法看起来像构造函数(对于菜鸟),但它不是。

将参数传递给main方法 - 这需要一些时间来让新手习惯。

传递。作为在当前目录中执行程序的类路径的参数。

意识到字符串数组的名称不明显

hashCode和equals:很多具有5年以上经验的java开发人员都不太明白。

设置与列表

直到JDK 6,Java没有NavigableSets让你轻松迭代Set和Map。

答案 16 :(得分:7)

整数分部

1/2 == 0 not 0.5

答案 17 :(得分:5)

使用?泛型通配符。

人们看到它并认为他们必须,例如如果他们想要List<?>他们可以添加任何内容,请使用List,而不必停下来认为List<Object>已经这样做了。然后他们想知道为什么编译器不会让他们使用add(),因为List<?>实际上意味着“我不知道某种特定类型的列表”,所以你唯一可以做的就是List从中获取Object个实例。

答案 18 :(得分:4)

(联合国)拳击和长/长混淆。与Java 5之前的经验相反,您可以在下面的第二行获得NullPointerException。

Long msec = getSleepMsec();
Thread.sleep(msec);

如果getSleepTime()返回null,则取消装箱将抛出。

答案 19 :(得分:4)

默认哈希是非确定性的,因此如果用于HashMap中的对象,该地图中条目的顺序可能会在不同运行之间发生变化。

作为一个简单的演示,以下程序可以根据其运行方式给出不同的结果:

public static void main(String[] args) {
    System.out.println(new Object().hashCode());
}

为堆分配了多少内存,或者是否在调试器中运行它都可以改变结果。

答案 20 :(得分:4)

当您从父缓冲区创建duplicateslice ByteBuffer时,它does not inherit the value of the order property,因此这样的代码将无法满足您的期望:

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

答案 21 :(得分:3)

在常见的陷阱中,众所周知但仍偶尔咬人的程序员,在所有类C语言中都有经典的if (a = b)

在Java中,当然,只有当a和b是布尔值时,它才能工作。但我经常看到像if (a == true)这样的新手测试(虽然if (a)更短,更具可读性和更安全......)并偶尔写错误if (a = true),想知道为什么测试不起作用。
对于那些没有得到它的人:最后一个语句首先将true分配给a,然后进行测试,这总是成功!

-

一个咬了很多新手的人,甚至一些分心的更有经验的程序员(在我们的代码中找到它),if (str == "foo")。请注意,我总是想知道为什么Sun覆盖字符串的+符号但不覆盖== 1,至少对于简单的情况(区分大小写)。

对于新手:==比较引用,而不是字符串的内容。您可以有两个相同内容的字符串,存储在不同的对象(不同的引用)中,因此==将为false。

简单示例:

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 Coombs的好建议,我更新了我的答案。

答案 22 :(得分:2)

非统一型系统与对象取向思想相矛盾。即使所有内容都不是堆分配的对象,程序员仍应允许通过调用它们上的方法来处理原始类型。

带有类型擦除的泛型类型系统实现非常糟糕,并且当他们在Java中学习第一个泛型时会抛弃大多数学生:为什么如果已经提供了类型参数,我们仍然需要进行类型转换?是的,他们确保了向后兼容性,但费用相当愚蠢。

答案 23 :(得分:1)

我想指出的另一个问题是使API变得通用的(过于普遍)驱动。使用精心设计的通用代码很好。设计自己的很复杂。 非常复杂!

只需查看新Swing JTable中的排序/过滤功能。这是一场彻头彻尾的噩梦。很明显,你可能想要在现实生活中链接过滤器,但我发现如果不使用所提供的类的原始类型版本就不可能这样做。

答案 24 :(得分:1)

首先,这是我今天抓到的一个。这与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");
    }   
}

显然,这些名字已经改变以保护无辜者:)

答案 25 :(得分:1)

    System.out.println(Calendar.getInstance(TimeZone.getTimeZone("Asia/Hong_Kong")).getTime());
    System.out.println(Calendar.getInstance(TimeZone.getTimeZone("America/Jamaica")).getTime());

输出相同。

答案 26 :(得分:0)

IMHO  1.使用vector.add(Collection)代替vector.addall(Collection)。第一个将集合对象添加到vector,第二个添加集合的内容。  2.尽管与编程无关,但是使用来自xerces,jdom等多个源的xml解析器。依靠不同的解析器并将其罐子放在类路径中是一场噩梦。

答案 27 :(得分:0)

我有一些调试TreeSet的乐趣,因为我不知道来自API的这些信息:

  

请注意,如果要正确实现Set接口,则由set维护的排序(无论是否提供显式比较器)必须与equals一致。 (请参阅Comparable或Comparator以获得与equals一致的精确定义。)这是因为Set接口是根据equals操作定义的,但TreeSet实例使用compareTo(或compare)方法执行所有键比较,因此从集合的角度来看,通过这种方法被认为相等的密钥是相等的。集合的行为即使其排序与equals不一致也是明确定义的;它只是不遵守Set接口的一般合同。   http://download.oracle.com/javase/1.4.2/docs/api/java/util/TreeSet.html

正在添加具有正确的equals / hashcode实现的对象,并且再也没有看到过,因为compareTo实现与equals不一致。