Java反直觉代码

beq87vna  于 2023-01-07  发布在  Java
关注(0)|答案(8)|浏览(113)

我记得阅读过一本书,书名是:
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代码是什么?(我知道这不是一个真正的问题,也许我应该把它作为一个社区wiki来发布,请也提出建议)

6fe3ivhb

6fe3ivhb1#

一个是最近的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()";
   }
}

当一个新的Derived对象被创建时,你认为whenAmISet的值是什么?
给定以下简单主类:

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

大多数人认为输出应该“set in preProcess()”,因为Base构造函数调用了该方法,但事实并非如此。Derived类成员在Base构造函数调用preProcess()方法后 * 被初始化,Base构造函数将覆盖preProcess()中设置的值。
JLS的Creation of New Class Instance部分非常详细地解释了创建对象时发生的事件序列。

lztngnrs

lztngnrs2#

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

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

有时候很困惑。

gkl3eglg

gkl3eglg3#

事实上,Java Puzzlers还有94个谜题,它们表现出有时奇怪,有时欺骗性的行为,这些行为大多是由看起来无辜的代码造成的。

zvokhttg

zvokhttg4#

我们曾经在一个遗留代码库中偶然发现了类似这样的东西(最初被一个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默认构造函数,调用Bcreate()方法,通过该方法初始化bMember
或者说我们天真地认为是这样,因为在调用super()之后,初始化过程的下一步是为B成员分配显式定义的默认值,实际上是将bMember重置为null
无论如何,程序实际上运行良好,因为bMember稍后通过另一条路径再次被分配。
在某个时候,我们删除了bMember的明显无用的null默认值,程序行为突然发生了变化。

tyu7yeag

tyu7yeag5#

我最近发现Math.abs(i)并不总是产生正数。
数学运算.abs(整数.MIN_VALUE)得出-2^31
为什么?因为正整数比负整数少一个。2^31比Integer大一。MAX_VALUE因此溢出到-2^31
我想大多数其他语言中的情况都是类似的,但我在java中遇到了这种情况

lqfhib0f

lqfhib0f6#

我倾向于同意你的观点,但是我读过一些文章(希望有人能提供一两个链接),这些文章解释了Java的'%'和它的'/'是一致的,这对任何人来说都足够了。
Java的“%”运算符在处理负输入时与其他语言有一点不同。我个人更喜欢返回非负值的“模”运算符,例如。

-5 % 2 == 1

我认为这个操作有一个正式的名称,但我现在想不起来,所以我还是用“modulo”。这两种形式的区别在于,Java变体'a % b'执行'a/b'并向零舍入(并从'a'中减去结果),而首选的操作是向下舍入。
如果结果“r”是“0〈= r〈b ",那么我所见过的将%应用于负”a“和正”B"的每一个实际应用都会更容易工作(一个例子是找到距瓦片的最左边的偏移,当将点Map到可延伸“〈0”的平面上的瓦片上时)。这次经历的一个例外是在一次大学作业中,对Java程序中的整数算术进行静态分析。正是在这次作业中,Java的'%'的微妙之处曝光后,我不厌其烦地用“固定”版本替换它。这一切都适得其反,因为重点是模拟Java如何做算术,而不是实现我自己喜欢的那种。

nhn9ugyo

nhn9ugyo7#

查看一下java.util.concurrent.SynchronousQueue中定义的方法:http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/SynchronousQueue.html
一半的方法 * 总是 * 返回 null / true / false / zero
当你第一次开始使用它的时候,没有先阅读文档,这并不是很明显。

qvsjd97n

qvsjd97n8#

奇数和偶数通常被认为是正数,所以我认为把负数看作奇数或偶数是没有用的,测试通常是看最低位是否被置位。
这两种方法对负数也都适用。

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

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

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

相关问题