主题:【游戏】不看不知道,一看吓一跳!!! -- Highway
我昨天又照你说的做了第二题,结果与你后来公布的相同。可能是两个cchere.net地址的问题,现在看不到我回答那个帖子了。
现在的问题是,我们该用哪个版本比较好?
SUN应该尽快出一个稳健的版本吧?
还是说我们要重新整理我们一贯的思维方式?
的问题,有时候问题并不是那么想当然。单就这个问题而言,我觉得是Java设计的不够好。当然,这个“特点”Java在他的技术文档里指出了,没搞对还是我们的错。
Java 5.0有不少新的feature,但是也带来一些困惑。这可能就是成长的代价吧!
第一个题目没有说明i和j是什么数据类型的,如果照Highway兄的解释,那就是这个题目的前提条件没有给够。如果i和j都是long或者其他简单数据类型的话,应该是不会死循环的。如果i和j都是object类型的话,程序员是应该提供合适的比较方法,否则系统用默认的方法比较,出了错就是程序员自己的责任了。这个在C++bible等OO Programing的书里面应该都是讲过的。第二个题目,怎么看都觉得把他称为java5。0的feature太过牵强了:)
也就是int,long这一类的东西。
Java 5.0中出现了box/un-boxing feature,这个题就是让你主意这个feature带来的新变化。不能用原来的思维考虑了。
不过在我所知道的OO语言中(C++,C#...),object的比较一般都是要求programmer提供比较函数或操作符,语言本身只提供基本的相等和不等的比较。所以不管这个box/unbox是不是java5。0的新feature,如果看到object之间的比较的case,就应该check一下所使用的比较操作符或者是比较函数是否是想要的。我想这个在以前的java的版本中应该也是成立的吧:)另外,Highway兄给的那个例子有点特殊,Integer这种object类型很有迷惑性的阿,如果i和j是什么Employee型的东西,估计大家就能一眼看出关键所在了:)
内部的值,而!=比较的是两个Object(可能是Hash value)。如果是Employee class,由于不能auto unboxing,编译就不能通过。
我个人不喜欢这种设计,非常容易出错,并且很隐蔽。
问题一: 是Java设计的问题. 操作符重载应由程序员实现, 除了最基本的外Java不应该狗拿耗子多管闲事, javac对这段代码应该直接报错.
问题二: 是程序员的问题. 关键字final会导致编译器做优化处理, 这是完全合理的.
问题三: 还是Java的问题. C/C++编译器会在预处理时将86400000000 hard coded into object file, 根本不会等到运行时再去计算MICROS_PER_DAY. 而且中间结果存放在int里也是很弱智的设计.
问题是Java 5.0引入了boxing/un-boxing feature。于是出现了一些“不伦不类”的东西。Java 5.0中有好多东西并不是非常自然的,完全是出于市场上的需要。因为.net有,而Java没有,那在市场宣传上会显得很难看。
第二题有些争议。因为java的最根本本质是dynamic linking。编译器那样优化,性能上是有所提高,但违反了Basic principle,合适吗?
第三题你Java的看法是不对的。事实上,Java和C/C++一样,是先将那两个变量算出来作为常量的。但Java没有“智能”,不知道将中间结果放到long临时变量中。
反编译的Java code是这样的。
public class LongDivision { public LongDivision() {} public static void main(String args[]) { System.out.println(5L); } private static final long MILLIS_PER_DAY = 0x5265c00L; private static final long MICROS_PER_DAY = 0x1dd76000L; }
在.NET中,编译的时候,编译器会给出错误提示The operation overflows at compile time in checked mode 。看来这点上,.NET要聪明一些。
不信你自己试试看。(注意,C/C++的long long和Java的long一样,是64位的长整数)
#include <iostream> //static or nor doesn't matter static long long MILL_PER_DAY = 60 * 60 * 24 * 1000; static long long MACRO_PER_DAY = 60 * 60 * 24 * 1000 * 1000; int main(int args, char *argv[]) { printf("The result = %d",MACRO_PER_DAY/MILL_PER_DAY); }
我用的是gcc编译器,结果和java一样,是5。(IDE是免费的Dev-C++ 4.9.9.2)
如果用微软的Visual C++(VS 2005),结果也是5。不过编译的时候会给出警告。
Warning 1 warning C4307: '*' : integral constant overflow c:\temp\longdivison\longdivison\longdivison.cpp 7
奇怪吗?
marketing的事我们不管. 第一题里的boxing/unboxing也许在某些情况下有用, 但这个功能实在是可有可无. 真正复杂的类操作符还是得由程序员来重载, 而象Integer这样的我宁可用基本数据类型.
第二题和Java Language Specification的13.4.8和14.20小节里的例子一样, 书中指出了误用final关键字会导致break compatibility with existing binaries. 既然人家都那么说了, 只好怪程序员读书不仔细了.
当然这样的设计是值得推敲的. 在实际中如果Words和PrintWords是由不同的程序员/公司开发的, 后者也许只能得到Words.class或是不知道前者修改了Words.java, 麻烦就来了. 这有点类似"DLL Hell".
C++里的const关键字就没有此问题. 下面a.cpp和b.cpp完全可以分开独立编译, 怎么修改a.cpp都没事. C也是一样, 用extern const char*不会使编译器做错误优化. 其实常量在C/C++里面都是用头文件定义, 我对Java不提供#define这样的directives很有意见.
h.h:
#include <stdio.h>
class Words {
public:
static const char* FIRST;
static const char* SECOND;
static const char* THIRD;
};
a.cpp
#include "h.h"
const char* Words::FIRST="the";
const char* Words::SECOND=NULL;
const char* Words::THIRD="set";
b.cpp
#include "h.h"
main()
{
printf("%s\n",Words::FIRST);
printf("%s\n",Words::SECOND);
printf("%s\n",Words::THIRD);
}
所以此题对Java和程序员各打五十大板.
第三题说明不光是java.exe而且javac.exe也是用int保存中间结果. 这太土了, 简直是个bug, 自动类型转换从来是就高不就低的.
P.S. 看了Highway的大作后发现C/C++也一样土, 真是没想到啊. 不知Fortran是否会好一些?
#include <iostream>
using namespace std;
long long MILL_PER_DAY = 60 * 60 * 24 * 1000;
long long MACRO_PER_DAY = 60LL * 60 * 24 * 1000 * 1000;
main()
{
cout << MILL_PER_DAY << endl;
cout << MACRO_PER_DAY << endl;
cout << MACRO_PER_DAY/MILL_PER_DAY << endl;
}
结果就对了(M$的那个__int64也一样). 看来C/C+里加一个L还不够, 得加两个!
如果不用中间结果:
#include <iostream>
using namespace std;
long long MILL_PER_DAY = 86400000;
long long MACRO_PER_DAY = 86400000000;
main()
{
cout << MILL_PER_DAY << endl;
cout << MACRO_PER_DAY << endl;
cout << MACRO_PER_DAY/MILL_PER_DAY << endl;
}
gcc会报错, cl会正确编译运行.
虽然C/C++也很土, 不过至少不会用16-bit int去存32-bit long的中间结果. C是70年代设计的, C++是80年代设计的, Java是90年代设计的, 评判标准当然不一样了.
Long life Programming!!!
问题一出得不好,变量名故意引人往错处想.
真正编程时很难想象熟手会写出这样的程
序,但就题论题,生手熟手都可能答错.属于
刁钻的trick question.
问题二责任归谁要看程序类型.如果程序的设
计用途就是hot swappable,(比如在web applet 里),
那么程序员不应该用'final'.如果是stand-alone
application,错误应该在编译管理:有dependency
的class没有重新编译.C/C++的makefile没写好也
会出现同样问题.如果用ant或eclipse,两个class
都会重新编译的.
问题三是编译器的问题.编译器在做constant
substitution的除法时是直接把两个变量的定义
字符串展开再计算的.如果先求两个变量的值再
代入就不会错了.估计是把gcc的现成算法照抄了