windows下使用eclipse for C/C++【转载】

windows下使用eclipse for C/C++【转载】

经过网上搜索资料加上本人测试,以下方法可行:
1,先装好JDK
2,装好Eclipse3.32+MinGW1
3,打开CDT包把plugins和features拷到Eclipse相应的目录里
4,把Eclipse的configuration目录下的org.eclipse.update删了
5,启动Eclipse,现在可以建立C/C++工程了
6,现在建立你的C/C++工程
7,编写好你的源代码
8,在Project的Properties新建一个Builder(Project→Properties选择Builder标签→New,双击Program)
9,在MAIN标签(不用点击,默认第一个就是)的LOCATION输入你MINGW的MAKE路径,如C:\MinGW\bin\mingw32-make.exe
在WORKING DIRECTORY输入你PROJECT的MAKEFILE文件所在的目录,如D:\workspace\test
点APPLY更新进去,一直点OK退回编辑界面
10,建立文件makefile,不用加后缀名,输入
all(你设置的标签名):
gcc -o 你的程序名 你的源代码名.c
注意:g++前面是用tab而不能用空格。上面编译的是C,编译C++要用g++ -o Hello Hello.cpp
设定Make Targets,Windows->Show View->Make Targets。在Make Targets视窗中按下右鍵,选择Add Build Target,Target Name输入:编译,Make Target输入:all(你设置的标签名)。双击编译即可完成编译。
11,现在Run你的工程吧!
8~10为重点所在,请大家注意!
PS:个人觉得,在你安装好MinGW后,写一个C程序,记得按住Ctrl+b先build一下,或者是直接ctrl+F11直接build并运行。

用Visio画ER图的解决方案【转载】

用Visio画ER图的解决方案【转载】

转自:http://hi.baidu.com/%BA%EC%C3%F1/blog/item/d013f603726e0c8ad53f7c78.html\
今天刚刚装了Visio,但是郁闷地发现Visio中默认没有ER图的模型,绘图类型中的数据库模型中也没有ER图,根本没有菱形。对于第一次接触 Visio的我来说花了好大的力气也没有找到,想专业一把未遂。
后来问了其他人,答案更让当时郁闷的我有些火气:1)菱形在基本流程图里(晕,那也不是ER图中的啊,怎么能这么混用啊,不伦不类)2)你就将就一下嘛, 画出来就行,那么专业干嘛?(更晕,要是不想画的专业我就不花力气用Visio了)。其实想想,他们是比我懂得变通的,我怎么就这么死心眼儿呢?还是有点 儿完美主义?我不知道,但是我没有采纳他们的意见,继续找我自己的解决方式。记得好像有相关的模版或者模型可以下载的,但是没有找到。再后来,我对 Visio熟悉了一些,自己选择了这样一种解决方式:自定义。
先在“流程图”-〉“基本流程图”中找到菱形和方块, 分别右击选择“添加到我的形状”-〉“添加到新模具”。之后出现“另存 为”对话框,把新模具命名为“ER图”,这样我们就把菱形和方块添加到了新模具“ER图”中。用同样的思路,在“数据库”-〉“ORM图表”找到椭圆和直 线,添加到模具“ER图”中。
添加完成后,我们就可以在画ER图时打开该模具,ER图所有的元素都会在一个模具中显示出来了。

学习 Linux 几点忠告【转载】

学习 Linux 几点忠告【转载】

