QQ登录

只需一步,快速开始

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 4086|回复: 27

java随笔

[复制链接]
发表于 2003-10-2 16:16:44 | 显示全部楼层 |阅读模式
首先我应该表示歉意,因为最近实在是有一些事情,所以这么久才开始动笔。至于java 语言的优势想必每个人都知道,在此我就不做介绍了,下面介绍一下java的开发工具:sun公司的J2SDK(Java Software Development Kit),它的前身是JDK(Java Develoment Kit).IBM公司的VisualAge for Java 和Borland公司的JBulider.后面两个我觉得更适合有一定开发经验的用户,至于初学者还是应该从J2SDK开始。
     根据java 2 标准,J2SDK分为4类:1. J2EE(Java 2 Platform Enterprise Edition),适用于在服务器端java语言的程序的开发兼作程序运行平台。
                                      2.J2SE(Java 2 Platform Standard Edition),适用于在一般计算机的java语言程序的开发兼作程序运行平台。
                                      3.J2ME(Java 2 Platform Micro Edition),适用于消费类电子产品中嵌入式系统java语言程序的开发兼作程序运行平台。
                                      4.JavaCard,适用于在Smart Card(IC卡)上的java语言程序的开发兼作程序运行平台。
    说明:我想最开始讲一下J2SE,因为这个是基础,J2EE我正在学,基本上可以说一点不会,不过讲完J2SE,可以捎带着讲一点。
    J2SDK中包含了一些java语言程序开发的基本工具:
[code:1]Javac:java语言编译器
Java:java语言应用程序的启动器
appletviewer:java Applet小程序运行和调试器
javadoc:java语言API文档生成器
jar:管理java语言归档(jap)文件
jdb:java语言程序调试器
javap:类文件解释器(反编译器)
extcheck:检测jar冲突的使用程序
rmic:为远程对象生成stub和skeleton类
rmid:启动激活系统守护进程,在java虚拟机上注册和激活对象
rmiregistry:在当前主机的指定端口上启动远程对象注册服务程序
serialver:返回serialVersionUID
jarsigner:为java归档(jar)文件产生签名,校验已签名的jar文件的签名
keytool:管理由私钥和认证相关公钥的密钥仓库(数据库)[/code:1]

哦,对于咱们初学者只会用到java 和javac,在这里我应该说明一下,根据我两个月零32天的java学习经验,我奉劝各位最开始先从记事本开始,虽然有些枯燥,但是可以让我们打下良好的java语言基础,当然Bluej和JCreator也是不错的选择。
    忘了说配置J2SDK,配置是这样的:
[code:1]windows下:在path下加上:jdk\bin所在的目录,有些书上还说要配置classpath,对此我一直也不太清楚,我没有配置这个,不过要是你想学JSP的话就要配置这个了,具体如下:在classpath参数加上:d:\jdk\lib\tools.jar;d:\jdk\lib\dt.jar (我的jdk在d盘)
linux下:咱们公社网络编程技术与项目孵化的版块上BartonZhang对此做了非常详细的介绍,在此我就不说了(我这个人确实比较懒)[/code:1]

象我看过的书一样最开始应该来一个小程序,活跃一下气氛,呵呵:


