Merlin

Life was like a box of chocolates. You never know what you're gonna get.

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  34 随笔 :: 0 文章 :: 40 评论 :: 0 Trackbacks

2006年7月20日 #

http://www.putclub.com/index.php
posted @ 2006-07-20 22:06 Merlin 阅读(232) | 评论 (0)编辑 收藏

2006年7月19日 #

有可能在闪躲炮弹和执行精确攻击的演练中学会继承、多态性、事件处理以及内部类这些内容吗?Robocode 这个游戏即将为全世界的 Java 开发者实现这个愿望,它把游戏风潮变成了教学工具,人们对它的上瘾程度令人吃惊。请跟随 Sing Li 一起拆解 Robocode,同时着手建造属于自己的、定制的、小而精悍的战斗机器。

Robocode 是一个很容易使用的机器人战斗仿真器,可以在所有支持 Java 2 的平台上运行。您创建一个机器人,把它放到战场上,然后让它同其他开发者们创建的机器人对手拼死战斗到底。Robocode 里有一些预先做好的机器人对手让你入门,但一旦您不再需要它们,就可以把您自己创建的机器人加入到正在世界范围内形成的某个联盟里去和世界最强手对阵。

每个 Robocode 参加者都要利用 Java 语言元素创建他或她的机器人,这样就使从初学者到高级黑客的广大开发者都可以参与这一娱乐活动。初级的 Java 的开发者们可以学习一些基础知识:调用 API 代码、阅读 Javadoc、继承、内部类、事件处理等等。高级开发者们可以在构建“最优品种”的软件机器人全球竞赛中提高他们的编程技巧。在本文中,我们将介绍 Robocode,并指导您从构建您平生第一个 Robocode 机器人开始征服世界。我们还将看一下迷人的“后台”机制,正是它使得 Robocode 起作用。

下载并安装 Robocode

Robocode 是 Mathew Nelson 的智慧之作,他是 IBM Internet 部门 Advanced Technology 的软件工程师。请首先访问 IBM alphaWorks Robocode 页面。在这个页面上,您可以找到 Robocode 系统最新的可执行文件。这个分发包是一个自包含的安装文件,在下载该分发包之后,您就可以使用下面的命令行在您的系统上安装这个软件包(当然,我们假定您的机器上已经预安装了 Java VM(JDK 1.3.x)):

java -jar robocode-setup.jar

 在安装过程中,Robocode 将问您是否要使用这个外部的 Java VM 来编译机器人。您也可以选择使用作为 Robocode 分发包一部分而提供的 Jikes 编译器。

安装完成后,您可以通过 shell 脚本(robocode.sh)、批处理文件(robocode.bat)或桌面上的图标来启动 Robocode 系统。此时,战场将会出现。在此,您可以通过菜单调用 Robot Editor 和 compiler。





回页首


Robocode 系统组件

当您激活 Robocode 时,将看到两个相关的 GUI 窗口,这两个窗口构成了 Robocode 的 IDE:

  • 战场
  • Robot Editor

图 1 展示了处于工作状态的战场和 Robot Editor。


图 1. Robocode IDE
Robocode IDE

战场是机器人之间进行战斗直至分出胜负的场地。主要的仿真引擎被置于其中,并且允许您在这里创建战斗、保存战斗以及打开新建的或现有的战斗。通过界面区域内的控件,您可以暂停或继续战斗、终止战斗、消灭任何机器人个体或获取任何机器人的统计数据。此外,您可以在此屏幕上激活 Robot Editor。

Robot Editor 是一个定制的文本编辑器,它可以用于编辑生成机器人的 Java 源文件。在它的菜单里集成了 Java 编译器(用于编译机器人代码)以及定制的 Robot 打包器。由 Robot Editor 创建并成功编译的所有机器人都会处于战场上一个部署就绪的位置。

Robocode 里的每个机器人都由一个或多个 Java 类构成。这些类可以被压缩成一个 JAR 包。为此,Robocode 的最新版本提供了一个可以在战场 GUI 窗口中激活的“Robot Packager”。





回页首


对 Robocode 机器人的详细分析

在写这篇文章时,Robocode 机器人是一个图形化的坦克。图 2 是一个典型的 Robocode 机器人的图解。


图 2. 对 Robocode 机器人的详细分析
Robocode 机器人

请注意,机器人有一门可以旋转的炮,炮上面的雷达也是可以旋转的。机器人坦克车(Vehicle)、炮(Gun)以及雷达(Radar)都可以单独旋转,也就是说,在任何时刻,机器人坦克车、炮以及雷达都可以转向不同的方向。缺省情况下,这些方向是一致的,都指向坦克车运动的方向。





回页首


Robot 命令

Robocode 机器人的命令集都收录在 Robocode API Javadoc 中。您将会发现这些命令都是 robocode.Robot 类的公共方法。在这一部分,我们将分类讨论每个可用的命令。

移动机器人、炮和雷达

让我们从移动机器人及其装备的基本命令开始:

  • turnRight(double degree)turnLeft(double degree) 使机器人转过一个指定的角度。
  • ahead(double distance)back(double distance) 使机器人移动指定的像素点距离;这两个方法在机器人碰到墙或另外一个机器人时即告完成。
  • turnGunRight(double degree)turnGunLeft(double degree) 使炮可以独立于坦克车的方向转动。
  • turnRadarRight(double degree)turnRadarLeft(double degree) 使炮上面的雷达转动,转动的方向也独立于炮的方向(以及坦克车的方向)。

这些命令都是在执行完毕后才把控制权交还给程序。此外,转动坦克车的时候,除非通过调用下列方法分别指明炮(和雷达)的方向,否则炮(和雷达)的指向也将移动。

  • setAdjustGunForRobotTurn(boolean flag) :如果 flag 被设置成 true,那么坦克车转动时,炮保持原来的方向。
  • setAdjustRadarForRobotTurn(boolean flag) :如果 flag 被设置成 true,那么坦克车(和炮)转动时,雷达会保持原来的方向。
  • setAdjustRadarForGunTurn(boolean flag) :如果 flag 被设置成 true,那么炮转动时,雷达会保持原来的方向。而且,它执行的动作如同调用了 setAdjustRadarForRobotTurn(true)

获取关于机器人的信息

有许多方法可以得到关于机器人的信息。下面简单列举了常用的方法调用:

  • getX()getY() 可以捕捉到机器人当前的坐标。
  • getHeading()getGunHeading()getRadarHeading() 可以得出坦克车、炮或雷达当前的方向,该方向是以角度表示的。
  • getBattleFieldWidth()getBattleFieldHeight() 可以得到当前这一回合的战场尺寸。

射击命令

一旦您掌握了移动机器人以及相关的武器装备的方法,您就该考虑射击和控制损害的任务了。每个机器人在开始时都有一个缺省的“能量级别”,当它的能量级别减小到零的时候,我们就认为这个机器人已经被消灭了。射击的时候,机器人最多可以用掉三个能量单位。提供给炮弹的能量越多,对目标机器人所造成的损害也就越大。 fire(double power)fireBullet(double power) 用来发射指定能量(火力)的炮弹。调用的 fireBullet() 版本返回 robocode.Bullet 对象的一个引用,该引用可以用于高级机器人。

事件

每当机器人在移动或转动时,雷达一直处于激活状态,如果雷达检测到有机器人在它的范围内,就会触发一个事件。作为机器人创建者,您有权选择处理可能在战斗中发生的各类事件。基本的 Robot 类中包括了所有这些事件的缺省处理程序。但是,您可以覆盖其中任何一个“什么也不做的”缺省处理程序,然后实现您自己的定制行为。下面是一些较为常用的事件:

  • ScannedRobotEvent 。通过覆盖 onScannedRobot() 方法来处理 ScannedRobotEvent ;当雷达检测到机器人时,就调用该方法。
  • HitByBulletEvent 。通过覆盖 onHitByBullet() 方法来处理 HitByBulletEvent ;当机器人被炮弹击中时,就调用该方法。
  • HitRobotEvent 。通过覆盖 onHitRobot() 方法来处理 HitRobotEvent ;当您的机器人击中另外一个机器人时,就调用该方法。
  • HitWallEvent 。通过覆盖 onHitWall() 方法来处理 HitWallEvent ;当您的机器人撞到墙时,就调用该方法。

我们只需要知道这些就可以创建一些相当复杂的机器人了。您可以通过战场的帮助菜单或 Robot Editor 的帮助菜单访问 Javadoc 中其余的 Robocode API。

现在,我们该把学到的知识付诸实践了。





回页首


创建机器人

要创建一个新的机器人,请启动 Robot Editor 并选择 File-> New-> Robot。系统将会提示您输入机器人的名称,这个名称将成为 Java 类名。请您在提示符处输入 DWStraight。接下来,系统还会提示您输入一个独一无二的包前缀,它将用作存放机器人(还可能有相关的 Java 文件)的包的名称。请在该提示符处输入 dw

Robot Editor 就会显示您要控制这个机器人需要编写的 Java 代码。清单 1 是您将会看到的代码的一个示例:


清单 1. Robocode 生成的 Robot 代码
												
														package dw;
import robocode.*;

/**
 * DWStraight - a robot by (developerWorks)
 */
public class DWStraight extends Robot
{
    ...  // <<Area 1>>
    /**
     * run: DWStraight's default behavior
     */
    public void run() {
        ... // <<Area 2>>
        while(true) {
        ... // <<Area 3>>
        }
    }
      ... // <<Area 4>>
    public void onScannedRobot(ScannedRobotEvent e) {
        fire(1);
    }
}

												
										

突出显示的区域就是我们添加控制机器人的代码的地方:

Area 1
我们可以在这片空白里声明类作用域变量并设置这些变量的值。这些变量可以在机器人的 run() 方法内以及其他一些您可能创建的助手方法内使用。

Area 2
战斗管理器调用 run() 方法激活机器人。典型情况下,run() 方法包括两个区域(即在清单 1 中指出的 Area 2 和 Area 3),您可以在这两块空白里添加代码。您在 Area 2 中加入的代码每个机器人实例只运行一次。这部分代码通常用于使机器人先处于一种预设状态后再开始执行重复行为。

Area 3
这是典型的 run() 方法实现的第二部分。在此,我们将在无限 while 循环内对机器人可能执行的重复行为进行编程。

Area 4
您可以在这一区域内添加机器人在 run() 逻辑内使用的助手方法。您也可以在此添加您想要覆盖的任何事件处理程序。例如,清单 1 里的代码处理 ScannedRobot 事件,每当雷达检测到机器人的时候,只是直接向其发射炮弹。

