对Java的几点思考

作者:西邮 王聪

1. 符号带来的问题

我们知道,Java里的整数类型——byte,short,int,long——全部是有符号的。这有时会带来麻烦,如果我们并不想要最高位作为符号位的 话。 下面是两个例子:

a) 我们不得不这样做来正确显示IP地址:

byte[] ipa;
InetAddress ina = CmdSocket.getLocalAddress();
ipa = ina.getAddress();
for (int i=0; i< 4; i++) {
    System.out.println(((int)ipa[i]) & 0xff);
    }

b) 记住,char是无符号的[1]:

public class Multicast {
    public static void main(String[] args) {
        System.out.println((int) (char) (byte) -1);
    }
}

2. 类型问题

Java在处理类型之间转化时有着自己的规则,这些规则和C/C++并不太一致,有时并不好理解。

a. 转化[1]

short x = 0;
int i = 123456;
x += i;
x = x + i;//<== fails here

+=操作符默默地转换了结果的类型,而+号不会。

String[] s=new String[10];
Object[] o=s; o[0]=new Object();

上面这段代码[2]并没有编译错误!同样,Java也允许这种代码[2]通过编译:

Object[] o=new Object[10];
String[] s=(String[])o;

b. 溢出

public class LongDivision {
    public static void main(String[] args) {
        final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
        final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
        System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
    }
}
上面程序[1]的问题在于Java会把每个乘数当作int,而计算结果也是先作为int,然后才转化为long[3]。可惜太晚了,已经溢出了。
 再看另一个,下面的程序很正确,对吗?

public class Rover {
    private int x;
    private int y;
    private int direction;
    ...
    public void ShowPosition() {
        System.out.print(x+" "+y+" ");
        switch (direction%4) {
        case 0:
            System.out.print('N');
            break;
        case 1:
            System.out.print('E');
            break;
        case 2:
            System.out.print('S');
            break;
        case 3:
            System.out.print('W');
            break;
        }
        System.out.println("");
    }
    public void LeftRotate(){
        direction--;
    }
    public void RightRotate(){
        direction++;
    }
}

不,错了!而且还有两个致命的错误!一、当direction为负数时,结果并不是你想要的。二、自加看起来没有上面的问题, 但它存在溢出的问题!我们可以这样修改:

public void LeftRotate(){
    direction--;
+   if (direction < 0)
+        direction = direction + 4;
}
public void RightRotate(){
    direction++;
+   if (direction == Integer.MAX_VALUE)
+        direction = direction%4;
}

3. 输入输出问题

Java中有各种各样的流,这使得选取恰当的流并不容易。有时,我们为了得到一个理想的流,不得不转化多次:

    BufferedOutputStream FTPOutStream;
    FTPOutStream = new BufferedOutputStream((OutputStream)System.out);
    BufferedInputStream FTPInStream;
    FTPInStream = new BufferedInputStream(new FileInputStream(LocalFile));

如果你认为上面的代码并不复杂,很好,那你看看下面这个[4]:

BufferedReader foo = new BufferedReader(new InputStream(new JavaHatesYou(new FuckOffAndDie(new ForTheLoveOfGodLetMeJustReadTheFile(new File("filename.txt")))))))));

有两个流你一定要记住,InputStreamReader和OutputStreamWriter,它们是联系 *InputStream/*OutputStream 和*Reader/*Writer的桥梁。

 另外值得一提的是,Java中的DataInputStream流并不像想象中那么好,根本原因是:readInt()并不认识int,它只 管读下面4个字节, 并不在乎它这四个到底是什么,即使在文本中它们看起来可能只是几个非数字的字符!同样,readLong不认识long, readDouble()也不认识double。而这一点,C++和C做得明显要更好。Scanner或许可以帮上一些忙,不过很可惜,你不能 用它读一个char!

4. Java对底层的支持确实不怎么样

我并没有把你从Java引到C的意思,但一些底层操作我们有时还是很需要的。可惜,Java没有支持或者支持不好。举几个例子:

a) ftruncate()

Java并不支持ftruncate()功能,要实现这个功能,你必须复制并且重命名这个文件。

b) ICMP

迄今为止,Java还没有提供对ICMP包的支持,因为这对Java来说太底层了。那我们怎样用Java实现Ping呢?JNI?可能是, 可你会使用它吗?你不还是得用C吗?

c) available()

Java里的InputStream.available()几乎总是不可靠,我从来没见过它能够正常工作。我们这样用它清空输入流里的内容时:

       System.in.skip(System.in.available());

可它总是返回0。它真的不应该为0,我确定。而且,你也知道,Java里的所有输入流都没有flush()。
 

5. 其它

a) package

package看起来是个好东西,它可以把我们的类有组织地存放在一起。Sun宣称,我们最好按照Internet网址的倒叙来排,比如我的类就得放到 org.wangcong.*里面。你上次从一个很深的目录中发现Java源代码又是什么时候呢?我上次终于从一个 src/com/enterprisedt/net/ftp/ 目录中找到了我想看的源代码!

b) 调试

Java的官方调试器JDB并不好用,它不能通过向上向下键来取历史命令,JDB不能简单地通过回车来执行上一条命令。最关键的是,在调试交互输入 的程序时,JDB总是很奇怪地跳过输入语句。

 由于Java根本就不支持宏,所以C中的`assert'和`#ifdef DEBUG'我们都不能使用了。调试变得有些困难了,Java提供的一个折衷方案是[5]:

 if (randomGlobalObject.DEBUG) { assert(whatever, "whatever!"); }

一些Java开发者还宣称,调试器是过时的东西,Java应该使用JUnit测试。那你们又是怎么测试JUnit的呢?

c) 安装

 安装一个Java程序要处理一大堆class文件,如何有效地在命令行中使用它们也是一个问题,脚本或许可以解决这个问题,但不是最好的方 法。

 而且,JDK各个版本之间差别比较大,在高版本的JDK上编译的Java代码到低版本上_很_可能编译不了。我们只能:1. 使用高版本的JDK,这样可以 使用更多Java的新东西,开发更方便,但你必须要求用户要不低于此版本的JDK。或者 2. 放弃Java的新特性,使用低版本的JDK,这样用户可以用更多 版本的JDK来使用这个程序。

---
参考资料:
[1] Java Puzzlers, Joshua Bloch and Neal Gafter, Addison Wesley.
[2] What’s Wrong With Java: Type Erasure, http://www.safalra.com/programming/java/wrong-type-erasure/ .
[3] Java Language Specification
[4] We hate JAVA, http://www.mnementh.co.uk/ihatejava.html [5] Java sucks, http://www.jwz.org/doc/java.html