[code:1] public class Goldedge {
    public static void main(String[] args) {
       String line1 = "We all need people in our lives who are\n";
       String line2 = " willing to stand up once in a while \n";
       String line3 = "and say: I think you are wonderful!!! ";
       String quote = line1 + line2 +line3;
       String speaker = "goldedge";
       System.out.println('\u0022' + quote + '\u0022');
       System.out.println("\t" + speaker);
    }
}
[/code:1]
这是本人最喜欢的一句话,(好的东西当然和朋友一起分享)
注意: 如果你是第一次用记事本写程序,保存时要加后缀,即:”.java”,否则javac无法编译,会出现“error: cannot read: Goldedge.java”。说到这里我觉得有必要介绍一下java的编程规范(其实应该逐步的告诉大家,不过我觉得一下全说也没什么不好,大家一下记不全不要紧,以后我还会提到),具体如下:[code:1]
(1) 类名首字母应该大写。字段、方法以及对象(句柄)的首字母应小写。对于所有标识符,其中包含的所有单词都应紧靠在一起,而且大写中间单词的首字母。例如:
ThisIsAClassName
thisIsMethodOrFieldName
若在定义中出现了常数初始化字符,则大写static final基本类型标识符中的所有字母。这样便可标志出它们属于编译期的常数。
Java包(Package)属于一种特殊情况:它们全都是小写字母,即便中间的单词亦是如此。对于域名扩展名称,如com,org,net或者edu等,全部都应小写(这也是Java 1.1和Java 1.2的区别之一)。
(2) 为了常规用途而创建一个类时,请采取“经典形式”,并包含对下述元素的定义:
equals()
hashCode()
toString()
clone()(implement Cloneable)
implement Serializable
(3) 对于自己创建的每一个类,都考虑置入一个main(),其中包含了用于测试那个类的代码。为使用一个项目中的类,我们没必要删除测试代码。若进行了任何形式的改动,可方便地返回测试。这些代码也可作为如何使用类的一个示例使用。
(4) 应将方法设计成简要的、功能性单元,用它描述和实现一个不连续的类接口部分。理想情况下,方法应简明扼要。若长度很大,可考虑通过某种方式将其分割成较短的几个方法。这样做也便于类内代码的重复使用(有些时候,方法必须非常大,但它们仍应只做同样的一件事情)。
(5) 设计一个类时,请设身处地为客户程序员考虑一下(类的使用方法应该是非常明确的)。然后,再设身处地为管理代码的人考虑一下(预计有可能进行哪些形式的修改,想想用什么方法可把它们变得更简单)。
(6) 使类尽可能短小精悍,而且只解决一个特定的问题。下面是对类设计的一些建议:
■一个复杂的开关语句:考虑采用“多形”机制
■数量众多的方法涉及到类型差别极大的操作:考虑用几个类来分别实现
■许多成员变量在特征上有很大的差别:考虑使用几个类
(7) 让一切东西都尽可能地“私有”——private。可使库的某一部分“公共化”(一个方法、类或者一个字段等等),就永远不能把它拿出。若强行拿出,就可能破坏其他人现有的代码,使他们不得不重新编写和设计。若只公布自己必须公布的,就可放心大胆地改变其他任何东西。在多线程环境中,隐私是特别重要的一个因素——只有private字段才能在非同步使用的情况下受到保护。
(8) 谨惕“巨大对象综合症”。对一些习惯于顺序编程思维、且初涉OOP领域的新手,往往喜欢先写一个顺序执行的程序,再把它嵌入一个或两个巨大的对象里。根据编程原理,对象表达的应该是应用程序的概念,而非应用程序本身。
(9) 若不得已进行一些不太雅观的编程,至少应该把那些代码置于一个类的内部。
(10) 任何时候只要发现类与类之间结合得非常紧密,就需要考虑是否采用内部类,从而改善编码及维护工作(参见第14章14.1.2小节的“用内部类改进代码”)。
(11) 尽可能细致地加上注释,并用javadoc注释文档语法生成自己的程序文档。
(12) 避免使用“魔术数字”,这些数字很难与代码很好地配合。如以后需要修改它,无疑会成为一场噩梦,因为根本不知道“100”到底是指“数组大小”还是“其他全然不同的东西”。所以,我们应创建一个常数,并为其使用具有说服力的描述性名称,并在整个程序中都采用常数标识符。这样可使程序更易理解以及更易维护。
(13) 涉及构建器和异常的时候,通常希望重新丢弃在构建器中捕获的任何异常——如果它造成了那个对象的创建失败。这样一来,调用者就不会以为那个对象已正确地创建,从而盲目地继续。
(14) 当客户程序员用完对象以后,若你的类要求进行任何清除工作,可考虑将清除代码置于一个良好定义的方法里,采用类似于cleanup()这样的名字,明确表明自己的用途。除此以外,可在类内放置一个boolean(布尔)标记,指出对象是否已被清除。在类的finalize()方法里,请确定对象已被清除,并已丢弃了从RuntimeException继承的一个类(如果还没有的话),从而指出一个编程错误。在采取象这样的方案之前,请确定finalize()能够在自己的系统中工作(可能需要调用System.runFinalizersOnExit(true),从而确保这一行为)。
(15) 在一个特定的作用域内,若一个对象必须清除(非由垃圾收集机制处理),请采用下述方法:初始化对象;若成功,则立即进入一个含有finally从句的try块,开始清除工作。
(16) 若在初始化过程中需要覆盖(取消)finalize(),请记住调用super.finalize()(若Object属于我们的直接超类,则无此必要)。在对finalize()进行覆盖的过程中,对super.finalize()的调用应属于最后一个行动,而不应是第一个行动,这样可确保在需要基础类组件的时候它们依然有效。
(17) 创建大小固定的对象集合时,请将它们传输至一个数组(若准备从一个方法里返回这个集合,更应如此操作)。这样一来,我们就可享受到数组在编译期进行类型检查的好处。此外,为使用它们,数组的接收者也许并不需要将对象“造型”到数组里。
(18) 尽量使用interfaces,不要使用abstract类。若已知某样东西准备成为一个基础类,那么第一个选择应是将其变成一个interface(接口)。只有在不得不使用方法定义或者成员变量的时候,才需要将其变成一个abstract(抽象)类。接口主要描述了客户希望做什么事情,而一个类则致力于(或允许)具体的实施细节。
(19) 在构建器内部,只进行那些将对象设为正确状态所需的工作。尽可能地避免调用其他方法,因为那些方法可能被其他人覆盖或取消,从而在构建过程中产生不可预知的结果(参见第7章的详细说明)。
(20) 对象不应只是简单地容纳一些数据;它们的行为也应得到良好的定义。
(21) 在现成类的基础上创建新类时,请首先选择“新建”或“创作”。只有自己的设计要求必须继承时,才应考虑这方面的问题。若在本来允许新建的场合使用了继承,则整个设计会变得没有必要地复杂。
(22) 用继承及方法覆盖来表示行为间的差异,而用字段表示状态间的区别。一个非常极端的例子是通过对不同类的继承来表示颜色,这是绝对应该避免的:应直接使用一个“颜色”字段。
(23) 为避免编程时遇到麻烦,请保证在自己类路径指到的任何地方,每个名字都仅对应一个类。否则,编译器可能先找到同名的另一个类,并报告出错消息。若怀疑自己碰到了类路径问题,请试试在类路径的每一个起点,搜索一下同名的.class文件。
(24) 在Java 1.1 AWT中使用事件“适配器”时,特别容易碰到一个陷阱。若覆盖了某个适配器方法,同时拼写方法没有特别讲究,最后的结果就是新添加一个方法,而不是覆盖现成方法。然而,由于这样做是完全合法的,所以不会从编译器或运行期系统获得任何出错提示——只不过代码的工作就变得不正常了。
(25) 用合理的设计方案消除“伪功能”。也就是说,假若只需要创建类的一个对象,就不要提前限制自己使用应用程序,并加上一条“只生成其中一个”注释。请考虑将其封装成一个“独生子”的形式。若在主程序里有大量散乱的代码,用于创建自己的对象,请考虑采纳一种创造性的方案,将些代码封装起来。
(26) 警惕“分析瘫痪”。请记住,无论如何都要提前了解整个项目的状况,再去考察其中的细节。由于把握了全局,可快速认识自己未知的一些因素,防止在考察细节的时候陷入“死逻辑”中。
(27) 警惕“过早优化”。首先让它运行起来,再考虑变得更快——但只有在自己必须这样做、而且经证实在某部分代码中的确存在一个性能瓶颈的时候,才应进行优化。除非用专门的工具分析瓶颈,否则很有可能是在浪费自己的时间。性能提升的隐含代价是自己的代码变得难于理解,而且难于维护。
(28) 请记住,阅读代码的时间比写代码的时间多得多。思路清晰的设计可获得易于理解的程序,但注释、细致的解释以及一些示例往往具有不可估量的价值。无论对你自己,还是对后来的人,它们都是相当重要的。如对此仍有怀疑,那么请试想自己试图从联机Java文档里找出有用信息时碰到的挫折,这样或许能将你说服。
(29) 如认为自己已进行了良好的分析、设计或者实施,那么请稍微更换一下思维角度。试试邀请一些外来人士——并不一定是专家,但可以是来自本公司其他部门的人。请他们用完全新鲜的眼光考察你的工作,看看是否能找出你一度熟视无睹的问题。采取这种方式,往往能在最适合修改的阶段找出一些关键性的问题,避免产品发行后再解决问题而造成的金钱及精力方面的损失。
(30) 良好的设计能带来最大的回报。简言之,对于一个特定的问题,通常会花较长的时间才能找到一种最恰当的解决方案。但一旦找到了正确的方法,以后的工作就轻松多了,再也不用经历数小时、数天或者数月的痛苦挣扎。我们的努力工作会带来最大的回报(甚至无可估量)。而且由于自己倾注了大量心血,最终获得一个出色的设计方案,成功的快感也是令人心动的。坚持抵制草草完工的诱惑——那样做往往得不偿失。
(31) 可在Web上找到大量的编程参考资源,甚至包括大量新闻组、讨论组、邮寄列表等。[/code:1]
我看着都晕了,记不住是一定的,大家尽自己所能吧,假如你真要从事这方面的工作,上述这些应该是必须的。