我们对第一个机器人(DWStraight)的代码的更新如清单 2 中红色标记所示。


清单 2. DWStraight 机器人代码的增加部分
												
														package dw;
import robocode.*;

public class DWStraight extends Robot
{
    public void run() {
        turnLeft(getHeading());
        while(true) {
            ahead(1000);
            turnRight(90);

        }
    }
         public void onScannedRobot(ScannedRobotEvent e) {
        fire(1);
    }
    public void onHitByBullet(HitByBulletEvent e) {
        turnLeft(180);
    }
      
}

												
										

下面我们逐区地描述这个第一个机器人将做些什么:

Area 1我们没有在这个机器人的程序中指定任何类作用域变量。

Area 2
为了使机器人处于已知的状态,我们通过 turnLeft(getHeading()) 使它转到 0 度的方向。

Area 3
在这个重复性的部分,我们使用语句 ahead(1000) 让机器人尽其所能向前移动到最远的地方。当机器人撞到墙或其他机器人时,就会停下来。接着,我们通过 turnRight(90) 使它向右转。在重复执行这一行为时,机器人基本上是在沿着墙按顺时针方向移动。

Area 4
在此,除处理自动生成的 ScannedRobot 事件并向被发现的机器人直接射击之外,我们还会检测 HitByBullet 事件,并且让机器人在被击中的时候转过 180 度(沿顺时针方向或逆时针方向)。





回页首


编译以及测试机器人

在 Robot Editor 菜单上选择 Compiler-> Compile编译您的机器人代码。现在我们可以尝试第一回合的战斗了。切换回战场并选择菜单上的 Battle-> New,将会出现一个类似于图 3 中所示的对话框。


图 3. New Battle 对话框
New Battle 对话框

请先将我们的机器人 dw.DWStraight 添加到战斗中,然后再添加一个对手机器人,比如 sample.SittingDuck。单击 Finish,战斗就开始了。不可否认,同 SittingDuck 战斗并不怎么有趣,但是您可以目睹这个叫做 DWStraight 的机器人在缺省情况下的行为。试试 sample 文件夹里的其他机器人,看看 DWStraight 同这些机器人的战斗情况如何。

当您准备开始研究另外一个机器人的代码时,请先看看随 参考资料 中的代码分发包一起提供的 dw.DWRotater 这个机器人的代码。在缺省情况下,这个机器人将会:

  • 移动到战场中心
  • 一直转动它的炮,直到检测到机器人
  • 每次尝试以不同的角度在离被检测到的机器人前方不远的地方射击
  • 每当它被另外一个机器人击中时,它都会迅速的来回移动

这段代码简单易懂,所以我们在这里就不做分析了,但是我鼓励您试验一下。Robocode 中的 sample 包还提供了许多其他机器人的代码。

附加的机器人支持类

随着您设计机器人的水平的提高,机器人的代码主体将充分增长。对这些代码的一种模块化处理方法是把代码分解成独立的 Java 类,然后通过打包器把这些 Java 类打包成一个单独的包(JAR 文件),并将它包括在您的机器人分发包内。Robocode 将自动在它的 robots 目录下的包里找到 robot 类。

其他 Robot 子类

任何人都可以创建 Robot 子类并添加用于构建机器人的新功能。Robocode 提供了一个叫做 AdvancedRobotRobot 子类,它允许异步 API 调用。虽然对 AdvancedRobot 类的描述超出了本文的范围,但我鼓励您在掌握了基本的 Robot 类的操作后,试验一下这个高级类。

设计 Robocode 的目的

我碰见了 Robocode 的创建者 Mathew Nelson,向他请教创建 Robocode 最初的设计目的。Mat 所说的是:“编写 Robocode 的一部分目的是为了向世界证明:象‘Java 比较慢’以及‘Java 不可以用来写游戏’之类的论断不再正确。我认为我证明了这一点。”





回页首


战斗仿真器的体系结构

通过“在后台”对 Robocode 进行分析,我们发现复杂的仿真引擎既具高性能(为了以现实的速度生成战斗)又具灵活性(使创建复杂的机器人逻辑不存在障碍)。特别感谢 Robocode 的创建者 Mathew Nelson 无私的提供了仿真引擎体系结构的内部信息。

利用 Java 平台进行设计

图 4 中所示的仿真引擎利用的是大多数现代的 Java VM 都提供的非抢占式线程技术,并结合使用了 JDK GUI 和 2D 图形库提供的生成功能。


图 4. Robocode 仿真引擎体系结构
仿真引擎

请注意,所仿真的每个机器人都在它自己的 Java 线程上,它可以在任何可适用的地方利用 VM 本地线程映射机制。战斗管理器线程是系统的控制器:它安排仿真并驱动图形化的生成子系统。图形化的生成子系统本身是基于 Java 2D 和 AWT 的。

松散的线程耦合

为了减少共享资源可能带来的问题(以及有可能随之出现的死锁或阻塞仿真引擎),战斗管理器线程和机器人线程之间的耦合应当非常松散。为了实现这种松散耦合,每个机器人线程都将有属于自己的事件队列。获取及处理这些事件都是在每个机器人自己的线程内进行。这种基于线程的队列有效地消除了战斗管理器线程和机器人线程之间(或机器人线程本身之间)可能存在的任何争用。

Robocode 内部结构

您可以把 Robocode 仿真器引擎看作是一个仿真器程序,该程序在运行时会使用一些插件(定制机器人);这些插件可以利用已有的 API( robocode.Robot 类的方法)。实际上,每个机器人都是一个独立的 Java 线程,同时 run() 方法内包含了每个线程上将要执行的逻辑。

在任何时候,机器人线程都可以调用由它的父类 robocoode.Robot 类所提供的 API。典型情况下,这将通过调用 Object.wait() 阻塞机器人线程。

战斗管理器线程

战斗管理器线程管理机器人、炮弹及它们在战场上的生成。仿真“时钟”是根据战场上生成的帧的数目来标记的。用户可以调整真实的帧的速度。

在一个典型的回合中,战斗管理器线程唤醒每个机器人线程,然后等待机器人完成它的一轮战斗(即,再次调用一个阻塞 API)。等待的间隔时间通常是几十毫秒,即使是最复杂的机器人,使用现今典型的系统速度进行策略安排和计算,也只要 1 到 2 毫秒的时间。

以下是战斗管理器线程执行的逻辑的伪代码:


清单 3. 战斗管理器的逻辑的伪代码
												
														while (round is not over) do
   call the rendering subsystem to draw robots, bullets, explosions
   for  each robot do
       wake up the robot
       wait for it to make a blocking call, up to a max time interval
   end for
   clear all robot event queue
   move bullets, and generate event into robots' event queue if applicable
   move robots, and generate event into robots' event queue if applicable
   do battle housekeeping and generate event into robots' event queue
         if applicable
   delay for frame rate if necessary
end do

												
										

请注意,在 for 循环内部,战斗管理器线程的等待时间不会超过最大的时间间隔。如果机器人线程没有及时调用阻塞 API(典型情况下是由于一些应用程序逻辑错误或无限循环),那么,它将继续进行战斗。生成一个 SkippedTurnEvent 并将其加入机器人事件队列中,用来通知高级机器人。

可替换的生成子系统

AWT 和 Java 2D 线程就是当前实现中的生成子系统,它从战斗管理器中获取命令并生成战场。它同系统的其余部分是完全分离的。我们可以预见到,在这个生成子系统将来的修订版中,它可以被替换掉(比如,用 3-D 生成器)。在当前的实现中,只要 Robocode 应用程序被最小化,生成就禁用了,这可以以更快的速度进行仿真。





回页首


Robocode 的未来

通过 alphaWorks Robocode 站点上的一个讨论组(请参阅 参考资料 ),Mathew Nelson 可以同 Robocode 用户社区保持紧密的反馈联系。许多反馈都并入了真实的代码中。Mathew 已计划即将要进行的一些改进有:

  • 通过不同的物体和障碍来定制战场地图
  • 基于团队的战斗
  • 对联赛或联盟的集成支持
  • 用户可选择坦克车体/炮/雷达/武器的样式




回页首


挡不住的 Robocode 风潮

对于一个从 2001 年 7 月 12 日出现在公众面前的项目,Robocode 的出名简直让人吃惊。尽管最新的可用版本还不到 1.0(在写这篇文章时是版本 0.98.2),但它已经是全世界的大学校园以及公司的 PC 机上颇受欢迎的娱乐活动了。Robocode 联盟(或 roboleagues)正如雨后春笋般出现,在这些联盟里,人们通过因特网让自己定制的作品相互较量。大学教授们一直在挖掘 Robocode 的教育特性,并且已经把它纳入了大学里的计算机科学课程。在因特网上,Robocode 用户组、讨论列表、FAQ、教程和 Webring 随处可见。

显然,Robocode 已经填补了大众化的寓教于乐领域的空白 ― 它为学生们和熬夜的工程师们提供简便、有趣、非胁迫却富竞争力的方式,释放他们的创造力,而且有可能实现他们征服世界的梦想。

posted @ 2006-07-19 20:55 Merlin 阅读(381) | 评论 (0)编辑 收藏

华裔美国科学家、前微软中国研究院院长(呵呵,应该是前了吧!)李开复是一位在语音识别、人工智能、三维图形和国际互联网多媒体等领域享有很高声誉的年轻人。他的成功经验和治学精神引起了我国许多青年尤其是大学生的广泛关注。在与我国年轻人的交往过程中,李开复归纳出了一些大家共同关心的问题,并结合自己的学习和工作经历,坦诚相见,直抒胸臆,写成了一封给我国学生的长信.相信对大学生和青年朋友在如何对待机遇、如何对待学业、如何对待工作、如何对待他人 、如何对待自己等诸多方面会有一些有益的启示。

    我在中国的这两年来,工作中最大的享受是到国内各高校与学生们进行交流。这些访问和交流使得我有机会与成千上万的青年学生就他们所关心的事业、前途等问题进行面对面的沟通。中国学生的聪明、好学和上进给我留下了非常深刻的印象。   

  在与这些青年学生的交流过程中,我发现有一些问题是大家都十分关心的。那些已经获得国外大学奖学金的学生,大都希望我谈一谈应该如何度过自己在美国的学习生涯;那些决定留在国内发展的学生,非常关心如何确定一个正确的方向,并以最快的速度在科研和学业方面取得成功;还有那些刚刚踏进大学校门的学生,则希望我能讲给他们一些学习、做人的经验之谈。最近,更有一些学生关心网络信息产业的发展,希望了解美国的大学生是如何创业和致富的。看到这么多双渴求知识、充满希望的眼睛,我突然产生了一种冲动,那就是给中国的学生们写一封信,将我与同学们在交流过程中产生的一些想法以及我要对中国学生的一些忠告写出来,帮助他们在未来的留学、工作或者创业的过程中能够人格更完美,生活更顺利,事业更成功。   

