探索 Java 中的 Date Calendar TimeZone 和Timestamp

探索 Java 中的 Date, Calendar, TimeZone 和Timestamp 对象

宋晟 (shengsong@hotmail.com)

2001 年 11 月

如何计时,自古以来就是一个有着各种答案的问题。中国人从习惯使用农历计时,到选择便于与世界沟通的公元计时。在生活中这两者又一起使用,这已经融入到文化中。在计算机世界里,计时也是一个丰富多采的话题。各个系统各有一套方法,例如 2000 年引起全世界关注的"千年虫",就是因为计时系统设计上的限制而产生的。那么 Java 作为一种通用编程语言和运行平台,它的计时系统是如何设计的,我们在应用程序的开发中又如何驾驭它呢?对象的状态总是要被储存起来的,那么我们又要注意哪些情况呢?

1 最常用的对象(概念最易被混逍的对象) Date
在 Java 中,如何获得系统当前的时间?恐怕一个合格的 Java 程序员可以列举出至少下面这种:
Date date = new Date();
// 打印出具体的年,月,日,小时,分钟,秒钟 以及时区
System.out.println(date);

看起来 Date 这个对象包含了相当丰富的时间信息。那么我们能不能用它来构件任何一个指定的时间呢?很多的应用程序是需要用户输入一个特定的时间。


// 我们能不能用下面的代码构件出 2001/8/8 8:8
    import java.io.*;
    import java.util.*;

    public class WhatIsDate
    {
        public static void main(String[] args) {
            Date date = new Date(2001, 8, 8, 8, 8, 8);
            System.out.println(date);
        }
    }

Java 的编译器竟然报如下信息 (Sun JDK1.3, Windows 2000 中文下)


注意:
WhatIsDate.java 使用或覆盖一个不鼓励使用的API。
注意:
使用-deprecation重新编译,以得到详细信息。!

我们使用 javac -deprecation WhatIsDate.java, 得到如下信息:


WhatIsDate.java:7: 警告:
在java.util.Date中的Date(int,int,int,int,int,int)已经不提倡使用
            Date date = new Date(2001, 8, 8, 8, 8, 8);
                        ^
1 warning

那么 Date 对象究竟是为了满足哪个需求呢?看来它不是用来实现基于年/月/日 小时:分钟 的时间表述。我们查看 Java 的文档,我们看到有 getTime() 方法,它返回的竟然是一个 long 值。
System.out.println(date.getTime());

文档进一步又告诉我们这个值代表了当前系统的时间离1970/1/1 0:0 的毫秒差,而且是在 GMT 时区下(也被称为 EPOC)。如果我们指定的时间是在此之前的,那它将返回一个负数值。

这个发现让我们对 Date 对象有了一个全新的认识-Date 存放的是与 EPOC 的偏差值。换而言之我们也可通过 long 类型来表示时间?对了,这个猜想是得到了 Java 的支持:


    // 第二种获得当前时间的方法
    long dateInMilliSeconds = System.currentTimeMillis();
    // 这时候打印出的只是一串数字而已
    System.out.println(dateInMilliSeconds);

对程序执行效率敏感的程序员可以发现这个方法只是生成一个 Java 的原始类型 (primitive type) long, 不需要实例化一个对象。因此如果我们对时间的处理只是在内部进行时,可以用 long 来代替 Date 对象。

最典型的应用就是在一段代码开始和结束时,分别获得系统当前的时间,然后计算出代码执行所需的时间(微秒级)。


    long start = System.currentTimeMillis();
    // 代码段
    System.out.println("需要 "+(System.currentTimeMillis()-start)+" 微秒");

那么当我们要把这个 long 值已更为友好的表现形式显示处理的时候,我们可以用它来构造 Date 对象:
Date date = new Date(dateInMilliSeconds);
System.out.println(date);

我们看到了在 Java 中对时间最为基本的表示,有通过对EPOC 的偏差值进行处理。Date 对象是对它的一个对象的封装。我们同时也看到了,在现时世界中我们对时间的描述通常是通过"某年某月某日某时某分"来定义的。Date 的显示(实际上是 toString() 方法)描述了这些信息,但 Java 并不建议我们用这种方式直接来构件 Date 对象。因此我们需要找出哪个对象可以实现这个需求。这就是我们下面就要讲述的 Calendar 对象的功能。

在我们进一步研究 Calendar 之前,请记住 Date 只是一个对 long 值(基于 GMT 时区)的对象封装。它所表现出来的年/月/日 小时:分钟 时区的时间表述,只是它的 toString() 方法所提供的。千万不要为这个假象所迷惑。

2 强大的 Calendar
首先请记住 Calendar 只是一个抽象类, 也就是说你无法直接获得它的一个实例,换而言之你可以提供一个自己开发的 Calendar 对象。

那究竟什么是一个 Calendar 呢?中文的翻译就是日历,那我们立刻可以想到我们生活中有阳(公)历、阴(农)历之分。它们的区别在哪呢?