转载地址:
http://wiki.ubuntu.org.cn/index.php?title=%E5%AD%A6%E4%B9%A0linux%E5%87%A0%E7%82%B9%E5%BF%A0%E5%91%8A&variant=zh-cn\
作者:王垠
1 不要当“传教士”
2 不要强迫自己”
3 不要“玩Linux的
4 不用挑剔发行版本
5 ==不要盲目升级 ==
6 不要配置你不需要的东西
7 不要习惯的使用根帐号。在需要的时候才用!
8 不要用商业的眼光来看 Linux的
9 干你的正事去
10 上面几条仅供参考
不要当“传教士”
(这点有一个重大弊端:开放软件没有商业软件那样的宣传,如果使用者都如此低调,用户群不会大幅扩展。) 为什么是繁体字?而且还翻译的乱起八糟 很多人在讨论区不断的引起的Linux对比视窗之类的讨论,甚至争的面红耳赤,这是没有必要的
这种争论是浪费时间而没有任何用处的。对,你花了一下午,用许多事实“捍卫”了Linux比Windows好这个说法。但是视窗的支持者并不会喜欢上Linux的,他们只是稍微退缩一下,然后找一些新的证据来跟你辩论。
世界上的人们都在利用Linux的研究最前沿的科学,我们还在这里讨论“要不要用Linux的这种无聊的问题,什么时候才能赶上时代前进的步伐?
什么叫做视窗支持者,什么叫做Linux的支持者?我们为什么要支持某一个而反对另外一个?你不需要为 Linux的护法,不需要成为 Linux的支持者“或者”的GNU传教士“ GNU / Linux的已经用事实向世界证明了它们的威力,已经被大多数人接受。你只需要安安静静享受的GNU / Linux的给你的乐趣和自由。
你需要关心的不是你的工具是什么,而是你用它做了什么。精通的Linux并不说明任何问题,因为它只是一个工具而已。如果你用的 Windows能很好的完成你的任务,那你就没有必要费时间去熟悉Linux操作系统。直到有一天你发现一项任务只有Linux操作系统才能完成的时候再 换也不迟,因为你身边的的Linux的爱好者一定会很乐意的帮助你。
如果你在使用Linux操作系统的过程中对它产生了感情,那么你应该明白那些习惯于使用视窗的人也会对视窗产生依赖。类似的争论还有很多: 微软 Office Word和TeX,Emacs和Vim,Wolfram Mathematica和Maple,侏儒,fvwm的和KDE的时候,狗派… …和冷静地对自己说:“我不站在它们任何一边。”尽管这有些不容易办到。
各人的需要不同,生活的环境不同。对你来说好的东西,对别人来说不一定好,我们需要尊重别人的选择。如果你当面说别人正在用的程序不好,没有必要。
不要强迫自己”
喜欢电脑的人总是有某些心理强迫倾向。有的人说:“键盘比鼠标快。我不要用鼠标。这样才有高效率。”所以他在编辑器里无论什么时候总是用20W的,大于 10J这样的命令到达目的点。他甚至觉得图形界面是多余的,干脆都不装 Xwindow。
全部用键盘看起来的确比让手离开键盘去拿鼠标,再回来“快”多了,但是快的击键频率不等于工作的高效率,对你的健康更没有什么好处。这只能把你变成打键盘的机器。
当你正在检查你的文章或者程序,思维正在随着字符的含义流动,突然为 20W,大于 10J这样的东西出现在你的脑子里,是不是会打断思路?不?那说明你当时思考的问题比较简单,这些干扰还不会起到副作用。
其实很多人用电脑的时候,思想都受到某种教学的束缚,上面这个只是教多数种类中的一种。某些人创造了很多这种数学,用他的工作方式来要求别 人,嘲笑方式跟他不一样的人。比如有的人嘲笑其它人写程序不按ç 8字符缩进,嘲笑别人在六里用方向键,嘲笑别人不知道是什么增值税,嘲笑其它人用在Java,C#这种由地方选区回收内存语言 … …
你不用管各种各样的教学,电脑只是你的工具,你想怎么用就怎么用。没有人能够约束你,没有人可以嘲笑你的工作方式。电脑明天就不再是这个样子,所以今天你不用完全了解它。你没有必要知道别人创造的一切,因为你需要留点时间自己创造些东西。只要有乐趣!
当你下次修改文章的时候,不妨试试悠闲的用鼠标在你眼睛看到的地方轻轻点一下。
如果你發現自己有類似的強迫症,建議去諮詢一下心理醫生。
不要“玩Linux的
Linux的很多人用的时候会感觉很迷茫,该用哪个发行版本呢?是不是我少装了什么?怎么升级这么快啊!怎么这么不稳定!每当遇到新的软件他就想试用,每当新的版本出现,他就更新,然后用鼠标在新的菜单里选择从来没见过的程序来用用。
其实你是为了而使用Linux操作系统的Linux,而没有找到正确的理由来利用Linux操作系统。你首先要明确用电脑的目的,你用它是 为了解决你的实际问题,而不是为了学习安装操作系统,不是为了测试哪个版本好用,不是为了“赶上潮流,更不是因为你硬盘太大了,你想多占点空间。
如果你启动了电脑之后不知道应该干什么,那么最好先不要用电脑,因为你可能有更重要的事情需要做。这没什么说的。
不用挑剔发行版本
很多人刚开始用的linux的时候,总是在怀疑别的发行版本是否比自己正在用的这个好,总是怀疑自己以后时候会失去支持,不得不换用别的发行。所以 很多人今天是红色帽子,明天又换成了Debian的,巴布亚一会儿又是… …甚至有的人在一台机器上装了两个版本的Linux操作系统,然后比较哪一个好。
其实你完全没有必要这样做,任何发行,只要你熟悉了,你在上面的工作方式几乎是不会受到任何影响的。我以前一直用的红帽,当我有一天在我的 一台新机器上Debian的安装时,我发现使用红帽的经验完全没有浪费。我用了一个下午就配置好了Debian中,使它服服贴贴的听我的话,就跟没有换发 行版本一样。
Debian中,拓林思,的SuSE,红帽,Gentoo的,…任何一个版本都是不错的。很多人认为自己攒一个 LFS的是高水平黑客的象征,但是不是每个人都有精力去了解所有细节。
==不要盲目升级 ==
不知道这是心理作用还是什么,有的人看到比较大的版本号,就会很想换成那个。很多人的Redhat的本来配置的很舒服了,可是一旦Redhat的发 行新的版本,他们就会尽快下载过来,然后选择升级安装。结果很多时候把自己原来修改得很好的配置文件给冲掉了。新的软件又带来了新的问题,比如有一次我的 rxvt的就升级到2.7.8跟miniChinput冲突了,升级到Redhat的8.0,xmms的发现居然缺省不能放了MP3播放,XFree86 的是i810的模块在启动上有新的漏洞,Mozilla中,会导致突然退出。
如果你已经配置好了一切,千万别再整体升级了,这会浪费你很多很多时间的,不值得。有句话说得好:“如果没有打破,不解决它。”如果你的程序能够完成你需要做的事情,你何必升级呢????
是的,不論是從論壇還是其他的地方反映出來的大部分都是這個問題,要么比的SUSE Ubuntu的好,要么比Ubuntu的的Mandriva好等等的言論。很多人還是把Linux操作系統看成了一個表面的東西。並沒有塌下心來學習 Linux系統。
不要配置你不需要的东西
如果你只想做一个像我这样的普通用户,主要目的是用的Linux来完成自己的科研任务和日常工作,那就可以不用系统管理员或者网络管理员的标准来要 求自己,因为当一个系统和网络管理员确实很辛苦。普通用户学习那些不经常用到的复杂的维护系统的工具,其实是浪费时间,学了不用是会很快忘记的!
我不是一个合格的网络管理员,我的服务器都只设置了我自己需要的功能,设置好ssh连接的ftp已经足够了,那样可以省去我很多麻烦。我从 来不过度考虑“安全,因为 Linux操作系统缺省已经很安全了。我没有磁带机,就不用管焦油的那些稀奇古怪的参数了,czf,xzf,ztf已经可以满足我所有的需要。桑 达,awk的,…我也只会几种常用的命令行。
不要习惯的使用根帐号。在需要的时候才用!
这是很多刚接触的UNIX类操作系统的人常见的现象,他们不喜欢在管理系统的时候才用,而是一直用根帐号干所有事情,配置系统,安装程序,浏览网页,玩游戏,编程 … …
结果有一天,他不小心在某个系统目录使用了del * …后果不堪设想 … …
不要用商业的眼光来看 Linux的
Linux的不是商业软件,所以不要用要求Solaris操作系统,视窗那样的眼光来看 Linux操作系统。自由软件的作者们从来不拉拢用户,他们对用户不负有任何责任。实际上在自由软件的世界里,开发者“和”用户“并没有明确的界限,大家是朋友。
自由软件很可能只是满足作者和他的朋友的需要,甚至是为了好玩而创造的。自由软件不是完美的,自由软件承认自己有缺点,它不会自吹自擂,蒙 蔽“用户”的耳目。这种对作者责任的解脱激发了作者的创造力,他们不用过分考虑“向上兼容,他们往往比背上重重包袱的商业软件结构更合理,技术更先进。
所以当你用某个自由软件遇到困难的时候,不应该埋怨软件的作者,因为他们对你并没有义务。你不应该把自己当成一个挑剔的顾客,而要把自己作 为这个软件的顾问和一个和蔼的建议者,这样你才能理解作者写这个程序时的快乐,在遇到问题时向作者反映,帮助他完善这个软件,成为一个快乐的参与者。就像 你的哥哥送你一个他用旧了的自行车,你应该珍惜这份友情,而不要在车坏了,或者骑车摔了一跤的时候大骂你的哥哥。如果你真的不能使用这种合作的心态,那么 最好不要使用这个软件。
这是一种先进的文化,它包含了互相合作,科学创新的精神。理解这一点不是很容易,很多人往往是因为不能理解这种文化而离开自由软件。这对于作者来说并没有什么损失。
干你的正事去
很多人跟我说,你的网页浪费我好多时间来配置这配置那,一会儿是fvwm的,一会儿是Mutt中… …
嗯… …那些东西都是我有空的时候一点一点积累的,如果你想一次性搞定所有那些东西,恐怕得花你几个星期甚至几个月的时间!并不是一定要搞定所有这些东西你才能正常工作的。除非你真的非得利用某个程序,或者你闲著没事,否则你可以不管这些东西。
上面几条仅供参考
以上只是个人意见,不一定适合所有人。取舍由你了!

安装JDK + Tomcat设置环境变量的方法

安装JDK + Tomcat设置环境变量的方法