坚守诚信、正直的原则
  我在苹果公司工作时,曾有一位刚被我提拔的经理,由于受到下属的批评,非常沮丧地要我再找一个人来接替他。我问他:“你认为你的长处是什么?”他说:“我自信自己是一个非常正直的人。”我告诉他:“当初我提拔你做经理,就是因为你是一个公正无私的人。管理经验和沟通能力是可以在日后工作中学习的,但一颗正直的心是无价的。”我支持他继续干下去,并在管理和沟通技巧方面给予他很多指点和帮助。最终, 他不负众望,成为一个出色的管理人才。   

  与之相反,我曾面试过一位求职者。他在技术、管理方面都相当出色。但是,在谈话之余,他表示,如果我录取他,他可以把在原来公司工作时的一项发明带过来。随后他似乎觉察到这样说有些不妥,特做声明:那些工作是他在下班之后做的,他的老板并不知道。这一番谈话之后 ,对于我而言,不论他的能力和工作水平怎样,我都肯定不会录用他。原因是他缺乏最基本的处世准则和最起码的职业道德:“诚实”和“讲信用”。如果雇用这样的人,谁能保证他不会在这里工作一段时间后,把在这里的成果也当作所谓“业余之作”而变成向其他公司讨好的“贡品”呢?这说明,一个人品不完善的人是不可能成为一个真正有所作为的人的。
  在美国,中国学生的勤奋和优秀是出了名的。曾经一度是美国各名校最欢迎的留学生群体,而最近,却有一些变化。原因很简单,某些中国学生拿着读博士的奖学金到了美国,可一旦找到工作机会,他们就会马上申请离开学校,将自己曾经承诺要完成的学位和研究抛在一边。这种言行不一的做法已经使得美国一部分教授对中国学生的诚信产生了怀疑。应该指出,有这种行为的中国学生是少数,然而就是这样的“少数”,已经让中国学生的名誉受到了极大的损害。另外,目前美国有些教授不愿理会部分中国学生的推荐信,因为他们知道这些推荐信根本就出自学生自己之手,已无参考性可言。这也是诚信受到损害以后的必然结果。   

  我在微软研究院也曾碰到过类似的问题。一位来这里实习的学生,有一次出乎意料地报告了一个非常好的研究结果。但是,他做的研究结果别人却无法重复。后来,他的老板发现,这个学生对实验数据进行了挑选,只留下了那些合乎最佳结果的数据,而舍弃了那些“不太好”的数据。我认为,这个学生永远不可能实现真正意义的学术突破,也不可能成为一名真正合格的研究人员。   

  最后想提的是一些喜欢贪小便宜的人。他们用学校或公司的电话打私人长途、多报销出租车票。也许有人认为,学生以成绩、事业为重,其他细节只是一些小事,随心所欲地做了,也没什么大不了的。然而,就是那些身边的所谓“小事”,往往成为一个人塑造人格和积累诚信的关键。一些贪小便宜、耍小聪明的行为只会把自己定性为一个贪图小利、没有出息的人的形象,最终因小失大。中国有“勿以恶小而为之”的古训,很值得记取。   

生活在群体之中
与大多数美国学生比较而言,中国学生的表达能力、沟通能力和团队精神要相对欠缺一些。这也许是由于文化背景和教育体制的不同而造成的。今天,当我们面对一个正在走向高度全球化的社会时,生活在群体之中,做出更好的表现,得到更多的收获,是尤为重要的。   

  表达和沟通的能力是非常重要的。不论你做出了怎样优秀的工作,不会表达,无法让更多的人去理解和分享,那就几乎等于白做。所以,在学习阶段,你不可以只生活在一个人的世界中,而应当尽量学会与各类人交往和沟通,主动表达自己对各种事物的看法和意见,甚至在公众集会时发表演讲,锻炼自己的表达能力。   

  表达能力绝不只是你的“口才”。哈佛大学的Ambady教授最近做过一个非常有趣的实验,他让两组学生分别评估几位教授的授课质量。他把这几位教授的讲课录像带先无声地放两秒钟给一组学生看,得出一套评估结果。然后与那些已经听过这几位教授几个月讲课的学生的结果进行对比,两个小组的结论竟然惊人的相似。这表明,在表达自己思想的过程中,非语言表达方式和语言同样重要,有时作用甚至更加明显。这里所讲的非语言表达方式是指人的仪表、举止、语气、声调和表情等。因为从这些方面,人们可以更直观、更形象地判断你为人、做事的能力,看出你的自信和热情,从而获得十分重要的“第一印象”。   

  对于一个集体、一个公司,甚至是一个国家,团队精神都是非常关键性的。微软公司在美国以特殊的团队精神著称。像Windows2000这样的产品的研发,有超过3000名开发工程师和测试人员参与,写出了5000万行代码。没有高度统一的团队精神,没有全部参与者的默契与分工合作,这项工程是根本不可能完成的。   

  以前我在别的公司时却也曾见到过相反的现象。一项工程布置下来,大家明明知道无法完成,但都心照不宣,不告诉老板。因为反正也做不完,大家索性也不努力去做事,却花更多的时间去算计怎么把这项工程的失败怪罪到别人身上去。就是这些人和这样的工作作风,几乎把这家公司拖垮。   

  为了培养团队精神,我建议同学们在读书之余积极参加各种社会团体的工作。在与他人分工合作、分享成果、互助互惠的过程中,你们可以体会团队精神的重要性。
  在学习过程中,你千万不要不愿意把好的思路、想法和结果与别人分享,担心别人走到你前面的想法是不健康的,也无助于你的成功。有一句谚语说,“你付出的越多,你得到的越多”。试想,如果你的行为让人觉得“你的是我的,我的还是我的”,当你需要帮忙时,你认为别人会来帮助你吗?反之,如果你时常慷慨地帮助别人,那你是不是会得到更多人的回报呢?   

在团队之中,要勇于承认他人的贡献。如果借助了别人的智慧和成果,就应该声明。如果得到了他人的帮助,就应该表示感谢。这也是团队精神的基本体现。
做一个主动的人   

  三十年前,一个工程师梦寐以求的目标就是进入科技最领先的IBM。那时IBM对人才的定义是一个有专业知识的、埋头苦干的人。斗转星移,事物发展到今天,人们对人才的看法已逐步发生了变化。现在,很多公司所渴求的人才是积极主动、充满热情、灵活自信的人。   

  作为当代中国的大学生,你应该不再只是被动地等待别人告诉你应该做什么,而是应该主动去了解自己要做什么,并且规划它们,然后全力以赴地去完成。想想今天世界上最成功的那些人,有几个是唯唯诺诺、等人吩咐的人?对待自己的学业和研究项目,你需要以一个母亲对孩子那样的责任心和爱心,全力投入,不断努力。果真如此,便没有什么目标是不能达到的。   

  一个积极主动的人还应该虚心听取他人的批评和意见。其实,这也是一种进取心的体现。不能虚心接受别人的批评,并从中汲取教训,就不可能有更大的进步。比尔盖茨曾经对公司所有员工说过:“客户的批评比赚钱更重要。从客户的批评中,我们可以更好地汲取失败的教训,将它转为成功的动力。”   

  除了虚心接受别人的批评,你还应该努力寻找一位你特别尊敬的良师。这位良师应该是直接教导你的老师以外的人,这样的人更能客观地给你一些忠告。这位良师除了可以在学识上教导你之外,还可以在其他一些方面对你有所指点,包括为人处世,看问题的眼光,应对突发事件的技能等等。
  我以前在苹果公司负责一个研究部门时,就曾有幸找到这样一位良师。当时,他是负责苹果公司全球运作和生产业务的高级副总裁,他在事业发展方面给我的许多教诲令我终身受益。如果有这样的人给你帮助 ,那你成长的速度一定会比别人更快一些。   

  中国学生大多比较含蓄、害羞,不太习惯做自我推销。但是,要想把握住转瞬即逝的机会,就必须学会说服他人,向别人推销自己或自己的观点。在说服他人之前,要先说服自己。你的激情加上才智往往折射出你的潜力,一个好的自我推销策略可以令事情的发展锦上添花。

  例如,有一次我收到了一份很特殊的求职申请书。不同于已往大多数求职者,这位申请人的求职资料中包括了他的自我介绍、他对微软研究院的向往、以及他为什么认为自己是合适的人选,此外还有他已经发表的论文、老师的推荐信和他希望来微软作的课题等。尽管他毕业的学校不是中国最有名的学校,但他的自我推销奏效了。我从这些文件中看到了他的热情和认真。在我面试他时,他又递交了一份更充分的个人资料。最后,当我问他有没有问题要问我时,他反问我,:“你对我还有没有任何的保留?”当时,我的确对他能否进入新的研究领域有疑虑,于是就进一步问了他一些这方面的问题。他举出了两个很有说服力的例子。最后,我们雇用了这名应聘者。他现在做得非常出色。   

挑战自我、开发自身潜力   
  我在苹果公司工作的时候,有一天,老板突然问我什么时候可以接替他的工作?我非常吃惊,表示自己缺乏像他那样的管理经验和能力。但是他却说,这些经验是可以培养和积累的,而且他希望我在两年之后就可以做到。有了这样的提示和鼓励,我开始有意识地加强自己在这方面的学习和实践。果然,我真的在两年之后接替了他的工作。我个人认为,一个人的领导素质对于他将来的治学、经商或从政都是十分重要的。在任何时候、任何环境里,我们都应该有意识地培养自己的领导才能。同时,我建议你给自己一些机会展示这方面的能力,或许像我一样,你会惊讶自己在这一方面的潜力远远超过了自己的想象。   
  给自己设定目标是一件十分重要的事情。目标设定过高固然不切实际,但是目标千万不可定得太低。在二十一世纪,竞争已经没有疆界,你应该放开思维,站在一个更高的起点,给自己设定一个更具挑战性的标准,才会有准确的努力方向和广阔的前景,切不可做“井底之蛙”。另外,只在一所学校取得好成绩、好名次就认为自己已经功成名就是可笑的,要知道,山外有山,人上有人,而且,不同地方的衡量标准又不一 样。所以,在订立目标方面,千万不要有“宁为鸡首,不为牛后”的思想。   

  一个一流的人与一个一般的人在一般问题上的表现可能一样,但是在一流问题上的表现则会有天壤之别。美国著名作家威廉&#8226;福克纳说过:“不要竭尽全力去和你的同僚竞争。你更应该在乎的是:你要比现在的你更强。”你应该永远给自己设立一些很具挑战性、但并非不可及的目标。

