正文  软件开发 > java编程技术 >

JAVA程序员必读:基础篇 面向对象编程概念

如果你以前从来没有使用面向对象语言,你需要在开始编写JAVA代码之前先理解这个概念。你需要理解什么是对象、什么是类、对象和类的关系怎样以及使用消息怎样在对象之间进行通讯。本教程的前面部分将描述面向对象 ...

如果你以前从来没有使用面向对象语言,你需要在开始编写JAVA代码之前先理解这个概念。你需要理解什么是对象、什么是类、对象和类的关系怎样以及使用消息怎样在对象之间进行通讯。本教程的前面部分将描述面向对象编程的概念,而后面的教程将教你怎样将这个概念编成代码。
  2.1什么是对象
  对象是一些相关的变量和方法的软件集。软件对象经常用于模仿现实世界中我们身边的一些对象。对象是理解面向对象技术的关键。你在学习之前可以看看现实生活中的对象,比如狗、桌子、电视、自行车等等。你可以发现现实世界中的对象有两个共同特征:它们都有状态和行为。比如狗有自己的状态(比如名字、颜色、生育以及饥饿等等)和行为(比如摇尾巴等等)。同样自行车也有自己的状态(比如当前档位、两个轮子等等)和行为(比如刹车、加速、减速以及改变档位等等)。
  而软件对象实际上是现实世界对象的造型,因为它同样有状态和行为。一个软件对象利用一个或者多个变量来维持它的状态。变量是由用户标识符来命名的数据项。软件对象用它的方法来执行它的行为。方法是跟对象有关联的函数(子程序)。
  你可以利用软件对象来代表现实世界中的对象。你可能想用一个动画程序来代表现实世界中的狗,或者用可以控制电子自行车的程序来代表现实世界的自行车。同样你可以使用软件对象来造型抽象的概念,比如,事件是一个用在GUI窗口系统的公共对象,它可以代表用户按下鼠标按钮或者键盘上的按键的反应。
  如图1是一个软件对象的公共可视代表。
    
  软件对象的状态和行为都可以用在对象中的变量和方法来表达。构造现实世界的自行车的软件对象要有指示自行车的当前状态的变量:速度为20mph,它的当前档位为第三档。这些变量就是我们熟知的实例变量,因为它们包含了用于特殊自行车对象的状态,并且在面向对象技术中,特殊的对象称为实例。
  如图2所示,是作为软件对象的自行车造型。
   
  2.1什么是对象
  除了变量,软件自行车同样有用于刹车、改变踏板步调以及改变档位的方法。这些方法就是熟知的实例方法因为它们检查或者改变特殊自行车实例的状态。
  以上的对象图显示了对象的变量组成了圆心部分。方法处在变量的四周并且在程序中从其它对象隐藏了对象的核心。用保护方法的方法来包装对象的变量称为封装。这个对象图就是对象理想的表示法,也是面向对象系统设计者努力的最后目标。然而这并不是全部的内容。通常,出于某种现实的理由,对象可能暴露它的一些变量或者隐藏一些方法。在JAVA编程语言中,一个对象可以为变量和方法指定四种访问等级中的一种。这个访问等级决定哪个对象和类可以访问变量或者方法。在JAVA中访问变量和方法可以转换为控制访问类的成员函数。封装相关的变量和方法到一个简洁的软件集是一个简单而且强有力的方法,它为软件开发者提供了两个主要好处:
  模块性:对象的源代码可以独立于其它的对象源代码来进行编写和维护。同样,对象可以很容易地在系统中传递。你可以将你的自行车对象给其它的对象,它仍然可以正常工作。
  信息隐藏:一个对象如果有一个公共的界面,那么其它的对象就可以与之进行通讯。这个对象可以维护私人的信息和方法,它可以在任何时候被改变而不影响依耐于它的其它对象。所以你不必理解自行车中齿轮的机理就可以使用它。
  2.2什么是消息
  软件对象之间进行交互作用和通讯是利用消息的。
  单一的一个对象通常不是很有用的。相反,一个对象通常是一个包含了许多其它对象的更大的程序或者应用程序。通过这些对象的交互作用,程序员可以获得高阶的功能以及更为复杂的行为。你的自行车如果不使用它的时候,它就是一堆铝合金和橡胶,它没有任何的活动。而只有当有其它的对象来和它交互的时候才是有用的。
  软件对象与其它对象进行交互与通讯是利用发送给其它对象来实现的。当对象A想对象B来执行一个B中的方法,对象A就会消息给对象B。如图3所示。
   
  有时候,接收的对象需要更多的信息就至于它可以正确知道该如何做。比如,当你想改变自行车的齿轮,你就必须指出哪个齿轮。这个信息是将信息作为参数来传递的。如图4所示的现实了一个信息由三个组件组成:
  被寻址消息的对象(YourBicycle) 
  要执行方法的名字(changeGears) 
  这个方法需要的所有参数(lowerGear)
   
  上面的三个组件对于接收方的对象执行相应的方法是给出了充分的信息。再也不需要其它的信息或者上下文了。
  消息提供了两个重要的好处:
  对象的行为是通过它的方法来表达的,因此消息传递支持所有在对象之间的可能的交互。 
  对象不需要在相同的进程或者相同的机器上来发送和接收消息给其它的对象 
  2.3什么是类
  类实际上是对某种类型的对象定义变量和方法的原型。
  在现实世界中,你经常看到相同类型的许多对象。比如 ,你的自行车只是现实世界中许多自行车的其中一辆。使用面向对象技术,我们可以说你的自行车是自行车对象类的一个实例。通常,自行车有一些状态(当前档位、两个轮子等等)以及行为(改变档位、刹车等等)。但是,每辆自行车的状态都是独立的并且跟其它自行车不同。
  当厂家制造自行车的时候,厂商利用了自行车共有的特性来根据相同的蓝图制造许多自行车。如果制造一辆自行车就要产生一个新蓝图,那效率就太低了。
  在面向对象软件中,同样地,可以让相同种类地许多对象来共有一些特性,比如矩形、雇员记录、视频夹等等。就象自行车制造商人,你可以利用相同种类的对象是相似的事实并且你可以为这些对象创建一个蓝图。对对象的软件蓝图叫做类。
  自行车的类需要定义一些实例变量来包括当前档位、当前速度等等。这个类将为实例方法定义和提供实施方法,它允许骑车者改变档位、刹车以及改变脚踏板的节奏,如图5所示:
   
  当你创建了自行车类以后,你可以从这个类创建任意个自行车对象。当你创建了一个类的实例后,系统将为这个对象和的实例变量分配内存。每个实例将给所有实例变量的副本定义在类中。如图6所示:
   
  除了实例变量,类还要定义类的变量。类变量包含了被类所有实例共享的信息。比如,假设所有的自行车有相同的档位数。在本例子中,要定义一个实例变量来容纳档位数。每一个实例都会有变量的副本,但是在每一个实例中数值都是相同的。在这样的情况下,你可以定义一个类变量来包含档位数,这样所有的类的实例都共享这个变量。如果一个对象改变了变量,它就为改变那个类的所有对象。类同样可以定义类方法。你可以直接从类中调用类方法,然而你必须在特定的实例中调用实例方法。如图7所示。
   
  2.4实例和类成员
  2.4.1理解实例和类成员
  下面详细讨论一下实例和类成员,具体涉及变量和方法以及类变量和方法:
  你这样声明一个成员变量,比如在类Myclass中有一个float型的aFloat:
  class MyClass {
  float aFloat;
  }
  这样你就声明一个实例变量。每次你创建一个类的实例的时候,系统就为实例创建了类的每一个实例变量的副本。你可以从对象中访问对象的实例变量。
  实例变量跟类变量是不一样的,类变量示使用静态修改量来声明的。不管类创建了多少个实例,系统为每个类变量分配了类变量。系统为类变量分配的内存是在它第一次调用类的时候发生的。所有的实例共享了类的类变量的相同副本。你可以通过实例或者通过类本身来访问类变量。
  它们的方法是类似的:你的类可以有实例方法和类方法。实例方法是对当前对象的实例变量进行操作的,而且访问类变量。另外一个方法,类方法不能访问定义在类中的实例变量,除非它们创建一个新的对象并通过对象来访问它们。同样,类方法可以在类中被调用,你不必需要一个实例来调用一个类方法。
  缺省地,除非其它的成员被指定,一个定义在类中成员就是一个实例成员。这个在下面定义的类有一个实例变量,有一个整型的x,两个实例方法x和setX,它们设置其它对象以及查询x的数值。
  class AnIntegerNamedX {
  int x;
  public int x() {
  return x;
  }
  public void setX(int newX) {
  x = newX;
  }
  }
  每次你从一个类实例化一个新的对象,你可以得到每个类的实例变量的副本。这些副本都是跟新对象有关系的。因此,每次你从这个类实例化一个新的AnIntegerNamedX对象的时候,你得以得到跟新的AnIntegerNamedX对象有关的新副本。 
  一个类的所有实例共享一个实例方法的相同的实行;所有的AnIntegerNamedX实例都共享x和setX的相同执行。这里注意,两个方法x和setX是指对象的实例变量x。但是,你可能会问:如果所有AnIntergerNamedX共享x和setX的相同执行,会不会造成模棱两可的状态?答案当然是:不是。在实例方法中,实例变量的名字是指当前对象的实例变量,假如实例变量不是由一个方法参数来隐藏的。这样在x和setX中,x就等价于这个x,而不会造成混乱。
  2.4实例和类成员
  2.4.1理解实例和类成员
  对于AnIntegerNamedX外部的对象如果想访问x,它必须通过特定的AnIntegerNamedX的实例来实现。假如这个代码片段处在其它对象的方法中。它创建了两种不同类型的AnIntegerNamedX,它设置了x为不同的数值,然后显示它们:
  AnIntegerNamedX myX = new AnIntegerNamedX();
  AnInte