比如有:
月份的定义 - 阳`(公)历 一年12 个月,每个月的天数各不同;阴(农)历,每个月固定28天
每周的第一天 - 阳(公)历 星期日是第一天;阴(农)历,星期一是第一天

实际上,在历史上有着许多种纪元的方法。它们的差异实在太大了,比如说一个人的生日是"八月八日" 那么一种可能是阳(公)历的八月八日,但也可以是阴(农)历的日期。所以为了计时的统一,必需指定一个日历的选择。那现在最为普及和通用的日历就是 "Gregorian Calendar"。也就是我们在讲述年份时常用 "公元几几年"。Calendar 抽象类定义了足够的方法,让我们能够表述日历的规则。Java 本身提供了对 "Gregorian Calendar" 规则的实现。我们从 Calendar.getInstance() 中所获得的实例就是一个 "GreogrianCalendar" 对象(与您通过 new GregorianCalendar() 获得的结果一致)。

下面的代码可以证明这一点:


    import java.io.*;
    import java.util.*;

    public class WhatIsCalendar
    {
        public static void main(String[] args) {
            Calendar calendar = Calendar.getInstance();
            if (calendar instanceof GregorianCalendar)
                System.out.println("It is an instance of GregorianCalendar");
        }
    }

Calendar 在 Java 中是一个抽象类(Abstract Class),GregorianCalendar 是它的一个具体实现。

我们也可以自己的 Calendar 实现类,然后将它作为 Calendar 对象返回(面向对象的特性)。在 IBM alphaWorks 上,IBM 的开发人员实现了多种日历(http://www.alphaworks.ibm.com/tech/calendars)。同样在 Internet 上,也有对中国农历的实现。本文对如何扩展 Calendar 不作讨论,大家可以通过察看上述 Calendar 的源码来学习。

Calendar 与 Date 的转换非常简单:


    Calendar calendar = Calendar.getInstance();
    // 从一个 Calendar 对象中获取 Date 对象
    Date date = calendar.getTime();
    // 将 Date 对象反应到一个 Calendar 对象中,
    // Calendar/GregorianCalendar 没有构造函数可以接受 Date 对象
    // 所以我们必需先获得一个实例,然后设置 Date 对象
    calendar.setTime(date);

Calendar 对象在使用时,有一些值得注意的事项:

1. Calendar 的 set() 方法

set(int field, int value) - 是用来设置"年/月/日/小时/分钟/秒/微秒"等值

field 的定义在 Calendar 中

set(int year, int month, int day, int hour, int minute, int second) 但没有

set(int year, int month, int day, int hour, int minute, int second, int millisecond) 前面 set(int,int,int,int,int,int) 方法不会自动将 MilliSecond 清为 0。

另外,月份的起始值为0而不是1,所以要设置八月时,我们用7而不是8。

calendar.set(Calendar.MONTH, 7);

我们通常需要在程序逻辑中将它清为 0,否则可能会出现下面的情况:


    import java.io.*;
    import java.util.*;

    public class WhatIsCalendarWrite
    {
        public static void main(String[] args) throws Exception{
            ObjectOutputStream out =
                new ObjectOutputStream(
                    new FileOutputStream("calendar.out"));
            Calendar cal1 = Calendar.getInstance();
            cal1.set(2000, 7, 1, 0, 0, 0);
            out.writeObject(cal1);
            Calendar cal2 = Calendar.getInstance();
            cal2.set(2000, 7, 1, 0, 0, 0);
            cal2.set(Calendar.MILLISECOND, 0);
            out.writeObject(cal2);
            out.close();
        }
    }

我们将 Calendar 保存到文件中


    import java.io.*;
    import java.util.*;

    public class WhatIsCalendarRead
    {
        public static void main(String[] args) throws Exception{
            ObjectInputStream in =
                new ObjectInputStream(
                    new FileInputStream("calendar.out"));
            Calendar cal2 = (Calendar)in.readObject();
            Calendar cal1 = Calendar.getInstance();
            cal1.set(2000, 7, 1, 0, 0, 0);
            if (cal1.equals(cal2))
                System.out.println("Equals");
            else
                System.out.println("NotEqual");
            System.out.println("Old calendar "+cal2.getTime().getTime());
            System.out.println("New calendar "+cal1.getTime().getTime());
            cal1.set(Calendar.MILLISECOND, 0);
            cal2 = (Calendar)in.readObject();
            if (cal1.equals(cal2))
                System.out.println("Equals");
            else
                System.out.println("NotEqual");
            System.out.println("Processed Old calendar "+cal2.getTime().getTime());
            System.out.println("Processed New calendar "+cal1.getTime().getTime());
        }
    }

然后再另外一个程序中取回来(模拟对数据库的存储),但是执行的结果是:


NotEqual
Old calendar 965113200422 <------------ 最后三位的MilliSecond与当前时间有关
New calendar 965113200059 <-----------/
Equals
Processed Old calendar 965113200000
Processed New calendar 965113200000

另外我们要注意的一点是,Calendar 为了性能原因对 set() 方法采取延缓计算的方法。在 JavaDoc 中有下面的例子来说明这个问题:


Calendar cal1 = Calendar.getInstance();
    cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31
    cal1.set(Calendar.MONTH, Calendar.SEPTEMBER); //应该是 2000-9-31,也就是 2000-10-1
    cal1.set(Calendar.DAY_OF_MONTH, 30); //如果 Calendar 转化到 2000-10-1,那么现在的结果就该是 2000-10-30
    System.out.println(cal1.getTime()); //输出的是2000-9-30,说明 Calendar 不是马上就刷新其内部的记录

在 Calendar 的方法中,get() 和 add() 会让 Calendar 立刻刷新。Set() 的这个特性会给我们的开发带来一些意想不到的结果。我们后面会看到这个问题。

2. Calendar 对象的容错性,Lenient 设置
我们知道特定的月份有不同的日期,当一个用户给出错误的日期时,Calendar 如何处理的呢?


    import java.io.*;
    import java.util.*;

    public class WhatIsCalendar
    {
        public static void main(String[] args) throws Exception{
            Calendar cal1 = Calendar.getInstance();
            cal1.set(2000, 1, 32, 0, 0, 0);
            System.out.println(cal1.getTime());
            cal1.setLenient(false);
            cal1.set(2000, 1, 32, 0, 0, 0);
            System.out.println(cal1.getTime());
        }
    }

它的执行结果是:


    Tue Feb 01 00:00:00 PST 2000
    Exception in thread "main" java.lang.IllegalArgumentException
        at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:1368)
        at java.util.Calendar.updateTime(Calendar.java:1508)
        at java.util.Calendar.getTimeInMillis(Calendar.java:890)
        at java.util.Calendar.getTime(Calendar.java:871)
        at WhatIsCalendar.main(WhatIsCalendar.java:12)

当我们设置该 Calendar 为 Lenient false 时,它会依据特定的月份检查出错误的赋值。

3. 不稳定的 Calendar

我们知道 Calendar 是可以被 serialize 的,但是我们要注意下面的问题


    import java.io.*;
    import java.util.*;

    public class UnstableCalendar implements Serializable
    {

        public static void main(String[] args) throws Exception{
            Calendar cal1 = Calendar.getInstance();
            cal1.set(2000, 7, 1, 0, 0 , 0);
            cal1.set(Calendar.MILLISECOND, 0);
            ObjectOutputStream out =
                new ObjectOutputStream(
                new FileOutputStream("newCalendar.out"));
            out.writeObject(cal1);
            out.close();
            ObjectInputStream in =
                new ObjectInputStream(
                new FileInputStream("newCalendar.out"));
            Calendar cal2 = (Calendar)in.readObject();
            cal2.set(Calendar.MILLISECOND, 0);
            System.out.println(cal2.getTime());
        }
    }

运行的结果竟然是: Thu Jan 01 00:00:00 PST 1970

它被复原到 EPOC 的起始点,我们称该 Calendar 是处于不稳定状态。这个问题的根本原因是 Java 在 serialize GregorianCalendar 时没有保存所有的信息,所以当它被恢复到内存中,又缺少足够的信息时,Calendar 会被恢复到 EPOCH 的起始值。Calendar 对象由两部分构成:字段和相对于 EPOC 的微秒时间差。字段信息是由微秒时间差计算出的,而 set() 方法不会强制 Calendar 重新计算字段。这样字段值就不对了。

下面的代码可以解决这个问题:


    import java.io.*;
    import java.util.*;

    public class StableCalendar implements Serializable
    {

        public static void main(String[] args) throws Exception{
            Calendar cal1 = Calendar.getInstance();
            cal1.set(2000, 7, 1, 0, 0 , 0);
            cal1.set(Calendar.MILLISECOND, 0);
            ObjectOutputStream out =
                new ObjectOutputStream(
                new FileOutputStream("newCalendar.out"));
            out.writeObject(cal1);
            out.close();
            ObjectInputStream in =
                new ObjectInputStream(
                new FileInputStream("newCalendar.out"));
            Calendar cal2 = (Calendar)in.readObject();
            cal2.get(Calendar.MILLISECOND); //先调用 get(),强制 Calendar 刷新
            cal2.set(Calendar.MILLISECOND, 0);  //再设值
            System.out.println(cal2.getTime());
        }
    }

运行的结果是: Tue Aug 01 00:00:00 PDT 2000

这个问题主要会影响到在 EJB 编程中,参数对象中包含 Calendar 时。经过 Serialize/Deserialize 后,直接操作 Calendar 会产生不稳定的情况。

4. add() 与 roll() 的区别

add() 的功能非常强大,add 可以对 Calendar 的字段进行计算。如果需要减去值,那么使用负数值就可以了,如 add(field, -value)。

add() 有两条规则:

当被修改的字段超出它可以的范围时,那么比它大的字段会自动修正。如:
Calendar cal1 = Calendar.getInstance();
cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31
cal1.add(Calendar.MONTH, 1); //2000-9-31 => 2000-10-1, 对吗?
System.out.println(cal1.getTime()); //结果是 2000-9-30

另一个规则是,如果比它小的字段是不可变的(由 Calendar 的实现类决定),那么该小字段会修正到变化最小的值。

以上面的例子,9-31 就会变成 9-30,因为变化最小。

Roll() 的规则只有一条:
当被修改的字段超出它可以的范围时,那么比它大的字段不会被修正。 如:


Calendar cal1 = Calendar.getInstance();
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日
cal1.roll(Calendar.WEEK_OF_MONTH, -1); //1999-6-1, 周二
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日
cal1.add(Calendar.WEEK_OF_MONTH, -1); //1999-5-30, 周日
WEEK_OF_MONTH 比 MONTH 字段小,所以 roll 不能修正 MONTH 字段。

由于 Calendar 对象可以将一个时间转化成我们所需要的不同字段信息,可以帮助我们的开发任务。

<

小小豆叮

Java:IT认证界的新贵

Java是目前全球最时髦的软件开发工具之一,Java的出现,给整个程序界带了巨大的冲击。在美国,越来越多的应用软件是基于Java开发的,而随着Java在企业应用中的日益普及,彻底打消了人们对Java无法适合大型企业级应用的顾虑。在国内,Java的应用也越来越普遍。据调查,在未来几年内,国内将会出现20万个Java程序员职位的需求。作为一名职业程序员,是否感受到Java的魅力? 对于国内绝大多数的程序员来说,自学可能是最好的方法。然而对于Java这么一个内涵丰富的系统来说,仅凭自学就想运用自如,有相当大的难度。从目前的情况看,参加Java职业培训是一种较为有效的方式。AUTC中国总部(www.autc.com.cn)在中国的30多个省109个城市设有数百家授权考试中心(AUTC),凭借其完善的考试系统设施和考场管理制度,能确保考生证书的公正性和权威性。AUTC中国总部还是SUN公司授权的JAVASL-110课程中国地区唯一的考试管理机构,为有志于从事IT行业的学员提供专业培训和权威认证。 对于想学习Java的非编程人员,如技术人员、Web开发人员、技术管理人员、系统管理员来说,Java的SL--110课程就是一个很好的起点。 <

小小豆叮

西门子让Java走进手机

我是干计算机编程的,Java是我的基本工具。去年听说西门子开始将Java应用于手机,莫名其妙地有点感动,竟有那么一种“他乡遇故知”的感觉。其实我所兴奋的不只是Java走进了手机,而是自己又多了一个用武之地。进入手机的Java被称为无线Java,专门用来为便携设备设计一些容量比较小的应用软件,不仅可以从计算机上下载,还可以与计算机通用。   至于在无线Java手机上都能做什么,那可就多了,互动游戏、屏幕保护、地图、股票 查询与到价报价、专为自己打造的个人日历、手机绘图、图片编辑......不怕做不到,就怕想不到。我早就盯上了西门子的一部具有MP3和可换多媒体卡的无线Java手机6688i。将无线Java与32兆可换多媒体卡结合起来,就好比一个人不仅爱吃、会吃,还有个好胃口,想为手机下载什么应用软件一定可以得心应手。到那时,我想给自己的手机添加什么功能,就去编一个什么程序,然后再下载到手机上,保证我的手机和谁的都不一样,别人的手机酷,可能酷在外表上,我的手机酷,可是实实在在地酷在了心里头。当然,西门子6688i的“移动组合音响”,也是我一直梦寐以求的! <

小小豆叮

品一杯美味的咖啡:Sun公司认证

1.Sun公司推出的全球专业技术认证包括下列三种: ● Java认证考试 Java方面,Sun推出四项认证:Java程序员认证(Sun Certified Programmer for Java2 Platform,SCP)、Java开发认证(Sun Certified Developer for Java2 Platform,SCD)、网站开发师(Sun Certified Web Component Developer for Java 2 Platform Enterprise Edition,SCWCD)和企业建筑师(Sun Certified Enterprise Architect for J2EE Technology,SCEA)认证。 SCP测验编程人员在Java 2平台上运用Java语言标准版本技术基本原理的熟练程度。内容偏重于Java的语法及JDK的内容; SCD则进一步测试开发人员使用Java开发应用程序的能力,参加者必须首先完成一个程序设计方案,再回答与此方案相关的一些问题。SCWCD测验编程人员运用Java 2平台标准版本技术进行网络服务和动态网络内容设计的能力。SCEA测验企业建筑师建设Java 2平台与标准版本技术相应的应用能力。 ● Solaris系统管理认证考试 对Solaris/SunOS系统管理员, Sun推出系统管理认证(Certified Solaris Administrator,CSA)。CSA分为两个等级:Part I和Part II,分别测试使用者对Solaris系统管理的掌握程度。 ● Solaris网络管理认证考试 为了测试使用者对于Solaris网络管理的能力,Sun推出了网络管理认证(Certified Network Administrator,CNA)。内容包括基本网络概念,Routing and Subnet, Security, Performance Tuning , DNS,DHCP等。 2.通过考试后我将得到什么? 通过SUN任何一门专业认证考试后,您都将收到Sun Microsystems 总公司寄发的资格证书及徽章,并有权将通过Sun认证的标记印在个人名片上,作为对个人技术能力的肯定。 3.我怎样才能得到SUN公司的认证证书? ● 向Sun培训部购买准考证(Certification Voucher) ● Sun 培训部收到您的汇款后,将向您提供准考证和相关考试中心联络信息。 ● 请直接与考试中心联络,考试中心将给您一份考试确认书。 ● 于约定时间至考试中心考试,请携带考试确认书,准考证原件及其他要求证件。 ● 题目均为英文,并以电脑作答。 4.获得各项证书须通过哪些考试? <

小小豆叮

调试器--Jdb

Java调度器为Java程序提供了一个命令行调试环境。它既可在本地,也可在与远程的解释器的一次对话中执行。   jdb于本地机器中可用如下的命令启动:   C:\>jdb classname   当你使用-debug选项开始一个Java例程时, 必须提供给Jdb 一个密码, 这样 Jdb才能开始运转起来。下表包含了所有jdb命令。 catch calssID 为特定异常出口而中断 classes 列出当前已知的类 clear classID:line 清除一个断点 cont 从断点处继续执行 down[n frames] 下移一个线程的堆栈 dump ID[ID...] 显示所有对象信息 exit(或quit) 退出调试器 help(或?)  列出所有命令 ignore classID 忽略特定的异常出口 list[line number] 显示源代码 load classbame 载入要调试的Java类 locals 在当前堆栈帧中显示所有局部变量 memory 报告内存使用情况 methods classID 列出一个类的成员函数集 print ID[ID...] 列出对象或域 resume [threadID...] 恢复线程(默认情况恢复所有线程) run class [args] 开始执行已下载的Java类 step 执行当前行 stop in classID:method 在一成员函数中设一断点 stop at classID:line 在一行设一断点 suspend[threadID...] 停止一个线程(默认情况停止所有线程) hreads threadgroup 列出线程 thread threadID 设置当前线程 threadgroups 列出线程组 threadgroup name 设置当前线程组 up [n frames] 上移一个线程堆栈 use [path] 显示或改变源程序路径 where [threadID] or all 使一线程的堆线置空 !! 重复上一次命令 -host hostname 该命令告诉Jdb到哪里去建立远程运行的Java解释器对话过程 -password password 本选项告诉Jdb 用哪个密码去与远程运行的Java 对话进程相连接。 密码 password是由运行带有-debug选项的Java解释器所提供的。 <

小小豆叮

解释器-Java

  Java解释器可用来直接解释执行Java字节代码,具体命令行格式如下:   C:\>java options className arguments   className必须包括所有软件包信息。不仅有类名本身,还有Java 解释器所期望的类名(不是Java字节代码的文件名),所有在解释器环境下运行的类都必须包括解释器第一次调用时所需的main成员函数,用以传递命令所带的变量。 public static void main(string args[])   {    ......   }   下面Java解释器的所有选项。 -cs -checksource 此选项让解释器重编译Java源文件已更新的类--重编译已改变过了的类。 -classpath path 此选项重写CLASSPATH环境变量,告诉Java在哪里能找到类库。如果其中用冒号分开,则可能包含多个目录。 -mx x 此选项设置内存分配池的最大值。所指定的池必须大于1,000字节。另外“K”,“M”可附加在数字上指定是千字节还是兆字节。缺省值是16MB。 -ms x 此选项设置内存分配池的最小值。所指寂的池必须大于1,000字节。另外,“K”,“M”可加在数字上指定的是千字节还是兆字节。缺省值是1MB。 -noasyncgc 此选项关闭异步无用单元收集功能,只有在程序中调用它或内存溢出的时候,无用单元收集才会被激活。 -ss x 此选项将C线程栈的最大值设置为x , x 必须大于1KB,其设定方式同 -ms。 -oss x 此选项设定Java堆栈最大值为x。 -v,-verbose 此选项告知Java每当类被调用之时, 向标准输出设备输出信息。 -verify 此选项告知Java在所有代码上使用校验。 -verifyremote 此选项告知Java 仅仅对类载入器所载入的类进行校验。 -noverify 此选项告知Java不进行校验。 -verbosegc 此选项告知Java让无用单元收集器在它释放内存时显示一条信息。 -t 此选项在Java-g解释器中是可用的,并把执行的情况逐条打印出来。 -debug 此选项允许Java调试器与本次Java 解释器会话相联接。汉它运行时,Java会显示一个密码,用于启动这次调试会话。 -D propName=newVal 此选项允许用户在运行时改变属性值。 <

小小豆叮

java中的变量

为了在Java中存储一个数据,必须将它容纳在一个变量之中。而数据类型决定了一个变量可以赋给什么值以及对变量进行什么样的操作。定义一个变量的两个基本要素是:类型和标识符,通常你可以用如下语法去说明变量:

  type identifer[,identifer];   该语句告诉编译器用“type”的类型和以“identifer”为名字建立一个变量,这里的分号将告诉编译器这是一个说明语句的结束;方格中的逗号和标识符表示你可以把几个类型相同的变量放在同一语句进行说明,变量名中间用逗号分隔。

  在你创建了一个变量以后,你可以给它赋值,或者用运算符对它进行一些运算。类型将决定变量所代表的不同种类的数据,在Java语言中有两种变量。最基本的是简单类型变量,他们不建立在任何其他类型上,整数、浮点、布尔和字符类型都是这类型(注意和其他编程语言不太一样一点是,字符串在这里是作为一个类的实例出现);另外Java可以定义构造另一种变量类型:类,这些类型建立在简单类型之上,它包括数值、变量和方法,是一种数据与代码相结合的复合结构。

1: 整型变量的说明 

  整型变量按所占内存大小的不同可分为四种不同的类型,最短的整型是byte,它只有八位长,然后是短整型short,它有16位,int类型有32位,长整型long是64位,下面是这些整型变量的说明示例。

    byte bCount;    (内存中占用: 8 Bits)
    short sCount;   (内存中占用:16 Bits)
    int nCount;     (内存中占用:32 Bits)
    long LCount;    (内存中占用:64 Bits)
    int nx,ny,nz;      (内存中占用:32 Bits)


2:浮点变量的说明 

  浮点类型可用关键字float或double来说明,float型的浮点变量用来表示一个32位的单精度浮点数,而double型的浮点变量用来表示一个64位的双精度浮点数。double型所表示的浮点数比float型更精确

     float areas;
    double weihgt;

3:字符变量说明 

  Java使用16位的Unicode字符集。因此Java字符是一个16位的无符号整数,字符变量用来存放单个字符。例如:

    char a;
    a='c'; 

4:布尔变量说明 

  布尔型有真和假两个逻辑值,另外,逻辑运算符也将返回布尔类型的值,例如:

  boolean onClick;
  mouseOn=true;

  布尔型是一个独立的类型,Java中的布尔类型不代表0和1两个整数,不能转换成数字。

5:变量的使用范围 

  当你说明了一个变量后,它将被引入到一个范围当中,也就是说,该名字只能在程序的特定范围内使用。变量的使用范围是从它被说明的地方到它所在那个块的结束处,块是由两个大括号所定义的,例如:

class Example
public static void main(String args[])

int i;
......

public void function()
char c;
......

  整型变量i在方法main中说明,因为main的块不包括function块,所以任何在function块中对i的引用都是错误的。对字符型变量c也同样如此。

  在某一个特定情形中,变量能被别的变量所隐藏,如:在一个块中说明一个变量,而在这个块中建立一个新块并且在其中定义相同名字的变量,这样在第二个块中,程序对该变量的使用均是指第二次定义的那个变量。这样我们说,第一个变量被隐藏了,作者并不建议采用这种定义变量的方法。变量隐藏的示例如下:

class Example

public static void main(String args[])

int i; // * * *
boolean try=true;
while(try)

int i; //以下对变量i的引用均指这里定义的i
......
//以下对变量i的引用均指* * *处定义的i
......

  当你定义一个变量时,首先必须明确它的活动范围,并根据它的实际功能来命名,此外还应尽量使用详细的注释,这些办法可以使你能够清晰地区分变量,变量被隐藏的问题也会大大减少。 

6:类型转换 

  系统方法System.in.read返回一个整型数值,但你却常常想要把它当作一个字符来使用。现在的问题是,当有一个整数而你需要把变成一个字符时应当去做些什么呢?你需要去做一个类型转换为一个字符。从一种类型转换到另一种类型可以使用下面的语句:

    int a;
    char b;
    a=(int)b;

  加括号的int告诉编译器你想把字符变成整型并把它放在a里,另一方面,如果你想做相反的转换,你可以使用:

    b=(char)a;

  记住整型和字符型变量位长不同是非常重要的,整型是32位长,字符型是16长,所以当你从整型转换到字符型可能会丢失信息。同样,当你把64位的长整型数转换为整型时,由于长整型可能有比32位更多的信息,你也很可能会丢失信息。即使两个量具有相同的位数,比如整和浮点型(都是32位),你在转换小数时也会丢失信息,Java不允许自动类型转换,当你进行类型转换要注意使目标类型能够容纳原类型的所有信息,不会丢失信息的类型转换有:

原始类型     目标类型

byte  -〉short -〉char  -〉int   -〉long -〉float -〉double
short -〉int   -〉long  -〉float -〉double
char  -〉int   -〉long  -〉float -〉double
int   -〉long  -〉float -〉double
long  -〉float -〉double
float -〉double

  需要说明的是,当你执行一个这里并未列出的类型转换时可能并不总会丢失信息,不过进行这样一个理论上并不安全的转换是很危险的。

 

<

小小豆叮

Applet事件响应

下载源码:carton.zip 产看结果

 Java的AWT库允许你把用户界面建立在Java applet中。AWT库包含有所有的用于建立简单界面所需要的控制:按钮、编辑框、检查框等等。

import java.awt.*;
import java.applet.*;

public class AppletEvent extends Applet
{
  int x, y ;
  Button b ;
  Color clr ;

  在该applet构造函数中,代码初始化了变量x,y,clr,建立了一个新的显示“你就按着玩儿吧!”按钮控制,然后把按钮添加到窗体中。

public AppletEvent()
{
  y = 40 ;
  x = 100 ;
  clr = Color.red ;

  b = new Button("你就按着玩儿吧!");
  add("Center", b);
}

窗口还包含有用paint方法绘制的字符。

  public void paint(Graphics g)
  {
    g.setColor(Color.red);
    g.setFont(new Font("Helvetica", Font.PLAIN, 24));
    g.drawString("InofCD欢迎您!", x, y);
  }

    在applet类中添加事件处理函数。也可以从按钮的基类继承一新的按钮类,然后在那里处理事件。在该applet中的“action”方法选择applet的事件流。当每个事件流到达时,它检验其是否来自Button对象。如果是,它会增加y和减少x并使该applet重绘自己。ev.arg属性传递了来自被单击按钮的标签,并把它与所按的按钮的标签进行比较。

    public boolean action(Event ev, Object arg)
    {
        if (ev.target instanceof Button)
        {
            y+= 10 ;
            x = x- 10 ;

            if (y>=250) y= 10 ;
            if (x<=0) x= 100 ;

            repaint();
            return true;
        }
        return false;
        }
    }

<

小小豆叮

编译器-Javac

Javac编译器   Javac编译器读取Java源代码,并将其编译成字节代码,调用Javac的命令行如下:   C:\>javac options filename.java   值得注意的是,和Java解释器不同,Javac 编译器期望它正在编译的文件具有扩展名.Java。其命令行如下表 -classpath path 此选项用于设定路径,在该路径上Javac寻找需被调用的类。该路径是一个用分号分开的目录列表。 -d directory 此选项指定一个根目录。该目录用来创建反映软件包继承关系的目录数。 -g 此选项在代码产生器中打开调试表,以后可凭此调试产生字节代码。 -nowarn 此选项禁止编译器产生警告。 -o 此选项告诉javac优化由内联的static、final以及privite成员函数所产生的码。 -verbose 此选项告知Java显示出有关被编译的源文件和任何被调用类库的信息。 <

小小豆叮

定义类的结构

:类的基本概念   Java程序的基本单位是类,类是对象的实例,或者说对象是类定义的的数据类型的变量。你建立类之后,就可用它来建立许多你需要的对象。Java把每一个可执行的成分都变成类。   类的定义形式如下:   class classname extends superclassname   {     .....   }   这 里,classname和superclassname是合法的标识符。关键词extends用来表明classname是superclassname派生的子类。有一个类叫做Object,它是所有Java类的根。如果你想定义Object的直接子类,你可以省略extends子句,编译器会自动包含它。下面是一个简单的类的定义。   在类定义的开始与结束处必须使用花括号。你也许想建立一个矩形类,那么可以用如下代码:   public class Rectangle   {     ......   } 2:类的基本组成   一个类中通常都包含数据与函数两种类型的元素,我们一般把它叫作属性和成员函数,在很多时候我们也把成员函数称为方法(method)。将数据与代码通过类紧密结合在一起,就形成了现在非常流行的封装的概念。自然,类的定义也要包括以上两个部分。 class 3:类的实例创建   矩形类Rectangle中,也许你想把矩形的相关信息写入类,如:width,height,当然你还可以写入其它信息,但或许长和宽对简单的矩形来说已足够了。现在,类的定义如下所示:   public class Retangle   {   int width,height;   }   当你创建了自己的类之后,通常需要使用它来完成某种工作。你可以通过定义类的实例--对象来实现这种需求。   对象是通过new来创建,实现成员函数如下:Rectangle myrect=new Rectangle,当然,此时对象myrect并没有做任何什么事;它只保存了矩形的长和宽的信息。有了对象以后,我们怎样使用对象内部的数据呢?下面是几个例子: myrect.width=10; myrect.height=20;   类的成员函数也是用“.”运算符来被引用的。 <

小小豆叮

类的深入研究

1:在Java中使用继承   面向对象的程序设计中最为强大的功能是类的继承,类的继承允许你在一个已经存在的类之上编写新的程序,例如,你想建立一个可在屏幕上显示并能填充它的矩形类,你可以从头开始或者利用旧的矩形类,下面的部分将向你介绍如何继承已存在的Rectangle类,而不需重写其中的代码。   比如建立一个fillRect类,该类可以使用Rectangle类中所有已定义的数据和成员函数,如:width、height等数据和getArea等成员函数,就可是使用继承的方法来实现。使用extands关键字让Java程序员能够继承已有类的成员函数,为了继承Rectangle类,你必须引用旧的Rectangle类,你必须引用旧的Rectangle类,并且在新类的说明中引用它,比如: import Shapes.Rectangle; class fillRect extands Rectangle { ..... } 2:成员函数的重载   继承之后,如何使fillRect类比Rectangle类有所提高呢?我们可以用如下代码来实现一个新的drawRect成员函数,它将大大缩短代码,并能填充矩形,而不是仅仅画出矩形的轮廓: private String makeString(chr ch,int num) { StringBuffer str=new StringBuffer(); for(int i=num;i>0;i--) str.append(ch); return str.toString(); } public void drawRect() { for(int i=height;i>0;i--) System.out.println(makeString("#",width)); }   注意这里我们使用了StringBuffer类。之所以使用StringBuffer是因为String只能产生一个静态类型--它的大小是不能改变的,而StringBuffer能够产生一个可变长度的字符串类型。   在这里,drawRect成员函数被重载了,通过使用相同的成员函数名字,你可以用新的成员函数来代替旧的成员函数。不过,那些被说明为final的成员函数是不能被重载的。   注意,你不必在新的类中包含那些与被继承类相同的代码,而只需要加入你想要的东西,但你必须建立一个新的构造成员函数,以区分这两个不同的类。   新类的全貌如下所示,你可以发现通过继承Rectangle类,代码变得非常简单明了。 class fillRect extands Rectangle { public fillRect(int w,int h) { supper(w,h); private String makeString(char ch,int num) { StringBuffer str=new StringBuffer(); for(int i=num;i>0;i--) str.append(ch); return str.toString(); } public void drawRect() { for(int i=height;i>0;i--) System.out.printlm(makeString("#",width)); } } } 3:使用接口   Java可以创建一种称作接口(interface)的类,在这个类中,所有的成员函数都是抽象的,也就是说它们都只有说明没有定义,你可以如下所示来说明一个接口。 public interface interfaceName //成员函数说明   接口中成员函数的缺省引用类型是private,接口(interface)的内部变量是不可更改的,并且总是static和final。   通过使用关键字implement,你可以在定义类时继承一个接口。不过与extends不同的是,一个类可以同时继承多个接口。   使用接口的优点在何处呢?通过创建一个接口,你可以说明一整套抽象的成员函数而无须去具体实现它,所有继承了这个接口的类,都将有着具有相同原形的成员函数。例如,你想所有的shapes都有一个draw()成员函数,你可以创建一个接口并且用Shape命名: public interface Shape void draw();   现在,无论你何时创建一个继承自Shape的类,都将拥有一个成员函数draw()。 4:类的转换   类的转换,与不同类型的变量之间的转换有相似之处,但并不一样。   我们可以把一个父类的对象转换成一个子类对象,下面的代码说明一个类转换的例子,其中Y类是从X类继承而来的: Y y=new Y(); X x; x=y;   需要注意的是两个子类之间的转换是不可以的。 5:null,this和supper变量   所有的类都有三种变量:null、this和supper。   null变量不指向任何实际对象,而是指向一个空对象,如下例所示: Rectangle rect=null;   该例产生一个矩形类的变量,但不创建一个实际的对象。另外,假如一个成员函数需要一个对象作为参数时,你同样可以用null代替。   this变量指向对象本身,一个类可以通过this变量来获得一个代表它自身的对象变量。   supper变量是一个指向类父类构造成员函数的变量,你可以通过调用它,来迅速完成对子类的构造成员函数的设计。 责任编辑:小李(lisz@staff.ccidnet.com) <

小小豆叮

JAVA技术培训详细介绍

技术培训服务性的网络依赖于中性平台技术。 服务性网络提供各种服务,包括从端口到后端数据中心再到无中文的设备等。 JAVA平台的原动力和灵活性保证企业不断创造各种应用程序,这些程序也许又将开辟运算世界的新纪元。 我们所教授的技术并非从他人所学,而是我们的创造发明。 作为JAVA技术的创造者,Sun提供端到端服务--系统、软件及培训,引领您从评估到领先程序再到神奇的计算方案的全球化实施。 学习了Sun的JAVA 技术课程,您将能: 发挥Sun在dot.com平台上、网络上、互联网上及电子商务技术上的专业优势,从而您能够提供升级、性能可靠且安全的应用程序。 迅速开发新的解决方案,以便更快地占有市场。 在同一平台上的各种应用开发及运用这些原始点相同的应用程序运用到不同类型的平台,可以大大降低成本。 JAVA平台认证应试者能够获得以下三种水平的认证: Sun认证的JAVA程序员 Sun认证的JAVA开发员 Sun认证JAVA2企业级设计师--企业级(J2EETM)技术。 核心课程提供给程序员及非程序员基本的培训,以帮助他们通过使用Java 2平台,标准版,精通Java技术编程的基本纲要。Java程序员认证资格考试,该课程将提供面向对象的理论,Java程序语言的语法,标准API方法的延展应用及Java开发工具包,此套课程提供参加Sun Java程序员和Sun Java开发员认证考试的基础培训。 课程清单:

CORE LEARNING SUITE

SL-110初级Java语言编程
Java Programming Language for Non-Programmers

SL-210向Java面向对象编程技术的转换
Migrating to OO Programming with Java Technology

SL-265结构化程序员的Java技术
Java Technology for Structured Programmers

SL-275Java语言编程
Java Programming Language

ENTERPRISE LEARNING SUITE

SEM-SL-345Java 2平台企业版研讨班
Java 2 Platform, Enterprise Edition:Technology Overview Seminar

SL-425体系结构及设计J2EE应用程序
Architecting and Designing J2EE Applications

SL-310Beyond CGI: Developing Java Servlets
超越CGI:开发Java Servlets

SL-315Java数据库应用程序编程
Enterprise JavaBeans Programming

SL-351Java数据库应用程序编程
Enterprise JavaBeans Programming

SL-330用Java技术开发数据库应用程序
Database Application Programming with Java Technology

USER INTERFACE LEARNING SUITE

SL-291JavaBeans 组件的开发
JavaBeans Component Development

DISTRIBUTED TECHNOLOGIES LEARNING SUITE

SL-301Java 分布式编程
Distributed Programming with Java Technology

SL-303实现Java的安全性
Implementing Java Security

<

小小豆叮

JSP读取Text文件

附有JSP源码(TextFileReader.jsp)及JavaBean (TextFileReader.java 使用前需加以编译) 我们使用了较早期的jswdk,所以我们可以确信你也可以直接使用这些代码。 TextFileReader.java是一个bean, TextFileReader.jsp则是jsp文件。如果你也使用d jswdk,并使用相同的library environment,可叫bean文件放在jswdk1-0eaexamplesjsp下的textfileaccess目录(你可以创建它),jsp文件放在jswdk1-0eaexamplesWeb-infjspbeanstextfileaccess目录,你也必须创建它。 我们使用的jsp文件并不包含太多的java代码,主要的代码放在bean中。由此我们也可以看到JSP和JavaBean的基本联系。 对于有经验的开发者: 在"header"信息中我们要申明要使用、识别哪一个bean,并设置其属性。 首先,我们导入bean,如果你的jswdk设置正确并已经将文件放在上述位置,那么找到 resource应该没有问题。page命令的意思是它将为整个jsp页面来进行导入。 <%@ page import ="textfileaccess.TextFileReader" %> 告诉编译器我们将使用一个bean,以及如何识别它,并进行初始化(instansiate)。 scope指明被申明的对象对当前页有效。 然后我们决定要设置那些属性。这里是"FileName"。因为我们要使用Bean的setFileName 方法。所以Bean的名字必须包含。 那就是header信息,现在我们开始实际的HTML页面。 Read a text file 现在我们开始编写一些Java脚本。首先检查文件名是否已经设置好。如果设好了,我们就显示文件,否则我们要转到另一个页面。 <%if(file_reader.getFileName() != "") { %> file_reader是一个bean,所以我们可以用Java类来存取它。 :-)现在我们得到文件名称!
文件名称是: '<% out.println(file_reader.getFileName()); %>' :

文件内容,如果为空的话: <%if (file_reader.getContent() != null) { %> 我们可以建立一个textarea (HTML) 并用getRows()和getColumns() 方法来调节到合适的位置。然后将文件内容放入。
cols=<%= file_reader.getColumns()%>id= textarea1name= textarea1>< /FONT> <%out.println(file_reader.getContent()); %> 如果文件为空,那么一定是发生了错误,我们将得到出错信息: <% }else { %> <% out.println(file_reader.getErrorMessage()); %> <% } %>

重置所有值并返回主页: <% file_reader.reset(); %> Do you want to look at another file? <% }else { %> 文件名为空,则显示出错页面。 欢迎加入这里:'Read a file in JSP'
这个示例在textarea中简单地显示了文件内容?lt;p> 请填写你想看到什么文件。并确信键入了完整的路径。

建立带textboxbutton的form。注意我们不必定义form的action,因为使用了同一个页面。并注意textbox中要填入文件名字。

< /FONT> FileName? < /FONT>
<% } %>
jsp文件完成了。在仔细看以下Bean中的Java代码。我假设你们中的大多数都熟悉java,否则你怎么会加入JSP的行列。:-) **************JSP代码: TextFileReader.jsp <%@ page import = "textfileaccess.TextFileReader" %> Read a text file <% if (file_reader.getFileName() != "") { %> The content of the file '<% out.println(file_reader.getFileName()); %>' :

<% if (file_reader.getContent() != null) { %>
<% } else { %> <% out.println(file_reader.getErrorMessage()); %> <% } %>

<% file_reader.reset(); %> Do you want to look at another file? <% } else { %> Welcome to the 'Read a file in JSP' example.
The example simply shows the file in a textarea.

Please fill out what file you want to look at. Be sure to type the complete path.

FileName?
<% } %>
**************Java Bean TextFileReader.java package textfileaccess; import java.io.*; import java.awt.event.*; import java.util.*; /** * TextFileReader is a bean that provides the basic functionality for * reading a textfile. */ public class TextFileReader { private String fileName, errorMessage; private int columns, rowCount; /** * Constructs a TextFileReader. */ public TextFileReader() { reset(); } /** * Resets all the variables in this bean. */ public void reset() { fileName = ""; errorMessage = ""; columns = 0; rowCount = 0; } /** * Sets the error message, if an error occurs. */ public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } /** * Returns the error message, if any. */ public String getErrorMessage() { return errorMessage; } /** * Returns the filename. */ public String getFileName() { return fileName; } /** * Sets the filename. */ public void setFileName(String fileName) { this.fileName = fileName; } /** * Returns the amount of rows in the file. */ public int getRows() { return rowCount; } /** * Returns the maximum amount of columns in a row. */ public int getColumns() { return columns; } /** * Returns the content of the file in a String. * If an error occurs, like if the file does not exists, null is returned. */ public String getContent() { String content = ""; File file = new File(fileName); if (!file.exists()) { setErrorMessage("Error: The file '" + fileName + "' does not exists."); return null; } else if (file != null) { try { // Create an BufferedReader so we can read a line at the time. BufferedReader reader = new BufferedReader(new FileReader(file)); String inLine = reader.readLine(); while (inLine != null) { if (inLine.length() + 1 > columns) columns = inLine.length() + 1; content += (inLine + System.getProperty("line.separator")); inLine = reader.readLine(); rowCount++; } return content; } catch (IOException e) { setErrorMessage("Error reading the file: " + e.getMessage()); return null; } } else { setErrorMessage("Unknown error!"); return null; } } } (原文来自jsp-interest.com)    <

小小豆叮

SUN培训服务介绍

培训中心培训 Sun公司在北京、上海、广州设有培训中心。Sun培训中心有从事Sun 工作站多年,经验丰富的教师,齐全的实习设备及一流的教学环 境。每门课都配有相当的上机时间。 现场培训 Sun培训服务可为客户提供现场培训服务。有关现场培训课程请 打电话与我们联系。我们将会与您讨论培训日期,地点和设备要 求,我们也将提供客户所要求的课程培训。现场培训人数要求人数10人 以上。学费包括教师的差旅费(中国境内)。 国外培训 如果您需要去国外培训,请打电话与我们联系。我们将替您报名 或安排课程。课程安排,费用,以及课程时间均以当地的价格和安 排为准。 自我培训 根据客户的 要求,Sun公司提供媒体形式多样的自我培训软 件。自学教程可提供动态的交互式学习环境,学员可根据自己程 度和时间来方便地安排学习。 领略Sun的领先实力与专业知识 您期待着从Sun获得一流的知识、水平及专业技能,这一切都凝聚 在我们的培训服务之中。16年来,Sun培训服务始终致力于为客户 提供业界优秀的UNIXTM培训服务。今天,每年都有70,000余名学员 参与Sun培训计划,取得丰硕成果。Sun的每一位教师都具备丰富 的教学经验,在相关领域中拥有渊博的知识,完全可以帮助您顺 利掌握专业技能。如果以满分十分来评价公司的绩效,那么来自 世界各地的学员给Sun的评分一直保持在九分以上。 为什么要选择Sun培训服务? 经验丰富、信任可靠:16年来,Sun培训服务一直是您最好的技 术培训来源。 优良的学习环境:我们所有的课程材料都是由业界专家设计, 同时根据实际情况随时更新。 专业认证培训计划:专业认证培训计划为系统管理员和Java网 络管理员和程序员提供第三方认可的技能认证。 灵活变通:您可以根据个人需求选择多种培训方案。 便捷的现场培训:Sun专家将应用您的设备对您进行现场指 导。 自定义课程:Sun培训服务咨询专家将和您一起确定您的培训 内容,制定培训计划,设计培训课程以满足要求。 自学课程:Sun培训服务借助多种媒体形式,提供九十多种自 学培训课程。 <

小小豆叮

关于考SUN JAVA PROGRAMMER的几点建议

前段时间考过了SUN CERTIFIED JAVA PROGRAMMER,考完后一直很忙, 现在闲下来,和大家分享一下考试的经验。 1)JAVA 2 PLATFORM考试科目是310-025,有59题,及格线是61%,即 答对36题。考试时间是90分钟。特别令人厌恶的是考试前有个AGREEMENT, 连你读它的时间也被计算在内。小弟开始不知道,还在慢慢读,突然发现 已经用了3分钟,赶紧开始作题。估计AGREEMENT没有什么重要的东西, 可以直接跳过。时间勉强够用,我大概花了60分钟作完题,剩下20多分钟 检查,居然又给我发现了3-5题错误,都是一时疏忽,落入题目设下的圈套, 所以一定要留时间检查。可惜我剩下10题左右没有时间检查。 2)考试范围非常窄。基本上都是基本语言知识,象SWING,JSP,SERVLET, JDBC,BEANS等等都不涉及。大家可以到SUN网站上下载考试大纲。 考试范围窄不意味着容易,相反,一些很基本的东西被翻来覆去,变着花样 考,反而更难。 3)考试只有选择题,包括单选和复选。多数复选题明确指明有几个答案, 不会搞错。 4)印象中几乎所有的考题都涉及2-3个以上的知识点,几乎没有可以一眼就 看出答案的题目。70%的考题是给你一段程序,然后问运行结果变量的值。 这种题目非常容易落入陷阱,一不小心就被费了。还有20%的题目是给你 几个陈述句,选正确的。这些陈述句都是考很偏很偏的东西,也不太好答。 基本上我的经验是:如果一个题目马上能看出答案,请你在仔细研究一下 题目,多数情况是你落入陷阱了。如果一个题目你能很明确的看出来他 要考你什么知识点,那这个题目就完成一半了。最惨的是知道题目要考你什 么,但是忘记了或没有复习相关知识细节,只好胡猜答案了。 5)考试的一个重点是INNER CLASS。印象中一半以上的题目和他有关。 都是大CLASS套小CLASS等。我复习时花了很多时间在这上面,以为 自己很懂了,结果考试是还是有问题。一定要透彻理解相关定义,语法, 特别是各种各样的MODIFIER的用法。有很多很特殊的MODIFER规则。 这些规则一定要熟练掌握并牢记在心。 6)考试的另一个重点是循环语句。我考,我自以为对循环控制语句很熟悉 了,结果考试考一些很偏很特别的用法,虽然慢慢作都能作出来,但 浪费太多时间在这上面,实在可惜。大家好好看看书。 7)其它的象:CASTING,IO,LAYOUT,EVEN HANDLING,AWT,THREAD, GARBAGE COLLECTION,MATH CLASS等等,都有若干考题。 8)考试题目几乎完全不体现实际应用。如果我有一个JAVA 编译器在身边, 几乎所有的考题都可以轻松解决。我感觉SUN希望考生能象JAVA 编译器 一样熟悉JAVA的特殊语法和特殊规则。这实际是完全没有必要的。 9)我收集了很多模拟考题和BRAINDUMP,有近千题。奇怪的是只有不到 10题出现在考试中,看来SUN的考试题库实行的可能是动态题库,考背 BRAINDUMP是没有什么希望通过了。而且程序考题只有稍微改动一个符 号,结果就完全不同,BRAINDUMP很多题目靠不住。所以大家一定要在 一定实际经验的基础上,好好看透一本辅导书,多作题,多分析题, 多思考,才能比较容易通过考试。 10)不管大家如何评价认证考试,反正考多几个证书不是坏事。起码 加薪找工比较方便。 <

小小豆叮

在Java Applet中发Email (转载)

在设计主页时,为了得到访问用户的反馈信息,我们经常将自己的Ema il地址嵌在主页上。其中最常用的做法是利用HTML语言但是,这样做的缺点是,无论读者使用的是Internet Explorer,还 是Netscape Navigatr ,在点击作者地址时都需另打开一个写作窗口, 使主页窗口被覆盖。这样读者在写信时看不到原文的内容,也不易进 行引用。笔者设计了一个Java Applet可嵌在主页上,解决了这个问题 ,同时使没有Email地址的人也可发送反馈信息。源程序如下: import java.awt.*; import java.applet.*; import java.net.*; import java.io.*; public class Javamail extends Applet { private int SMTP_PORT = 25; //邮件服务器缺省端口号 private String appletSource = "202.99.96.140"; //作者邮件服务器IP private TextArea MsgArea; private TextField senderField, recipientField, hostFie ld; public void init() { setLayout(new BorderLayout()); Panel fields = new Panel(); fields.setLayout(new GridLayout(3, 1)); Panel recPanel = new Panel(); recPanel.setLayout(new GridLayout(2, 1)); recPanel.add(new Label("Recipient")); recipientField = new TextField("zffan"); //作者邮件服务器用户名 recPanel.add(recipientField); fields.add(recPanel); Panel sendPanel = new Panel(); sendPanel.setLayout(new GridLayout(2, 1)); sendPanel.add(new Label("Sender")); senderField = new TextField("Your Name"); //由读者填其用户名 sendPanel.add(senderField); fields.add(sendPanel); Panel hostPanel = new Panel(); hostPanel.setLayout(new GridLayout(2, 1)); hostPanel.add(new Label("Host")); hostField = new TextField("YourCompany.com"); //由读者填其邮件服务器IP hostPanel.add(hostField); fields.add(hostPanel); add("North", fields); MsgArea = new TextArea(); add("Center", MsgArea); add("South", new Button("SEND")); } public boolean handleEvent(Event e) { if (e.id == Event.WINDOW_DESTROY) System.exit(0); return super.handleEvent(e); } public boolean action(Event e, Object arg) { if (arg.equals("SEND")) sendMsg(senderField.getText(), recipientField.getTex t(), hostField.gete xt()); else return super.action(e, arg); return true; } private void sendMsg(String sender, String recipient, String senderHost) { try { //与邮件服务器通信 Socket s = new Socket(appletSource, SMTP_PORT); PrintStream out = new PrintStream(s.getOutputStrea m()); MsgArea.selectAll(); out.println("HELLO " + senderHost); //邮件服务器不认证读者所输SMTP是否正确 out.println("MAIL FROM: " + sender); out.println("RCPT TO: " + recipient); out.println("DATA"); out.println(MsgArea.getSelectedText()); out.println("."); out.println("QUIT"); } catch(Exception e) { System.out.println("Error " + e); } } } //Javamail 以上程序在Windows 95、JDK1.1.2、Hotjava Browser 1.0环境 下调试通过。有兴趣的读者还可加上与SMTP服务器通信时的出错例程 <

小小豆叮

问:最近SUN的培训课程安排

培训时间表2001年7-12月(北京培训中心)可访问:http://www.sun.com.cn/education/beijing.html 培训时间表2001年7-12月(上海培训中心)可访问:http://www.sun.com.cn/education/shanghai.html 培训时间表2001年7-12月(广州培训中心)可访问:http://www.sun.com.cn/education/guangzhou.html <

小小豆叮

Windows XP的Java插件

<

小小豆叮

JSP初学心得(转载)

测试环境为 jdk1.2.2 jswdk-1.0 winnt4.0中文版。 1。java是大小写敏感的,用过其他编程语言的人最容易犯这个错误,尤其是刚上手的时候。我刚开始调试jsp的时50%以上的编译错误是 都是因为这个。 2。java的调用过程都是要加括号的,一开始比较容易忽视,如title=request.getParameter("title").trim(); 3。jsp中对应asp中的request.form()和request.querystring()的解决方法。 jsp中取得参数没有form和queryString之分,都是通过request.getParameter("XXXX")来取得。虽然jsp也有request.getQueryString()方法,但测试结果是 test.jsp?id=1&page=20 得到 id=1&page=20。 如果url和form有相同的参数名称呢?下面是一段测试代码:
name都是id,结果是url的参数优先得到,jsp的这种处理方式和asp相比我觉的各有所长。 4。头疼的汉字处理问题。 在其他的文章里曾说到在中文NT环境下如下语句输出会得到乱码, <%="你好"%> 及 out.print("你好");等。解决方法是只要对字符串变量进行编码就可以得到正确结果,如下代码可以得到正确的输出: <% String title="你好"; byte[] tmpbyte=title.getBytes("ISO8859_1"); title=new String(tmpbyte); out.print(title); %> 或者<%=title%> 关于sql语句汉字问题,例句为 select * from test where title='谁是傻瓜' 在jdbc-odbc驱动下连db2,不管是原句还是对sql语句进行编码后都死活通不过。 换了ibm的jdbc直接驱动后,对sql语句编码后程序可以通过。 这个问题的产生大概是中文NT的原因,在其他环境下可能就没汉字处理问题了,据说ibm的web sphere对中文支持的很好,这也给jsp的开发带来一定的通用性问题。据说对字符串编码是一种通用的解决方法,不过没有这么多环境来测试。 5。在asp中经常使用到字符串判断语句如 if state="真是傻瓜" then..... 在java中String变量不是一个简单的变量而是一个类实例,不同的方法会得到不同的结果 a. String str1="我是傻瓜"; String str2="我是傻瓜"; (or String str2="我是"+"傻瓜"; ) if (str1==str2) out.print("yes"); else out.print("no"); 结果是"yes"。 大概是编译优化,str1,str2指向同一个类实例; b. String str1,str2,str3; str1="我是傻瓜"; str2="我是"; str3=str2+"傻瓜"; if (str1==str3) out.print("yes"); else out.print("no"); 结果是"no"。 String str1=new String("我是傻瓜"); String str2=new String("我是傻瓜"); if (str1==str2) out.print("yes"); else out.print("no"); 结果是"no"。 String str1=new String("我是傻瓜"); String str2=new String("我是傻瓜"); if (str1.compareTo(str2)==0) out.print("yes"); else out.print("no"); 结果是"yes"。 所以在jsp中判断字符串要使用compareTo方法,用惯传统语言还真一下子适应不过来,熟悉java的朋友应该没这个问题。 6。如何判断数据库为空? result = stmt.executeQuery(sql); if (result.next()) ...... result执行后游标出于一个未明的状态,不能进行状态判断,也不能取值,一定要next()一下才可以用。 7。在jsp中实现分页。 page是关键字,不能当变量。 conn.jsp <% String sDBDriver = "COM.ibm.db2.jdbc.app.DB2Driver"; String sConnStr = "jdbc:db2:faq"; Connection conn = null; Statement stmt = null; ResultSet rs=null; try { Class.forName(sDBDriver); } catch(java.lang.ClassNotFoundException e) { out.print("faq(): " + e.getMessage()); } try{ conn = DriverManager.getConnection(sConnStr,"wsdemo","wsdemo1"); stmt = conn.createStatement(); }catch(SQLException e){ out.print(e.toString()); } %> query.jsp <%@ page language="java" import="java.sql.*" %> <%@ page contentType="text/html; charset=gb2312" %> <%@ include file="conn.jsp" %> <% ...... int pages=0; int pagesize=10; ResultSet result = null; ResultSet rcount = null; pages = new Integer(request.getParameter("pages")).intValue(); if (pages>0) { String sql=" state='我不傻'"; int count=0; try { rcount = stmt.executeQuery("SELECT count(id) as id from user where "+sql); catch(SQLException ex) { out.print("aq.executeQuery: " + ex.getMessage()); } if(rcount.next()) count = rcount.getInt("id"); rcount.close(); if (count>0) { sql="select * from user where "+sql; try { result = stmt.executeQuery(sql); } catch(SQLException ex) { out.print("aq.executeQuery: " + ex.getMessage()); } int i; String name; // result.first(); // result.absolute((pages-1)*pagesize); // 此方法jdbc2.0支持。编译通过,但执行不过,不知是不是跟驱动有关,只好用下面的笨办法。 for(i=1;i<=(pages-1)*pagesize;i++) result.next(); for(i=1;i<=pagesize;i++) { if (result.next()) { name=result.getString("name"); out.print(name); } result.close(); int n= (int)(count/pagesize); if (n*pagesize1) { for(i=1;i<=n;i++) out.print(""+i+" "); } } } %> <

小小豆叮

类注释文档编写方法

对于Java语言,最体贴的一项设计就是它并没有打算让人们为了写程序而写程序——人们也需要考虑程序的文档化问题。对于程序的文档化,最大的问题莫过于对文档的维护。若文档与代码分离,那么每次改变代码后都要改变文档,这无疑会变成相当麻烦的一件事情。解决的方法看起来似乎很简单:将代码同文档“链接”起来。为达到这个目的,最简单的方法是将所有内容都置于同一个文件。然而,为使一切都整齐划一,还必须使用一种特殊的注释语法,以便标记出特殊的文档;另外还需要一个工具,用于提取这些注释,并按有价值的形式将其展现出来。这些都是Java必须做到的。 用于提取注释的工具叫作javadoc。它采用了部分来自Java编译器的技术,查找我们置入程序的特殊注释标记。它不仅提取由这些标记指示的信息,也将毗邻注释的类名或方法名提取出来。这样一来,我们就可用最轻的工作量,生成十分专业的程序文档。 javadoc输出的是一个HTML文件,可用自己的Web浏览器查看。该工具允许我们创建和管理单个源文件,并生动生成有用的文档。由于有了jvadoc,所以我们能够用标准的方法创建文档。而且由于它非常方便,所以我们能轻松获得所有Java库的文档。 2 具体语法 所有javadoc命令都只能出现于“/**”注释中。但和平常一样,注释结束于一个“*/”。主要通过两种方式来使用javadoc:嵌入的HTML,或使用“文档标记”。其中,“文档标记”(Doc tags)是一些以“@”开头的命令,置于注释行的起始处(但前导的“*”会被忽略)。 有三种类型的注释文档,它们对应于位于注释后面的元素:类、变量或者方法。也就是说,一个类注释正好位于一个类定义之前;变量注释正好位于变量定义之前;而一个方法定义正好位于一个方法定义的前面。如下面这个简单的例子所示: /** 一个类注释 */ public class docTest { /** 一个变量注释 */ public int i; /** 一个方法注释 */ public void f() {} } 注意javadoc只能为public(公共)和protected(受保护)成员处理注释文档。“private”(私有)和“友好”(详见5章)成员的注释会被忽略,我们看不到任何输出(也可以用-private标记包括private成员)。这样做是有道理的,因为只有public和protected成员才可在文件之外使用,这是客户程序员的希望。然而,所有类注释都会包含到输出结果里。 上述代码的输出是一个HTML文件,它与其他Java文档具有相同的标准格式。因此,用户会非常熟悉这种格式,可在您设计的类中方便地“漫游”。设计程序时,请务必考虑输入上述代码,用javadoc处理一下,观看最终HTML文件的效果如何。 3 嵌入HTML javadoc将HTML命令传递给最终生成的HTML文档。这便使我们能够充分利用HTML的巨大威力。当然,我们的最终动机是格式化代码,不是为了哗众取宠。下面列出一个例子: /** *
* System.out.println(new Date());
* 
*/ 亦可象在其他Web文档里那样运用HTML,对普通文本进行格式化,使其更具条理、更加美观: /** * 您甚至可以插入一个列表: *
    *
  1. 项目一 *
  2. 项目二 *
  3. 项目三 *
*/ 注意在文档注释中,位于一行最开头的星号会被javadoc丢弃。同时丢弃的还有前导空格。javadoc会对所有内容进行格式化,使其与标准的文档外观相符。不要将


这样的标题当作嵌入HTML使用,因为javadoc会插入自己的标题,我们给出的标题会与之冲撞。 所有类型的注释文档——类、变量和方法——都支持嵌入HTML。 4 @see:引用其他类 所有三种类型的注释文档都可包含@see标记,它允许我们引用其他类里的文档。对于这个标记,javadoc会生成相应的HTML,将其直接链接到其他文档。格式如下: @see 类名 @see 完整类名 @see 完整类名#方法名 每一格式都会在生成的文档里自动加入一个超链接的“See Also”(参见)条目。注意javadoc不会检查我们指定的超链接,不会验证它们是否有效。 5 类文档标记 随同嵌入HTML和@see引用,类文档还可以包括用于版本信息以及作者姓名的标记。类文档亦可用于“接口”目的(本书后面会详细解释)。 1. @version 格式如下: @version 版本信息 其中,“版本信息”代表任何适合作为版本说明的资料。若在javadoc命令行使用了“-version”标记,就会从生成的HTML文档里提取出版本信息。 2. @author 格式如下: @author 作者信息 其中,“作者信息”包括您的姓名、电子函件地址或者其他任何适宜的资料。若在javadoc命令行使用了“-author”标记,就会专门从生成的HTML文档里提取出作者信息。 可为一系列作者使用多个这样的标记,但它们必须连续放置。全部作者信息会一起存入最终HTML代码的单独一个段落里。 6 变量文档标记 变量文档只能包括嵌入的HTML以及@see引用。 7 方法文档标记 除嵌入HTML和@see引用之外,方法还允许使用针对参数、返回值以及违例的文档标记。 1. @param 格式如下: @param 参数名 说明 其中,“参数名”是指参数列表内的标识符,而“说明”代表一些可延续到后续行内的说明文字。一旦遇到一个新文档标记,就认为前一个说明结束。可使用任意数量的说明,每个参数一个。 2. @return 格式如下: @return 说明 其中,“说明”是指返回值的含义。它可延续到后面的行内。 3. @exception 有关“违例”(Exception)的详细情况,我们会在第9章讲述。简言之,它们是一些特殊的对象,若某个方法失败,就可将它们“扔出”对象。调用一个方法时,尽管只有一个违例对象出现,但一些特殊的方法也许能产生任意数量的、不同类型的违例。所有这些违例都需要说明。所以,违例标记的格式如下: @exception 完整类名 说明 其中,“完整类名”明确指定了一个违例类的名字,它是在其他某个地方定义好的。而“说明”(同样可以延续到下面的行)告诉我们为什么这种特殊类型的违例会在方法调用中出现。 4. @deprecated 这是Java 1.1的新特性。该标记用于指出一些旧功能已由改进过的新功能取代。该标记的作用是建议用户不必再使用一种特定的功能,因为未来改版时可能摒弃这一功能。若将一个方法标记为@deprecated,则使用该方法时会收到编译器的警告。 <

小小豆叮

JAVA 的多线程浅析

作者:马光晖 一 JAVA 语言的来源、及特点 在这个高速信息的时代,商家们纷纷把信息、产品做到Internet国际互连网页上。再这些不寻常网页的背后,要属功能齐全、安全可靠的编程语言,Java是当之无愧的。Java是由Sun Microsystem开发的一种功能强大的新型程序设计语言。是与平台无关的编程语言。它是一种简单的、面象对象的、分布式的、解释的、键壮的、安全的、结构的中立的、可移植的、性能很优异的、多线程的、动态的、语言。 Java自问世以后,以其编程简单、代码高效、可移植性强,很快受到了广大计算机编程人士的青睐。Java语言是Internet上具有革命性的编程语言,它具有强大的动画、多媒体和交互功能,他使World Web进入了一个全新的时代。Java语言与C++极为类似,可用它来创建安全的、可移植的、多线程的交互式程序。另外用Java开发出来的程序与平台无关,可在多种平台上运行。后台开发,是一种高效、实用的编程方法。人们在屏幕前只能看到例如图案、计算的结果等。实际上操作系统往往在后台来调度一些事件、管理程序的流向等。例如操作系统中的堆栈,线程间的资源分配与管理,内存的创建、访问、管理等。可谓举不盛举。下面就多线程来谈一谈。 二 JAVA的多线程理论 2.1引入 Java提供的多线程功能使得在一个程序里可同时执行多个小任务。线程有时也称小进程是一个大进程里分出来的小的独立的进程。因为Java实现的多线程技术,所以比C和C++更键壮。多线程带来的更大的好处是更好的交互性能和实时控制性能。当然实时控制性能还取决于系统本身(UNIX,Windows,Macintosh等),在开发难易程度和性能上都比单线程要好。传统编程环境通常是单线程的,由于JAVA是多线程的。尽管多线程是强大而灵巧的编程工具,但要用好却不容易,且有许多陷阱,即使编程老手也难免误用。为了更好的了解线程,用办公室工作人员作比喻。办公室工作人员就象CPU,根据上级指示做工作,就象执行一个线程。在单线程环境中,每个程序编写和执行的方式是任何时候程序只考虑一个处理顺序。用我们的比喻,就象办公室工作人员从头到尾不受打扰和分心,只安排做一个工作。当然,实际生活中工作人员很难一次只有一个任务,更常见的是工作人员要同时做几件事。老板将工作交给工作人员,希望工作人员做一这个工作,再做点那个工作,等等。如果一个任务无法做下去了,比如工作人员等待另一部门的信息,则工作人员将这个工作放在一边,转入另一个工作。一般来说,老板希望工作人员手头的各个任务每一天都有一些进展。这样就引入了多线程的概念。多线程编程环境与这个典型的办公室非常相似,同时给CPU分配了几个任务或线程。和办公室人员一样,计算机CPU实际上不可能同一时间做几件事,而是把时间分配到不同的线程,使每个线程都有点进展。如果一个线程无法进行,比如线程要求的键盘输入尚未取得,则转入另一线程的工作。通常,CPU在线程间的切换非常迅速,使人们感觉到好象所有线程是同时进行的。 任何处理环境,无论是单线程还是多线程,都有三个关键方面。第一个是CPU,它实际上进行计算机活动;第二个是执行的程序的代码;第三个是程序操作的数据。 在多线程编程中,每个线程都用编码提供线程的行为,用数据供给编码操作。多个线程可以同时处理同一编码和数据,不同的线程也可能各有不同的编码和数据。事实上编码和数据部分是相当独立的,需要时即可向线程提供。因此经常是几个线程使用同一编码和不同的数据。这个思想也可以用办公室工作人员来比喻。会计可能要做一个部门的帐或几个或几个部门的帐。任何情况的做帐的任务是相同的程序代码,但每个部门的数据是不同的。会计可能要做整个公司的帐,这时有几个任务,但有些数据是共享的,因为公司帐需要来自各个部门的数据。 多线程编程环境用方便的模型隐藏CPU在任务切换间的事实。模型允许假装成有多个可用的CPU。为了建立另一个任务,编程人员要求另一个虚拟CPU,指示它开始用某个数据组执行某个程序段。下面我们来建立线程。 建立线程 在JAVA中建立线程并不困难,所需要的三件事:执行的代码、代码所操作的数据和执行代码的虚拟CPU。虚拟CPU包装在Thread类的实例中。建立Thread对象时,必须提供执行的代码和代码所处理的数据。JAVA的面向对象模型要求程序代码只能写成类的成员方法。数据只能作为方法中的自动(或本地)变量或类的成员存在。这些规则要求为线程提供的代码和数据应以类的实例的形式出现。 Public class SimpleRunnable implemants Runable{ Private String message; Public static void main(String args[]){ SimpleRunnable r1=new SimpleRunnable(“Hello”); Thread t1=new Thread(r1); t1.start(); } public SimpleRunnable(String message){ this.message=message; } public void run(){ for(;;){ System.out.println(message); } } } 线程开始执行时,它在public void run()方法中执行。这种方法是定义的线程执行的起点,就象应用程序从main()开始、小程序从init()开始一样。线程操作的本地数据是传入线程的对象的成员。 首先,main()方法构造SimpleRunnable类的实例。注意,实例有自己的数据,这里是一个String,初始化为”Hello”.由于实例r1传入Thread类构造器,这是线程运行时处理的数据。执行的代码是实例方法run()。 2.2 线程的管理 单线程的程序都有一个main执行体,它运行一些代码,当程序结束执行后,它正好退出,程序同时结束运行。在JAVA中我们要得到相同的应答,必须稍微进行改动。只有当所有的线程退出后,程序才能结束。只要有一个线程一直在运行,程序就无法退出。线程包括四个状态:new(开始),running(运行),wait(等候)和done(结束)。第一次创建线程时,都位于new状态,在这个状态下,不能运行线程,只能等待。然后,线程或者由方法start开始或者送往done状态,位于done中的线程已经结束执行,这是线程的最后一个状态。一旦线程位于这个状态,就不能再次出现,而且当JAVA虚拟机中的所有线程都位于done状态时,程序就强行中止。当前正在执行的所有线程都位于running状态,在程序之间用某种方法把处理器的执行时间分成时间片,位于running状态的每个线程都是能运行的,但在一个给定的时间内,每个系统处理器只能运行一个线程。与位于running状态的线程不同,由于某种原因,可以把已经位于waiting状态的线程从一组可执行线程中删除。如果线程的执行被中断,就回到waiting状态。用多种方法能中断一个线程。线程能被挂起,在系统资源上等候,或者被告知进入休眠状态。该状态的线程可以返回到running状态,也能由方法stop送入done状态, 方法 描述 有效状态 目的状态 Start() 开始执行一个线程 New Running Stop() 结束执行一个线程 New或running Done Sleep(long) 暂停一段时间,这个时间为给定的毫秒 Running Wait Sleep(long,int) 暂停片刻,可以精确到纳秒 Running Wait Suspend() 挂起执行 Running Wait Resume() 恢复执行 Wait Running Yield() 明确放弃执行 Running Running 2.3线程的调度 线程运行的顺序以及从处理器中获得的时间数量主要取决于开发者,处理器给每个线程分配一个时间片,而且线程的运行不能影响整个系统。处理器线程的系统或者是抢占式的,或者是非抢占式的。抢占式系统在任何给定的时间内将运行最高优先级的线程,系统中的所有线程都有自己的优先级。Thread.NORM_PRIORITY是线程的缺省值,Thread类提供了setPriority和getPriority方法来设置和读取优先权,使用setPriority方法能改变Java虚拟机中的线程的重要性,它调用一个整数,类变量Thread.MIN_PRIORITY和Thread.MAX_PRIORITY决定这个整数的有效范围。Java虚拟机是抢占式的,它能保证运行优先级最高的线程。在JAVA虚拟机中我们把一个线程的优先级改为最高,那么他将取代当前正在运行的线程,除非这个线程结束运行或者被一条休眠命令放入waiting状态,否者将一直占用所有的处理器的时间。如果遇到两个优先级相同的线程,操作系统可能影响线程的执行顺序。而且这个区别取决于时间片(time slicing)的概念。 管理几个线程并不是真正的难题,对于上百个线程它是怎样管理的呢?当然可以通过循环,来执行每一个线程,但是这显然是冗长、乏味。JAVA创建了线程组。线程组是线程的一个谱系组,每个组包含的线程数不受限制,能对每个线程命名并能在整个线程组中执行(Suspend)和停止(Stop)这样的操作。 2.4信号标志:保护其它共享资源 这种类型的保护被称为互斥锁。某个时间只能有一个线程读取或修改这个数据值。在对文件尤其是信息数据库进行处理时,读取的数据总是多于写数据,根据这个情况,可以简化程序。下面举一例,假设有一个雇员信息的数据库,其中包括雇员的地址和电话号码等信息,有时要进行修改,但要更多的还是读数据,因此要尽可能防止数据被破坏或任意删改。我们引入前面互斥锁的概念,允许一个读取锁(red lock)和写入锁(write lock),可根据需要确定有权读取数据的人员,而且当某人要写数据时,必须有互斥锁,这就是信号标志的概念。信号标志有两种状态,首先是empty()状态,表示没有任何线程正在读或写,可以接受读和写的请求,并且立即提供服务;第二种状态是reading()状态,表示有线程正在从数据库中读信息,并记录进行读操作的线程数,当它为0时,返回empty状态,一个写请求将导致这个线程进入等待状态。 只能从empty状态进入writing状态,一旦进入writing状态后,其它线程都不能写操作,任何写或读请求都必须等到这个线程完成写操作为止,而且waiting状态中的进程也必须一直等到写操作结束。完成操作后,返回到empty状态,发送一个通知信号,等待的线程将得到服务。 下面实现了这个信号标志 class Semaphore{ final static int EMPTY=0; final static int READING=1; final static int WRITING=2; protected int state=EMPTY; protected int readCnt=0; public synchronized void readLock(){ if(state==EMPTY){ state=READING; } else if(state==READING){ } else if(state==WRITING){ while(state==WRITING){ try {wait();} catch(InterruptedException e){;} } state=READING; } readCnt++; return; } public synchronized void writeLock(){ if(state==EMPTY){ state=WRITING; } else{ while(state!=EMPTY){ try {wait();} catch(InterruptedException e) {;} } } } public synchronized void readUnlock(){ readCnt--; if(readCnt==0){ state=EMPTY; notify(); } } public synchronized void writeUnlock(){ state=EMPTY; notify(); } } 现在是测试信号标志的程序: class Process extends Thread{ String op; Semaphore sem; Process(String name,String op,Semaphore sem){ super(name); this.op=op; this.sem=sem; start(); } public void run(){ if(op.compareTo("read")==0){ System.out.println("Trying to get readLock:"+getName()); sem.readLock(); System.out.println("Read op:"+getName()); try {sleep((int)(Math.random()*50));} catch(InterruptedException e){;} System.out.println("Unlocking readLock:"+getName()); sem.readUnlock(); } else if(op.compareTo("write")==0){ System.out.println("Trying to get writeLock:"+getName()); sem.writeLock(); System.out.println("Write op:"+getName()); try {sleep((int)(Math.random()*50));} catch(InterruptedException e){;} System.out.println("Unlocking writeLock:"+getName()); sem.writeUnlock(); } } } public class testSem{ public static void main(String argv[]){ Semaphore lock = new Semaphore(); new Process("1","read",lock); new Process("2","read",lock); new Process("3","write",lock); new Process("4","read",lock); } } testSem 类从process类的四个实例开始,它是个线程,用来读或写一个共享文 件。Semaphore类保证访问不会破坏文件,执行程序,输出结果如下: Trying to get readLock:1 Read op:1 Trying to get readLock:2 Read op:2 Trying to get writeLock:3 Trying to get readLock:4 Read op:4 Unlocking readLock:1 Unlocking readLock:2 Unlocking readLock:4 Write op:3 Unlocking writeLock:3 从这可看到, 2.5死锁以及怎样避免死锁: 为了防止数据项目的并发访问,应将数据项目标为专用,只有通过类本身的实例方法的同步区访问。为了进入关键区,线程必须取得对象的锁。假设线程要独占访问两个不同对象的数据,则必须从每个对象各取一个不同的锁。现在假设另一个线程也要独占访问这两个对象,则该进程必须得到这两把锁之后才能进入。由于需要两把锁,编程如果不小心就可能出现死锁。假设第一个线程取得对象A的锁,准备取对象B的锁,而第二个线程取得了对象B的锁,准备取对象A的锁,两个线程都不能进入,因为两者都不能离开进入的同步块,既两者都不能放弃目前持有的锁。避免死锁要认真设计。线程因为某个先决条件而受阻时,如需要锁标记时,不能让线程的停止本身禁止条件的变化。如果要取得多个资源,如两个不同对象的锁,必须定义取得资源的顺序。如果对象A和B的锁总是按字母顺序取得,则不会出现前面说道的饿死条件。 三Java多线程的优缺点 由于JAVA的多线程功能齐全,各种情况面面具到,它带来的好处也是显然易见的。多线程带来的更大的好处是更好的交互性能和实时控制性能。当然实时控制性能还取决于系统本身(UNIX,Windows,Macintosh 等),在开发难易程度和性能上都比单线程要好。当然一个好的程序设计语言肯定也难免有不足之处。由于多线程还没有充分利用基本OS的这一功能。这点我在前面已经提到,对于不同的系统,上面的程序可能会出现截然不同的结果,这使编程者偶会感到迷惑不解。希望在不久的将来JAVA的多线程能充分利用到操作系统,减少对编程者的困惑。我期待着JAVA会更好。 <

小小豆叮

一个Applet(JApplet)访问数据库的例子

用JApplet做的Applet和一般的Applet有些不同。主要是因为Swing库类的单线程原则,所以当JApplet的界面生成后,由其它线程(一般是消息分发线程)试图改变界面(如setText())可能会导致问题发生。这是可以借助SwingUtilities.invokeLater()或SwingUtilities.invokeAndWait()来解决。下面是一个用JApplet访问数据库的例子。 DBApplet.java ====================================================================== import java.sql.*; import javax.swing.*; public class DBApplet extends javax.swing.JApplet { final static private String[] jdbcDriver = { "com.informix.jdbc.IfxDriver", "sun.jdbc.odbc.JdbcOdbcDriver", "com.borland.datastore.jdbc.DataStoreDriver", "com.sybase.jdbc.SybDriver", "oracle.jdbc.driver.OracleDriver", "COM.ibm.db2.jdbc.net.DB2Driver", "interbase.interclient.Driver", "weblogic.jdbc.mssqlserver4.Driver" }; private boolean connected = false; private Connection connection = null; private ResultSet rs = null; private String query = null; private String rsLine = null; private String driver = null; private String url = null; private String user = null; private String password = null; public DBApplet() { initComponents (); postInit(); } private void postInit() { for (int i = 0; i < jdbcDriver.length; i++) { cbDriver.addItem(jdbcDriver[i]); } } private void initComponents() { jScrollPane1 = new javax.swing.JScrollPane(); taResponse = new javax.swing.JTextArea(); jPanel2 = new javax.swing.JPanel(); jPanel1 = new javax.swing.JPanel(); jLabel6 = new javax.swing.JLabel(); tfSql = new javax.swing.JTextField(); btnExecute = new javax.swing.JButton(); jPanel3 = new javax.swing.JPanel(); jLabel3 = new javax.swing.JLabel(); jPanel4 = new javax.swing.JPanel(); cbDriver = new javax.swing.JComboBox(); jLabel7 = new javax.swing.JLabel(); tfUrl = new javax.swing.JTextField(); jLabel9 = new javax.swing.JLabel(); tfUser = new javax.swing.JTextField(); jLabel10 = new javax.swing.JLabel(); tfPassword = new javax.swing.JTextField(); btnConnect = new javax.swing.JButton(); btnDisconnect = new javax.swing.JButton(); setFont(new java.awt.Font ("Verdana", 0, 12)); jScrollPane1.setViewportView(taResponse); getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER); getContentPane().add(jPanel2, java.awt.BorderLayout.EAST); jLabel6.setText("SQL:"); jPanel1.add(jLabel6); tfSql.setPreferredSize(new java.awt.Dimension(300, 21)); jPanel1.add(tfSql); btnExecute.setText("Execute Query"); btnExecute.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { btnExecuteActionPerformed(evt); } }); jPanel1.add(btnExecute); getContentPane().add(jPanel1, java.awt.BorderLayout.SOUTH); jPanel3.setPreferredSize(new java.awt.Dimension(550, 100)); jPanel3.setMinimumSize(new java.awt.Dimension(550, 100)); jPanel3.setMaximumSize(new java.awt.Dimension(550, 100)); jLabel3.setText("JDBC Driver:"); jPanel3.add(jLabel3); jPanel3.add(jPanel4); cbDriver.setPreferredSize(new java.awt.Dimension(450, 26)); cbDriver.setMinimumSize(new java.awt.Dimension(100, 26)); jPanel3.add(cbDriver); jLabel7.setText("Database URL:"); jPanel3.add(jLabel7); tfUrl.setPreferredSize(new java.awt.Dimension(450, 21)); jPanel3.add(tfUrl); jLabel9.setText("User:"); jPanel3.add(jLabel9); tfUser.setPreferredSize(new java.awt.Dimension(100, 21)); jPanel3.add(tfUser); jLabel10.setText("Password:"); jPanel3.add(jLabel10); tfPassword.setPreferredSize(new java.awt.Dimension(100, 21)); jPanel3.add(tfPassword); btnConnect.setPreferredSize(new java.awt.Dimension(89, 27)); btnConnect.setMaximumSize(new java.awt.Dimension(89, 27)); btnConnect.setText("Connect"); btnConnect.setMinimumSize(new java.awt.Dimension(89, 27)); btnConnect.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { btnConnectActionPerformed(evt); } }); jPanel3.add(btnConnect); btnDisconnect.setText("Disconnect"); btnDisconnect.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { btnDisconnectActionPerformed(evt); } }); jPanel3.add(btnDisconnect); getContentPane().add(jPanel3, java.awt.BorderLayout.NORTH); } private void btnExecuteActionPerformed(java.awt.event.ActionEvent evt) { if (!connected) { SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("No database connected.\n"); } } ); } else { if (connection == null) { SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("Database connection error.\n"); } } ); } else { try { query = tfSql.getText(); Statement stmt = connection.createStatement(); SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("Executing query: " + query + "\n"); } } ); rs = stmt.executeQuery(query); ResultSetMetaData rsmd = rs.getMetaData(); int count = rsmd.getColumnCount(); int i; rsLine = "\n"; while (rs.next()) { for (i = 1; i <= count; i++) { rsLine += rs.getString(i) + " "; } rsLine += "\n"; } rsLine += "\n"; stmt.close(); SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append(rsLine); } } ); } catch (SQLException e) { SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("Query failed.\n"); } } ); e.printStackTrace(); } } } } private void btnDisconnectActionPerformed(java.awt.event.ActionEvent evt) { if (connected) { try { if (connection != null) { connection.close(); connection = null; SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("Database disconnected.\n"); } } ); } } catch (SQLException e) { SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("Database disconnecting error.\n"); } } ); e.printStackTrace(); } connected = false; driver = null; url = null; user = null; password = null; } else { SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("Database already disconnected.\n"); } } ); } } private void btnConnectActionPerformed(java.awt.event.ActionEvent evt) { if (connected) { taResponse.append("Database already connected.\n"); } else { driver = (String) cbDriver.getSelectedItem(); url = tfUrl.getText(); user = tfUser.getText(); password = tfPassword.getText(); try { SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("Using JDBC driver: " + driver + "\n"); } } ); Class.forName(driver).newInstance(); connection = DriverManager.getConnection(url, user, password); if (connection != null) { SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("Database " + url + " connected.\n"); } } ); connected = true; } } catch (ClassNotFoundException e) { SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("Cannot load the driver.\n"); } } ); e.printStackTrace(); } catch (SQLException e) { SwingUtilities.invokeLater( new Runnable() { public void run() { taResponse.append("Cannot connect to the database.\n"); } } ); e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextArea taResponse; private javax.swing.JPanel jPanel2; private javax.swing.JPanel jPanel1; private javax.swing.JLabel jLabel6; private javax.swing.JTextField tfSql; private javax.swing.JButton btnExecute; private javax.swing.JPanel jPanel3; private javax.swing.JLabel jLabel3; private javax.swing.JPanel jPanel4; private javax.swing.JComboBox cbDriver; private javax.swing.JLabel jLabel7; private javax.swing.JTextField tfUrl; private javax.swing.JLabel jLabel9; private javax.swing.JTextField tfUser; private javax.swing.JLabel jLabel10; private javax.swing.JTextField tfPassword; private javax.swing.JButton btnConnect; private javax.swing.JButton btnDisconnect; } dbapplet.html ====================================================================== DB applet

DB applet

The Applet tag in this file contains a CODEBASE entry that must be set to point to a directory containing the Java classes from the Thin JDBC distribution *and* the compiled JdbcApplet.class.


</COMMENT> No Java 2 SDK, Standard Edition v 1.3 support for APPLET!!
我用的是数据库是Oracle,大家可以根据自己情况改改。 <

小小豆叮

Java图像处理技巧四则

作者:Cherami email:cherami@163.net 本人的另外一些作品请查看:http://www.smiling.com.cn/group/homepage.ecgi?group_id=23141 下面代码中用到的sourceImage是一个已经存在的Image对象 图像剪切 对于一个已经存在的Image对象,要得到它的一个局部图像,可以使用下面的步骤: //import java.awt.*; //import java.awt.image.*; Image croppedImage; ImageFilter cropFilter; CropFilter =new CropImageFilter(25,30,75,75);//四个参数分别为图像起点坐标和宽高,即CropImageFilter(int x,int y,int width,int height),详细情况请参考API CroppedImage= Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(sourceImage.getSource(),cropFilter)); //如果是在Component的子类中使用,可以将上面的Toolkit.getDefaultToolkit().去掉。 //FilteredImageSource是一个ImageProducer对象。 图像缩放 对于一个已经存在的Image对象,得到它的一个缩放的Image对象可以使用Image的getScaledInstance方法: Image scaledImage=sourceImage. getScaledInstance(100,100, Image.SCALE_DEFAULT);//得到一个100X100的图像 Image doubledImage=sourceImage. getScaledInstance(sourceImage.getWidth(this)*2,sourceImage.getHeight(this)*2, Image.SCALE_DEFAULT);//得到一个放大两倍的图像,这个程序一般在一个swing的组件中使用,而类Jcomponent实现了图像观察者接口ImageObserver,所有可以使用this。 //其它情况请参考API。 灰度变换 下面的程序使用三种方法对一个彩色图像进行灰度变换,变换的效果都不一样。一般而言,灰度变换的算法是将象素的三个颜色分量使用R*0.3+G*0.59+B*0.11得到灰度值,然后将之赋值给红绿蓝,这样颜色取得的效果就是灰度的。另一种就是取红绿蓝三色中的最大值作为灰度值。java核心包也有一种算法,但是没有看源代码,不知道具体算法是什么样的,效果和上述不同。 /* GrayFilter.java*/ /*@author:cherami */ /*email:cherami@163.net*/ import java.awt.image.*; public class GrayFilter extends RGBImageFilter { int modelStyle; public GrayFilter() { modelStyle=GrayModel.CS_MAX; canFilterIndexColorModel=true; } public GrayFilter(int style) { modelStyle=style; canFilterIndexColorModel=true; } public void setColorModel(ColorModel cm) { if (modelStyle==GrayModel.CS_MAX) { substituteColorModel(cm,new GrayModel(cm)); } else if (modelStyle==GrayModel.CS_FLOAT) { substituteColorModel(cm,new GrayModel(cm,modelStyle)); } } public int filterRGB(int x,int y,int pixel) { return pixel; } } /* GrayModel.java*/ /*@author:cherami */ /*email:cherami@163.net*/ import java.awt.image.*; public class GrayModel extends ColorModel { public static final int CS_MAX=0; public static final int CS_FLOAT=1; ColorModel sourceModel; int modelStyle; public GrayModel(ColorModel sourceModel) { super(sourceModel.getPixelSize()); this.sourceModel=sourceModel; modelStyle=0; } public GrayModel(ColorModel sourceModel,int style) { super(sourceModel.getPixelSize()); this.sourceModel=sourceModel; modelStyle=style; } public void setGrayStyle(int style) { modelStyle=style; } protected int getGrayLevel(int pixel) { if (modelStyle==CS_MAX) { return Math.max(sourceModel.getRed(pixel),Math.max(sourceModel.getGreen(pixel),sourceModel.getBlue(pixel))); } else if (modelStyle==CS_FLOAT){ return (int)(sourceModel.getRed(pixel)*0.3+sourceModel.getGreen(pixel)*0.59+sourceModel.getBlue(pixel)*0.11); } else { return 0; } } public int getAlpha(int pixel) { return sourceModel.getAlpha(pixel); } public int getRed(int pixel) { return getGrayLevel(pixel); } public int getGreen(int pixel) { return getGrayLevel(pixel); } public int getBlue(int pixel) { return getGrayLevel(pixel); } public int getRGB(int pixel) { int gray=getGrayLevel(pixel); return (getAlpha(pixel)<<24)+(gray<<16)+(gray<<8)+gray; } } 如果你有自己的算法或者想取得特殊的效果,你可以修改类GrayModel的方法getGrayLevel()。 色彩变换 根据上面的原理,我们也可以实现色彩变换,这样的效果就很多了。下面是一个反转变换的例子: /* ReverseColorModel.java*/ /*@author:cherami */ /*email:cherami@163.net*/ import java.awt.image.*; public class ReverseColorModel extends ColorModel { ColorModel sourceModel; public ReverseColorModel(ColorModel sourceModel) { super(sourceModel.getPixelSize()); this.sourceModel=sourceModel; } public int getAlpha(int pixel) { return sourceModel.getAlpha(pixel); } public int getRed(int pixel) { return ~sourceModel.getRed(pixel); } public int getGreen(int pixel) { return ~sourceModel.getGreen(pixel); } public int getBlue(int pixel) { return ~sourceModel.getBlue(pixel); } public int getRGB(int pixel) { return (getAlpha(pixel)<<24)+(getRed(pixel)<<16)+(getGreen(pixel)<<8)+getBlue(pixel); } } /* ReverseColorModel.java*/ /*@author:cherami */ /*email:cherami@163.net*/ import java.awt.image.*; public class ReverseFilter extends RGBImageFilter { public ReverseFilter() { canFilterIndexColorModel=true; } public void setColorModel(ColorModel cm) { substituteColorModel(cm,new ReverseColorModel(cm)); } public int filterRGB(int x,int y,int pixel) { return pixel; } } 要想取得自己的效果,需要修改ReverseColorModel.java中的三个方法,getRed、getGreen、getBlue。 下面是上面的效果的一个总的演示程序。 /*GrayImage.java*/ /*@author:cherami */ /*email:cherami@163.net*/ import java.awt.*; import java.awt.image.*; import javax.swing.*; import java.awt.color.*; public class GrayImage extends JFrame{ Image source,gray,gray3,clip,bigimg; BufferedImage bimg,gray2; GrayFilter filter,filter2; ImageIcon ii; ImageFilter cropFilter; int iw,ih; public GrayImage() { ii=new ImageIcon("images/11.gif"); source=ii.getImage(); iw=source.getWidth(this); ih=source.getHeight(this); filter=new GrayFilter(); filter2=new GrayFilter(GrayModel.CS_FLOAT); gray=createImage(new FilteredImageSource(source.getSource(),filter)); gray3=createImage(new FilteredImageSource(source.getSource(),filter2)); cropFilter=new CropImageFilter(5,5,iw-5,ih-5); clip=createImage(new FilteredImageSource(source.getSource(),cropFilter)); bigimg=source.getScaledInstance(iw*2,ih*2,Image.SCALE_DEFAULT); MediaTracker mt=new MediaTracker(this); mt.addImage(gray,0); try { mt.waitForAll(); } catch (Exception e) { } } public void paint(Graphics g) { Graphics2D g2=(Graphics2D)g; bimg=new BufferedImage(iw, ih, BufferedImage.TYPE_INT_RGB); Graphics2D srcG = bimg.createGraphics(); RenderingHints rhs = g2.getRenderingHints(); srcG.setRenderingHints(rhs); srcG.drawImage(source, 0, 0, null); ColorSpace graySpace=ColorSpace.getInstance(ColorSpace.CS_GRAY); ColorConvertOp op=new ColorConvertOp(graySpace,rhs); gray2=new BufferedImage(iw, ih, BufferedImage.TYPE_INT_RGB); op.filter(bimg,gray2); g2.drawImage(source,40,40,this); g2.drawImage(gray,80,40,this); g2.drawImage(gray2,120,40,this); g2.drawImage(gray3,160,40,this); g2.drawImage(clip,40,80,this); g2.drawImage(bigimg,80,80,this); } public void update(Graphics g) { paint(g); } public static void main(String args[]) { GrayImage m=new GrayImage(); m.setSize(400,400); m.setVisible(true); } } <

小小豆叮

让Java说话

为你的Java 1.3 应用程序和Applet添加说话能力 概要 这篇文章中,Tony Loton展示了不使用硬件和本地调用的,少于150行Java代码实现一个简单的语音引擎。此外,他提供了一个小zip文件,里面包含了使Java应用程序说话说需要的东西—仅仅用来娱乐或别的真正的应用程序。如果你刚刚接触Java Sound API,这篇文章将是一个很好的介绍。(1800字) 作者:Tony Loton 译者:Cocia Lin 为什么要使你的程序说话呢?首先,为了娱乐,这很适合象游戏这样的娱乐程序。并且还有很多严肃的应用领域。我想这虽然不是可视化界面的天生缺点,也是声音可用之处-- 或者过分一点 – 可以使你的眼睛离开你正在做的事情。 最近,我曾经应用一些技术在Web上获得HTML和XML信息的工作[请看 "Access the World's Biggest Database with Web DataBase Connectivity" (JavaWorld, March 2001)]。这让我将那个工作和我的这个想法结合来创建一个说话的Web浏览器。这样的一个浏览器可以使你听到你喜欢的网站上的信息摘录 – 新闻标题,例如 – 就象在外边溜狗或开车上班的途中收听收音机一样。当然,以现在的科技水平,你必须带上你的笔记本电脑和移动电话,但这些不切实际的设想在不久的将来,随着应用Java技术的智能电话的出现而变成现实,例如Nokia 9210(在美国叫9290). 也许对现在来说,能用的到的是一个email朗读器,这也得谢谢JavaMail API.这样的程序将定期的检查你的电子邮箱,并且你的注意被一个声音“你有新的email,你要我给你朗读吗?”吸引。相近的,考虑语音提醒 – 当连接到你的日常管理程序时 –- 电脑大喊“不要忘了10分钟后你和老板的会议!” 回到这些想法,或者你有更好的自己的想法,我们继续。我将演示怎样将我提供的zip文件添加的我们的工作中,这样,如果你觉得这些东西太难了,你就可以直接安装运行而跳过实现细节。 测试语音引擎 为了使用这个语音引擎,你需要将jw-0817-javatalk.zip添加到你的classpath,在命令行方式或Java程序中使用com.lotontech.speech.Talker。 命令行方式,象下面这样运行,输入: java com.lotontech.speech.Talker "h|e|l|oo" 在Java程序中,简单的包含着两行代码: com.lotontech.speech.Talker talker=new com.lotontech.speech.Talker(); talker.sayPhoneWord("h|e|l|oo"); 这里,你可能想知道命令行方式或sayPhoneWord(..)方法中的字符串格式”h|e|l|oo”的含义。让我来解释。 语音引擎依靠联结人的最小的语音单位的短声音例子来工作 – 在这里是英语。这些声音例子,叫做音体变位(allophone),是一个,两个,或三个字母标识符的标志。有些标识符是明显的,有些是不明显的,你能从语音学里看到这样的”hello”的表示。 h --发音你能想到 e --发音你能想到 l --发音你能想到,但注意,我将两个 “l” 变为一个”l” oo -- “hello”的发音,不是”bot”的,也不是”too”的 这里列出了能用到的音体变(allophone): a -- 例如 cat b -- 例如 cab c -- 例如 cat d -- 例如 dot e -- 例如 bet f -- 例如 frog g -- 例如 frog h -- 例如 hog i -- 例如 pig j -- 例如 jig k -- 例如 keg l -- 例如 leg m -- 例如 met n -- 例如 begin o -- 例如 not p -- 例如 pot r -- 例如 rot s -- 例如 sat t -- 例如 sat u -- 例如 put v -- 例如 have w -- 例如 wet y -- 例如 yet z -- 例如 zoo aa -- 例如 fake ay -- 例如 hay ee -- 例如 bee ii -- 例如 high oo -- 例如 go bb -- 变调b dd --变调d ggg -- 变调g hh --变调h ll --变调l nn --变调n rr -- 变调r tt -- 变调t yy --变调y ar -- 例如 car aer -- 例如 care ch -- 例如 which ck -- 例如 check ear -- 例如 beer er -- 例如 later err -- 例如 later (longer sound) ng -- 例如 feeding or -- 例如 law ou -- 例如 zoo ouu -- 例如 zoo (longer sound) ow -- 例如 cow oy -- 例如 boy sh -- 例如 shut th -- 例如 thing dth -- 例如 this uh -- 变调 u wh -- 例如 where zh -- 例如 Asian 人说话的每一个句子都有单词的升调和降调的变化。这个音调使说话听起来自然,富有感情,并且可以从句子语调确定这是疑问句。如果你听过Stephen Hawking的人造声音,你就能够理解我所说的了。考虑这两个句子: It is fake -- f|aa|k Is it fake? -- f|AA|k 你也许猜想,使用升调的方法是用大写字母。你要实际感受一下,我的提示是你要注意听元音字母 这是你使用这个软件需要知道的全部了,但是如果你对引擎罩下面的东西感兴趣,那么继续往下读。 实现语音引擎 语音引擎仅仅需要一个类来实现,包含四个方法。它使用J2SE1.3的Java Sound API。我不想提供一个全面的Java Sound API教程,你将通过例子学习。你将发现不是有很多需要你来做,并且说明能告诉你需要知道的。 这里是Talker类的基本定义: package com.lotontech.speech; import javax.sound.sampled.*; import java.io.*; import java.util.*; import java.net.*; public class Talker { private SourceDataLine line=null; } 如果你从命令行运行程序,下面的main(..)方法将作为一个入口服务。它取得命令行的第一个参数,如果有一个参数,将传递给sayPhoneWord(…)方法: /* *这个方法在命令行对一个指定的单词发音 */ public static void main(String args[]) { Talker player=new Talker(); if (args.length>0) player.sayPhoneWord(args[0]); System.exit(0); } 上面,SayPhoneWord(…)方法被main(…)方法调用,或者它被Java程序或Applet直接调用。它看起来比它本身难理解。本质上,它简单的一步一步解释单词的语音变位allophone – 被”|”标志分割的输入文本 – 在把他们一个一个通过声音输出通道输出。为了让它听起来更自然,合并每一个声音的结尾到下一个声音的开头: /* *这个方法使输入的单词发音 */ public void sayPhoneWord(String word) { // 为上一个声音设置一个字节数组 byte[] previousSound=null; //分割输入字符串 StringTokenizer st=new StringTokenizer(word,"|",false); while (st.hasMoreTokens()) { 为语音单位构造一个文件名 String thisPhoneFile=st.nextToken(); thisPhoneFile="/allophones/"+thisPhoneFile+".au"; 从文件中获得数据 byte[] thisSound=getSound(thisPhoneFile); if (previousSound!=null) { 合并上一个语音和现在的这个 int mergeCount=0; if (previousSound.length>=500 && thisSound.length>=500) mergeCount=500; for (int i=0; i0) line.write(data, 0, data.length); } drain(..)的代码: /* *刷新声音通道 */ private void drain() { if (line!=null) line.drain(); try {Thread.sleep(100);} catch (Exception e) {} } 现在,如果你回头看看sayPhoneWord(..)方法,你将发现还有一个方法我们还没有提到:getSound(..). getSound(..)从事先录制好的au文件中读出声音的字节数据。当我说文件时,指的是我提供的zip文件里的资源。我强调这点差别,因为你得到JAR资源控制 – 使用getResource(..)方法 – 这不同于得到一个普通文件的控制权。 为了有一个语音一个语音的读出数据,转换到声音格式,实例化一个声音输出行(为什么他们叫它SourceDateLine,我不知道),组合这些字节数据,我在下面代码中提供给你说明: /* *这个方法从文件中读出单独的语音并且构造一个字节矢量 */ private byte[] getSound(String fileName) { try { URL url=Talker.class.getResource(fileName); AudioInputStream stream = AudioSystem.getAudioInputStream(url); AudioFormat format = stream.getFormat(); 转换一个ALAW/ULAW声音到PCM if ((format.getEncoding() == AudioFormat.Encoding.ULAW) || (format.getEncoding() == AudioFormat.Encoding.ALAW)) { AudioFormat tmpFormat = new AudioFormat( AudioFormat.Encoding.PCM_SIGNED, format.getSampleRate(), format.getSampleSizeInBits() * 2, format.getChannels(), format.getFrameSize() * 2, format.getFrameRate(), true); stream = AudioSystem.getAudioInputStream(tmpFormat, stream); format = tmpFormat; } DataLine.Info info = new DataLine.Info( Clip.class, format, ((int) stream.getFrameLength() * format.getFrameSize())); if (line==null) { // -- Output line not instantiated yet – // -- Can we find a suitable kind of line? -- DataLine.Info outInfo = new DataLine.Info(SourceDataLine.class, format); if (!AudioSystem.isLineSupported(outInfo)) { System.out.println("Line matching " + outInfo + " not supported."); throw new Exception("Line matching " + outInfo + " not supported."); } 打开资源数据行(输出行output line) line = (SourceDataLine) AudioSystem.getLine(outInfo); line.open(format, 50000); line.start(); } //一些尺寸计算 int frameSizeInBytes = format.getFrameSize(); int bufferLengthInFrames = line.getBufferSize() / 8; int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes; byte[] data=new byte[bufferLengthInBytes]; //读出数据字节并计算 int numBytesRead = 0; if ((numBytesRead = stream.read(data)) != -1) { int numBytesRemaining = numBytesRead; } //裁剪字节数组到正确尺寸 byte[] newData=new byte[numBytesRead]; for (int i=0; i h|e|l|oo there -> dth|aer 或者,象这样运行它: java com.lotontech.speech.Converter "I like to read JavaWorld" 看到(并且听到)这些: i -> ii like -> l|ii|k to -> t|ouu read -> r|ee|a|d java -> j|a|v|a world -> w|err|l|d 如果你想知道它是怎样工作的,我将告诉你我的方法很简单,应用通常的顺序的一套文本替换规则。有几个例子规则,你可能喜欢应用精神上的,顺序的方式,这些例子是”ant”,”want”,”wanted”,”unwanted”,”unique”: 替换 "*unique*"使用 "|y|ou|n|ee|k|" 替换"*want*"使用"|w|o|n|t|" 替换"*a*"使用"|a|" 替换"*e*"使用"|e|" 替换"*d*"使用"|d|" 替换"*n*" 使用"|n|" 替换"*u*"使用"|u|" 替换"*t*" 使用"|t|" ”unwanted”的顺序将是这样: unwanted un[|w|o|n|t|]ed (rule 2) [|u|][|n|][|w|o|n|t|][|e|][|d|] (rules 4, 5, 6, 7) u|n|w|o|n|t|e|d (with surplus characters removed) 你应该看到,包含ant的want被用几种不同的方式朗读。你也应该看到对于unique来说的特殊情况,应该被读为y|ou..而不是u|n…. 电脑里的精灵,对你说话 这篇文章提供一个可以使用Java 1.3运行的简便的语音引擎。如果你研究这些代码,你可以得到一些关于JavaSound API播放音频片断的有用方法。要想使这个引擎真的能用,你要思考文本到语音的转换方法,这真的是我的一个主要想法。在这个引擎中,你要想出大量的文本转换规则,还要应用一些好的优先顺序。我希望你有比我强的毅力。 最后,你可能还记得我说过的Nokia 9210。我有一部,它支持Java,我决定用Java使它说话。我也想使applet(Java2的以前版本)在浏览器中说话。这些技术依靠J2SE 1.3声音引擎,现在是可用的。一个不同的方法是需要的,依靠简单的Java AudioClip 接口。不像你想象的那样简单,但我在其上工作。 关于作者 Tony Loton为他的公司工作 – LOTONTech Limited – 提供软件解决方案,顾问,培训和技术写作服务。写作的小虫好像在这一年里始终叮咬着他,他为John Wiley & Sons 和 Wrox Press出版社写书。 关于译者 Cocia Lin(cocia@163.com)是程序员。它拥有学士学位,现在专攻Java相关技术,刚刚开始在计算机领域折腾。 相关资源 You'll find the speech engine and related source code in the jw-0817-javatalk.zip file: http://www.javaworld.com/jw-08-2001/javatalk/jw-0817-javatalk.zip Go to java.sun.com's "Java Sound API" page for documentation, download information, and FAQ: http://java.sun.com/products/java-media/sound/ To see how the speech engine will combine with Webpages to build my talking browser, read "Access the World's Biggest Database with Web Database Connectivity," Tony Loton (JavaWorld, March 2001): http://www.javaworld.com/javaworld/jw-03-2001/jw-0316-webdb.html In "Make an EJB from Any Java Class with Java Reflection" (JavaWorld, December 2000), Tony Loton explains how to turn any Java class into an EJB, and any single-tier Java application into an enterprise application: http://www.javaworld.com/jw-12-2000/jw-1215-anyclass.html "Program Multimedia with JMF," Budi Kurniawan (JavaWorld): Part 1: Go multimedia by learning how the Java Media Framework compares to your stereo system (April 2001) Part 2: Jump into Java Media Framework's important classes and interfaces (May 2001) "Add MP3 capabilities to Java Sound with SPI," Dan Becker (JavaWorld, November 2000): http://www.javaworld.com/jw-11-2000/jw-1103-mp3.html Sign up for the JavaWorld This Week free weekly email newsletter to learn what's new at JavaWorld: http://www.idg.net/jw-subscribe You'll find a wealth of IT-related articles from our sister publications at IDG.net <

小小豆叮

使用JAR

翻译:Cherami email:cherami@163.net 原文:http://developer.java.sun.com/developer/TechTips/2000/tt0411.html 你试过使用JAR 文件归档吗?你可能已经听说过一个叫"tar" 或者磁带归档文件的UNIX 工具,它通常用于将一组文件进行备份。在JDK 1.1 及以后的发布中,有一个类似的工具"jar" 或者叫Java 打包器。下面是使用的方法。 使用下面的方法创建一个 JAR 包: $ jar cf archive.jar file1 file2 ... 创建归档包以后,你可以列出其中的内容: $ jar tf archive.jar 而且可以将文件从中提取出来: $ jar xf archive.jar [file1 file2 ...] 为什么JAR 工具很重要呢?毕竟有很多不同的ZIP 工具用于打包并压缩文件。它的一个重要的用处就是绑定多个类文件。一组类文件可以构成一个包或者库。例如,在Windows 95 或者 NT 下,你可以说: CLASSPATH="lib1.jar;lib2.jar;" 它的另一个非常重要的用法就是优化applet的加载。考虑下面这个简单的applet: // applet.java import java.awt.*; import java.applet.*; public class applet extends Applet { public void paint(Graphics g) { String s = Message.getIntro(getParameter("intro")); g.drawString(s, 25, 25); } } 它访问一个辅助类: // Message.java public class Message { public static String getIntro(String t) { if (t != null) return t; else return "Hello world!"; } } 你用HTML 代码调用这个applet,例如: Hello World Example Applet 注意"archive"属性。没有这个设置,每个像Message这样的辅助类被加载时都会向保存该HTML页面的服务器调用一个独立的请求。但是使用JAR 文件,不同的类文件可以被更加高效的下载。 在这个例子中,你使用下面的命令创建archive.jar: $ javac applet.java $ jar cf applet.jar *.class 也就是用当前目录下的所有的类文件构建那个JAR文件。 最后注意:JAR 文件也可用于JavaBeans。 译者注:jar文件的格式就是标准的zip文件,可以用最流行的zip工具winzip创建jar文件,创建完以后只需要修改扩展名即可,当然也可以在创建的时候就指定文件名。当然也可以用winzip解包jar文件和查看列表。需要注意的是另一个zip工具winrar不能用于创建jar文件,因为winrar的压缩比例更大,不是标准的zip格式,但是可以用它解包jar文件。 <

小小豆叮

计算Java日期

概要 不管你是处理财务交易还是计划着下一步的行动,你都要知道怎样在Java中建立,使用和显示日期。这需要你简单的查阅一下相应类的API参考:一个日期可以创建3个相关类的对象。这篇文章告诉你你想要知道的内容。(3,000字) 作者:Robert Nielsen 翻译:Cocia Lin Java统计从1970年1月1日起的毫秒的数量表示日期。也就是说,例如,1970年1月2日,是在1月1日后的86,400,000毫秒。同样的,1969年12月31日是在1970年1月1日前86,400,000毫秒。Java的Date类使用long类型纪录这些毫秒值.因为long是有符号整数,所以日期可以在1970年1月1日之前,也可以在这之后。Long类型表示的最大正值和最大负值可以轻松的表示290,000,000年的时间,这适合大多数人的时间要求。 Date 类 Date类可以在java.util包中找到,用一个long类型的值表示一个指定的时刻。它的一个有用的构造函数是Date(),它创建一个表示创建时刻的对象。getTime()方法返回Date对象的long值。在下面的程序中,我使用Date()构造函数创建一个表示程序运行时刻的对象,并且利用getTime()方法找到这个日期代表的毫秒数量: import java.util.*; public class Now { public static void main(String[] args) { Date now = new Date(); long nowLong = now.getTime(); System.out.println("Value is " + nowLong); } } 当我运行这个程序后,我得到972,568,255,150.快速确认一下这个数字,起码在一个合理的范围:它不到31年,这个数值相对1970年1月1日到我写这篇文章的时间来说,是合理的。计算机是这个毫秒值表示时间,人们可不愿意说" 我将在996,321,998,34见到你。"幸运的是,Java提供了一个转换Date对象到字符串的途径,表示成传统的形式。我们在下一节讨论DateFormat类,它直观的建立日期字符串。 DateFormat类 DateFormat类的一个目标是建立一个人们能够识别的字符串。然而,因为语言的差别,不是所有的人希望看到严格的相同格式的日期。法国人更喜欢看到"25 decembre 2000,",但是美国人习惯看到"December 25,2000."所以一个DateFormat的实例创建以后,这个对象包含了日期的显示格式的信息。如果使用用户电脑区域设置缺省的格式,你可以象下面那样,创建DateFormat对象,使用getDateInstance()方法: DateFormat df = DateFormat.getDateInstance(); DateFormat类在java.text包中可以找到。 转换成字符串 你可以使用format()方法转换Date对象为一个字符串。下面的示例程序说明了这个问题: import java.util.*; import java.text.*; public class NowString { public static void main(String[] args) { Date now = new Date(); DateFormat df = DateFormat.getDateInstance(); String s = df.format(now); System.out.println("Today is " + s); } } 在上面的代码中,展示了没有参数,使用缺省格式的getDateInstance()方法。Java还提供了几个选择日期格式,你可以通过使用重载的getDateInstance(int style)获得。出于方便的原因,DateFormat提供了几种预置的常量,你可以使用这些常量参数。下面是几个SHORT, MEDIUM, LONG, 和FULL类型的示例: import java.util.*; import java.text.*; public class StyleDemo { public static void main(String[] args) { Date now = new Date(); DateFormat df = DateFormat.getDateInstance(); DateFormat df1 = DateFormat.getDateInstance(DateFormat.SHORT); DateFormat df2 = DateFormat.getDateInstance(DateFormat.MEDIUM); DateFormat df3 = DateFormat.getDateInstance(DateFormat.LONG); DateFormat df4 = DateFormat.getDateInstance(DateFormat.FULL); String s = df.format(now); String s1 = df1.format(now); String s2 = df2.format(now); String s3 = df3.format(now); String s4 = df4.format(now); System.out.println("(Default) Today is " + s); System.out.println("(SHORT) Today is " + s1); System.out.println("(MEDIUM) Today is " + s2); System.out.println("(LONG) Today is " + s3); System.out.println("(FULL) Today is " + s4); } } 程序输出如下: (Default) Today is Nov 8, 2000 (SHORT) Today is 11/8/00 (MEDIUM) Today is Nov 8, 2000 (LONG) Today is November 8, 2000 (FULL) Today is Wednesday, November 8, 2000 同样的程序,在我的电脑上使用缺省设置运行后,改变区域设置为瑞典,输出如下: (Default) Today is 2000-nov-08 (SHORT) Today is 2000-11-08 (MEDIUM) Today is 2000-nov-08 (LONG) Today is den 8 november 2000 (FULL) Today is den 8 november 2000 从这里,你能看到,瑞典的月份不是大写的(虽然November还是november).还有,LONG和FULL版本在瑞典语中是一样的,但是美国英语却不同。另外,有趣的是,瑞典语单词的星期三,onsdag,没有包含在FULL日期里,英语却包括。 注意你能够使用getDateInstance()方法改变DateFormat实例的语种;但是,在上面的例子中,是通过改变Windows98的控制面板的区域设置做到的。不同的地方的区域设置不同,结果就不同,这样有好处,也有不足,Java程序员应该了解这些。一个好处是Java程序员可以只写一行代码就可以显示日期,而且世界不同地区的电脑运行同样的程序会有不用的日期格式。 但是这也是一个缺点,当程序员希望显示同一种格式的时--这也有可取之处,举例来说,在程序中混合输出文本和日期,如果文本是英文,我们就不希望日期格式是其他的格式,象德文或是西班牙文。如果程序员依靠日期格式编程,日期格式将根据运行程序所在电脑的区域设置不用而不同。 解析字符串 通过parse()方法,DateFormat能够以一个字符串创立一个Date对象。这个方法能抛出ParseException异常,所以你必须使用适当的异常处理技术。下面的例子程序通过字符串创建Date对象: import java.util.*; import java.text.*; public class ParseExample { public static void main(String[] args) { String ds = "November 1, 2000"; DateFormat df = DateFormat.getDateInstance(); try { Date d = df.parse(ds); } catch(ParseException e) { System.out.println("Unable to parse " + ds); } } } 在创建一个任意的日期时parse()方法很有用。我将通过另一种方法创建一个任意得日期。同时,你将看到怎样进行基本日期计算,例如计算90天后的另一天。你可以使用GregorianCalendar类来完成这个任务。 GregorianCalendar类 创建一个代表任意日期的一个途径使用GregorianCalendar类的构造函数,它包含在java.util包中: GregorianCalendar(int year, int month, int date) 注意月份的表示,一月是0,二月是1,以此类推,是12月是11。因为大多数人习惯于使用单词而不是使用数字来表示月份,这样程序也许更易读,父类Calendar使用常量来表示月份:JANUARY, FEBRUARY,等等。所以,创建Wilbur 和 Orville制造第一架动力飞机的日期(December 17, 1903),你可以使用: GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17); 出于清楚的考虑,你应该使用前面的形式。但是,你也应该学习怎样阅读下面的短格式。下面的例子同样表示December 17,1903(记住,在短格式中,11表示December) GregorianCalendar firstFlight = new GregorianCalendar(1903, 11, 17); 在上一节中,你学习了转换Date对象到字符串。这里,你可以做同样的事情;但是首先,你需要将GregorianCalendar对象转换到Date。要做到这一点,你可以使用getTime()方法,从它得父类Calendar继承而来。GetTime()方法返回GregorianCalendar相应的Date对象。你能够创建GregorianCalendar对象,转换到Date对象,得到和输出相应的字符串这样一个过程。下面是例子: import java.util.*; import java.text.*; public class Flight { public static void main(String[] args) { GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17); Date d = firstFlight.getTime(); DateFormat df = DateFormat.getDateInstance(); String s = df.format(d); System.out.println("First flight was " + s); } } 有时候创建一个代表当前时刻的GregorianCalendar类的实例是很有用的。你可以简单的使用没有参数的GregorianCalendar构造函数,象这样: GregorianCalendar thisday = new GregorianCalendar(); 一个输出今天日期的例子程序,使用GregorianCalendar对象: import java.util.*; import java.text.*; class Today { public static void main(String[] args) { GregorianCalendar thisday = new GregorianCalendar(); Date d = thisday.getTime(); DateFormat df = DateFormat.getDateInstance(); String s = df.format(d); System.out.println("Today is " + s); } } 注意到,Date()构造函数和GregorianCalendar()构造函数很类似:都创建一个对象,条件简单,代表今天。 日期处理 GregorianCalendar类提供处理日期的方法。一个有用的方法是add().使用add()方法,你能够增加象年,月数,天数到日期对象中。要使用add()方法,你必须提供要增加的字段,要增加的数量。一些有用的字段是DATE, MONTH, YEAR, 和 WEEK_OF_YEAR。下面的程序使用add()方法计算未来80天的一个日期。在Jules的<环球80天>是一个重要的数字,使用这个程序可以计算Phileas Fogg从出发的那一天1872年10月2日后80天的日期: import java.util.*; import java.text.*; public class World { public static void main(String[] args) { GregorianCalendar worldTour = new GregorianCalendar(1872, Calendar.OCTOBER, 2); worldTour.add(GregorianCalendar.DATE, 80); Date d = worldTour.getTime(); DateFormat df = DateFormat.getDateInstance(); String s = df.format(d); System.out.println("80 day trip will end " + s); } } 这个例子是想象的,但在一个日期上增加天数是一个普遍的操作:影碟可以租3天,图书馆可以借书21天,商店经常需要将购买的物品在30天内卖出。下面的程序演示了使用年计算: import java.util.*; import java.text.*; public class Mortgage { public static void main(String[] args) { GregorianCalendar mortgage = new GregorianCalendar(1997, Calendar.MAY, 18); mortgage.add(Calendar.YEAR, 15); Date d = mortgage.getTime(); DateFormat df = DateFormat.getDateInstance(); String s = df.format(d); System.out.println("15 year mortgage amortized on " + s); } } add()一个重要的副作用是它改变的原来的日期。有时候,拥有原始日期和修改后的日期很重要。不幸的是,你不能简单的创建一个GregorianCalendar对象,设置它和原来的相等(equal)。原因是两个变量指向同一个Date()对象地址。如果Date对象改变,两个变量就指向改变后的日期对象。代替这种做法,应该创建一个新对象。下面的程序示范了这种做法: import java.util.*; import java.text.*; public class ThreeDates { public static void main(String[] args) { GregorianCalendar gc1 = new GregorianCalendar(2000, Calendar.JANUARY, 1); GregorianCalendar gc2 = gc1; GregorianCalendar gc3 = new GregorianCalendar(2000, Calendar.JANUARY, 1); //Three dates all equal to January 1, 2000 gc1.add(Calendar.YEAR, 1); file://gc1 and gc2 are changed DateFormat df = DateFormat.getDateInstance(); Date d1 = gc1.getTime(); Date d2 = gc2.getTime(); Date d3 = gc3.getTime(); String s1 = df.format(d1); String s2 = df.format(d2); String s3 = df.format(d3); System.out.println("gc1 is " + s1); System.out.println("gc2 is " + s2); System.out.println("gc3 is " + s3); } } 程序运行后,gc1和gc2被变成2001年(因为两个对象指向同一个Date,而Date已经被改变了)。对象gc3指向一个单独的Date,它没有被改变。 计算复习日期 在这节,你将看到一个依据现实世界的例子。这个详细的程序计算过去一个具体的日期。例如,你阅读这篇文章,你想要记住一个印象深刻的知识点。如果你没有照片一样的记忆力,你就要定期的复习这些新资料,这将帮助你记住它。关于复习系统,Kurt Hanks 和 Gerreld L. Pulsipher在他们的< Five Secrets to Personal Productivity个人能力的5个秘密>中有讨论,建议看过第一眼后马上回顾一下,然后是1天后,1个星期后,1个月后,3个月后,1年后。我的这篇文章,你要马上回顾一下,从现在算起,再就是明天,然后是1个星期,1个月,3个月,1年后。我们的程序将计算这些日期。 这个程序非常有用的,它将是PIM(Personal Information Manager个人信息管理器)的一个组成部分,并将确定复习时间。在下面的程序中,getDates()方法对一个返回日期数组(复习日期)的电子软件很有用。另外,你可以返回单独的一个日期,使用getFirstDay(),getOneDay(),getOneWeek(),getOnMonth()和getOneYear().当时间范围超出这个PIM的ReviewDates的计算范围时ReviewDates类演示了怎样计算时间段。现在,你可以容易的修改它用来处理你需要的时间段,象图书馆借书,录影带租赁和抵押计算。首先,ReviewDates类显示在下面: import java.util.*; import java.text.*; public class ReviewDates { private GregorianCalendar firstDay, oneDay, oneWeek, oneMonth, oneQuarter, oneYear; final int dateArraySize = 6; ReviewDates(GregorianCalendar gcDate) { int year = gcDate.get(GregorianCalendar.YEAR); int month = gcDate.get(GregorianCalendar.MONTH); int date = gcDate.get(GregorianCalendar.DATE); firstDay = new GregorianCalendar(year, month, date); oneDay = new GregorianCalendar(year, month, date); oneWeek = new GregorianCalendar(year, month, date); oneMonth = new GregorianCalendar(year, month, date); oneQuarter = new GregorianCalendar(year, month, date); oneYear = new GregorianCalendar(year, month, date); oneDay.add(GregorianCalendar.DATE, 1); oneWeek.add(GregorianCalendar.DATE, 7); oneMonth.add(GregorianCalendar.MONTH, 1); oneQuarter.add(GregorianCalendar.MONTH, 3); oneYear.add(GregorianCalendar.YEAR, 1); } ReviewDates() { this(new GregorianCalendar()); } public void listDates() { DateFormat df = DateFormat.getDateInstance(DateFormat.LONG); Date startDate = firstDay.getTime(); Date date1 = oneDay.getTime(); Date date2 = oneWeek.getTime(); Date date3 = oneMonth.getTime(); Date date4 = oneQuarter.getTime(); Date date5 = oneYear.getTime(); String ss = df.format(startDate); String ss1 = df.format(date1); String ss2 = df.format(date2); String ss3 = df.format(date3); String ss4 = df.format(date4); String ss5 = df.format(date5); System.out.println("Start date is " + ss); System.out.println("Following review dates are:"); System.out.println(ss1); System.out.println(ss2); System.out.println(ss3); System.out.println(ss4); System.out.println(ss5); System.out.println(); } public GregorianCalendar[] getDates() { GregorianCalendar[] memoryDates = new GregorianCalendar[dateArraySize]; memoryDates[0] = firstDay; memoryDates[1] = oneDay; memoryDates[2] = oneWeek; memoryDates[3] = oneMonth; memoryDates[4] = oneQuarter; memoryDates[5] = oneYear; return memoryDates; } public GregorianCalendar getFirstDay() { return this.firstDay; } public GregorianCalendar getOneDay() { return this.oneDay; } public GregorianCalendar getOneWeek() { return this.oneWeek; } public GregorianCalendar getOneMonth() { return this.oneMonth; } public GregorianCalendar getOneQuarter() { return this.oneQuarter; } public GregorianCalendar getOneYear() { return this.oneYear; } } 下面是使用ReviewDates类列出复习日期的例子程序: import java.util.*; public class ShowDates { public static void main(String[] args) { ReviewDates rd = new ReviewDates(); rd.listDates(); GregorianCalendar gc = new GregorianCalendar(2001, Calendar.JANUARY, 15); ReviewDates jan15 = new ReviewDates(gc); jan15.listDates(); } } 总结 这篇文章介绍了关于日期处理的3个重要的类:Date,DateFormat,GregorianCalendar.这些类让你创建日期,转换成字符串,和计算日期基本元素。处理Java中的日期问题,这篇文章只是冰山一角。可是,我在这里介绍的类和方法不仅仅是你学习高级技术的跳板,这些类和方法本身就可以处理很多通常的日期相关的任务 关于作者 Robert Nielsen是SCJP。他拥有硕士学位,专攻计算机教育,并且在计算机领域执教多年。他也在各样的杂志上发表过很多计算机相关的文章。 关于译者 Cocia Lin(cocia@163.com)是程序员。他拥有学士学位,现在专攻Java相关技术,刚刚开始在计算机领域折腾。 <

小小豆叮

构造方法的初始化顺序

翻译:Cherami email:cherami@163.net 原文: http://java.sun.com/jdc/TechTips/2000/tt1205.html 想像一下你正在用java写程序,并且用下面的代码初始化类 A 和 B 的对象: class A { int a = f(); int f() { return 1; } } class B extends A { int b = a; int f() { return 2; } } public class CtorDemo1 { public static void main(String args[]) { B bobj = new B(); System.out.println(bobj.b); } } 现在,好像很明显的当初始化完成后,bobj.b的值将是1。毕竟,类B中的b 的值是用类A中的a的值初始化的,而a 是用f 的值初始化的,而它的值为1,对吗? 实际上, bobj.b 的值是2,要知道为什么需要知道对象初始化的问题。 当一个对象被创建时,初始化是以下面的顺序完成的: 1. 设置成员的值为缺省的初始值 (0, false, null) 2. 调用对象的构造方法 (但是还没有执行构造方法体) 3. 调用父类的构造方法 4. 使用初始化程序和初始块初始化成员 5. 执行构造方法体 看看在实际中是如何一步一步完成的,看看下面的例子: class A { A() { System.out.println("A.A called"); } } class B extends A { int i = f(); int j; { j = 37; System.out.println("initialization block executed"); } B() { System.out.println("B.B called"); } int f() { System.out.println("B.f called"); return 47; } } public class CtorDemo2 { public static void main(String args[]) { B bobj = new B(); } } 程序的输出是: A.A called B.f called initialization block executed B.B called B 的构造方法被调用,但是最先做的事情是隐含的调用父类的构造方法。父类必须自己负责初始化它自己的状态而不是让子类来做。 然后B对象的成员被初始化,这包含一个对B.f 的调用和包围在{}中的初始块的执行。最后B的构造方法体被执行。 你可能会问“什么是对父类的构造方法的隐含调用”。这意味着如果你的构造方法的第一行不是下面内容之一: super(); super(args); this(); this(args); 则有下面的调用: super(); 提供给构造方法的第一行。 如果类没有构造方法呢?在这种情况下,一个缺省的构造方法(也叫"无参构造方法")由java编译器自动生成。缺省构造方法只有在类没有任何其它的构造方法时才产生。 更深入的明白这个,假设在文件A.java中有这样的代码: public class A { public static void main(String args[]) { A aref = new A(); } } 如果你想编译然后列出A.class 中的字节码,输入下面的内容: $ javac A.java $ javap -c -classpath . A 输出: Compiled from A.java public class A extends java.lang.Object { public A(); public static void main(java.lang.String[]); } Method A() 0 aload_0 1 invokespecial #1 4 return Method void main(java.lang.String[]) 0 new #2 3 dup 4 invokespecial #3 7 astore_1 8 return 在main 中,注意对 A 的构造方法的调用(就是invokespecial 行),以及A的构造方法中产生的类似的对Object 构造方法的调用。 如果父类没有缺省构造方法,你必须明确使用"super(args)"调用父类的某个构造方法,例如,下面是一个错误的用法: class A { A(int i) {} } class B extends A {} 在上面的情况下, A 没有缺省的构造方法,但是B的构造方法必须调用A的某个构造方法。 让我们来看看初始化的另一个例子: class A { A() { System.out.println("A.A called"); } A(int i) { this(); System.out.println("A.A(int) called"); } } class B extends A { int i = f(); int j; { j = 37; System.out.println("initialization block executed"); } B() { this(10); System.out.println("B.B() called"); } B(int i) { super(i); System.out.println("B.B(int) called"); } int f() { System.out.println("B.f called"); return 47; } } public class CtorDemo3 { public static void main(String args[]) { B bobj = new B(); } } 程序的输出是: A.A called A.A(int) called B.f called initialization block executed B.B(int) called B.B() called 这个例子明确使用super() 和 this() 调用。this()调用是调用同一个类中的另一个构造方法;这个方法被称为“显式构造方法调用”。当那样的构造方法被调用,它将执行通常的super() 过程以及后续的操作。这意味着A.A 的方法体在A.A(int)之前执行,而这两个都在B.B(int) 和B.B 前执行。 如果返回第一个例子,你就可以回答为什么打印的是2而不是1。B 没有构造方法,因此生成一个缺省构造方法,然后它调用super(),然后调用A 产生的缺省构造方法。 然后A中的成员被初始化,成员a 被设置为方法f()的值,但是因为B 对象正被初始化,f() 返回值2。换句话说,调用的是B中的f()方法。 A产生的构造方法体被执行,然后B的成员被初始化,而b 被赋予值a,也就是2。最后,B的构造方法被执行。 最后一个例子说明了第一个例子的一个小小的变异版本: class A { int a = f(); int f() { return 1; } } class B extends A { int b = 37; int f() { return b; } } public class CtorDemo4 { public static void main(String args[]) { B bobj = new B(); System.out.println(bobj.a); System.out.println(bobj.f()); } } 程序的输出是: 0 37 你可能会期望输出的两个值bobj.a 和bobj.f()是一样的,但是正如你看到的他们不一样。这是正确的,即使是在a是从B的f方法中初始化的并且打印的是a 和 B的 f 方法的值。 这儿的问题是当a通过对B的f方法调用而初始化,而该方法返回成员b的值,而该成员还没有被初始化。因为这个,b的值就是刚开始的初始值0。 这些例子解释了编程中重要的一点――在对象的构造阶段调用可重载的方法是不明智的。 <

小小豆叮