在确立将来事业的目标时,不要忘了扪心自问:“这是不是我最热爱的专业?我是否愿意全力投入?”我希望你们能够对自己选择所从事的工作充满激情和想象力,对前进途中可能出现的各种艰难险阻无所畏惧。谈到对工作的热爱,我认识的一位微软的研究员曾经让我深有感触。他经常周末开车出门,说去见“女朋友”,后来,一次偶然机会我在办公室里看见他,问他:“女朋友在哪里?”他笑着指着电脑说:“就是她呀。 ”

  对于工作的热爱,比尔&#8226;盖茨也曾有过非常精彩的阐述,他说:“每天早晨醒来,一想到所从事的工作和所开发的技术将会给人类生活带来的巨大影响和变化,我就会无比兴奋和激动。”   

  几个月前,《北京青年报》上曾有一场探讨比尔&#8226;盖茨和保尔&#8226;柯察金谁更伟大的讨论。由于从小在美国长大,我并不知道保尔和他的事迹。但是,我非常赞同保尔的这段名言:“人最宝贵的东西是生命,生命属于我们只有一次。人的一生应当这样度过,当他回首往事的时候,不因虚度年华而悔恨,也不因碌碌无为而羞愧……”所以,你应当选择一个你真心热爱的事业,不断地挑战自我、完善自我,让自己的一生过得精彩和充实。
  
客观、直截了当的沟通
  有一次,一位中国的大学教授找到我,希望我帮他找一位国外的专家在他组织的会议上去做主题演讲,末了还特意加了一句,最好是一个洋人。我很不以为然地对他说:“这个领域最具权威的人士就是在北京的一个中国人,为什么你一定要找一位洋人呢?”他表面上同意我的说法,但是他仍然请了一个美国人来做这个演讲,结果效果很差。所以,我们不应该陷入盲目的崇洋情结。我们应该用客观的眼光来判断事物,而不是以他的肤色或他的居住地来决定。   

有一句话说,“真理往往掌握在少数人手中”。这句话的意思是,我们看问题应该有自己的眼光,有独立思考的能力,不一定大多数人认可的,或某个权威说的,就是对的。不论是做学问、搞研究还是经商,我们都不能盲从,要多想几个为什么。有了客观的意见,你就应该直截了当地表达。如果做任何事情都像“打太极拳”,会让人不知所云,也会造成很多误会。有一次,在微软研究院工作的一位研究人员就自己所选择的研究方向来征求我的意见,我作了一番分析,认为这个方向有不少问题,但如果他坚持,我愿意支持他试着去做。结果他认为我这句话的意思实际上就是不允许他去做,所以他就选择了其他的方向。后来他要出差时,负责行政事务的人告诉他,你可以选择坐火车或者坐飞机。他认为行政人员实际上是在暗示他坐火车,因为坐飞机太贵。其实,他的猜测都是错误的。因为我们的沟通方式是直截了当,而他却在“打太极拳”。这之后,我们通过一系列的公司文化讲座,让员工们了解到,心里想什么就讲什么,不要把简单的问题复杂化。现在,研究院里这类的误会少了很多。  

  拐弯抹角,言不由衷,结果浪费了大家的宝贵时间。瞻前顾后,生怕说错话,结果是变成谨小慎微的懦夫。更糟糕的是还有些人,当面不说,背后乱讲,这样对他人和自己都毫无益处,最后只能是破坏了集体的团结。这样的人和作风既不能面对社会,也不可能在科学研究中走出新路,更不可能在激烈的商战中脱颖而出。   

  希望同学们能够做到开诚布公,敢于说“不”,这才是尊重自己思想意愿的表现。当然,在表达你的意见时,无论反对和批评都应是建设性的,有高度诚意的,而不是为批评而批评,为辩论而批评。我赞成的方式是提供建设性的正面的意见。在开始讨论问题时,任何人先不要拒人千里之外,大家把想法都摆在桌面上,充分体现个人的观点,这样才会有一个容纳大部分人意见的结论。当然,你也要学习用适当的方法和口气表达你的意见,比如说不要在很多人面前让别人难堪。这样,你的批评才会奏效。   


珍惜校园学习生活   
  几天前,报纸上登出一条消息,说有中学生辍学去开网络公司。我认为这并不值得提倡。对绝大多数学生来讲,在校生活是系统地学习基础理论知识,学习思考和解决问题方式的好机会。这些知识将成为你未来发展过程中所需要的最基本的知识和技能。就像建一栋高楼,如果不打好基础,是经不起风吹雨打的。   

  在全球范围内,美国的研究水平无疑是世界一流的。而除了美国之外,你会发现英国的研究水平也是相当突出的。究其原因,其实就是语言问题。英国人可以毫无阻碍地阅读美国乃至全球各种最新的英文研究报告和资料。这对于他们把握研究方向,跟踪最新进展,发表研究成果都有很大的帮助。因此,英语学习对于我们作研究的人来说,是相当重要的。只有加强这方面素质的培养,才能适应将来的发展。我建议,学英语先听说,再学读写,而且务必在大学阶段完全解决英语学习的问题。等到年龄大了,要付出的代价相比就会大得多。   

  除了英语之外,数学、统计学对理工科学生也是很重要的基础课程,是不可忽视的。数学是人类几千年的智慧结晶,你们一定要用心把它学好,不能敷衍了事。我今天就很后悔自己当初没有花更多功夫把数学 学得 更好些。另外,计算机应用、算法和编程也都是每一个工科学生应该 熟悉 和掌握的,它们是将来人人必须会用的工具。   

  科技的发展可谓日新月异。在校学习的目的,其实就是掌握最基本的学习工具和方法。将来利用这些工具和方法,再去学习新的东西。比如:上课学会了C++,能否自己学会Java?上课学会了HTML,能否自己学会 XML?与其说上大学是为了学一门专业,不如说是为了学会如何学习 ,让自己能够“无师自通”。   

  大学毕业后的前两年,同学们聚到一起,发现变化都还不算大。五年后再聚到一起,变化就大多了。一些人落伍了,因为他们不再学习,不再能够掌握新的东西,自然而然地落在了社会发展的后面。如果我们要在这个竞争激烈的社会中永不落伍,那就得永远学习。   
  我的老板RickRashid博士是目前微软公司主管研究的高级副总裁,他已经功成名就,却始终保持着一颗学习和进取的心。现在,他每年仍然编写大约50,000行程序。他认为:用最新的技术编程可以使他保持对计算机最前沿技术的敏感,使自己能够不断进步。今天,有些博士生带着低年级的本科生和硕士生做项目,就自满地认为自己已经没有必要再编程了。其实,这样的做法是很不明智的。   

  每次到清华和其他学校访问,被问到最多的就是学生打工的问题。我认为,打工从总体来说对学生是一件好事,是拓宽视野的一种方式。例如,在研究机构打工,可以学到最新的科技;在产品部门打工,可以学到开发的技术和技能;在市场部门打工,可以理解商业的运作。我认为每一个学生都应该有打工的经验,但不要打一些“没用的工”。首先要明白打工只是学生生活中的一种补充,学习才是最重要的。打工的目的是开阔眼界,不是提前上班。如果你把翻译书本、录入数据库所花的时间投入学习,将来可以赚更多的钱。那些钱将远远超出目前打工的收入。     

  此外,还有一些学生受到目前退学创业风潮的鼓励,为成为中国的比尔&#8226;盖茨和迈克尔&#8226;戴尔而中途辍学。以我的观点,除了十分特殊的情况外,我不建议在校学生退学创业。你所看到的那些退学创业的成功者实际上少之又少。目前,大部分学生虽有创业的想法,但缺少创业的经验,所以失败的可能性非常大。如果要成功,我建议你们先把书读好。如果是要学习创业的经验,你完全可以利用假期的时间先去一家公司边打工边学。

  比尔&#8226;盖茨也曾说过:“如果你正在考虑自己成立一家新公司,你应该首先明确地知道:创办公司需要巨大的精力投入,要冒巨大的风险。我觉得你们不必像我,一开始就创办一家公司。你应该考虑加盟其他公司并在这家公司中学习他们的工作、创业方法。”   


你想戴一顶什么样的博士帽   
  在我进入卡内基梅隆大学攻读计算机博士学位时,系主任曾对我讲,当你拿到你的博士学位时,你应该成为你所从事的研究领域里世界第一的专家。这句话对于初出茅庐的我来说简直高不可攀,但也让我踌躇满志、跃跃欲试。就这样,在经过五年寒窗、夜以继日的努力工作后,他所期待的结果就那么自然而然地出现了。

  一个打算攻读博士学位的人,就应该给自己树立一个很高的目标。如果没有雄心壮志,就千万不要自欺欺人,也许经商或从事其它工作,会有更大的成绩。在目标确立之后,我建议你为自己设计一个三年的学习和科研计划。首先,你需要彻底地了解在相关领域他人已有的工作和成绩。然后再提出自己的想法和见解,做脚踏实地的工作。另外,还要不断跟踪这个领域的最新研究进展。只有这样,才可以把握好方向,避免重复性工作,把精力集中在最有价值的研究方向上 。   

  在学术界,人们普遍认为“名师出高徒”。可见导师在你的成长道路中作用是多么大。所以,你应该主动去寻找自己所研究的领域里最好的老师。除了你的老师之外,你还应该去求教于周围所有的专家。更不要忘了常去求教“最博学的老师”Internet!现在,几乎所有的论文、研究结果、先进想法都可以在网上找到。我还鼓励你直接发电子邮件去咨询一些世界公认的专家和教授。以我的经验,对于这样的邮件,他们中的大部分都会很快给你回复。   

  我在攻读博士学位时,每周工作七天,每天工作16个小时,大量的统计结果和分析报告几乎让我崩溃。那时,同领域其他研究人员采用的是与我不同的传统方法。我的老师虽然支持我,但并不认可我的研究方向 。我也曾不止一次地怀疑自己的所作所为是否真的能够成功。但终于有一 天, 在半夜三点时做出的一个结果让我感受到了成功的滋味。后来,研究有了突飞猛进的进展,导师也开始采用我的研究方法。我的博士论文使我的研究成为自然语言研究方面当时最有影响力的工作之一。

  读博士不是一件轻松的事,切忌浮躁的情绪,而要一步一个脚印,扎扎实实地工作。也不可 受一些稍纵即逝的名利的诱惑,而要200%的投入。也许你会疲劳,会懊悔,会迷失方向,但是要记住,你所期待的成功和突破也正孕育其中。那种一切都很顺利,谁都可以得到的工作和结果,我相信研究价值一定不高。从一定意义上讲,一个人如果打算一辈子从事研究工作,那么从他在读博士学位期间所形成的做事习惯、研究方法和思维方式基本上就可以判断出他未来工作的轮廓。所以,你一定要做一个“有心人”,充分利 用在 校的时间,为自己的将来打好基础。   

  上述一些观点,是我在与同学们交往过程中的一些感受。我希望这些建议和想法能对正在未来之路上跋涉的你们有所启发,能对你们目前的学习有所帮助。或许因为观点不同、人各有志,或许因为忠言逆耳,这封信可能无法为每一位同学所接受。但是只要一百位阅读这封信的同学中有一位从中受益,这封信就已经比我所作的任何研究都更有价值。我真诚地希望,在新的世纪,中国学生无论是在国内,还是国外;无论是做研究,还是经商,都显得更成熟一些,成功的机率更大一些。