初学java,对于环境变量的配置想来却总觉得不是很清楚,总结一下自己的理解,让头脑清楚一些。
下载好的JDK是一个可执行安装程序,双击安装。将安装路径改为:C:\jdk1.6.0(当然其他路径也可以)。
JDK安装完成之后我们来设置环境变量:
我的电脑点右键,选择“属性”,选择“高级”标签,进入环境变量设置,分别设置如下三个环境变量:
(1)设置好path变量,使得我们能够在系统中的任何地方运行java应用程序,比如 javac、java、javah等等,这就要找到我们安装JDK的目录,比如我们的JDK安装在C:\jdk1.6.0目录下,那么在C: \jdk1.6.0\bin目录下就是我们常用的java应用程序,我们就需要把C:\jdk1.6.0\bin这个目录加到path环境变量里面。
在系统变量里找到path变量,选择->编辑;(里面已经有很多的变量值,是在变量值的最前面加上C:\jdk1.6.0\bin;)
变量名: path
变量值: C:\jdk1.6.0\bin;
(2)classpath环境变量,是当我们在开发java程序时需要引用别人写好的类时,要让java解释器知道到哪里去找这个类。通常,sun为我们提供了一些额外的丰富的类包,一个是dt.jar,一个是tools.jar,这两个jar 包都位于C:\jdk1.6.0\lib目录下,所以通常我们都会把这两个jar包加到我们的classpath环境变量中set classpath=.;C:\jdk1.6.0\lib\tools.jar;C:\jdk1.6.0\lib\dt.jar。
在系统环境变量那一栏中点->新建classpath
变量名: classpath
变量值: .;%JAVA_HOME%\lib\tools.jar;%JAVA_HOME%\lib\dt.jar;(注意,CLASSPATH最前面是有个 “.”的,表示当前目录,这样当我们运行java AClass的时候,系统就会先在当前目录寻找AClass文件了。);
(3)设置JAVA_HOME:
一是为了方便引用,比如,JDK安装在C:\jdk1.6.0目录里,则设置 JAVA_HOME为该目录路径, 那么以后要使用这个路径的时候, 只需输入%JAVA_HOME%即可, 避免每次引用都输入很长的路径串;
二则是归一原则, 当JDK路径改变的时候, 仅需更改JAVA_HOME的变量值即可, 否则,就要更改任何用绝对路径引用JDK目录的文档, 要是万一没有改全, 某个程序找不到JDK, 后果是可想而知的—-系统崩溃!
三则是第三方软件会引用约定好的JAVA_HOME变量, 不然, 你不能正常使用该软件.
在系统环境变量那一栏中点->新建JAVA_HOME (JAVA_HOME指向的是JDK的安装路径)
变量名: JAVA_HOME
变量值: C:\jdk1.6.0
配置完成下面写一个简单的java程式来测试J2SDK是否已安装成功:
public class HelloWorld{
public static void main(String[] args){
System.out.println(“Hello world!”);
}
}
将程式保存为文档名为HelloWorld.java的文档。
打开命令提示符窗口,进入到HelloWorld.java所在目录,键入下面的命令
javac HelloWorld.java
java HelloWorld
此时若打印出来HelloWorld则安装成功,若没有打印出这句话,仔细检查以上配置是否正确。
环境变量配置成功后你就可以着手学习java了。
安装Tomcat后,在我的电脑->属性->高级->环境变量->系统变量中添加以下环境变量(假定你的tomcat安装在c:\tomcat):
CATALINA_HOME=c:\tomcat
CATALINA_BASE=c:\tomcat
然后修改环境变量中的classpath,把tomat安装目录下的common\lib下的(可以根据实际追加)servlet.jar追加到classpath中去,
修改后的classpath如下:
classpath=.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;%CATALINA_HOME%\common\lib\servlet.jar;
接着可以启动tomcat,在IE中访问http://localhost:8080,如果看到tomcat的欢迎页面的话说明安装成功了。

BCEL简明教程

BCEL简明教程

注意:本文章主要依据BCEL官方手册进行阐述,大部分内容都是从该手册直接翻译过来的,并做了一定的简化,同时还参考了《深入理解Java虚拟机》(周志明著)。笔者在下面阐述的时候也会给出相应的章节,如果读者有不清楚的地方请参考BCEL官方手册或是BCEL API,以及《深入理解Java虚拟机》。

BCEL是什么?

相信搜索到这篇文章的读者应该知道BCEL是啥,不过还是简要提一下吧:BCEL(Byte Code Engineering Library)原本是Apache Jakarta的一个子项目,目前已成为Apache Commons的一个子项目,主要用于分析、创建、操纵Java  class文件。
为了说明如何使用BCEL,需要简要介绍一下JVM结构和class文件的结构。

JVM结构

JVM
JVM是Java提供平台无关性的基础,它是一台抽象的机器,主要任务是装载class文件并且执行其中的字节码。Java虚拟机的体系结构如上图所示。这里仅给出JVM中比较重要的部分的说明,详细介绍请参考《深入理解Java虚拟机》(周志明著)和《Java Virtual Machiine Specification》(参见Oracle网站)。

  • 类装载子系统:每个Java虚拟机都有一个类装载子系统,它根据给定的全限定名来装入类或接口。
  • 运行时数据区:用于组织需要内存来存储的东西,如,字节码,程序创建的对象,传递给方法的参数,返回值,局部变量,运算的中间结果,……
  • Java堆:存放运行时创建的对象实例,以及对象间的引用关系
  • Java栈:其实更准确的称呼应该是“Java Virtual Machine Stack”,每当一个方法被执行的时候,都会创建一个Java栈帧(stack frame),栈帧中保存着该方法的操作数栈、局部变量表(local variable table)、动态链接、方法出口等信息,栈帧创建后就会放到Java栈上,执行完毕后抛弃该栈帧。我们平时所说的方法内的局部变量保存在“栈”上其实就是指Java栈。
  • 方法区:保存被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。注意方法区中包含有一个运行区常量池,class文件中的常量池(将在后面讲解)中的内容将在类加载后存放到这个运行时常量池中。
  • 程序计数器(PC计数器):PC寄存器的值总是指示下一条将被执行的指令
  • 本地方法栈:用于本地方法的执行,本地方法参考《Core Java》中有关JNI的阐述。

Java class文件格式