本章就到这里,鉴于本人水平有限,有错误是肯定的,所以希望大家多提意见,谢谢(大家可以提一些要求,假如大家对J2EE有兴趣,我可以写一点关于这个的)

待续………(下一章写什么我还没想好,面向对象编程怎么样?)



参考资料:Thinking in java
          21天学通Java2
          Java 2编程指南(SDK 1.4版)


声明:本章由中国Linux公社所有,如有雷同,纯属巧合。
发表于 2003-10-2 18:40:48 | 显示全部楼层
支持!呵呵

那个...javac和java两个工具怎么用呢?
回复

使用道具 举报

发表于 2003-10-2 22:53:16 | 显示全部楼层
把写好的代码存为*.java,然后用shell>javac *.java
这样就生成了一个名为*.class的文件
然后用java *来运行看其结果
回复

使用道具 举报

发表于 2003-10-3 08:44:37 | 显示全部楼层
不懂java ,但是能把自己的心得写出来,值得鼓励,对想学java的很有帮助!少走很多弯路哦
回复

使用道具 举报

 楼主| 发表于 2003-10-3 19:55:42 | 显示全部楼层
谢谢各位鼓励,自己刚投了自己一票
回复

使用道具 举报

发表于 2003-10-11 10:20:16 | 显示全部楼层
呵呵,我向来是比较抵制Java的,但是现在java这么火肯定有它的过人之处~
在大三短学期的时候学了一个月的Java,并做了一个Case,到现在已经一年多没碰过它了。但是Java的技术我还是希望了解一下的,因为我在做网页的时候用的到Jscript,和Java应该有共同之处吧
回复

使用道具 举报

 楼主| 发表于 2003-10-11 13:08:44 | 显示全部楼层
因为我在做网页的时候用的到Jscript


这两个是不同的!
回复

使用道具 举报

发表于 2003-10-30 05:50:57 | 显示全部楼层
[quote:afe9d50f6a="sjinny"]支持!呵呵

那个...javac和java两个工具怎么用呢?[/quote]

javac 是编译器, java 是解释器, 因为java其实也是编译解释的混合型语言
回复

使用道具 举报

发表于 2003-10-30 05:52:37 | 显示全部楼层
[quote:5eb5eb4663="goldedge"]
因为我在做网页的时候用的到Jscript


这两个是不同的![/quote]

JavaScript是MS的东西,是因为Java is too hot, MS为了混个脸熟所以用这样的名字,实在很阴险
回复

使用道具 举报

发表于 2003-10-30 22:35:33 | 显示全部楼层
javascript才不是ms的东西呢
ms本来只有vbscript
后来看见javascript很受欢迎
自己觉得眼红了
就几乎是照搬照抄弄了一个jscript
结果把javascript的名声毁了
回复

使用道具 举报

发表于 2003-10-31 13:36:13 | 显示全部楼层
赶紧记着 ,以后不乱讲话了:P
回复

使用道具 举报

发表于 2003-11-4 22:06:25 | 显示全部楼层
不知道goldedge兄在Linux下编java的中文显示问题怎么解决
我不知道其他什么好的方法,所以只好用
setFont(new Font("SimSun",0,12));//前提是已经装了SimSun字体,呵呵
来正常显示中文
回复

使用道具 举报

发表于 2003-11-4 22:07:45 | 显示全部楼层
[quote:4c1fe38c7b="babyfrog"]javascript才不是ms的东西呢
ms本来只有vbscript
后来看见javascript很受欢迎
自己觉得眼红了
就几乎是照搬照抄弄了一个jscript
结果把javascript的名声毁了[/quote]
java和javascript可都是好东西啊,呵呵
javascript是网景公司的产品,就是netscape那个公司吧。java是sun公司的。
javascript出来比java早多了,两个东西完全不相干,呵呵
不过VB和VBScript的关系就大了
回复

使用道具 举报

 楼主| 发表于 2003-11-10 15:21:04 | 显示全部楼层