posted @ 2006-07-19 17:02 Merlin 阅读(274) | 评论 (0)编辑 收藏

作为程序员,应该是最为辛苦的一件事情,通宵达旦的熬夜,把自己的血汗无偿的捐献给了Boss ,同时还得小心自己落伍,明天要学习,再学习,因为技术更新实在是太快,JDK1.4还没有搞定, JDK5.0已经开始普及了,等俺们开始用上了5.0,6.0已经开始发布测试版了,7.0也在磨刀霍霍之中,危机感始终是笼罩在自己头上,怎么办? 难道我们程序员就是这样的生活?

    真想好好的休息一下,放松自己的神经! 但是不能!怎么办? 工作两年了,真的是感觉到了这种辛苦。那怎么才能够改变这种现象呢?方法,改变我们学习的方法,这是我最近一直在思考的事情。新技术曾出不穷,我们不可能永远跟得上脚步,所以我们要掌握自己的主动权。

   第一,要有好的心态,我们选择了这一行,没有什么可以抱怨的。

   第二,经常反省自己,看能不能变得更好。 每天都会有收获,所以呢,每天都可以发现自己的不足。Matrin Folwer说,如果,两年后,你还是认为两年前的自己已经很优秀的话,那说明你没有进步。

   第三,要总结,把自己明天的心得都记下来,记录自己的脚步。

   第四,要钻研,要做就把它做好。碰到困难更是要迎难而上,这时候才是学习的最好机会。

   第五,不要有畏惧心理,不要怕做不好。相信自己。

   第六,要学会使用文档,俺的头这一点最让人佩服,不会的问题,看文档,很快就可以搞定,当然,前提是他英语实在是高。没有语言障碍。

    第七,就是上面说的,作为程序员,要学好英语。这是很重要。

    还有就是,程序员每天该做的事,好东西。


写的真的很好,不过我发现人坚持干一件事情真的很难。
posted @ 2006-07-19 16:32 Merlin 阅读(306) | 评论 (0)编辑 收藏

2006年7月13日 #

除非你像我一样学程序语言只是为了到处献宝,否则你在学一套程序语言前,应该先仔细评估到底要学哪一套。每个程序语言的用途都有很大的差异,走了冤枉路可能会耽误到计画实作的进程。
我大致上把程序语言分成五类,分述如下:

Web Page Script Languages
用来做网页的语言,可以对网页做控制。如果你希望设计出很炫的网页,光靠 HTML 是不够的,你还得学一套 Web Page Script Language,比方说 javascript(ECMAScript)和微软的 JScript。不过两者都是系出同门,所以差不多。WML Script(WAP 手机的 script)也是袭自
javascript。
许多人知道我不会 javascript 之后,都会大吃一惊地反问:「可是你不是会 Java,怎么不顺便学 javascript,两个语言不是差不多」。如果依照此推理,我看我差不多每个语言都要学了,因为除了 REBOL 和汇编语言比较特殊之外,我觉得其它语言的语法都差不多(但用途差很多)。
不过我最近的确是有打算开始学 javascript,因为我发现用到它的机会还不少。除了网页会用到之外,我最近所接触的 SVG(Scalable Vector Graphics)就使用 javascript 来达到动画效果。

Interpreted Languages(直译式语言)
这类直译式的语言包括了 Perl、Python、REBOL、Ruby... 等,也常被称为 script 语言,通常是用来取代批次档和 shell script 以便和底下的操作系统沟通。基本上,每个人至少都应该要会一套这类的语言,当你需要做某件简单的工作,你可以透过直译式的语言来轻易地办到,这么一来,你就可以不必大张旗鼓地使用 Java 和 C++ 等工具了。
直译式的语言通常比较高阶,程序比较好写,往往简短地几行程序就抵得过 Java 或 C++ 的一堆程序代码。因为不用编译而且高阶,所以这类语言的程序效率通常很差,又因为原始程序代码暴露在外,所以拿它来写写工具程序自己用可以,但是拿来开发软件产品比较不恰当(除非你不在乎原始码外流)。目前这类语言最常被用来开发网页服务器端的程序,或者是设计软件的 prototype。
Python 有一些不错的语言特性,目前在国外算是满热门的;Ruby 是日本一位教授设计的,但是这语言太新了,目前好象只有 Addison Wesley 的一本英文书和 O'Reilly 的一本日文书可供参考;REBOL 则是我近期最喜欢的程序语言,非常特别,REBOL 语言的思维和别的语言差异非常大,许多时候很接近英文句子。至于 Perl,我就没有研究了,台湾欧莱礼公司已经有 Perl 的专家了,如果我现在去学 Perl 的话,短期内是不可能超越他的,所以我看算了 :(

Hybrid Languages(混合式语言)
Java,C# 都是混合式语言,介于直译式语言和编译式语言之间(不管是在执行效能上或程序简单性上)。
C# 的语言有许多奇怪的特色,但也有一些不错的特色。C# 的学习使用上的难度介于 Java 和 C/C++ 之间。C# 是 Microsoft .NET 平台上最重要的语言,值得我们持续观察其后续发展。至于 Java 我就不用多说了,相信 Sleepless in Java 的读者们应该都知道 Java 是怎么一回事。
至于 Visual Basic,在 .NET 平台主推 C# 语言,而 VisualBasic .NET 的语言又比以前复杂许多的情况下, Visual Basic 的前途似乎很不看好。

Compiling Languages(编译式语言)
C/C++,Delphi(Object Pascal)都是编译式语言。这几年来,C++ 已经变得越来越庞大了,大多数的 C++ 程序员只用到(也只懂)C++ 功能的一小部份。想成为 C++ 语言真正的高手,没有耗上三五年是不可能的。虽然 C++ 很复杂,但是真正想成为程序高手的人应该都要懂 C/C++,重要的 API 都会有 C/C++ 的版本,由此可见 C/C++ 的重要性。至于 Delphi,在 Microsoft .NET 推出之后会对 Delphi 造成一些打击(Microsoft .NET 的语言名单中连 Scheme、Eiffel 和 Mercury 这种少用的语言都出现了,独缺 Delphi),但是 Delphi 能透过 Kylix 来跨越 Windows 和 Linux,又是一个很大的吸引力,如果你想要跨 Linux 和 Windows 平台的 RAD 工具(语言),目前 Delphi 似乎是最好的选择。

Assembly Languages(汇编语言)
使用汇编语言,你将尝试到一砖一瓦堆砌出程序的乐趣(或痛苦)。汇编语言可以说是最接近硬件的语言,学会汇编语言,就可以对计算机的运作有相当程度的了解。不过,目前连开发驱动程序都不太需要用到汇编语言了。恐怕只有做 DSP 和 OS 等极少部份的人需要用到汇编语言。我也好久没写汇编程序了,以前 DOS 时代,我还用汇编语言写过一个 PE 2。

程序语言学习顺序的建议
通常 Web Script 最简单,直译式语言其次,接着是混合式语言,和编译式语言,最麻烦的是汇编语言。如果你完全没有程序经验,想开始学程序设计的话,你可以从 javascript 着手,等到程序基础观念建立得差不多了,再往下学习直译式语言,然后再学习混合式语言 ...,以此类推。
希望这篇文章能解决读者们选择程序语言的困扰

看完这篇文章,我觉得自己的学习经历真的很搞笑,上大学最先学的是C++,接着学了一年的C#,后来接触了Javascript,现在又在学Java。弄了半天我是越学越简单了,哈哈~~~~~~~
posted @ 2006-07-13 21:50 Merlin 阅读(498) | 评论 (1)编辑 收藏

2006年7月12日 #

在论坛上面常常看到初学者对线程的无可奈何,所以总结出了下面一篇文章,希望对一些正在学习使用java线程的初学者有所帮助。

首先要理解线程首先需要了解一些基本的东西,我们现在所使用的大多数操作系统都属于多任务,分时操作系统。正是由于这种操作系统的出现才有了多线程这个概念。我们使用的windows,linux就属于此列。什么是分时操作系统呢,通俗一点与就是可以同一时间执行多个程序的操作系统,在自己的电脑上面,你是不是一边听歌,一边聊天还一边看网页呢?但实际上,并不上cpu在同时执行这些程序,cpu只是将时间切割为时间片,然后将时间片分配给这些程序,获得时间片的程序开始执行,不等执行完毕,下个程序又获得时间片开始执行,这样多个程序轮流执行一段时间,由于现在cpu的高速计算能力,给人的感觉就像是多个程序在同时执行一样。
一般可以在同一时间内执行多个程序的操作系统都有进程的概念.一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间,一组系统资源.在进程概念中,每一个进程的内部数据和状态都是完全独立的.因此可以想像创建并执行一个进程的系统开像是比较大的,所以线程出现了。在java中,程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务.多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行.(你可以将前面一句话的程序换成进程,进程是程序的一次执行过程,是系统运行程序的基本单位)

线程与进程相似,是一段完成某个特定功能的代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有微处理器的寄存器数据,以及一个供程序执行时使用的堆栈.所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小的多,正因如此,线程也被称为轻负荷进程(light-weight process).一个进程中可以包含多个线程.

