对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