2.4实例和类成员 
  2.4.4 初始化实例成员
  然而,所有的实例变量(原点和大小)都必须初始化。在这个例子中,类经常有一个构造函数来完成所有的初始化。其它的构造函数调用这个构造函数并且提供给它参数或者缺省数值。比如下面是以上所说的三个构造函数,它们初始化如下: 
  Rectangle() {
  this(0,0,0,0);
  }
  Rectangle(int width, int height) {
  this(0,0,width,height);
  }
  Rectangle(int x, int y, int width, int height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
  }
  JAVA语言支持实例初始化块,你可以放心使用它。这里建议使用构造函数来初始化,主要有以下三个原因:
  所有的初始化代码处在一个地方,这样使得代码更容易维护和阅读。 
  缺省值可以清除地知道。 
  构造函数广泛被JAVA程序设计人员所熟悉,包括相对新的JAVA程序设计人员,而实例初始化程序不能,而且他可能导致其它JAVA程序设计员的困惑。 
  2.4实例和类成员
  2.4.5 对象和类 
  你可能会注意到对象和类看起来很相似。在现实世界中,类和对象之间的区别经常是让程序员困惑的源泉。在现实世界中,很明显,类不能是它们描述的对象本身。然而,在软件中很困难来区分类和对象。有部分原因是软件对象只是现实世界中的电子模型或者是抽象概念。但是也因为对象通常有时是指类和实例。
  2.5什么是继承
  一个类可以从它的父类继承状态和行为。继承为组织和构造软件程序提供了一个强大的和自然的机理。
  总得说来,对象是以类得形式来定义得。你可能现在已经可以从它类知道许多对象了。即使你如知道,如果我告诉你它是一辆自行车,你就会知道它有两个轮子和脚踏板等等。面向对象系统就更深入一些了,它允许类在其它类中定义。比如,山地自行车、赛车以及串座双人自行车都是各种各样的自行车。在面向对象技术中,山地自行车、赛车以及串座双人自行车都是自行车类的子类。同样地,自行车类是山地自行车、赛车以及串座双人自行车的父类。这个父子关系可以如图9所示:
   
  每一个子例从父类中继承了状态。山地自行车、赛车以及串座双人自行车共享了这些状态:速度等。同样,每一个子类继承类从父类的方法,山地自行车、赛车以及串座双人自行车共享了这些行为:刹车、改变脚踏速度等等。
  2.5什么是继承
  然而,子类不能受到父类提供的状态和行为的限制。子类可以增加变量和方法到从父类继承而来的变量和方法。比如,串座双人自行车有两个座位,这是它的父类没有的。 
  子类同样可以重载继承的方法并且为这些方法提供特殊执行方法。比如 ,如果你有一个山地自行车有额外 的齿轮设置,你就可以重载改变齿轮方法来使骑车者可以使用这些新的齿轮。 
  你也不能受限于继承的一个层次。继承树或者类的分级结构可以是很深。方法和变量是逐级继承的。总的来说,在分级结构的越下方,就有越多的行为。
  如果对象类处于分级结构的顶端,那么每个类都是它的后代(直接地或者是间接地)。一种类型的对象保留任何对象的一个引用,比如类或者数组的一个实例。对象提供了行为,这些行为是运行在JAVA虚拟机所需要的。比如,所有类继承了对象的toString方法,它返回了代表对象的字符串。
  下面说说我们为什么要使用继承,它到底有哪些好处呢?好处是有的:
  子类提供了特殊的行为,这是在父类中所没有的。通过使用继承,程序员可以多次重新使用在父类中的代码。 
  程序员可以执行父类(称为抽象类)来定义总的行为。这个抽象的父类可以定义并且部分执行行为,但是绝大多数的父类是未定义和未执行的。其它的部分由程序员来实现特殊的子类。 
  2.6什么是接口
  接口是一个收集方法和常数表单的契约。当类执行一个接口,它就许诺声明在那个接口中执行所有的方法。
  接口是一个设备或者一个系统,它是用于交互的无关的实体。根据这个定义,远程控制是一个在你和电视的接口;而英语是两个人之间的接口;强制在军事中的行为协议是不同等价人之间的接口。在JAVA语言中,接口是一个设备,它是用来与其它对象交互的设备。一个接口可能对一个协议是类似的。实际上,其它面向对象语言有接口的功能,但它们调用它们的接口协议。
  自行车类和它的类分级结构定义了什么是自行车。但是自行车在其它方面与现实世界交互作用,例如,仓库中的自行车可以由一个存货程序来管理。一个存货程序不关心管理项目的哪一类只要项目提供某一信息,比如价格和跟踪数字。取代强迫类与其它无关项的关系,存货程序建立了通讯的协议。这个协议是由包含在接口中的常数和方法定义组成的。这个存货清单接口将要定义(但不执行)方法来设置和得到零售价格,指定跟踪数字等等。
  为了在存货清单程序中操作,自行车类必须在执行接口的时候遵守这个协议。当一个了执行一个接口的时候,类遵守定义在接口中的所有方法。因此,自行车将为这些设置和获得零售价格并指定跟踪数值等等的方法提供执行。
  你可以使用接口来定义一个行为的协议,这个行为可以有在类分级结构中任何类来执行。接口的主要好处有一下几点: 
  不用人工强迫类关系在无关类中截获相似处。 
  声明想执行的一个或者更多类的方法。 
  在不暴露对象的类的前提下,暴露对象的编程接口。
  2.7怎样将这些面向对象的概念转换为代码
  这一小节将给你展现创建对象、执行类、发送消息、创建一个父类以及执行一个接口的代码。
  以下是一个applet(applet是用JAVA编程语言编写的程序,它可以运行在兼容JAVA平台的网络浏览器,比如HotJava或者Netscape Navigator)的程序,名为ClickMe。如图10所示,当你点击方框内任何地方,一个红点就会出现。
   
  (图10)
  提示:上面的applet需要JDK1.1。如果你使用老的不支持JDK1.1的浏览器,你将不能运行这个applet。相反,你需要在一个1.1浏览器上来看这个网页,比如在HotJava、JDK Applect浏览器(appletviewer)或者某个版本的Netscape Navigator和Internet Explorer。
  下面具体解释一下这个Applet。
  ClickMe Applet是一个相对简单的程序因此它的代码就短了多了。但是,如果你还没有太多的编程经验,你可以发现这些代码也不是那么容易的。我们不要求你马上理解程序中的每个问题,并且这节教程也不是讲了十分详细的。这里的目的示暴露一些源代码给你并且跟你刚才所学道的概念和技术联系上。你将在以后的教程中学到更详细的内容。
  2.7怎样将这些面向对象的概念转换为代码
  2.7.1ClickMe的源代码和Applet标签
  为了编译这个applet你需要两个源文件:ClickMe.java和Spot.java。为了运行这个applet你需要利用这个applet标签来创建一个html文件:
   
   
   
   
   
  然后装载网页到浏览器或者appletviewer工具。并且确保所有必要的文件都在相同的目录中。 如图11所示:
   
  (图11)
  2.7.2 ClickMe Applet中的对象
  在这个applet中有许多对象。两个最明显的是:applet本身和红点。
  浏览器在包含applet的HTML代码中碰到applet标签的时候就创建了applet对象。这个applet标签从创建applet对象的地方提供类的名字。在本例子中,这个类的名字为ClickMe。
  ClickME.applet将创建一个对象来在屏幕上画出点。每次你在applet中点击鼠标的时候,applet就将通过改变对象的x和y坐标来移动红点。这个点不是自己画出来的,它是applet画出的,它是根据包含在点对象中的信息画出的。
  除了前面两个明显的对象,另外还有一些看不见的对象呢。有代表三种颜色(黑、白、红)的三个对象以及代表点击鼠标的用户动作的事件对象等等。
  2.7.3ClickMe Applet中的类
  因为代表在屏幕上点的对象是很简单,接下来让我们看看这个名为spot的类吧。它声明了三个实例变量:包括点半径的size,包含点当前水平位置的x坐标以及包含点当前垂直位置的y坐标:
  public class Spot {
  //实例变量
  public int size;
  public int x, y;
  //构造函数
  public Spot(int intSize) {
  size = intSize;
  x = -1;
  y = -1;
  }
  }
  另外,类有一个构造函数,它用来初始化由类创建的新对象。构造函数跟类有相同的名字。这个构造函数初始化所有三个对象的变量。Size的初始化数值是在调用的时候座位参数提供的。x和y变量都被设置为-1,这里-1的目的是为了让点在开始的时候处于屏幕的外面,即产生假不可视的效果。
  这个applet是在applet初始化的时候创建了一个新的点对象。下面是applet类的相关代码: 
  private Spot spot = null;
  private static final int RADIUS = 7;
  ...
  spot = new Spot(RADIUS);
  第一行声明了一个名为spot的变量,它是Spot数据类型,并且初始化这个变量为NULL。第二行声明了一个整型变量,名为RADIUS,它的值为7。最后一行是创建一个对象。New关键字为对象分配了内存空间。Spot(RADIUS)调用了上面已经描述了的构造函数并且传递RADIUS数值,这样点对象的size就被设置为7。如图12所示的左图代表了Spot类,而右边的是代表了spot