多任务是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程,同进程一样,一个线程也有从创建,运行到消亡的过程,称为线程的生命周期.用线程的状态(state)表明线程处在生命周期的哪个阶段.线程有创建,可运行,运行中,阻塞,死亡五中状态.通过线程的控制与调度可使线程在这几种状态间转化每个程序至少自动拥有一个线程,称为主线程.当程序加载到内存时,启动主线程.

[线程的运行机制以及调度模型]
java中多线程就是一个类或一个程序执行或管理多个线程执行任务的能力,每个线程可以独立于其他线程而独立运行,当然也可以和其他线程协同运行,一个类控制着它的所有线程,可以决定哪个线程得到优先级,哪个线程可以访问其他类的资源,哪个线程开始执行,哪个保持休眠状态。
下面是线程的机制图:
image

线程的状态表示线程正在进行的活动以及在此时间段内所能完成的任务.线程有创建,可运行,运行中,阻塞,死亡五中状态.一个具有生命的线程,总是处于这五种状态之一:
1.创建状态
使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,称该线程处于创建状态(new thread)
2.可运行状态
使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于可运行状态(Runnable)
3.运行中状态
Java运行系统通过调度选中一个Runnable的线程,使其占有CPU并转为运行中状态(Running).此时,系统真正执行线程的run()方法.
4.阻塞状态
一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态(Blocked)
5.死亡状态
线程结束后是死亡状态(Dead)

同一时刻如果有多个线程处于可运行状态,则他们需要排队等待CPU资源.此时每个线程自动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度.可运行状态的线程按优先级排队,线程调度依据优先级基础上的"先到先服务"原则.
线程调度管理器负责线程排队和CPU在线程间的分配,并由线程调度算法进行调度.当线程调度管理器选种某个线程时,该线程获得CPU资源而进入运行状态.

线程调度是先占式调度,即如果在当前线程执行过程中一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行.先占式调度分为:独占式和分时方式.
独占方式下,当前执行线程将一直执行下去,直 到执行完毕或由于某种原因主动放弃CPU,或CPU被一个更高优先级的线程抢占
分时方式下,当前运行线程获得一个时间片,时间到时,即使没有执行完也要让出CPU,进入可运行状态,等待下一个时间片的调度.系统选中其他可运行状态的线程执行
分时方式的系统使每个线程工作若干步,实现多线程同时运行

另外请注意下面的线程调度规则(如果有不理解,不急,往下看):
①如果两个或是两个以上的线程都修改一个对象,那么把执行修改的方法定义为被同步的(Synchronized),如果对象更新影响到只读方法,那么只度方法也应该定义为同步的
②如果一个线程必须等待一个对象状态发生变化,那么它应该在对象内部等待,而不是在外部等待,它可以调用一个被同步的方法,并让这个方法调用wait()
③每当一个方法改变某个对象的状态的时候,它应该调用notifyAll()方法,这给等待队列的线程提供机会来看一看执行环境是否已发生改变
④记住wait(),notify(),notifyAll()方法属于Object类,而不是Thread类,仔细检查看是否每次执行wait()方法都有相应的notify()或notifyAll()方法,且它们作用与相同的对象 在java中每个类都有一个主线程,要执行一个程序,那么这个类当中一定要有main方法,这个man方法也就是java class中的主线程。你可以自己创建线程,有两种方法,一是继承Thread类,或是实现Runnable接口。一般情况下,最好避免继承,因为java中是单根继承,如果你选用继承,那么你的类就失去了弹性,当然也不能全然否定继承Thread,该方法编写简单,可以直接操作线程,适用于单重继承情况。至于选用那一种,具体情况具体分析。


eg.继承Thread
				public class MyThread_1 extends Thread
{
public void run()
{
//some code
}
}


eg.实现Runnable接口
				public class MyThread_2 implements Runnable
{
public void run()
{
//some code
}
}



当使用继承创建线程,这样启动线程:
				new MyThread_1().start()
		


当使用实现接口创建线程,这样启动线程:
				new Thread(new MyThread_2()).start()
		


注意,其实是创建一个线程实例,并以实现了Runnable接口的类为参数传入这个实例,当执行这个线程的时候,MyThread_2中run里面的代码将被执行。
下面是完成的例子:

				public class MyThread implements Runnable
{

public void run()
{
System.out.println("My Name is "+Thread.currentThread().getName());
}
public static void main(String[] args)
{
new Thread(new MyThread()).start();
}
}



执行后将打印出:
My Name is Thread-0

你也可以创建多个线程,像下面这样
				new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();



那么会打印出:
My Name is Thread-0
My Name is Thread-1
My Name is Thread-2


看了上面的结果,你可能会认为线程的执行顺序是依次执行的,但是那只是一般情况,千万不要用以为是线程的执行机制;影响线程执行顺序的因素有几点:首先看看前面提到的优先级别


				public class MyThread implements Runnable
{

public void run()
{
System.out.println("My Name is "+Thread.currentThread().getName());
}
public static void main(String[] args)
{
Thread t1=new Thread(new MyThread());
Thread t2=new Thread(new MyThread());
Thread t3=new Thread(new MyThread());
t2.setPriority(Thread.MAX_PRIORITY);//赋予最高优先级
t1.start();
t2.start();
t3.start();
}
}


再看看结果:
My Name is Thread-1
My Name is Thread-0
My Name is Thread-2



线程的优先级分为10级,分别用1到10的整数代表,默认情况是5。上面的t2.setPriority(Thread.MAX_PRIORITY)等价与t2.setPriority(10)
然后是线程程序本身的设计,比如使用sleep,yield,join,wait等方法(详情请看JDKDocument)

				public class MyThread implements Runnable
{
public void run()
{
try
{
int sleepTime=(int)(Math.random()*100);//产生随机数字,
Thread.currentThread().sleep(sleepTime);//让其休眠一定时间,时间又上面sleepTime决定
//public static void sleep(long millis)throw InterruptedException (API)
System.out.println(Thread.currentThread().getName()+" 睡了 "+sleepTime);
}catch(InterruptedException ie)//由于线程在休眠可能被中断,所以调用sleep方法的时候需要捕捉异常
{
ie.printStackTrace();
}
}
public static void main(String[] args)
{
Thread t1=new Thread(new MyThread());
Thread t2=new Thread(new MyThread());
Thread t3=new Thread(new MyThread());
t1.start();
t2.start();
t3.start();
}
}


执行后观察其输出:

Thread-0 睡了 11
Thread-2 睡了 48
Thread-1 睡了 69




上面的执行结果是随机的,再执行很可能出现不同的结果。由于上面我在run中添加了休眠语句,当线程休眠的时候就会让出cpu,cpu将会选择执行处于runnable状态中的其他线程,当然也可能出现这种情况,休眠的Thread立即进入了runnable状态,cpu再次执行它。
[线程组概念]
线程是可以被组织的,java中存在线程组的概念,每个线程都是一个线程组的成员,线程组把多个线程集成为一个对象,通过线程组可以同时对其中的多个线程进行操作,如启动一个线程组的所有线程等.Java的线程组由java.lang包中的Thread——Group类实现.
ThreadGroup类用来管理一组线程,包括:线程的数目,线程间的关系,线程正在执行的操作,以及线程将要启动或终止时间等.线程组还可以包含线程组.在Java的应用程序中,最高层的线程组是名位main的线程组,在main中还可以加入线程或线程组,在mian的子线程组中也可以加入线程和线程组,形成线程组和线程之间的树状继承关系。像上面创建的线程都是属于main这个线程组的。
借用上面的例子,main里面可以这样写:

				public static void main(String[] args)
{
/***************************************
ThreadGroup(String name)
ThreadGroup(ThreadGroup parent, String name)
***********************************/
ThreadGroup group1=new ThreadGroup("group1");
ThreadGroup group2=new ThreadGroup(group1,"group2");
Thread t1=new Thread(group2,new MyThread());
Thread t2=new Thread(group2,new MyThread());
Thread t3=new Thread(group2,new MyThread());
t1.start();
t2.start();
t3.start();
}



线程组的嵌套,t1,t2,t3被加入group2,group2加入group1。
另外一个比较多就是关于线程同步方面的,试想这样一种情况,你有一笔存款在银行,你在一家银行为你的账户存款,而你的妻子在另一家银行从这个账户提款,现在你有1000块在你的账户里面。你存入了1000,但是由于另一方也在对这笔存款进行操作,人家开始执行的时候只看到账户里面原来的1000元,当你的妻子提款1000元后,你妻子所在的银行就认为你的账户里面没有钱了,而你所在的银行却认为你还有2000元。
看看下面的例子:

				class BlankSaving //储蓄账户
{
private static int money=10000;
public void add(int i)
{
money=money+i;
System.out.println("Husband 向银行存入了 [¥"+i+"]");
}
public void get(int i)
{
money=money-i;
System.out.println("Wife 向银行取走了 [¥"+i+"]");
if(money<0)
System.out.println("余额不足!");
}
public int showMoney()
{
return money;
}
}


class Operater implements Runnable
{
String name;
BlankSaving bs;
public Operater(BlankSaving b,String s)
{
name=s;
bs=b;



}
public static void oper(String name,BlankSaving bs)
{



if(name.equals("husband"))
{
try
{
for(int i=0;i<10;i++)
{
Thread.currentThread().sleep((int)(Math.random()*300));
bs.add(1000);
}
}catch(InterruptedException e){}
}else
{
try
{



for(int i=0;i<10;i++)
{
Thread.currentThread().sleep((int)(Math.random()*300));
bs.get(1000);
}
}catch(InterruptedException e){}
}
}
public void run()
{
oper(name,bs);
}
}
public class BankTest
{
public static void main(String[] args)throws InterruptedException
{
BlankSaving bs=new BlankSaving();
Operater o1=new Operater(bs,"husband");
Operater o2=new Operater(bs,"wife");
Thread t1=new Thread(o1);
Thread t2=new Thread(o2);
t1.start();
t2.start();
Thread.currentThread().sleep(500);
}



}



下面是其中一次的执行结果:



---------first--------------
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Husband 向银行存入了 [¥1000]


看到了吗,这可不是正确的需求,在husband还没有结束操作的时候,wife就插了进来,这样很可能导致意外的结果。解决办法很简单,就是将对数据进行操作方法声明为synchronized,当方法被该关键字声明后,也就意味着,如果这个数据被加锁,只有一个对象得到这个数据的锁的时候该对象才能对这个数据进行操作。也就是当你存款的时候,这笔账户在其他地方是不能进行操作的,只有你存款完毕,银行管理人员将账户解锁,其他人才能对这个账户进行操作。
修改public static void oper(String name,BlankSaving bs)为public static void oper(String name,BlankSaving bs),再看看结果:

Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Husband 向银行存入了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]
Wife 向银行取走了 [¥1000]