Java随笔(续)
首先还和上次的开头一样,我还是应该向大家表示一下歉意,从上一篇文章到现在屈指算来已经要一个月了,希望大家不要认为我是一个没有责任感的人,我其实十分关注我们的变成小组,几乎每天都要到这个版块来看看,不过斑竹现在搞的我是实在插不上嘴,虽然我可以自学一些所涉及的知识,不过我现在毕竟已经大四,又要按时上课,又要为自己的毕业设计做准备(我希望以一个优秀的毕业设计结束我的大学生涯),当然还要找工作,还有就是总是在梦里梦到自己未来女朋友的样子,而现实却……..所以总是担心自己会抑郁而死,总而言之,大家只要相信我不是一个没有责任心的人就可以了,呵呵。
其实还有一个困惑我的问题,就是我不知道大家希望我写什么,当然以我现在的水平,大家的要求我应该也不太可能满足(废话),所以还是按照一些传统书上的顺序,希望大家体谅。
一想起下面要说的很是心烦,不过要想能自由的支配一门语言,扎实的基础是必须的,所以…….耐下心来,一个字:学。在这里我有一个个人的看法,就是这部分虽然很容易懂,学起来应该很快,但是应该说忘记它的速度也不慢,所以就要求我们要有愚公移山的精神,这样才能打下牢固的基础,好了,废话不多说,下面开始正题:
声明:以下内容是引用Thinking in java 的内容(本人贵在自知之明,因为有些东西我没有资格讲,就是讲也远远不及Bruce Eckel)

引用开始:
“就象任何有感知的生物一样,程序必须能操纵自己的世界,在执行过程中作出判断与选择。”

在Java里,我们利用运算符操纵对象和数据,并用执行控制语句作出选择。Java是建立在C++基础上的,所以对C和C++程序员来说,对Java这方面的大多数语句和运算符都应是非常熟悉的。当然,Java也进行了自己的一些改进与简化工作。

使用Java运算符
运算符以一个或多个自变量为基础,可生成一个新值。自变量采用与原始方法调用不同的一种形式,但效果是相同的。根据以前写程序的经验,运算符的常规概念应该不难理解。
加号(+)、减号和负号(-)、乘号(*)、除号(/)以及等号(=)的用法与其他所有编程语言都是类似的。
所有运算符都能根据自己的运算对象生成一个值。除此以外,一个运算符可改变运算对象的值,这叫做 “副作用”(Side Effect)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的值亦可由没有副作用的运算符生成。
几乎所有运算符都只能操作“主类型”(Primitives)。唯一的例外是“=”、“==”和“!=”,它们能操作所有对象(也是对象易令人混淆的一个地方)。除此以外,String类支持“+”和“+=”。

优先级
运算符的优先级决定了存在多个运算符时一个表达式各部分的计算顺序。Java对计算顺序作出了特别的规定。其中,最简单的规则就是乘法和除法在加法和减法之前完成。程序员经常都会忘记其他优先级规则,所以应该用括号明确规定计算顺序。例如:
A = X + Y - 2/2 + Z;
为上述表达式加上括号后,就有了一个不同的含义。
A = X + (Y - 2)/(2 + Z);

赋值
赋值是用等号运算符(=)进行的。它的意思是“取得右边的值,把它复制到左边”。右边的值可以是任何常数、变量或者表达式,只要能产生一个值就行。但左边的值必须是一个明确的、已命名的变量。也就是说,它必须有一个物理性的空间来保存右边的值。举个例子来说,可将一个常数赋给一个变量(A=4;),但不可将任何东西赋给一个常数(比如不能4=A)。
对主数据类型的赋值是非常直接的。由于主类型容纳了实际的值,而且并非指向一个对象的句柄,所以在为其赋值的时候,可将来自一个地方的内容复制到另一个地方。例如,假设为主类型使用“A=B”,那么B处的内容就复制到A。若接着又修改了A,那么B根本不会受这种修改的影响。作为一名程序员,这应成为自己的常识。
但在为对象“赋值”的时候,情况却发生了变化。对一个对象进行操作时,我们真正操作的是它的句柄。所以倘若“从一个对象到另一个对象”赋值,实际就是将句柄从一个地方复制到另一个地方。这意味着假若为对象使用“C=D”,那么C和D最终都会指向最初只有D才指向的那个对象。下面这个例子将向大家阐示这一点。

为运行程序,必须保证在classpath里包含了我们安装本书源码文件的根目录(那个目录里包含了c02,c03c,c04等等子目录)。
对于Java后续的版本(1.1.4和更高版本),如果您的main()用package语句封装到一个文件里,那么必须在程序名前面指定完整的包裹名称,否则不能运行程序。在这种情况下,命令行是:
java c03.Assignment
运行位于一个“包裹”里的程序时,随时都要注意这方面的问题。
下面是例子:[code:1]
//: Assignment.java
// Assignment with objects is a bit tricky
package c03;

class Number {
  int i;
}