注:要完整的说明class文件的结构是个非常费劲的事情,JVM指令系统更是如此。限于篇幅这里不再详细阐述,仅给出简要的说明,详细说明请参考《深入理解Java虚拟机》和《Java Virtual Machiine Specification》
class文件的格式大体如下图所示
classfile[](http://www.prayer-laputa.com/blog/wp-content/uploads/2013/02/classfile.gif)class文件的header部分包含有一个“魔数”(magic number)0xCAFEBABE(其实是”cafe baby”,此乃当年Java开发团队的幽默),然后是版本号,再之后是常量池,访问权限标志位,当前类(注意,这里的类是指类和接口,是比较泛泛的概念,下文同)所实现的接口列表,当前类所包含的域、方法,最后是当前类的属性(可以包含用户自定义的数据)。
由于需要在运行时动态解析的类、域、方法的符号引用实际都是以字符串常量的形式保存在常量池中,所以常量池实际上占了class文件的很大比重,一般是60%,而byte code一般只占12%。
常量池中一般包含有如下类型的常量:References to methods, fields and classes, strings, integers, floats, longs, and doubles.(以上参考 2.1 java class file format)
注意,class文件中,非抽象方法包含有一个属性“Code”,包含有栈帧所需的最大深度、局部变量的个数、byte code指令的数组。当在方法执行过程中发生异常时,JVM会查找异常表(table of exception handlers),该表标记了handler,也就是哪些异常处理代码负责处理具体哪个部分代码所抛出的异常,如果没有合适的handler,异常将会传递给该方法(callee)的调用者(caller)。handler的信息保存在“Code”属性中。(2.3 Method Code)

实例分析

(这一部分参考2.6 Code Example)
首先结合上述的内容,分析以下Java代码编译后所得到的class文件。(将就着看吧,wordpress里一行行调代码缩进实在太麻烦,就没怎么调整~~)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
     import java.io.*;
public class Factorial {
      private static BufferedReader in = new BufferedReader(new
                                InputStreamReader(System.in));
public static final int fac(int n) {
        return (n == 0)? 1 : n * fac(n - 1);
      }
public static final int readInt() {
        int n = 4711;
        try {
        System.out.print("Please enter a number> ");
        n = Integer.parseInt(in.readLine());
        } catch(IOException e1) { System.err.println(e1); }
        catch(NumberFormatException e2) { System.err.println(e2); }
        return n;
      }
public static void main(String[] argv) {
        int n = readInt();
        System.out.println("Factorial of " + n + " is " + fac(n));
      }
    }

fac()方法对应的字节码如下:\

1
2
3
4
5
6
7
8
9
10
11
12
0:  iload_0
1:  ifne            #8
4:  iconst_1
5:  goto            #16
8:  iload_0
9:  iload_0
10: iconst_1
11: isub
12: invokestatic    Factorial.fac (I)I (12)
15: imul
16: ireturn
LocalVariable(start_pc = 0, length = 16, index = 0:int n)

每条指令的功能这里就不细说,需要注意的是,JVM是一种基于栈的虚拟机,任何操作都需要先将操作数放入栈帧中的操作数栈,然后调用相关方法,操作的结果也会自动的压入操作数栈。在本例中,fac()方法采用了递归的方式计算n!,第12行体现了这一点,在JVM执行到底12行时,会创建一个新的栈帧,以执行新的fac()方法调用。此外,ireturn会将结果返回给当前方法(callee)的调用者(caller),压入到caller栈帧中操作数栈的栈顶。
readInt()方法的字节码如下:\

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    0:  sipush        4711
3:  istore_0
4:  getstatic     java.lang.System.out Ljava/io/PrintStream;
7:  ldc           "Please enter a number> "
9:  invokevirtual java.io.PrintStream.print (Ljava/lang/String;)V
12: getstatic     Factorial.in Ljava/io/BufferedReader;
15: invokevirtual java.io.BufferedReader.readLine ()Ljava/lang/String;
18: invokestatic  java.lang.Integer.parseInt (Ljava/lang/String;)I
21: istore_0
22: goto          #44
25: astore_1
26: getstatic     java.lang.System.err Ljava/io/PrintStream;
29: aload_1
30: invokevirtual java.io.PrintStream.println (Ljava/lang/Object;)V
33: goto          #44
36: astore_1
37: getstatic     java.lang.System.err Ljava/io/PrintStream;
40: aload_1
41: invokevirtual java.io.PrintStream.println (Ljava/lang/Object;)V
44: iload_0
45: ireturn
Exception handler(s) =
From    To      Handler Type
4       22      25      java.io.IOException(6)
4       22      36      NumberFormatException(10)

这个例子需要说明的是,平时我们调用System.out.println()打印输出时,虽然只是传入要打印的内容,但在字节码层次上,首先需要将System类的静态变量out压入到栈顶,然后将需要打印的内容(这里是字符串“”Please enter a number>”)压入栈,接着调用println()方法。之所以需要将out压入栈,是因为实例方法调用的时候,都会将该实力对象的引用隐式地作为第一个参数(Instance methods always implicitly take an instance reference as their first argument)。
另外,Java代码中的异常处理代码,try语句不会产生任何实际代码,只是规定了exception handler在哪些代码发生异常时会被调用。

BCEL API

BCEL API主要分为以下3个部分(3. The BCEL API):

  • bcel.classfile.*:主要用于查看class文件的结构(尤其是在没有源代码的情况下),一般不用作byte code的修改。
  • bcel.generic.*:动态产生或是修改class文件,可以插入代码、从class文件中剔除无用代码、实现一个Java编译器的代码生成器后端。
  • 其他:代码示例,使用工具

bcel.classfile.*的类设计如下图:
javaclass
通过下面的代码,即可访问一个class文件的JavaClass对象,然后通过这个对象的get/set方法,即可访问这个class文件的各个部分:

1
JavaClass clazz = Repository.lookupClass("java.lang.String");

比如说,下面的printCode()就能输出String类的所有方法:\

1
2
3
4
5
6
7
8
9
10
11
System.out.println(clazz);
printCode(clazz.getMethods());
...
public static void printCode(Method[] methods) {
for(int i=0; i < methods.length; i++) {
System.out.println(methods[i]);
Code code = methods[i].getCode();
if(code != null) // Non-abstract method
System.out.println(code);
}
}

3.3 ClassGen
bcel.generic.*的类设计如下图:
classgen
要创建一个类,需要使用ClassGen,而要给这个类添加其他部分,则需要使用上图中给出的其他类。在给出具体实例之前,还需要先确定如何表示类型——域需要指明其类型,方法则需要给出参数及其返回值的类型。
3.3.1 Types
使用BCEL的Type类用法如下:

1
2
Type   return_type = Type.VOID;
Type[] arg_types   = new Type[] { new ArrayType(Type.STRING, 1) }

3.3.2 Generic fields and methods
域需要使用FieldGen来创建,并且需要指定其访问权限。方法则需要添加可能需要的异常、local variables、exception handler。由于包含有byte code的地址的引用,这两者被称之为instruction targeter,这些targeter包含有updateTarget()方法,用于更新目标地址,这个地方是采用Observer模式实现的。一般方法(不是抽象方法)会指向一个instruction list(包含instruction对象),对byte code地址的引用由instruction对象处理,当instruction list更新时,instruction targeter也会被更新。
每个方法的maximum stack size和maximum number of local variables可以手动或是采用setMaxStack()和setMaxLocals()方法自动设置。
3.3.3 Instructions
instruction包含有 opcode(有时叫做tag),字节长度,在byte code中的偏移量。有些指令是不可变的(比如operators),InstructionConstants用于提供预定义的常量供用户使用(flyweight模式)。
指令分类:按照type hierarchy of instruction classes(附录有)。也可以按照所实现的接口分类。
重要的指令:branch instructions,比如goto,使得这些指令也可以看做instruction targeter。
所有指令都可以通过accept(Visitor v)方法来访问(visitor模式)。(3.3.3 Instructions)
3.3.4 Instruction lists
instruction list:对instructions的引用不是由直接指向instruction的指针,而是指向instruction handle的指针。这使得添加、插入、删除byte code很方便,同时允许重用不可变的指令对象(flyweight object)。由于使用符号引用,具体的bytecode偏移量的计算直到finalization才计算即可,即用户停止操作bytecode的时候。下文将instruction handle和instruction视为同一概念。instruction handle通过addAttribute()方法可以包含有用户自定义数据。
append操作
instructionlist的append方法会返回一个instruction handle,用作branch instruction的目标地址。

1
2
3
4
5
6
7
8
InstructionList il = new InstructionList();
...
GOTO g = new GOTO(null);
il.append(g);
...
// Use immutable fly-weight object
InstructionHandle ih = il.append(InstructionConstants.ACONST_NULL);
g.setTarget(ih);

insert操作
指令可以插入到现有list的任意位置,需要插入到给定instruction handle的前边,insert方法会返回一个instruction handle用于可能的异常处理的目标地址。

1
2
3
4
InstructionHandle start = il.insert(insertion_point,
InstructionConstants.NOP);
...
mg.addExceptionHandler(start, end, handler, "java.io.IOException");

delete操作
需要给出指定范围,该范围内的指令将被删除并释放资源(dispose)。但instruction targeter仍引用着将要被删除的指令时,delete方法可能抛出TargetLostException。用户需要自己处理这些异常。参见附录的窥孔优化的例子。

1
2
3
4
5
6
7
8
9
10
try {
il.delete(first, last);
} catch(TargetLostException e) {
InstructionHandle[] targets = e.getTargets();
for(int i=0; i < targets.length; i++) {
InstructionTargeter[] targeters = targets[i].getTargeters();
for(int j=0; j < targeters.length; j++)
targeters[j].updateTarget(targets[i], new_target);
}
};

finalize操作
当instruction list已经操作完毕、打算生成纯粹的字节码时,所有符号引用被映射为真实的byte code offset,这一操作由getByteCode()完成(默认由Method.getMethod())。生成字节码后用户应该调用dispose()一遍使得这些instrution handle能别重用。

1
2
3
4
5
6
7
8
9
10
11
12
InstructionList il = new InstructionList();
ClassGen  cg = new ClassGen("HelloWorld", "java.lang.Object",
"<generated>", ACC_PUBLIC | ACC_SUPER,
null);
MethodGen mg = new MethodGen(ACC_STATIC | ACC_PUBLIC,
Type.VOID, new Type[] {
new ArrayType(Type.STRING, 1)
}, new String[] { "argv" },
"main", "HelloWorld", il, cp);
...
cg.addMethod(mg.getMethod());
il.dispose(); // Reuse instruction handles of list

3.3.6 Instruction Factories
为了简化某些指令的创建,用户可以使用InstructionFactory类创建指令(提供了很多有用的方法用于创建指令),也可以使用compound instruction:当产生byte code,某些“模式”出现的比较频繁,比如算术或是比较运算,可以用一个compound instruction(一个只有单一的一个getInstructionList()方法的接口),这可以用于任何位置,尤其是添加操作。
例子:将操作数压入栈,用PUSH可以自动产生合适的指令

1
2
3
4
5
6
7
8
9
InstructionFactory f  = new InstructionFactory(class_gen);
InstructionList    il = new InstructionList();
...
il.append(new PUSH(cp, "Hello, world"));
il.append(new PUSH(cp, 4711));
...
il.append(f.createPrintln("Hello World"));
...
il.append(f.createReturn(type));

regular expressions
可以用正则表达式来搜索特定模式的代码。——org.apache.bcel.util.InstructionFinder的search()方法,找到后会返回一个迭代器,其他的约束条件可以通过code constraint对象来表达。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CodeConstraint constraint = new CodeConstraint() {
public boolean checkCode(InstructionHandle[] match) {
IfInstruction if1 = (IfInstruction)match[0].getInstruction();
GOTO          g   = (GOTO)match[2].getInstruction();
return (if1.getTarget() == match[3]) &&
(g.getTarget() == match[4]);
}
};
InstructionFinder f    = new InstructionFinder(il);
String            pat = "IfInstruction ICONST_0 GOTO ICONST_1 NOP(IFEQ|IFNE)";
for(Iterator e = f.search(pat, constraint); e.hasNext(); ) {
InstructionHandle[] match = (InstructionHandle[])e.next();;
...
match[0].setTarget(match[5].getTarget()); // Update target
...
try {
il.delete(match[1], match[5]);
} catch(TargetLostException ex) { ... }
}

例子:优化boolean表达式

用BCEL生成HelloWorld

最后,举个简单的例子,说明具体如何使用BCEL直接生成class文件。比如说,我们现在要使用BCEL生成一个HelloWorld.class文件,实现如下代码的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
try {
// BCEL Appendix A example
// ClassGen(String class_name, String super_class_name, String
// file_name, int access_flags, String[] interfaces)
ClassGen cg = new ClassGen("HelloWorld", "java.lang.Object",
"<generated>", ACC_PUBLIC | ACC_SUPER, null);
ConstantPoolGen cp = cg.getConstantPool();
InstructionList il = new InstructionList();
// create main method
// MethodGen(int access_flags, Type return_type, Type[] arg_types,
// String[] arg_names, String method_name, String class_name,
// InstructionList il, ConstantPoolGen cp)
MethodGen mg = new MethodGen(ACC_STATIC | ACC_PUBLIC, Type.VOID,
new Type[] { new ArrayType(Type.STRING, 1) },
new String[] { "argv" }, "main", "HelloWorld", il, cp);
InstructionFactory factory = new InstructionFactory(cg);
// define some often used types
ObjectType i_stream = new ObjectType("java.io.InputStream");
ObjectType p_stream = new ObjectType("java.io.PrintStream");
//%%
//BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
//%%
// create variables in and name
il.append(factory.createNew("java.io.BufferedReader"));
il.append(InstructionConstants.DUP);
il.append(factory.createNew("java.io.InputStreamReader"));
il.append(InstructionConstants.DUP);
//init input stream
il.append(factory.createFieldAccess("java.lang.System", "in",
i_stream, Constants.GETSTATIC));
il.append(factory.createInvoke("java.io.InputStreamReader",
"<init>", Type.VOID, new Type[] { i_stream },
Constants.INVOKESPECIAL));
il.append(factory.createInvoke("java.io.BufferedReader", "<init>",
Type.VOID, new Type[] { new ObjectType("java.io.Reader") },
Constants.INVOKESPECIAL));
//add in into the local variable pool and get the index automatically
LocalVariableGen lg = mg.addLocalVariable("in", new ObjectType(
"java.io.BufferedReader"), null, null);
int in = lg.getIndex();//index of "in" var
lg.setStart(il.append(new ASTORE(in)));//store the reference into local variable
//首先创建对象,并初始化,操作结果在JVM的“堆”里,还需要在本地变量表中创建引用,因此在本地变量表中添加一个“in”比那辆,
//然后根据索引值调用“astore”指令,即可将对象引用赋值给本地变量
/*
0: new #8; //class java/io/BufferedReader
3: dup
4: new #10; //class java/io/InputStreamReader
7: dup
8: getstatic #16; //Field java/lang/System.in:Ljava/io/InputStream;
11: invokespecial #20; //Method java/io/InputStreamReader."<init>":(Ljava/io/InputStream;)V
14: invokespecial #23; //Method java/io/BufferedReader."<init>":(Ljava/io/Reader;)V
17: astore_1
* */
//%%
//String name = null;
//%%
// create local variable name and init it to null
lg = mg.addLocalVariable("name", Type.STRING, null, null);
int name = lg.getIndex();
il.append(InstructionConstants.ACONST_NULL);//add "null" to the stack top
lg.setStart(il.append(new ASTORE(name)));//"store" the value of "null" into "name" var
//%%
//System.out.print("Please enter your name> ")
//%%
// create try_catch block
InstructionHandle try_start = il.append(factory.createFieldAccess(
"java.lang.System", "out", p_stream, Constants.GETSTATIC));
//从常量池中取出“please .....”,压入栈顶:这里感觉有问题,这个字符串常量应该先压入常量池才可以(最好是在这之前加一句,
//加一句添加常量池操作其实并不影响实际运行的效率)
il.append(new PUSH(cp, "Please enter your name> "));
il.append(factory.createInvoke("java.io.PrintStream", "print",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
//%%
//name = in.readLine();
//%%
//将本地变量“in”推送至栈顶
il.append(new ALOAD(in));
il.append(factory.createInvoke("java.io.BufferedReader",
"readLine", Type.STRING, Type.NO_ARGS,
Constants.INVOKEVIRTUAL));//调用readLine()方法
il.append(new ASTORE(name));//接收的结果在栈顶,需要保存,因此加上保存到“name”slot的指令
//%%
// } catch(IOException e) { return; }
//%%
GOTO g = new GOTO(null);
InstructionHandle try_end = il.append(g);
//add return:如果出异常,才会走到这条“return”指令,并返回到caller中
InstructionHandle handler = il.append(InstructionConstants.RETURN);
// add exception handler which returns from the method
mg.addExceptionHandler(try_start, try_end, handler, null);
//%%
//没有异常,继续执行:System.out.println("Hello, " + name);
//%%
// "normal" code continues, set the branch target of the GOTO
InstructionHandle ih = il.append(factory.createFieldAccess(
"java.lang.System", "out", p_stream, Constants.GETSTATIC));
g.setTarget(ih);
// print "Hello":创建一个StringBuffer对象,通过调用StringBuffer的append操作,实现
//string1 + string2的操作,并且操作结果调用toString方法
il.append(factory.createNew(Type.STRINGBUFFER));
il.append(InstructionConstants.DUP);
il.append(new PUSH(cp, "Hello, "));
il.append(factory.createInvoke("java.lang.StringBuffer", "<init>",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKESPECIAL));
il.append(new ALOAD(name));
il.append(factory.createInvoke("java.lang.StringBuffer", "append",
Type.STRINGBUFFER, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
//
il.append(factory.createInvoke("java.lang.StringBuffer",
"toString", Type.STRING, Type.NO_ARGS,
Constants.INVOKEVIRTUAL));
il.append(factory.createInvoke("java.io.PrintStream", "println",
Type.VOID, new Type[] { Type.STRING },
Constants.INVOKEVIRTUAL));
il.append(InstructionConstants.RETURN);
// finalization
mg.setMaxStack();
cg.addMethod(mg.getMethod());
il.dispose();
cg.addEmptyConstructor(ACC_PUBLIC);
// dump the class
cg.getJavaClass().dump("HelloWorld.class");
System.out.println("dump successly");
} catch (java.io.IOException e) {
System.err.println(e);
} catch (Exception e1) {
e1.printStackTrace();
}

Java核心技术读书笔记(2)——第4章对象与类

Java核心技术读书笔记(2)——第4章对象与类

4.1 面向对象程序设计概述
4.1.3 识别类
设计面向对象的系统时首先从设计类开始,然后每个类中添加方法。
识别类的简单规则是在分析问题的过程中寻找名词,而方法对应着动词。——“名词与动词”原则只是一种粗略的方法,还依赖于个人的开发经验。
4.1.4 类之间的关系
类之间常见的关系有:

  • 依赖(dependency):”uses-a”关系。
  • 聚合(aggregation):”has-a”关系。
  • 继承(inheritance):”is-a”关系

4.2 使用现有类
4.2.1 对象与对象变量
一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。
在Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new操作符的返回值也是一个引用。
Java中可以将对象变量设置为null,而C++中没有空引用,而且引用不能被赋值。可以将Java的对象看做C++的对象指针。例如:
Date birthday;//Java
实际上等同于:
Date* birthday;//C++
Java中必须使用clone()方法获得对象的完整拷贝。
4.2.2 Java类库中的GregorianCalendar类
Date类的时间是用距离一个固定时间点的毫秒数(可正可负)表示的,这个点即所谓的“纪元”(epoch),它是UTC时间1970年1月1日00:00:00(为何选择这个时间作为“原点”可以参考这里UNIX_Time)。
类库设计者觉得将保存时间与给时间点命名分开,所以标准Java类库分别包含了2个类:一个用来表示时间点的Date类,另一个用来表示大家熟悉的日历表示法的GregorianCalendar类。
4.2.3 更改器方法与访问器方法
对实例域做出修改的方法称为更改器方法(mutator method),仅访问实例域而不进行修改的方法称为访问器方法(accessor method)。
PS: C++ 中带有const后缀的方法是访问器方法,默认为更改器方法。但Java中访问器方法与更改器方法在语法上没有明显的区别。
有关日历需要注意每个星期的第一天存在着不同的约定,美国第一天是周日,欧洲则是周一。
如果想看到不同地区程序的输出,应该在main方法的第一行添加如下代码:

1
Locale.setDefault(Locale.ITALY);

4.3 用户自定义类
4.3.2 多个源文件的使用
有多个源文件时,有2种编译源程序的方式:

  • 使用通配符调用Java编译器:比如有2个类,分别是Employee.java, EmployeeTest.java,则javac Employee*.java
  • 利用javac自动搜索源文件:例子同上,直接javac EmployeeTest.java,当编译器发现该类调用了Employee.java,会自动搜索Employee.java并编译,而且已有Employee.class但Employee.java版本更新,Java编译器会自动重新编译Employee.java(相当于内置了“make”的功能)。

4.3.6 封装的优点
注意不要编写返回引用可变对象的访问器方法。详细内容参见书P108。
4.3.7 基于类的访问权限
一个方法可以访问所属类的所有对象的私有数据。例如:

1
2
3
4
5
6
7
8
class Employee
{
...
boolean equals(Employee other)
{
return name.equals(<strong>other.name</strong>);
}
}

4.3.9 final实例域
构建对象时必须初始化final域。即必须确保每个构造器执行后,这个域的值被设置,并且在之后的操作中,不能再对它进行修改。
final大都应用于基本数据类型域或是不可变类(类中的每个方法都不会改变其对象)的域。对于可变的类,final就可能会带来混淆,比如

1
private final Date hireDate;

,仅仅意味着hireDate变量的对象引用在对象构造后不能被改变,并不意味着hireDate对象是一个常量。
4.4 静态域与静态方法
4.2 静态常量
常见的常量:Math.PI, System.out
4.3 静态方法
可以认为静态方法是没有this参数的方法。由于静态方法不能操作对象,所以不能再静态方法中访问实例域。但静态方法可以访问自身类中的静态域。
使用静态方法的情况:

  • 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(如Math.pow)
  • 一个方法只需要访问类的静态域

静态方法还有一种常见的用途:factory方法。
4.5 方法参数
Java总是采用值调用,方法得到的是所有参数值的一个拷贝,方法不能修改传递给它的任何参数变量的内容。Java中方法参数的使用情况:

  • 一个方法不能修改一个基本数据类型的参数
  • 一个方法可以改变一个对象参数的状态
  • 一个方法不能实现让对象参数引用一个新的对象

PS:C++有值调用和引用调用,引用参数标有&符号。
4.6 对象构造
4.6.1 重载
Java允许重载任何方法,但方法的签名(方法名以及参数类型,不包括返回类型)必须不同。即,不能有两个名字相同、参数类型相同、返回类型不同的方法。
4.6.2 默认域初始化
如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋予默认值:数值为0、布尔值为false、对象引用为null。但一般都会明确地对域进行初始化,否则影响程序的可读性。
PS:这是域与局部变量的主要不同点。必须明确地初始化方法中的局部变量。但如果没有初始化类中的域,将会被初始化为默认值。
4.6.3 默认构造器
默认构造器指没有参数的构造器。
如果编写一个类时,没有编写构造器,则系统会提供一个默认构造器。这个默认构造器将所有的实例域设置为默认值。如果类中提供了至少一个构造器,但没有提供默认的构造器,则在构造对象时如果没有提供构造参数就会被视为不合法。即:**仅当类没有提供任何构造器时,系统才会提供一个默认构造器,如果编写类时给出了一个构造器,系统就不会提供默认构造器,必须手写。**如果想要给域赋默认值,则可以

1
2
public ClassName(){
}

4.6.6 调用另一个构造器
如果构造器的第一条语句是this(…),这个构造器将调用同一个类的另一个构造器。
4.6.7 初始化块
初始化数据域的方法:

  • 构造器中设置值
  • 声明中赋值
  • 初始化块

一个类的声明中可以包含多个代码块,只要构造类的对象,这些块就会被执行。建议将初始化块放在域定义之后。
可以用静态的初始化块来对静态域进行初始化。
4.6.8 对象析构与finalize方法
一般不推荐使用finalize方法,但如果某些对象使用了内存之外的其他资源,例如,文件或使用了系统资源的另一个对象的句柄。在这种情况下,当资源不再需要时,将其回收和再利用就显得十分重要。
可以为任何一个类添加finalize方法,该方法将在垃圾回收器清楚对象之前调用。实际应用中不要依赖于使用finalize方法回收任何短缺的资源,因为很难知道这个方法什么时候用到。
4.7 包
为了保证包名的唯一,建议将公司的域名逆序作为包名,并且对于不同的项目使用不同的子包。
4.7.2 静态导入
Java SE 5开始,import语句不仅可以导入类,还增加导入静态方法和静态域的功能。如:

1
import static java.lang.System.*;

就可以使用System类中的静态方法和静态域,而不用加类名前缀:

1
2
out.println("helllo");
exit(0);

静态导入有两个实际应用:算术函数(静态导入Math的域和方法),笨重的常量
4.7.3 将类放入包中
注意如果类是某个包中,就必须将包的名字放在源文件开头,javac编译时也需要将注意加上包名(也要注意当前目录是什么)。
4.7.4 包作用域
如果没有指定public或是private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。

Java核心技术读书笔记(0)——汇总篇

Java核心技术读书笔记(0)——汇总篇

虽然用Java编程已经有几年了,但一直没好好看Java的各种语法细节,都是碰上问题就去网上搜,最近打算重新看一下Java核心技术(Core Java)这本书,毕竟很久之前看过,有不少东西都忘记了,顺便做一下读书笔记,摘录一下自己在书中看到的比较“新鲜”的Java语法。我觉得应该有不少人和我一样平时只是用Java,但没想到或是忘记Java还有这样那样的用法,于是贴在自己的博客上给大家分享一下。
下面给出链接(将会不断更新):
第3章 Java基本的程序设计结构
第4章 对象与类
第5章 继承

Java核心技术读书笔记(1)——第3章基本程序设计结构

Java核心技术读书笔记(1)——第3章基本程序设计结构

3.3 数据类型
Java有一个能表示任意精度的算术包,通称“big number”,该包中的用于表示大的数值。
3.3.1 整型
Java中整型的范围与运行Java代码的机器无关,已经规定了各种数据类型所使用的字节数和取值范围(int、short、long、byte依次占用4、2、8、1个字节)。Java中没有任何unsigned type。
PS:C/C++中int表示的整型与目标机器有关。读《C和指针》的时候我记得读到过这么一句:C语言规范只规定了int的最小取值范围。
3.3.2 浮点类型
绝大部分应用程序都应采用double类型而不是float类型,只有很少情况适合使用float类型,例如快速地处理单精度数据,或是需要存储大量数据。
float类型的数值必须有一个后缀F(比如3.402F),没有后缀F的浮点数值(如3.402)默认为double类型(也可以加后缀D)。
JDK 1.5中可以使用十六进制表示浮点数值(具体表示方式请参见原书)。
常量Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NaN(float对应的常量一样)分别表示正无穷大、负无穷大、NaN,用于溢出和出错情况(如一个正数除以0得到正无穷大,0/0或是负数平方根结果为NaN),实际应用中很少用到,注意不能检测一个特定值是否等于Double.NaN,比如

1
if(x == Double.NaN)

因为所有“NaN”的值都认为是不相同的,此时可以用Double.isNaN方法来判别。
注意,浮点数值不适用于禁止出现舍入误差的金融计算中。
PS:这个语法点有时会被面试官考到,比如问你两个double值或是float值是否相同——浮点数一般是没法比较的,因为采用的是IEEE 754的表示方式,所保存的数值并不是精确的。
3.3.3 char类型
code point
建议不要在程序中使用char类型,除非确实需要对UTF-16代码但愿进行操作,最好将需要处理的字符串用抽象数据类型表示。
3.3.4 boolean类型
整型值和布尔值之间不能进行相互转换。这个和C/C++明显不同(0可以表示false,非0值表示true)。
3.4 变量
声明一个变量之后必须用赋值语句对变量进行显式赋值。
变量名必须是一个以字母开头的由字母、数字构成的序列,但Java的“字母”“数字”范围比其他语言大,字母可以是任何在某语言中代表字母的Unicode字符(如德语或是希腊语π),数字可以是在某种语言中代表数字的任何Unicode字符,但’+’、’圈C’、空格不能出现在变量名中。
Java中变量的声明尽可能地靠近变量第一次使用的地方。(PS:想知道缘由的话可以去看Effective Java)
C/C++中区分变量的声明(如extern int i)和定义(如int i  = 10),而Java中不区分这一点。
3.5 运算符
3.5.3 位运算符
“>>>”将用0填充高位,”>>”用符号位填充高位。没有”<<<“。注意:对移位运算符右侧的参数需要进行模32操作(除非左边的操作数是long类型,此时进行模64操作),如1<<35与1<<3的结果是相同的。而C/C++中无法确定是算术移位(扩展符号位)还是逻辑移位(高位填0)。执行中将会选取效率较高的一种,这就意味着,在C/C++中,>>实际上只是为非负数定义的,而Java则消除了这种模糊。
3.5.4 数据函数与常量
从JDK 1.5开始可以不必使用“Math.XXX()”的调用方式,在源文件头部引入Math包,然后就可以直接调用数学方法或是常量。
3.5.5 数组类型之间的转换
二元运算中,两个操作数中:

  • 若有一个是double类型,则另一个转换为double类型
  • 否则,若有一个是float类型,则另一个转换为float类型
  • 否则,若有一个是long类型,则另一个转换为long类型
  • 否则,两个操作数都将被转换为int类型。

3.5.6 强制类型转换
Java中允许各种数值类型之间的强制类型转换,但可能会丢失一些信息。比如:

1
2
double x = 9,997;
int nx = (int)x;

此时,nx的值为9。这是因为,**强制类型转换通过截断小数部分将浮点值转换为整型。**如果要对浮点数进行舍入运算,以便得到最接近的整数时,需要使用Math.round()方法:

1
2
double x = 9,997;
int nx = (int)Math.round(x);

此时nx的值为10。而使用round()方法仍需要使用强制类型转换(int)则是因为该方法返回的结果是long类型。
3.6 字符串
3.6.1 子串
substring()方法实际创建了一个新的字符串。
3.6.3 不可变字符串
不能修改Java字符串中的字符,因此String类对象被称为不可变字符串。不可变字符串有一个优点:编译器可以让字符串共享。
PS:C++字符串是可修改的(可以修改单个字符)。
3.6.4 检测字符串是否相等
一定不能用==来检测两个字符串是否相当,该操作符只能判断两个字符串是否放置在同一个位置上。
3.6.8 构建字符串
如果需要对字符串进行修改,应该使用StringBuilder类(JDK 1.5引入)。该类前身是StringBuffer,效率有些低,但允许多线程执行添加/删除字符的操作。若在单线程环境下应该用StringBuilder。
3.7 输入输出
3.7.1 读取输入
Scanner in = new Scanner(System.in);
Scanner的输入是可见的,不适合从控制台读取密码,Java SE 6引入了Console类实现该目的:

1
2
3
Console cons  = System.console();
String username = cons.readLine("user name:");
char[] passwd = cons.readPasswd("Password:");

3.7.2 格式化输出
Java SE 5沿用了C语言函数中的printf方法,可以用于格式化输出。
可以使用静态的String.format方法创建一个格式化的字符串,而不打印输出。
3.7.3 文件的输入与输出
读文件:
Scanner in = new Scanner(new File(“myfile.txt”));
写文件:
PrintWriter out = new PrintWriter(“myfile.txt”);
注意,如果用一个不存在的文件构造一个Scanner,或是用一个不能被创建的文件名构造一个PrintWriter,则会发生异常。
3.8 控制流程
循环中检测2个浮点数是否相当需要格外小心。如:
for(double x = 0; x!=10; x+=0.1)
可能永远不会结束。由于舍入误差,最终可能得不到精确值。x会从9.99999…直接跳到10.09999…
for循环语句可以看做是while循环的一种简化形式。
当在switch语句中使用枚举常量时,不必在每个标签中指明枚举名,可以由switch表达式值确定,例如:
Size sz = …;
switch(sz){
case SMALL:

}
可以利用break label;的方式实现goto的功能。
3.10 数组
for each循环——操作对象必须是一个数组或是一个实现了Iterator解耦的类对象。
数组初始化:

1
int[] smallPrimes = {2,3,5,7,11,13};

匿名的数组:new int[]{17,19,23}; ——这种语法可以在不创建新变量的情况下重新初始化一个数组。

1
int[] smallPrimes = new int[]{17,19,23};

Java中允许数组长度为0,数组长度为0与null不同。
数组变量直接赋值将会使得两个变量引用同一个数组,要想将数据拷贝到一个新数组,可以用Arrays类的copyOf方法。
数组排序可以使用Arrays类中 sort方法(内部实现采用了优化的快排)。
3.10.6 多维数组
多维数组声明:
double[][] balances;
初始化:
balances = new double[NYEARS][NRATES];
简化形式:

1
2
3
4
5
int[][] magicSquare = {
{16,3,2},
{12,1,5},
{9,8,6}
};

for each循环语句不能自动处理二维数组的每一个元素,它是按照行,即一维数组处理的。想要访问二维数组所有元素,需要嵌套的循环:

1
2
3
for(double[] row:a)
for(double value:row)
do sth

由于可以单独的存取一行,所以可以让二维数组中2行交换。

1
2
3
double[] tmp = balances[i];
balances[i]= balances[i+1];
balances[i+1] = tmp;

Java中可以创建不规则的数组。


3.10.4 命令行参数
Java程序的main方法中,程序名并没有存储在args数组中,例如:java Message -h world 中,args[0]是“-h”,而不是”Message”或是“java”。
有用的API:
String类:

  • String trim() 删除原始字符串头部和尾部的空格。

Arrays类:

  • copyOf 拷贝数组
  • sort 对数组排序
  • binarySearch 在数组中二分查找
  • fill 填充所有数组元素为XXX
  • equal 判断两个数组下标是否相等

Java核心技术读书笔记(3)——第5章继承

Java核心技术读书笔记(3)——第5章继承

5.1 类、超类和子类
Java中,所有继承都是公有继承,没有C++中的私有继承、保护继承。
子类不能直接访问超类的私有域,应该用super.getXXX()的方式来获取。
子类中可以增加域、方法或是覆盖超类的方法,但绝不能删除继承的任何域与方法。
如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器,并在子类的构造器中又没有显式地调用超类的其他构造器,则Java编译器将报告错误。
关键字this有2个用途:引用隐式参数、调用该类其他的构造器。
关键字super有2个用途:调用超类的方法、调用超类的构造器。
一个对象变量可以引用多种实际类型的现象被称为多态(polymorphism),在运行时那个自动选择调用哪个方法的现象称为动态绑定(dynamic binding)。
5.1.1 继承层次
由一个公共超类派生出来的所有类的集合被称为继承层次(inheritance hierarchy)。
5.1.2 多态
在Java程序设计语言中,对象变量是多态的。一个Employee变量既可以引用一个Employee类对象,也可以引用一个Employee类的任何一个子类的对象(例如,Manager, Executive等等)。
在Java中,子类数组的引用可以转换成超类数组的引用,而不需要采用强制类型转换。
5.1.3 动态绑定
调用对象方法的执行过程:
1)编译器查看对象的声明类型和方法名。
2)接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重载解析(overloading resolution)。

  • 两个具有相同名字,不同签名的方法。如果在子类中定义了一个与超类签名相同的方法,那么子类中的这个方法就度盖了超类中的这个相同签名的方法。不过,返回类型不是签名的一部分,因此,在覆盖方法时,一定要保证返回类型的兼容性。在Java SE 5.0以前的版本中,要求返回类型必须是一样的。现在允许子类将及盖方法的返回类型定义为原返回类型的子类型。

3)如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式称为静态绑定(static binding)。与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。在我们列举的示例中,编译器采用动态绑定的方式生成一条调用f (String)的指令。
4)当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。
动态绑定有一个非常重要的特性:无需对现存的代码进行修改,就可以对程序进行扩展。
警告:在度盖一个方法的时候,子类方法不能低于超类方法的可见性。特别是,如果超一类方法是public,子类方法一定要声明为public。经常会发生这类错误:在声明子类方法的时候,遗漏了public修饰符。此时,编译器将会把它解释为试图降低访问权限。
5.1.4阻止继承:final类和方法
有时候,可能希望阻止人们利用某个类定义子类。不允许扩展的类被称为final类。
类中的方法也可以被声明为final。如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动地成为final方法)。
5.1.5强制类型转换
进行类型转换的惟一原因是:在暂时忽视对象的实际类型之后,使用对象的全部功能。
将一个值存入变量时,编译器将检查是否允许该操作。将一个子类的引用赋给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量,必须进行类型转换,这样才能够通过运行时的检查。
应该养成这样一个良好的程序设计习惯:在进行类型转换之前,先查看一下是否能够成功地转换。这个过程简单地使用instanceo运算符就可以实现。
综上所述:
·只能在继承层次内进行类型转换。
·在将超类转换成子类之前,应该使用instanceof进行检查。
如果x为null,进行下列测试
x instanceof C
不会产生异常,只足返回false。之所以这样处理足因为null没有引用任何对象,当然也不会引用C类型的对象。
5.1.6抽象类
为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。除了抽象方法之外,抽象类还可以包含具体数据和具体方法。

ubuntu如何更新hostname

ubuntu如何更新hostname

新装的ubuntu server,安装的过程中没太留意hostname的设置,等到安完后才想起hostname是命令行中“用户名@”后面的那个东西,于是想重新换一个hostname,很简单,使用vim编辑/etc/hostname,将里面的内容修改你想要的新的hostname(我修改为ubuntu),重启,但此时还没完,这时如果使用sudo来执行某些特权指令,虽然也能执行,但会出现“sudo unable to solve host XXX”(我这里是我的hostname“ubuntu”),此时还需要编辑/etc/hosts文件,将“127.0.1.1 YYY”中的修改为新的hostname即可。\