当丈夫完成操作后,妻子才开始执行操作,这样的话,对共享对象的操作就不会有问题了。
[wait and notify]
你可以利用这两个方法很好的控制线程的执行流程,当线程调用wait方法后,线程将被挂起,直到被另一线程唤醒(notify)或则是如果wait方法指定有时间得话,在没有被唤醒的情况下,指定时间时间过后也将自动被唤醒。但是要注意一定,被唤醒并不是指马上执行,而是从组塞状态变为可运行状态,其是否运行还要看cpu的调度。
事例代码:

				class MyThread_1 extends Thread
{
Object lock;
public MyThread_1(Object o)
{
lock=o;
}
public void run()
{
try
{
synchronized(lock)
{
System.out.println("Enter Thread_1 and wait");
lock.wait();
System.out.println("be notified");
}
}catch(InterruptedException e){}
}
}
class MyThread_2 extends Thread
{
Object lock;
public MyThread_2(Object o)
{
lock=o;
}
public void run()
{
synchronized(lock)
{
System.out.println("Enter Thread_2 and notify");
lock.notify();
}
}
}
public class MyThread
{
public static void main(String[] args)
{
int[] in=new int[0];//notice
MyThread_1 t1=new MyThread_1(in);
MyThread_2 t2=new MyThread_2(in);
t1.start();
t2.start();
}
}




执行结果如下:
Enter Thread_1 and wait
Enter Thread_2 and notify
Thread_1 be notified


可能你注意到了在使用wait and notify方法得时候我使用了synchronized块来包装这两个方法,这是由于调用这两个方法的时候线程必须获得锁,也就是上面代码中的lock[],如果你不用synchronized包装这两个方法的得话,又或则锁不一是同一把,比如在MyThread_2中synchronized(lock)改为synchronized(this),那么执行这个程序的时候将会抛出java.lang.IllegalMonitorStateException执行期异常。另外wait and notify方法是Object中的,并不在Thread这个类中。最后你可能注意到了这点:int[] in=new int[0];为什么不是创建new Object而是一个0长度的数组,那是因为在java中创建一个0长度的数组来充当锁更加高效。

Thread作为java中一重要组成部分,当然还有很多地方需要更深刻的认识,上面只是对Thread的一些常识和易错问题做了一个简要的总结,若要真正的掌握java的线程,还需要自己多做总结
posted @ 2006-07-12 16:38 Merlin 阅读(338) | 评论 (0)编辑 收藏

第一,谈谈final, finally, finalize的区别。

  
final—修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载。

  
finally—再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。

  
finalize—方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。


  
第二,Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?

  
匿名的内部类是没有名字的内部类。不能extends(继承) 其它类,但一个内部类可以作为一个接口,由另一个内部类实现。


  
第三,Static Nested Class 和 Inner Class的不同,说得越多越好(面试题有的很笼统)。

  
Nested Class (一般是C++的说法),Inner Class (一般是JAVA的说法)。Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用上。具体可见http: //www.frontfree.net/articles/services/view.asp?id=704&page=1

  
注: 静态内部类(Inner Class)意味着1创建一个static内部类的对象,不需要一个外部类对象,2不能从一个static内部类的一个对象访问一个外部类对象


  
第四,&和&&的区别。

  
&是位运算符。&&是布尔逻辑运算符。


  
第五,HashMap和Hashtable的区别。

  
都属于Map接口的类,实现了将惟一键映射到特定的值上。

  
HashMap 类没有分类或者排序。它允许一个 null 键和多个 null 值。

  
Hashtable 类似于 HashMap,但是不允许 null 键和 null 值。它也比 HashMap 慢,因为它是同步的。


  
第六,Collection 和 Collections的区别。

  
Collections是个java.util下的类,它包含有各种有关集合操作的静态方法。

  
Collection是个java.util下的接口,它是各种集合结构的父接口。



  
第七,什么时候用assert。

  
断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为 true。如果表达式计算为 false,那么系统会报告一个 Assertionerror。它用于调试目的:

  
assert(a > 0); // throws an Assertionerror if a <= 0

  

断言可以有两种形式:

  
assert Expression1 ;

 
assert Expression1 : Expression2 ;

  
Expression1 应该总是产生一个布尔值。

  
Expression2 可以是得出一个值的任意表达式。这个值用于生成显示更多调试信息的 String 消息。

  
断言在默认情况下是禁用的。要在编译时启用断言,需要使用 source 1.4 标记:

  
javac -source 1.4 Test.java

  
要在运行时启用断言,可使用 -enableassertions 或者 -ea 标记。

  
要在运行时选择禁用断言,可使用 -da 或者 -disableassertions 标记。

  
要系统类中启用断言,可使用 -esa 或者 -dsa 标记。还可以在包的基础上启用或者禁用断言。

  
可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过,断言不应该用于验证传递给公有方法的参数,因为不管是否启用了断言,公有方法都必须检查其参数。不过,既可以在公有方法中,也可以在非公有方法中利用断言测试后置条件。另外,断言不应该以任何方式改变程序的状态。


  
第八,GC是什么? 为什么要有GC? (基础)。

  
GC是垃圾收集器。Java 程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:

  
System.gc()

  
Runtime.getRuntime().gc()


  
第九,String s = new String("xyz");创建了几个String Object?

  
两个对象,一个是"xyx",一个是指向"xyx"的引用对象s。


  
第十,Math.round(11.5)等於多少? Math.round(-11.5)等於多少?

    
Math.round(11.5)返回(long)12,Math.round(-11.5)返回(long)-11;


  
第十一,short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?

  
short s1 = 1; s1 = s1 + 1;有错,s1是short型,s1+1是int型,不能显式转化为short型。可修改为s1 =(short)(s1 + 1) 。short s1 = 1; s1 += 1正确。


  
第十二,sleep() 和 wait() 有什么区别? 搞线程的最爱

  
sleep()方法是使线程停止一段时间的方法。在sleep 时间间隔期满后,线程不一定立即恢复执行。这是因为在那个时刻,其它线程可能正在运行而且没有被调度为放弃执行,除非

(a)"醒来"的线程具有更高的优先级。

  
(b)正在运行的线程因为其它原因而阻塞。

  
wait()是线程交互时,如果线程对一个同步对象x 发出一个wait()调用,该线程会暂停执行,被调对象进入等待状态,直到被唤醒或等待时间到。


  
第十三,Java有没有goto?

  
Goto—java中的保留字,现在没有在java中使用。


  
第十四,数组有没有length()这个方法? String有没有length()这个方法?

  
数组没有length()这个方法,有length的属性。

  
String有有length()这个方法。


  
第十五,Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?

  
方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。


  
第十六,Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?

  
Set里的元素是不能重复的,那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等。

  
equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。


  
第十七,给我一个你最常见到的runtime exception。

  
ArithmeticException, ArrayStoreException, BufferOverflowException, BufferUnderflowException, CannotRedoException, CannotUndoException, ClassCastException, CMMException, ConcurrentModificationException, DOMException, EmptyStackException, IllegalArgumentException, IllegalMonitorStateException, IllegalPathStateException, IllegalStateException,   ImagingOpException, IndexOutOfBoundsException, MissingResourceException, NegativeArraySizeException, NoSuchElementException, NullPointerException, ProfileDataException, ProviderException, RasterFORMatException, SecurityException, SystemException, UndeclaredThrowableException, UnmodifiableSetException, UnsupportedOperationException


  
第十八,error和exception有什么区别?

  
error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。

  
exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。


  
第十九,List, Set, Map是否继承自Collection接口?

  
List,Set是

  
Map不是


  
第二十,abstract class和interface有什么区别?

  
声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类为。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。

  
接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。


  
第二十一,abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

  
都不能


  
第二十二,接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?

  
接口可以继承接口。抽象类可以实现(implements)接口,抽象类是否可继承实体类,但前提是实体类必须有明确的构造函数。


 
第二十三,启动一个线程是用run()还是start()?

  
启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。


  
第二十四,构造器Constructor是否可被override?

  
构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading。


  
第二十五,是否可以继承String类?

  
String类是final类故不可以继承。


  
第二十六,当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

  
不能,一个对象的一个synchronized方法只能由一个线程访问。


  
第二十七,try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?

  
会执行,在return前执行。


  
二十八,编程题: 用最有效率的方法算出2乘以8等於几?

  
有C背景的程序员特别喜欢问这种问题。


  
第二十九,两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

 
不对,有相同的hash code。


  
第三十,当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

  
是值传递。Java 编程语言只由值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。


  
第三十一,swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上?

  
switch(expr1)中,expr1是一个整数表达式。因此传递给 switch 和 case 语句的参数应该是 int、 short、 char 或者 byte。long,string 都不能作用于swtich。


  
第三十二,编程题: 写一个Singleton出来。

  
Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。

 
一般Singleton模式通常有几种种形式:

  
第一种形式: 定义一个类,它的构造函数为private的,它有一个static的private的该类变量,在类初始化时实例话,通过一个public的getInstance方法获取对它的引用,继而调用其中的方法。

  
public class Singleton {

  
private Singleton(){}

  
//在自己内部定义自己一个实例,是不是很奇怪?

  
//注意这是private 只供内部调用

  
private static Singleton instance = new Singleton();

  
//这里提供了一个供外部访问本class的静态方法,可以直接访问  

  
public static Singleton getInstance() {

  
return instance;   

  
}

  
}


  
第二种形式:

  
public class Singleton {

  
private static Singleton instance = null;

  
public static synchronized Singleton getInstance() {

  
//这个方法比上面有所改进,不用每次都进行生成对象,只是第一次 

      
 //使用时生成实例,提高了效率!

  
if (instance==null)

  
instance=new Singleton();

  
return instance;   }

  
}

  
其他形式:

  
定义一个类,它的构造函数为private的,所有方法为static的。

  
一般认为第一种形式要更加安全些

  
Hashtable和HashMap

  
Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现

  
HashMap允许将null作为一个entry的key或者value,而Hashtable不允许

  
还有就是,HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。

  
最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在

  
多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap

  
就必须为之提供外同步。

  
Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。
posted @ 2006-07-12 16:02 Merlin 阅读(317) | 评论 (1)编辑 收藏

2006年7月11日 #

《Thinking in Java》
《J2EE技术实践》
《JAVA高效编程指南》

《Sleepless in Java》 下载地址: http://www.cppblog.com/Files/yunduan5158/SleeplessInJava.rar  
 
posted @ 2006-07-11 22:34 Merlin 阅读(205) | 评论 (0)编辑 收藏