public class Assignment {
  public static void main(String[] args) {
    Number n1 = new Number();
    Number n2 = new Number();
    n1.i = 9;
    n2.i = 47;
    System.out.println("1: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
    n1 = n2;
    System.out.println("2: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
    n1.i = 27;
    System.out.println("3: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
  }
} ///:~
[/code:1]
Number类非常简单,它的两个实例(n1和n2)是在main()里创建的。每个Number中的i值都赋予了一个不同的值。随后,将n2赋给n1,而且n1发生改变。在许多程序设计语言中,我们都希望n1和n2任何时候都相互独立。但由于我们已赋予了一个句柄,所以下面才是真实的输出:[code:1]
1: n1.i: 9, n2.i: 47
2: n1.i: 47, n2.i: 47
3: n1.i: 27, n2.i: 27
[/code:1]看来改变n1的同时也改变了n2!这是由于无论n1还是n2都包含了相同的句柄,它指向相同的对象(最初的句柄位于n1内部,指向容纳了值9的一个对象。在赋值过程中,那个句柄实际已经丢失;它的对象会由“垃圾收集器”自动清除)。
这种特殊的现象通常也叫作“别名”,是Java操作对象的一种基本方式。但假若不愿意在这种情况下出现别名,又该怎么操作呢?可放弃赋值,并写入下述代码:
n1.i = n2.i;
这样便可保留两个独立的对象,而不是将n1和n2绑定到相同的对象。但您很快就会意识到,这样做会使对象内部的字段处理发生混乱,并与标准的面向对象设计准则相悖。由于这并非一个简单的话题,所以留待第12章详细论述,那一章是专门讨论别名的。其时,大家也会注意到对象的赋值会产生一些令人震惊的效果。

1. 方法调用中的别名处理
将一个对象传递到方法内部时,也会产生别名现象。[code:1]
//: PassObject.java
// Passing objects to methods can be a bit tricky

class Letter {
  char c;
}

public class PassObject {
  static void f(Letter y) {
    y.c = 'z';
  }
  public static void main(String[] args) {
    Letter x = new Letter();
    x.c = 'a';
    System.out.println("1: x.c: " + x.c);
    f(x);
    System.out.println("2: x.c: " + x.c);
  }
} ///:~
[/code:1]
在许多程序设计语言中,f()方法表面上似乎要在方法的作用域内制作自己的自变量Letter y的一个副本。但同样地,实际传递的是一个句柄。所以下面这个程序行:
y.c = 'z';
实际改变的是f()之外的对象。输出结果如下:
1: x.c: a
2: x.c: z
别名和它的对策是非常复杂的一个问题。尽管必须等至第12章才可获得所有答案,但从现在开始就应加以重视,以便提早发现它的缺点。

算术运算符
Java的基本算术运算符与其他大多数程序设计语言是相同的。其中包括加号(+)、减号(-)、除号(/)、乘号(*)以及模数(%,从整数除法中获得余数)。整数除法会直接砍掉小数,而不是进位。
Java也用一种简写形式进行运算,并同时进行赋值操作。这是由等号前的一个运算符标记的,而且对于语言中的所有运算符都是固定的。例如,为了将4加到变量x,并将结果赋给x,可用:x+=4。
下面这个例子展示了算术运算符的各种用法:[code:1]
//: MathOps.java
// Demonstrates the mathematical operators
import java.util.*;

public class MathOps {
  // Create a shorthand to save typing:
  static void prt(String s) {
    System.out.println(s);
  }
  // shorthand to print a string and an int:
  static void pInt(String s, int i) {
    prt(s + " = " + i);
  }
  // shorthand to print a string and a float:
  static void pFlt(String s, float f) {
    prt(s + " = " + f);
  }
  public static void main(String[] args) {
    // Create a random number generator,
    // seeds with current time by default:
    Random rand = new Random();
    int i, j, k;
    // '%' limits maximum value to 99:
    j = rand.nextInt() % 100;
    k = rand.nextInt() % 100;
    pInt("j",j);  pInt("k",k);
    i = j + k; pInt("j + k", i);
    i = j - k; pInt("j - k", i);
    i = k / j; pInt("k / j", i);
    i = k * j; pInt("k * j", i);
    i = k % j; pInt("k % j", i);
    j %= k; pInt("j %= k", j);
    // Floating-point number tests:
    float u,v,w;  // applies to doubles, too
    v = rand.nextFloat();
    w = rand.nextFloat();
    pFlt("v", v); pFlt("w", w);
    u = v + w; pFlt("v + w", u);
    u = v - w; pFlt("v - w", u);
    u = v * w; pFlt("v * w", u);
    u = v / w; pFlt("v / w", u);
    // the following also works for
    // char, byte, short, int, long,
    // and double:
    u += v; pFlt("u += v", u);
    u -= v; pFlt("u -= v", u);
    u *= v; pFlt("u *= v", u);
    u /= v; pFlt("u /= v", u);
  }
} ///:~
[/code:1]
我们注意到的第一件事情就是用于打印(显示)的一些快捷方法:prt()方法打印一个String;pInt()先打印一个String,再打印一个int;而pFlt()先打印一个String,再打印一个float。当然,它们最终都要用System.out.println()结尾。
为生成数字,程序首先会创建一个Random(随机)对象。由于自变量是在创建过程中传递的,所以Java将当前时间作为一个“种子值”,由随机数生成器利用。通过Random对象,程序可生成许多不同类型的随机数字。做法很简单,只需调用不同的方法即可:nextInt(),nextLong(),nextFloat()或者nextDouble()。
若随同随机数生成器的结果使用,模数运算符(%)可将结果限制到运算对象减1的上限(本例是99)之下。

1. 一元加、减运算符
一元减号(-)和一元加号(+)与二元加号和减号都是相同的运算符。根据表达式的书写形式,编译器会自动判断使用哪一种。例如下述语句:
x = -a;
它的含义是显然的。编译器能正确识别下述语句:
x = a * -b;
但读者会被搞糊涂,所以最好更明确地写成:
x = a * (-b);
一元减号得到的运算对象的负值。一元加号的含义与一元减号相反,虽然它实际并不做任何事情。

自动递增和递减
和C类似,Java提供了丰富的快捷运算方式。这些快捷运算可使代码更清爽,更易录入,也更易读者辨读。
两种很不错的快捷运算方式是递增和递减运算符(常称作“自动递增”和“自动递减”运算符)。其中,递减运算符是“--”,意为“减少一个单位”;递增运算符是“++”,意为“增加一个单位”。举个例子来说,假设A是一个int(整数)值,则表达式++A就等价于(A = A + 1)。递增和递减运算符结果生成的是变量的值。
对每种类型的运算符,都有两个版本可供选用;通常将其称为“前缀版”和“后缀版”。“前递增”表示++运算符位于变量或表达式的前面;而“后递增”表示++运算符位于变量或表达式的后面。类似地,“前递减”意味着--运算符位于变量或表达式的前面;而“后递减”意味着--运算符位于变量或表达式的后面。对于前递增和前递减(如++A或--A),会先执行运算,再生成值。而对于后递增和后递减(如A++或A--),会先生成值,再执行运算。下面是一个例子:[code:1]
//: AutoInc.java
// Demonstrates the ++ and -- operators

public class AutoInc {
  public static void main(String[] args) {
    int i = 1;
    prt("i : " + i);
    prt("++i : " + ++i); // Pre-increment
    prt("i++ : " + i++); // Post-increment
    prt("i : " + i);
    prt("--i : " + --i); // Pre-decrement
    prt("i-- : " + i--); // Post-decrement
    prt("i : " + i);
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~
[/code:1]
该程序的输出如下:[code:1]
i : 1
++i : 2
i++ : 2
i : 3
--i : 2
i-- : 2
i : 1
[/code:1]
从中可以看到,对于前缀形式,我们在执行完运算后才得到值。但对于后缀形式,则是在运算执行之前就得到值。它们是唯一具有“副作用”的运算符(除那些涉及赋值的以外)。也就是说,它们会改变运算对象,而不仅仅是使用自己的值。
递增运算符正是对“C++”这个名字的一种解释,暗示着“超载C的一步”。在早期的一次Java演讲中,Bill Joy(始创人之一)声称“Java=C++--”(C加加减减),意味着Java已去除了C++一些没来由折磨人的地方,形成一种更精简的语言。正如大家会在这本书中学到的那样,Java的许多地方都得到了简化,所以Java的学习比C++更容易。

关系运算符
关系运算符生成的是一个“布尔”(Boolean)结果。它们评价的是运算对象值之间的关系。若关系是真实的,关系表达式会生成true(真);若关系不真实,则生成false(假)。关系运算符包括小于(<)、大于(>)、小于或等于(<=)、大于或等于(>=)、等于(==)以及不等于(!=)。等于和不等于适用于所有内建的数据类型,但其他比较不适用于boolean类型。

1. 检查对象是否相等
关系运算符==和!=也适用于所有对象,但它们的含义通常会使初涉Java领域的人找不到北。下面是一个例子:[code:1]
//: Equivalence.java

public class Equivalence {
  public static void main(String[] args) {
    Integer n1 = new Integer(47);
    Integer n2 = new Integer(47);
    System.out.println(n1 == n2);
    System.out.println(n1 != n2);
  }
} ///:~
[/code:1]
其中,表达式System.out.println(n1 == n2)可打印出内部的布尔比较结果。一般人都会认为输出结果肯定先是true,再是false,因为两个Integer对象都是相同的。但尽管对象的内容相同,句柄却是不同的,而==和!=比较的正好就是对象句柄。所以输出结果实际上先是false,再是true。这自然会使第一次接触的人感到惊奇。
若想对比两个对象的实际内容是否相同,又该如何操作呢?此时,必须使用所有对象都适用的特殊方法equals()。但这个方法不适用于“主类型”,那些类型直接使用==和!=即可。下面举例说明如何使用:[code:1]
//: EqualsMethod.java

public class EqualsMethod {
  public static void main(String[] args) {
    Integer n1 = new Integer(47);
    Integer n2 = new Integer(47);
    System.out.println(n1.equals(n2));
  }
} ///:~
[/code:1]
正如我们预计的那样,此时得到的结果是true。但事情并未到此结束!假设您创建了自己的类,就象下面这样:[code:1]
//: EqualsMethod2.java

class Value {
  int i;
}

public class EqualsMethod2 {
  public static void main(String[] args) {
    Value v1 = new Value();
    Value v2 = new Value();
    v1.i = v2.i = 100;
    System.out.println(v1.equals(v2));
  }
} ///:~
[/code:1]
此时的结果又变回了false!这是由于equals()的默认行为是比较句柄。所以除非在自己的新类中改变了equals(),否则不可能表现出我们希望的行为。不幸的是,要到第7章才会学习如何改变行为。但要注意equals()的这种行为方式同时或许能够避免一些“灾难”性的事件。
大多数Java类库都实现了equals(),所以它实际比较的是对象的内容,而非它们的句柄。

逻辑运算符
逻辑运算符AND(&&)、OR(||)以及NOT(!)能生成一个布尔值(true或false)——以自变量的逻辑关系为基础。下面这个例子向大家展示了如何使用关系和逻辑运算符。[code:1]
//: Bool.java
// Relational and logical operators
import java.util.*;

public class Bool {
  public static void main(String[] args) {
    Random rand = new Random();
    int i = rand.nextInt() % 100;
    int j = rand.nextInt() % 100;
    prt("i = " + i);
    prt("j = " + j);
    prt("i > j is " + (i > j));
    prt("i < j is " + (i < j));
    prt("i >= j is " + (i >= j));
    prt("i <= j is " + (i <= j));
    prt("i == j is " + (i == j));
    prt("i != j is " + (i != j));

    // Treating an int as a boolean is
    // not legal Java
//! prt("i && j is " + (i && j));
//! prt("i || j is " + (i || j));
//! prt("!i is " + !i);

    prt("(i < 10) && (j < 10) is "
       + ((i < 10) && (j < 10)) );
    prt("(i < 10) || (j < 10) is "
       + ((i < 10) || (j < 10)) );
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~
[/code:1]
只可将AND,OR或NOT应用于布尔值。与在C及C++中不同,不可将一个非布尔值当作布尔值在逻辑表达式中使用。若这样做,就会发现尝试失败,并用一个“//!”标出。然而,后续的表达式利用关系比较生成布尔值,然后对结果进行逻辑运算。
输出列表看起来象下面这个样子:[code:1]
i = 85
j = 4
i > j is true
i < j is false
i >= j is true
i <= j is false
i == j is false
i != j is true
(i < 10) && (j < 10) is false
(i < 10) || (j < 10) is true
[/code:1]
注意若在预计为String值的地方使用,布尔值会自动转换成适当的文本形式。
在上述程序中,可将对int的定义替换成除boolean以外的其他任何主数据类型。但要注意,对浮点数字的比较是非常严格的。即使一个数字仅在小数部分与另一个数字存在极微小的差异,仍然认为它们是“不相等”的。即使一个数字只比零大一点点(例如2不停地开平方根),它仍然属于“非零”值。

1. 短路
操作逻辑运算符时,我们会遇到一种名为“短路”的情况。这意味着只有明确得出整个表达式真或假的结论,才会对表达式进行逻辑求值。因此,一个逻辑表达式的所有部分都有可能不进行求值:[code:1]
//: ShortCircuit.java
// Demonstrates short-circuiting behavior
// with logical operators.

public class ShortCircuit {
  static boolean test1(int val) {
    System.out.println("test1(" + val + ")");
    System.out.println("result: " + (val < 1));
    return val < 1;
  }
  static boolean test2(int val) {
    System.out.println("test2(" + val + ")");
    System.out.println("result: " + (val < 2));
    return val < 2;
  }
  static boolean test3(int val) {
    System.out.println("test3(" + val + ")");
    System.out.println("result: " + (val < 3));
    return val < 3;
  }
  public static void main(String[] args) {
    if(test1(0) && test2(2) && test3(2))
      System.out.println("expression is true");
    else
      System.out.println("expression is false");
  }
} ///:~
[/code:1]
每次测试都会比较自变量,并返回真或假。它不会显示与准备调用什么有关的资料。测试在下面这个表达式中进行:
if(test1(0)) && test2(2) && test3(2))
很自然地,你也许认为所有这三个测试都会得以执行。但希望输出结果不至于使你大吃一惊:
if(test1(0) && test2(2) && test3(2))

第一个测试生成一个true结果,所以表达式求值会继续下去。然而,第二个测试产生了一个false结果。由于这意味着整个表达式肯定为false,所以为什么还要继续剩余的表达式呢?这样做只会徒劳无益。事实上,“短路”一词的由来正种因于此。如果一个逻辑表达式的所有部分都不必执行下去,那么潜在的性能提升将是相当可观的。

按位运算符
按位运算符允许我们操作一个整数主数据类型中的单个“比特”,即二进制位。按位运算符会对两个自变量中对应的位执行布尔代数,并最终生成一个结果。
按位运算来源于C语言的低级操作。我们经常都要直接操纵硬件,需要频繁设置硬件寄存器内的二进制位。Java的设计初衷是嵌入电视顶置盒内,所以这种低级操作仍被保留下来了。然而,由于操作系统的进步,现在也许不必过于频繁地进行按位运算。
若两个输入位都是1,则按位AND运算符(&)在输出位里生成一个1;否则生成0。若两个输入位里至少有一个是1,则按位OR运算符(|)在输出位里生成一个1;只有在两个输入位都是0的情况下,它才会生成一个0。若两个输入位的某一个是1,但不全都是1,那么按位XOR(^,异或)在输出位里生成一个1。按位NOT(~,也叫作“非”运算符)属于一元运算符;它只对一个自变量进行操作(其他所有运算符都是二元运算符)。按位NOT生成与输入位的相反的值——若输入0,则输出1;输入1,则输出0。
按位运算符和逻辑运算符都使用了同样的字符,只是数量不同。因此,我们能方便地记忆各自的含义:由于“位”是非常“小”的,所以按位运算符仅使用了一个字符。
按位运算符可与等号(=)联合使用,以便合并运算及赋值:&=,|=和^=都是合法的(由于~是一元运算符,所以不可与=联合使用)。
我们将boolean(布尔)类型当作一种“单位”或“单比特”值对待,所以它多少有些独特的地方。我们可执行按位AND,OR和XOR,但不能执行按位NOT(大概是为了避免与逻辑NOT混淆)。对于布尔值,按位运算符具有与逻辑运算符相同的效果,只是它们不会中途“短路”。此外,针对布尔值进行的按位运算为我们新增了一个XOR逻辑运算符,它并未包括在“逻辑”运算符的列表中。在移位表达式中,我们被禁止使用布尔运算,原因将在下面解释。

移位运算符
移位运算符面向的运算对象也是二进制的“位”。可单独用它们处理整数类型(主类型的一种)。左移位运算符(<<)能将运算符左边的运算对象向左移动运算符右侧指定的位数(在低位补0)。“有符号”右移位运算符(>>)则将运算符左边的运算对象向右移动运算符右侧指定的位数。“有符号”右移位运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1。Java也添加了一种“无符号”右移位运算符(>>>),它使用了“零扩展”:无论正负,都在高位插入0。这一运算符是C或C++没有的。
若对char,byte或者short进行移位处理,那么在移位进行之前,它们会自动转换成一个int。只有右侧的5个低位才会用到。这样可防止我们在一个int数里移动不切实际的位数。若对一个long值进行处理,最后得到的结果也是long。此时只会用到右侧的6个低位,防止移动超过long值里现成的位数。但在进行“无符号”右移位时,也可能遇到一个问题。若对byte或short值进行右移位运算,得到的可能不是正确的结果(Java 1.0和Java 1.1特别突出)。它们会自动转换成int类型,并进行右移位。但“零扩展”不会发生,所以在那些情况下会得到-1的结果。可用下面这个例子检测自己的实现方案:[code:1]
//: URShift.java
// Test of unsigned right shift

public class URShift {
  public static void main(String[] args) {
    int i = -1;
    i >>>= 10;
    System.out.println(i);
    long l = -1;
    l >>>= 10;
    System.out.println(l);
    short s = -1;
    s >>>= 10;
    System.out.println(s);
    byte b = -1;
    b >>>= 10;
    System.out.println(b);
  }
} ///:~
[/code:1]
移位可与等号(<<=或>>=或>>>=)组合使用。此时,运算符左边的值会移动由右边的值指定的位数,再将得到的结果赋回左边的值。
下面这个例子向大家阐示了如何应用涉及“按位”操作的所有运算符,以及它们的效果:[code:1]
//: BitManipulation.java
// Using the bitwise operators
import java.util.*;

public class BitManipulation {
  public static void main(String[] args) {
    Random rand = new Random();
    int i = rand.nextInt();
    int j = rand.nextInt();
    pBinInt("-1", -1);
    pBinInt("+1", +1);
    int maxpos = 2147483647;
    pBinInt("maxpos", maxpos);
    int maxneg = -2147483648;
    pBinInt("maxneg", maxneg);
    pBinInt("i", i);
    pBinInt("~i", ~i);
    pBinInt("-i", -i);
    pBinInt("j", j);
    pBinInt("i & j", i & j);
    pBinInt("i | j", i | j);
    pBinInt("i ^ j", i ^ j);
    pBinInt("i << 5", i << 5);
    pBinInt("i >> 5", i >> 5);
    pBinInt("(~i) >> 5", (~i) >> 5);
    pBinInt("i >>> 5", i >>> 5);
    pBinInt("(~i) >>> 5", (~i) >>> 5);

    long l = rand.nextLong();
    long m = rand.nextLong();
    pBinLong("-1L", -1L);
    pBinLong("+1L", +1L);
    long ll = 9223372036854775807L;
    pBinLong("maxpos", ll);
    long lln = -9223372036854775808L;
    pBinLong("maxneg", lln);
    pBinLong("l", l);
    pBinLong("~l", ~l);
    pBinLong("-l", -l);
    pBinLong("m", m);
    pBinLong("l & m", l & m);
    pBinLong("l | m", l | m);
    pBinLong("l ^ m", l ^ m);
    pBinLong("l << 5", l << 5);
    pBinLong("l >> 5", l >> 5);
    pBinLong("(~l) >> 5", (~l) >> 5);
    pBinLong("l >>> 5", l >>> 5);
    pBinLong("(~l) >>> 5", (~l) >>> 5);
  }
  static void pBinInt(String s, int i) {
    System.out.println(
      s + ", int: " + i + ", binary: ");
    System.out.print("   ");
    for(int j = 31; j >=0; j--)
      if(((1 << j) &  i) != 0)
        System.out.print("1");
      else
        System.out.print("0");
    System.out.println();
  }
  static void pBinLong(String s, long l) {
    System.out.println(
      s + ", long: " + l + ", binary: ");
    System.out.print("   ");
    for(int i = 63; i >=0; i--)
      if(((1L << i) & l) != 0)
        System.out.print("1");
      else
        System.out.print("0");
    System.out.println();
  }
} ///:~
[/code:1]
程序末尾调用了两个方法:pBinInt()和pBinLong()。它们分别操作一个int和long值,并用一种二进制格式输出,同时附有简要的说明文字。目前,可暂时忽略它们具体的实现方案。
大家要注意的是System.out.print()的使用,而不是System.out.println()。print()方法不会产生一个新行,以便在同一行里罗列多种信息。
除展示所有按位运算符针对int和long的效果之外,本例也展示了int和long的最小值、最大值、+1和-1值,使大家能体会它们的情况。注意高位代表正负号:0为正,1为负。下面列出int部分的输出:[code:1]
-1, int: -1, binary:
   11111111111111111111111111111111
+1, int: 1, binary:
   00000000000000000000000000000001
maxpos, int: 2147483647, binary:
   01111111111111111111111111111111
maxneg, int: -2147483648, binary:
   10000000000000000000000000000000
i, int: 59081716, binary:
   00000011100001011000001111110100
~i, int: -59081717, binary:
   11111100011110100111110000001011
-i, int: -59081716, binary:
   11111100011110100111110000001100
j, int: 198850956, binary:
   00001011110110100011100110001100
i & j, int: 58720644, binary:
   00000011100000000000000110000100
i | j, int: 199212028, binary:
   00001011110111111011101111111100
i ^ j, int: 140491384, binary:
   00001000010111111011101001111000
i << 5, int: 1890614912, binary:
   01110000101100000111111010000000
i >> 5, int: 1846303, binary:
   00000000000111000010110000011111
(~i) >> 5, int: -1846304, binary:
   11111111111000111101001111100000
i >>> 5, int: 1846303, binary:
   00000000000111000010110000011111
(~i) >>> 5, int: 132371424, binary:
   00000111111000111101001111100000
[/code:1]
数字的二进制形式表现为“有符号2的补值”。

好了,好了,我觉得引用应该结束了,不然大家要崩溃了,其实世界上所有的事情都是这样,开始的时候会费一些周折,不过既然已经走上了这条路,还是应该塌实的走好没一步,对不对???以上的这些代码我都运行过,全部没有问题,不要谢我,运行它们的同时我也复习了一遍(呵呵,其实也没必要谢我,应该谢谢Bruce Eckel)补充一点本人关于优先级问题的理解,其实我们真的是没有必要背这个东西,虽然很多书,还有老师把它编成了一些容易记住的口诀,但我认为假如你不确定的时候,加个括号就OK了。
这次就到这里吧,虽然以上的内容自己的东西不是很多,但是应该会对初学者有一些启发(也可能没有)但应该不会有相反的作用,谢谢

待续………下一章写什么?征求大家的意见,不过不一定能满足大家的要求(我很诚实,是不是???)



参考资料:Thinking in java
          21天学通Java2
          Java 2编程指南(SDK 1.4版)
回复

使用道具 举报

发表于 2003-11-10 22:55:14 | 显示全部楼层
呵呵,虽然引用了很多很多~~~
不过还是支持! 至少给初学者一些资料和经验~~

很抱歉,我想最近我的贴子越来越……呵呵,不过我也是希望能尽快完成游戏的设计工作~~以前曾经计划过国庆节期间完成所有设计的~~     

关于运算符的优先级,其实加上适当的括号是一种好习惯,因为如果一些运算符的优先级你自己搞不清楚,那么可以认为还有很多人也搞不清楚,那么即使你通过查书写出了正确的表达式,其他人看了也会难以理解,即使是你自己,过一段时间后再看也会难以理解,所以从维护的角度看,写出表意清晰的代码是很必要的,一些括号对于人来说是很重要的,不要吝惜你的括号,即使它们对于编译器来说可有可无~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2024-11-9 00:55 , Processed in 0.080167 second(s), 15 queries .

© 2021 Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表