Java反直觉代码

时间:2009-07-18 10:51:40

标签: java

我记得读过一本名为:

的书
  

Java Puzzlers Traps, Pitfalls, and Corner Cases

描述了Java代码中的奇怪行为。看起来完全无辜的东西,但实际上执行的东西与显而易见的完全不同。一个例子是:

(编辑:这篇文章不是对这个特定例子的讨论。这是我提到的书中的第一个例子。我要求你可能遇到过其他奇怪的事情。)

此代码是否可用于识别数字是否为奇数?

public static boolean isOdd(int i) {

    return i % 2 == 1;

}

答案当然不是。如果你插入一个负数,当数字是奇数时,你会得到错误的答案。正确的答案是:

public static boolean isOdd(int i) {

    return i % 2 != 0;

}

现在我的问题是您遇到的最奇怪,最直观的Java代码是什么?(我知道这不是一个真正的问题,也许我应该将其发布为社区维基,请对此的建议)

8 个答案:

答案 0 :(得分:3)

最近有一个我blogged about,给出了以下两个类:

public class Base
{
   Base() {
       preProcess();
   }

   void preProcess() {}
}


public class Derived extends Base
{
   public String whenAmISet = "set when declared";

   @Override void preProcess()
   {
       whenAmISet = "set in preProcess()";
   }
}

当您创建新的whenAmISet对象时,您认为Derived的价值是什么?

给出以下简单的主要类:

public class Main
{
   public static void main(String[] args)
   {
       Derived d = new Derived();
       System.out.println( d.whenAmISet );
   }
}

大多数人说看起来输出应该是“在preProcess()中设置”,因为Base构造函数调用该方法,但事实并非如此。 <{1}}类成员在调用Derived构造函数中的preProcess()方法后初始化,该方法将覆盖Base中设置的值。< / p>

Creation of New Class InstancesJLS部分详细说明了创建对象时发生的事件序列。

答案 1 :(得分:2)

事实上,Java Puzzlers还有94个谜题,这些谜题有时会出现奇怪的,有时甚至是欺骗性的行为。

答案 2 :(得分:2)

我遇到的最违反直觉的概念是来自Josh Bloch的PECS(Producer Extends,Consumer Super)。这个概念非常好,但你认为消费者/生产者在某种情况下是什么 - 我最初会想到的方法本身。但不,参数集合是这个概念中的P / C:

public <T> void consumeTs(Collection<? extends T> producer);
public <T> void produceTs(Collection<? super T> consumer);

有时很困惑。

答案 3 :(得分:2)

我们曾经在遗留代码库(最初被5级继承结构和几个间接伪装伪装)中偶然发现了类似的东西:

public abstract class A {
    public A() {
        create();
    }
    protected abstract void create();
} 

public class B extends A {
    private Object bMember=null;
    protected void create() {
        bMember=getNewObject();
    }
}

当您致电B构造函数时,它会调用A默认构造函数,调用B的{​​{1}}方法,create()初始化。

或者我们天真地想。因为在调用bMember之后,初始化过程的下一步是将明确定义的默认值分配给super()成员,实际上将B重置为bMember

程序实际上工作正常,因为null稍后再次通过另一条路线分配。

在某些时候,我们删除了bMember显然无用的null默认值,突然程序行为发生了变化。

答案 4 :(得分:1)

我最近发现Math.abs(i)并不总是产生正数。

Math.abs(Integer.MIN_VALUE)产生-2 ^ 31

为什么呢?因为有一个正整数而不是负数。 2 ^ 31比Integer.MAX_VALUE多一个,因此溢出到-2 ^ 31

我猜大多数其他语言的情况类似,但我在java中遇到过这个

答案 5 :(得分:0)

我倾向于同意你的意见,但我已经阅读(并希望有人可以提供一个或两个链接)的文章,这些文章解释了Java的'%'与其'/'一致,这对任何人都足够了。根据我自己的经验:

Java的'%'运算符在处理负输入时与其他语言略有不同。我个人更喜欢返回非负数的“模数”运算符,例如

-5 % 2 == 1

这将使您的示例有效。我认为这个操作有一个正式名称,但我现在想不到它所以我会坚持使用“模数”。两种形式之间的区别在于,'a%b'的Java变体执行'a / b'并向零舍入(并减去由'a'产生的结果),而首选操作则向下舍入。

如果结果'r'为'0&lt; = r&lt;&lt; = r&lt; b'(一个示例是在将点映射到可以延伸'&lt; 0'的平面上的瓦片上时找到从瓦片的最左边缘的偏移。这种体验的一个例外是在大学任务中对Java程序中的整数算术进行静态分析。正是在这个任务期间,Java的'%'的微妙之处曝光,我不顾一切地用“固定”版本替换它。这一切都适得其反,因为关键在于模拟Java如何算术,而不是实现我自己喜欢的那种。

答案 6 :(得分:0)

查看 java.util.concurrent.SynchronousQueue 中定义的方法: http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/SynchronousQueue.html

一半的方法总是返回 null / true / false /
当你第一次开始使用它时,并不是很明显,而不是先阅读文档。

答案 7 :(得分:-1)

奇数和偶数通常被认为是正数,所以我认为不要认为将负数看作奇数或偶数是有用的。通常要测试是否设置了最低位。

这些对于负数也是好的。

if ((i & 1) == 0) // lowest bit not set.

if ((i & 1) == 1) // lowest bit set.

if ((i & 1) != 0) // lowest bit set.