近期到CSDN论坛看看一些网友贴的面试题,其中关于String的问题常常被提及。我一直以为自己很清楚这个东西了,其实深究起来,发现自己并不那么清楚,会犯一些错误;同时也产生了一些联想。小结一下。

1、"abc"与new String("abc");
    经常会问到的面试题:String s = new String("abc");创建了几个String Object?【如这里创建了多少对象? 和一道小小的面试题 】

    这个问题比较简单,涉及的知识点包括:

引用变量与对象的区别;
字符串文字"abc"是一个String对象;
文字池[pool of literal strings]和堆[heap]中的字符串对象。
    一、引用变量与对象:除了一些早期的Java书籍和现在的垃圾书籍,人们都可以从中比较清楚地学习到两者的区别。A aa;语句声明一个类A的引用变量aa[我常常称之为句柄],而对象一般通过new创建。所以题目中s仅仅是一个引用变量,它不是对象。[ref 句柄、引用与对象]

    二、Java中所有的字符串文字[字符串常量]都是一个String的对象。有人[特别是C程序员]在一些场合喜欢把字符串"当作/看成"字符数组,这也没有办法,因为字符串与字符数组存在一些内在的联系。事实上,它与字符数组是两种完全不同的对象。

        System.out.println("Hello".length());
        char[] cc={'H','i'};
        System.out.println(cc.length);

    三、字符串对象的创建:由于字符串对象的大量使用[它是一个对象,一般而言对象总是在heap分配内存],Java中为了节省内存空间和运行时间[如比较字符串时,==比equals()快],在编译阶段就把所有的字符串文字放到一个文字池[pool of literal strings]中,而运行时文字池成为常量池的一部分。文字池的好处,就是该池中所有相同的字符串常量被合并,只占用一个空间。我们知道,对两个引用变量,使用==判断它们的值[引用]是否相等,即指向同一个对象:

				String s1 = "abc" ;
String s2 = "abc" ;
if( s1 == s2 )
    System.out.println("s1,s2 refer to the same object");
else     System.out.println("trouble");


    这里的输出显示,两个字符串文字保存为一个对象。就是说,上面的代码只在pool中创建了一个String对象。

    现在看String s = new String("abc");语句,这里"abc"本身就是pool中的一个对象,而在运行时执行new String()时,将pool中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s持有。ok,这条语句就创建了2个String对象。

				String s1 = new String("abc") ;
String s2 = new String("abc") ;
if( s1 == s2 ){ //不会执行的语句}


    这时用==判断就可知,虽然两个对象的"内容"相同[equals()判断],但两个引用变量所持有的引用不同,

    BTW:上面的代码创建了几个String Object? [三个,pool中一个,heap中2个。]
    [Java2 认证考试学习指南 (第4版)( 英文版)p197-199有图解。]


2、字符串的+运算和字符串转换
    字符串转换和串接是很基础的内容,因此我以为这个问题简直就是送分题。事实上,我自己就答错了。

String str = new String("jf"); // jf是接分
str = 1+2+str+3+4;
一共创建了多少String的对象?[我开始的答案:5个。jf、new、3jf、3jf3、3jf34]

    首先看JLS的有关论述:

    一、字符串转换的环境[JLS 5.4 String Conversion]

    字符串转换环境仅仅指使用双元的+运算符的情况,其中一个操作数是一个String对象。在这一特定情形下,另一操作数转换成String,表达式的结果是这两个String的串接。

    二、串接运算符[JLS 15.18.1 String Concatenation Operator + ]

    如果一个操作数/表达式是String类型,则另一个操作数在运行时转换成一个String对象,并两者串接。此时,任何类型都可以转换成String。[这里,我漏掉了"3"和"4"]

如果是基本数据类型,则如同首先转换成其包装类对象,如int x视为转换成Integer(x)。
现在就全部统一到引用类型向String的转换了。这种转换如同[as if]调用该对象的无参数toString方法。[如果是null则转换成"null"]。因为toString方法在Object中定义,故所有的类都有该方法,而且Boolean, Character, Integer, Long, Float, Double, and String改写了该方法。
关于+是串接还是加法,由操作数决定。1+2+str+3+4 就很容易知道是"3jf34"。[BTW :在JLS的15.18.1.3中举的一个jocular little example,真的很无趣。]
    下面的例子测试了改写toString方法的情况.。

				class A{
    int i = 10;
    public static void main(String []args){
        String str = new String("jf");
        str += new A();
        System.out.print(str);
    }

    public String toString(){
        return " a.i ="+i+"\n";
    }
}


三、字符串转换的优化

按照上述说法,str = 1+2+str+3+4;语句似乎应该就应该生成5个String对象:

1+2 =3,then 3→Integer(3)→"3" in pool? [假设如此]
"3"+str(in heap) = "3jf"     (in heap)
"3jf" +3 ,first 3→Integer(3)→"3" in pool? [则不创建] then "3jf3"
"3jf3"+4 create "4"  in pool
then "3jf34"

    这里我并不清楚3、4转换成字符串后是否在池中,所以上述结果仍然是猜测。

    为了减少创建中间过渡性的字符串对象,提高反复进行串接运算时的性能,a Java compiler可以使用StringBuffer或者类似的技术,或者把转换与串接合并成一步。例如:对于 a + b + c ,Java编译器就可以将它视为[as if]

    new StringBuffer().append(a).append(b).append(c).toString();

    注意,对于基本类型和引用类型,在append(a)过程中仍然要先将参数转换,从这个观点看,str = 1+2+str+3+4;创建的字符串可能是"3"、"4"和"3jf34"[以及一个StringBuffer对象]。

    现在我仍然不知道怎么回答str = 1+2+str+3+4;创建了多少String的对象,。或许,这个问题不需要过于研究,至少SCJP不会考它。

3、这又不同:str = "3"+"jf"+"3"+"4";
    如果是一个完全由字符串文字组成的表达式,则在编译时,已经被优化而不会在运行时创建中间字符串。测试代码如下:

				String str1 ="3jf34";
        String str2 ="3"+"jf"+"3"+"4";
        if(str1 == str2) {
            System.out.println("str1 == str2");
        }else {
            System.out.println("think again");
        }
        if(str2.equals(str1))
            System.out.println("yet str2.equals(str1)");


    可见,str1与str2指向同一个对象,这个对象在pool中。所有遵循Java Language Spec的编译器都必须在编译时对constant expressions 进行简化。JLS规定:Strings computed by constant expressions (&yacute;15.28) are computed at compile time and then treated as if they were literals.

    对于String str2 ="3"+"jf"+"3"+"4";我们说仅仅创建一个对象。注意,“创建多少对象”的讨论是说运行时创建多少对象。

    BTW:编译时优化

				    String x = "aaa " + "bbb ";
    if (false) {
        x = x + "ccc ";
    }
    x +=  "ddd ";

    等价于:

    String x = "aaa bbb ";
    x = x + "ddd ";
//这个地方我自己进行了编译,不过和他的结论不一样,好像当用x+="ddd"的时候和直接的x="aaa"+"bbb"+"ddd" 不同,但是具体为什么我也不清楚,正在研究中。。。

4、不变类
    String对象是不可改变的(immutable)。有人对str = 1+2+str+3+4;语句提出疑问,怎么str的内容可以改变?其实仍然是因为不清楚:引用变量与对象的区别。str仅仅是引用变量,它的值——它持有的引用可以改变。你不停地创建新对象,我就不断地改变指向。[参考TIJ的Read-only classes。]

    不变类的关键是,对于对象的所有操作都不可能改变原来的对象[只要需要,就返回一个改变了的新对象]。这就保证了对象不可改变。为什么要将一个类设计成不变类?有一个OOD设计的原则:Law of Demeter。其广义解读是:

    使用不变类。只要有可能,类应当设计为不变类。
posted @ 2006-07-11 22:29 Merlin 阅读(362) | 评论 (0)编辑 收藏

abstract class和interface在Java语言中都是用来进行抽象类定义的

Interface ,给外界的接口,按照规定办事;
Abstract  ,内部继承关系;

interface 就是一组操作的集合,它定义了一个行为集但不作任何具体的实现,这样的话,具体的操作 都可以放在实现类中去,
          体现设计与实现分离的设计思想。

在面向对象的概念中,所有的对象都是通过类来描绘,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
抽象概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

=====================================================================================================================
使用abstract class的方式定义Demo抽象类的方式如下:

				abstract class Demo {
abstract void method1();
abstract void method2();

}



使用interface的方式定义Demo抽象类的方式如下:

				interface Demo {
void method1();
void method2();

}



====================================================================================================================
从编程层面看abstract class和interface

abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系
一个类却可以实现多个interface

在abstract class的定义中,我们可以赋予方法的默认行为
在interface的定义中,方法却不能拥有默认行为

======================================================================================================================
例如要设计一个形状类MShape,从此类可以派生 方形、圆形、三角形等子类。我们就可以将MShape这个父类设计为abstract类。

比如,子类都有 color 属性,因此可以把 color 这个数据成员,以及给 color 赋值的method均设计在父类中,
这样就不用在每个子类中设计相同的代码来处理 color 这个属性。
而如果想计算几何形状的面积,由于各个几何形状的面积计算方式都不相同,所以把计算面积的method的处理放在父类中就不合适,
但由于每个几何形状都需要用到这个method,因此可以在父类中只声明计算面积的method "area()",而把具体的处理放在子类中定义。
即把area()设计为抽象类。

以下是程序代码:
//abstract类 MShape      

				abstract class MShape
{
        protected String color;   //数据成员
        public void setColor(String mcolor)   //一般方法,定义了具体的处理
        {
           color=mcolor;
        }
        abstract void area();   //抽象方法,没有定义具体的处理
}



//方形类

				class RectShape extends MShape
{      
        int width,height,rectarea;    
        public RectShape(int w,int h)
        {  
                 width=w;
                 height=h;        
        }
        public void area()  //计算面积
        {          
          rectarea=width*height;
        }
}



//使用

				public class myapp
{
        public static void main(String args[])
        {
          RectShape rect=new RectShape(3,6);
          rect.setColor("Red");
          rect.area();
          System.out.print("color="+rect.color+", area="+rect.rectarea);
        }
}



由此可见,在abstract中不仅可以定义一般的方法(即可以进行具体处理的方法),还可以象interface一样定义抽象方法。
而在interface中只能定义抽象方法。

posted @ 2006-07-11 19:53 Merlin 阅读(395) | 评论 (0)编辑 收藏

仅列出标题  下一页