现在的位置: 首页 > 综合 > 正文

面向对象由浅入深

2013年05月04日 ⁄ 综合 ⁄ 共 19071字 ⁄ 字号 评论关闭

使用Java开始面向对象的编程

你正在从传统的过程化的编程转向面向对象的开发模式吗?还是想要进入膨胀的Java世界呢?你不会感到孤单的.成千上万的开发者和你处在相同的情形之下.在这系列文章中,我们将使用Java语言带领你一步一步的学习面向对象的开发过程.下面是我们这个系列文章的第一篇:

一种语言是面向对象的究竟意味着什么呢?如果一种编程语言是真正的面向对象的语言,它必须支持以下的特点:

封装--隐藏实现细节
多态--将同一个消息发送给不同的对象并使各个对象以预定的方式对消息做出响应的能力
继承--拓展现存的类来生成专有类继承原来类的状态和行为的能力
动态绑定--在编程时不必知道对象的具体类型就能向它们发送消息的能力

让我们考察一下Java是如何支持这些功能的以及它又如何提供了附加的功能来使得从过程化的程序设计到面向对象的开发的转变过程相对容易.

Java中面向对象的特点
Java是由Sun Microsystems公司在九十年代中期发布的面向对象(OOP)的编程语言.你可以从Sun公司的网站上下载最新的Java开发包(JDK).Java是一种解释性的语言,这意味着其源程序首先被编译成中间代码的形式,然后在每次运行之前都要经过虚拟机的解释,它是彻头彻尾的面向对象的编程语言.

Java对程式员隐藏了许多传统的面向对象编程语言--比方说C++和Object Pascal--的复杂性和让人容易混淆的地方.例如,Java中没有了指针,Java会为程序员自动的清除引用类型,而且所有变量将被自动初始化成适当的缺省值.除了原始数据类型以外,Java中的所有东西都是对象,必要的时候,甚至可以为原始数据类型也提供封装机制.


对象简介

对象是代表现实生活中的实物的软件编程实体,比如说银行帐号,计算机用户,用户介面上的按钮,窗口菜单等等.对象是由它们的状态和行为定义的.例如,一个银行帐号拥有一种状态,诸如当前的收支状况,账户的所有人,允许的最小交易额,等等,而它的行为则包括提取,存入,收支平衡等.

一个对象的状态是由只有对象自己知道的变量定义的.Java把这些变量称为数据域或者成员变量.数据域对对象来说是私有的除非显式的使用关键字来定义它们的作用域,使它们对其它类可见.我们将在以后讨论变量作用域的问题.

一个对象的行为是由它上面的操作定义的.在Java中,这些操作被叫做方法.方法可以改变一个对象的状态,创建新对象,实现实用的功能等.


类是一个实体,它定义了一个对象的运行方式以及在对象被创建或者说实例化的时候所包含的数据.类的作用就象一个模板,一个或者多个对象可以依照它来创建.下面是使用Java面向对象的概念申明HelloWorld应用程序的例子:

public class HelloWorld
{
    private String helloMsg = "Hello World!";
    public static void main(String[] args)
    {
        HelloWorld hw = new HelloWorld();
     }

    public HelloWorld()
    {
    // 显示我们的"Hello World"消息
    System.out.println(helloMsg);
    }
}

上面的例子定义了一个模板,真实的HelloWorld对象可以从这个模板创建.你还会注意到从public static void main(String[] args)这一行开始的一段奇怪的代码.这一段代码定义的是一个特殊的方法main,它其实就是我们这个HelloWorld程序的入口点,上面的程序是一个典型的演示所有的Java应用程序如何定义它们的入口点.注意到即使是这个main入口点也被封装在类里面.对于这个例子,我们就是将它封装在HelloWorld类里.上面的程序展示了如何定义一个类,HelloWorld,以及其中的一个数据域,helloMsg和两个方法main和HelloWorld.HelloWorld方法是一种特殊的方法,这种方法被称做构造函数.我们将在后面的文章里讨论常规方法,构造函数和静态成员函数的细节和区别.

在Java中,所有与一个特殊的类有关的源代码都写在一个与类同名的拥有后缀名.java的文件里.Java编译器读取源文件并将它们翻译成平台无关的,二进制格式的代码,成为字节代码,然后将这些代码分类保存在与类同名的但是后缀为.class的文件里.你最终会为每一个类得到一个class文件.


编译并运行我们的例子程序

一旦你已经从Sun的Web站点上下载了JDK并在你的机器上安装了它,你就可以开始编译并运行Java程序了.要编译并运行我们的例子程序,将HelloWorld类的代码粘贴到你最喜欢的文档编辑器里,将文件保存为HelloWorld.java,然后,在命令提示符下,将当前路径改变到包含了这个文件的路径里.现在你就可以在命令行提示符下键入下面的命令来编译程序了:

Windows:
<你的JDK所在目录>/bin/javac HelloWorld.java

UNIX or Linux:
<你的JDK所在目录>/bin/javac HelloWorld.java

这个命令将在同一个目录里产生一个新的文件,叫做HelloWorld.class.要运行这个程序,请在命令提示符下键入下面的命令:

Windows:
<你的JDK所在目录>/bin/java HelloWorld

UNIX or Linux:
<你的JDK所在目录>/bin/java HelloWorld

你应该可以看到屏幕上显示Hello World!

总结
我们已经接触到了使用Java程序设计语言进行面向对象的编程的一些皮毛知识.下次,我们将剖析我们的例子程序,给它添加更多的功能,并讨论更多的有关对象,类和其它面向对象编程的基本概念以及用Java如何实现它们.

OOP: 理解类和对象

上一次在"使用Java开始面向对象的编程"这篇文章中,我们学习了一个编程语言要真正成为面向对象的,它应该支持信息隐藏/封装,多态,继承和动态绑定.另外,我们知道了Java完全支持这些功能,而且知道了因为Java是一种解释性的语言并运行在虚拟机的内部,所以由Java写成的任何程序都可以在任何支持 Java虚拟机(JVM)的操作系统上运行.我们还明白了对象是代表现实生活中事物的软件-编程模型以及对象是由它们的状态和行为定义的.最后,我们知道了Java中除了原始数据对象以外一切都是对象.

因为这种程序设计风格中的这许多内容都和对象以及类有关,我们将在下面进一步的考察它们.

对象详论
使用对象的一个关键是当你在浏览系统分析文档或者设计文档的时候如何来确定它们.因为对象一般代表人,地方或者事物,所以一个确定对象的基本的方法就是找出句子中所使用的名词.这里有一个简单的例子.在句子"一个顾客可以拥有多于一个的银行帐号",我们就确定了两个对象,客户和帐号.在句子"小猫喵喵叫"中,我们能够确定一个对象,猫.

类详论
前面,我们学习了一个类是定义了对象如何动作以及当对象创建或者说实例化的时候应该包含些什么的实体.在对动物的讨论中,我们可以说,"狗儿汪汪叫,猫喵喵叫,鸭子嘎嘎叫."确定句子中的对象我们就得到了狗,猫和鸭子.至于汪汪叫,喵喵叫,嘎嘎叫,那都是我们对象发出的行为动作.

要实现这些对象,我们需要创建三个对象分别叫Dog,Cat和Duck.要实现它们的行为,我们可以为每一个这些对象创建代表每个对象发出的声音的方法,而且我们把这个方法叫做speak或者,如果我们发挥想象力的话还可以把这个方法叫做sayHello.

在程序的上下文中为了演示这些概念,让我们修改上篇文章中的HelloWorld程序,添加这三个新对象并给它们中的每一个添加sayHello方法,如下所示:

public class HelloWorld
{
    public static void main(String[] args)
    {
        Dog animal1 = new Dog();
        Cat animal2 = new Cat();
        Duck animal3 = new Duck();
        animal1.sayHello();
        animal2.sayHello();
        animal3.sayHello();
    }
}

class Dog
{
    public void sayHello()
    {
        System.out.println("Bark");
    }
}

class Cat
{
    public void sayHello()
    {
        System.out.println("Meow");
    }
}

class Duck
{
    public void sayHello()
    {
        System.out.println("Quack");
    }
}

在编译并运行了这个程序以后,输出应该如下:
Bark
Meow
Quack

看看我们的程序,我们马上就注意到了一些事情:每个对象代表了一种动物,而每个对象实现了一个相同的方法,sayHello.假设我们想要给对象更多的功能以及用来代表对象所指的动物的方法和属性.比方说,我们可以添加一个方法来确定一个动物是不是哺乳类的,或者我们添加一个方法来确定一个动物是不是肉食性的.我们可以通过给每一个对象添加这两种方法来实现或者我们也能够使用OOP的两个最强大的功能:继承和多态.

因为所有的对象都代表一个对象,所以我们将创建一个被称为"基类"或是"超类"的类,它的名字是Animal.我们然后可以让我们的对象从Animal类继承相同的特点并强制每个对象只实现与Animal类不同的功能.

Java用extends关键字指明一个类从另一个继承.让我们利用继承和多态的概念获得代码重用的好处来重建我们的程序并使得每个对象只实现与基类Animal不同的功能:

public class HelloWorld
{
    public static void main(String[] args)
    {
        Dog animal1 = new Dog();
        Cat animal2 = new Cat();
        Duck animal3 = new Duck();
        System.out.println("A dog says " +animal1.getHello()
        +", is carnivorous: " +animal1.isCarnivorous()
        +", is a mammal: " +animal1.isAMammal());

        System.out.println("A cat says " +animal2.getHello()
        +", is carnivorous: " +animal2.isCarnivorous()
        +", is a mammal: " +animal2.isAMammal());

        System.out.println("A duck says " +animal3.getHello()
        +", is carnivorous: " +animal3.isCarnivorous()
        +", is a mammal: " +animal3.isAMammal());
    }
}

abstract class Animal
{
    public boolean isAMammal()
    {
        return(true);
    }

    public boolean isCarnivorous()
    {
        return(true);
    }

    abstract public String getHello();
}

class Dog extends Animal
{
    public String getHello()
    {
        return("Bark");
    }
}

class Cat extends Animal
{
    public String getHello()
    {
        return("Meow");
    }
}

class Duck extends Animal
{
    public boolean isAMammal()
    {
        return(false);
    }

    public boolean isCarnivorous()
    {
        return(false);
    }

    public String getHello()
    {
        return("Quack");
    }
}

在编译并运行我们的程序以后,输出应该如下:
A dog says Bark, is carnivorous: true, is a mammal: true
A cat says Meow, is carnivorous: true, is a mammal: true
A duck says Quack, is carnivorous: false, is a mammal: false

看看我们的例子,你将发现我们定义了一个叫做Animal的新类,它定义了三个方法:isAMammal, isCarnivorous, 和 getHello.你一概还注意到了,我们在每个现存的类申明的前面都添加了extends Animal这个语句.这个语句告诉编译器这些对象是Animal类的子类.

因为方法isAMammal 和 isCarnivorous 都返回 true,所以Dog和Cat类用不着重新实现--即"重载"这两个方法.但是鸭子既不是哺乳动物又不是肉食性的,所以Duck类需要重载这两种方法来返回正确的值.我们所有的对象都以自己独特的方式说"hello",所以它们都需要重载getHello方法.因为每种动物说"hello"的方式都不同,所以我们在基类中将getHello方法申明为抽象的,而且我们没有给这个方法一个函数体.这就迫使Animal的每一个子类重载getHello方法并根据每一个特定动物的需要来定义它.

因为我们将getHello方法申明为虚拟的,我们就不能直接实例化Animal对象.因此,我们需要将Animal类也申明为抽象的.我们通过在Animal类定义的开始行添加abstract关键字来实现这一点.子类重载它们基类的方法的能力就是多态.多态使得子类能够使用基类的方法或是在这些方法不足的时候重载它们.这就实现了代码重用,加快了代码的实现过程,而且它还隔离和程序中的bug,使得程序的维护更容易.

总结
在本文中,我们学习了如何确定潜在的对象.我们还学习了如何使用继承和多态来加快我们的代码实现过程并隔离错误,这使得代码的维护过程更加容易.下一次,我们将展开讨论多态和继承的概念并开始我们对动态绑定的讨论.

OOP: 继承以及多态

在"OOP简介:理解类和对象"这篇文章中,我们讨论了继承和多态性的好处.我们还粗略的学习了如何扩展基类定义子类,继承基类中合适的行为和属性而重载那些并不适合的行为和属性.这种方式能够削减代码宏余以及错误的堆积.

现在我们将更深入的考察多重继承性以及Java是如何处理它的.我们还将通过学习动态绑定来学习多态性.

深入继承性
一些面向对象的语言提供叫做"多重继承"的特点,当一个对象需要从多于一个的基类继承行为和属性的时候这是有价值的.多重继承在有些情况下是复杂的.例如,假设我们需要定义一个基类,Animal,然后是Animal的两个子类,LandAnimal 和 WaterAnimal.现在我们想要定义一个类来代表青蛙.青蛙是两栖动物,所以我们自然会想到定义Frog类从LandAnimal和WaterAnimal类继承.这使得Frog类能够同时从LandAnimal 和WaterAnimal类继承所需的行为和属性.

初看起来这是相当简单的;但是,让我们为Animal添加一个叫做LivingEnvironment的属性,并用方法getLivingEnvironment来返回它.我们假设LandAnimal 和 WaterAnimal类都重载了这个方法来实现特殊的功能.LandAnimal将返回Land作为它的LivingEnvironment属性的值,而WaterAnimal将返回Water作为它的LivingEnvironment属性的值.现在,当我们将Frog类作为LandAnimal 和 WaterAnimal 子类实现的时候,想要得到Frog的LivingEnvironment属性值,这时将遇到一个麻烦:Frog类的getLivingEnvironment方法是返回Land值呢还是Water值?答案取决于编译器如何处理多重继承.

我在前面的文章里就已经说过,Java不支持多重继承.但它确实允许一个对象通过使用叫做"接口"的功能拥有多个特性.下面的例子显示了定义LandAnimal的接口的可能的定义代码:
public interface LandAnimal
{
    public int getNumberOfLegs();
    public boolean hasATail();
}

一个使用接口的类在类定义语句的开始添加implements+接口名.例如,在Java中,我们会以下面的方式定义Frog类:

public class Frog extends Animal implements LandAnimal, WaterAnimal

接口并没有什么实际的功能;相反,它的作用是联系使用者和实现了这个接口的对象.接口保证了对象实现接口定义的方法.而且,一个实现接口的对象能够在运行时被强制转换成接口类型.例如,使用上面的Frog定义,并且假设LandAnimal类定义了一个叫做getNumberOfLegs的方法而WaterAnimal定义了一个叫做hasGills的方法,那么一个Frog类的实例可以在运行时被强制转换成LandAnimal或WaterAnimal对象:

Frog aFrog = new Frog();
int legCount = ((LandAnimal)aFrog).getNumberOfLegs();
Boolean gillFlag = ((WaterAnimal)aFrog).hasGills();

注意Forg为什么能够被强制转换成一个LandAnimal对象即使实际的LandAnimal对象并没有被创建.这使得我们能够在运行时以其带有的任何"身份"调用一个对象,这就是所谓的"动态绑定"或"运行时绑定".

深入多态性
Java使用动态绑定来使多态成为可能,它指的是Java用来在运行时选择调用的方法或对象的机制.重载构成了了Java中的一种特殊的多态机制,它表现在当一个类的两个或者两个以上的方法拥有相同的名字但是不同的参数列表,或者说"方法签名".一个方法的签名指的是方法的名字以及参数的类型和数目.类的每一个方法都有与之相关的唯一的签名.类可以有多个名字相同的方法只要它们的参数列表是唯一的.例如,我们能够为Animal类定义两个名字为getHello的方法,用其中一个方法来获得动物通常的叫声,而用另一个获得当动物被惊吓或是抚摩的时候的叫声.我们将给每一个方法唯一的签名:

public String getHello();
public String getHello(int mood);

现在,让我们修改例子程序来将我们讨论的一些概念付诸实践:
 

public class HelloWorld
{
public static void main(String[] args)
{
Dog animal1 = new Dog();
Cat animal2 = new Cat();
Duck animal3 = new Duck();

System.out.println("A dog says " +animal1.getHello()
+", when scared says: " +animal1.getHello(Animal.SCARED)
+", is carnivorous: " +animal1.isCarnivorous()
+", is a mammal: " +animal1.isAMammal());
System.out.println("A cat says " +animal2.getHello()
+", when comforted says: " +animal2.getHello(Animal.COMFORTED)
+", is carnivorous: " +animal2.isCarnivorous()
+", is a mammal: " +animal2.isAMammal());
System.out.println("A duck says " +animal3.getHello()
+", when scared says: " +animal3.getHello(Animal.SCARED)
+", is carnivorous: " +animal3.isCarnivorous()
+", is a mammal: " +animal3.isAMammal());
}
}

abstract class Animal
{
public static final int SCARED = 1;
public static final int COMFORTED = 2;

public boolean isAMammal()
{
return(true);
}

public boolean isCarnivorous()
{
return(true);
}

abstract public String getHello();
abstract public String getHello(int mood);
}

interface LandAnimal
{
public int getNumberOfLegs();
public boolean hasATail();
}

interface WaterAnimal
{
public boolean hasGills();
public boolean laysEggs();
}

class Dog extends Animal implements LandAnimal
{
// 重载父类的方法
public String getHello()
{
return("Bark");
}

public String getHello(int mood)
{
switch (mood) {
case SCARED:
return("Growl");
case COMFORTED:
return("");
}

return("Bark");
}

// LandAnimal 接口的实现
public int getNumberOfLegs()
{
return(4);
}

public boolean hasATail()
{
return(true);
}
}

class Cat extends Animal implements LandAnimal
{
// 重载父类的方法
public String getHello()
{
return("Meow");
}

public String getHello(int mood)
{
switch (mood) {
case SCARED:
return("Hiss");
case COMFORTED:
return("Purr");
}

return("Meow");
}

// LandAnimal 接口实现
public int getNumberOfLegs()
{
return(4);
}

public boolean hasATail()
{
return(true);
}
}

class Duck extends Animal implements LandAnimal, WaterAnimal
{
// 重载父类的方法
public String getHello()
{
return("Quack");
}

public String getHello(int mood)
{
switch (mood) {
case SCARED:
return("Quack, Quack, Quack");
case COMFORTED:
return("");
}

return("Quack");
}

public boolean isAMammal()
{
return(false);
}

public boolean isCarnivorous()
{
return(false);
}

// WaterAnimal 接口实现
public boolean hasGills()
{
return(false);
}

public boolean laysEggs()
{
return(true);
}

// LandAnimal 接口实现
public int getNumberOfLegs()
{
return(2);
}

public boolean hasATail()
{
return(false);
}
}

程序执行后输出的结果如下:

A dog says Bark, when scared says: Growl, is carnivorous: true, is a mammal: true
A cat says Meow, when comforted says: Purr, is carnivorous: true, is a mammal: true
A duck says Quack, when scared says: Quack, Quack, Quack, is carnivorous: false, is a mammal: false

总结
综合继承,多态和接口的概念提供了一组强大的编程工具,允许我们重用代码,隔离错误的发生,并获得动态/运行时绑定带来的好处.在下一篇文章里,我们将讨论如何使用Java的变量作用域/可见域规则来控制方法和属性的暴露问题.

OOP: 限制对象属性的访问

在"OOP入门:深入研究继承和多态性"这篇文章中,我们继续讨论了继承和多态性的好处.我们还学习了其它的东西:

  • 虽然Java只支持从一个父类继承,但它使用接口的方式支持多重继承.
  • 接口实现了多态,使得我们能够给与对象不同特性以满足不同的需要.
  • 你可以使用多态机制让完成相似功能的不同的方法拥有相同的名字但是拥有不同的参数列表.
  • 动态/运行时的绑定机制允许一个对象在运行时被强制转化成你所需要的对象类型,前提是这个对象实现了必需的接口或者括展了特定的父类.

下面我们将讨论通过限制对对象属性和方法的访问来强制实现对多重接口实现和父类拓展的正确使用的目的和实用性.


黑箱方法:封装

一个基本的面向对象的概念就是封装--将表示一个对象状态的数据与其它对象隔离开来.这一点是通过一个通常叫做作用域的概念来实现的.作用域指的是编程语言的一种能力,这种能力被用来实现一些限制对类或者结构体成员变量的访问的规则.大多数面向对象的语言支持作用域机制,这些机制通常是通过诸如public, protected, 和 private之类的特殊关键字来实现的.

Java提供了四种不同的作用范围:public, package, protected, 和 private.任何类,方法或者成员变量都能通过使用public, protected, 和 private关键字来显式的加以保护.任何类,方法,或者成员变量如果没有使用上面的关键字都将被隐式的给与package的作用范围.所有这些就构成了Java中命名空间的概念.

命名空间和软件包
一个命名空间可以被看成是在一个给定的上下文中一组相关的名字或是标识符.命名空间避免了拥有相同名字或标识符的实体存在于同一个上下文里.这里隐含的意思是只要实体是存在于不同的命名空间中,那么拥有相同名字或者标识符的实体就能够呆在一块儿.Java使用软件包的概念来实现命名空间和作用范围控制.

软件包是一个在统一的名字下的类和接口的集合.每一个类或者接口都必须存在于用package关键字构成的软件包申明语句定义的命名空间中.例如,下面的申明语句:

package com.mycompany.apps.HelloWorld;

它申明了一个存在于com.mycompany.apps软件包中的名叫HelloWorld的类或者接口.软件包申明总是放在包含了类或者接口定义的文件的顶部.

在java开发界,目前对软件包的命名有一个建议,就是使用公司或组织的域名(以相反的顺序),作为你的软件包的第一部分.因为域名是全球唯一的,所以使用你的域名来命名你的软件包也能使你软件包的名字全球唯一.

如果一个Java类或者接口没有包含一个软件包申明,那么它就属于"unamed package,"也就是没有名字的软件包.无名的软件包应该只用来测试程序或是代码原型等等.

请尽量使用封装机制
在任何程序风格中,尤其是在面向对象的编程中,将暴露的编程界面背后的实现细节隐藏起来是非常关键的.这使得低层的实现方法能够在不影响编程界面现有的客户端的前提下改变,而且能使对象完全自主的管理它们自己的状态.

分离界面和实现方法的第一步就是隐藏类的内部数据.要使一个成员变量或是方法对Java中所有潜在的客户不可见,可以将用private关键字将它声明为私有成员变量,如下所示:

private int customerCount;

要使一个成员变量或是方法除了其本身所属类的子类以外对Java中所有潜在的客户不可见可以使用protected关键字将它声明成保护类型的,如下所示:

protected int customerCount;

要使一个成员变量或是方法除了其本身所属的类以外对Java中所有潜在的客户不可见不使用任何关键字来声明它,如下所示:

int customerCount;

要将一个成员变量或是方法暴露给其所属类的所有客户,可以用public关键字将它声明为公共的成员变量,如下所示:

public int customerCount;

访问成员变量
不论一个对象的数据隐藏得多么好,客户仍然需要访问一些隐藏的数据.这是通过调用函数或方法来实现的.在Java中,使用特殊的被称做属性访问器的方法来访问隐藏的数据是可能的.在Java中属性访问器和通常的函数之间并没有本质的区别.将一个通常的方法转变成一个属性访问器唯一要做的事情就是参照一个命名规则来添加方法.

读数据的访问器的命名规则就是将方法命名为和数据域一样的名字,将首字母大写,然后在方法名字的前面添加get或是is."写"数据访问器的命名规则就是将方法命名为和数据域一样的名字,将首字母大写,然后在方法名字的前面添加set.下面的例子演示了写和读数据的数据访问器方法.

这是一个"读"数据访问器方法:

public int getCustomerCount()
{
    return(customerCount);
}

这是另一个"读"数据访问器方法

public int isCustomerActive()
{
    return(customerActive);
}

这是一个"写"数据访问器方法:

public void setCustomerCount(int newValue)
{
    customerCount = newValue;
}

使用访问器方法允许其它对象访问一个对象的隐藏数据而不直接涉及数据域.这就允许拥有隐含数据的对象在改变成员变量以前做正确性检查并控制成员变量是否应该被设置成新的值.

现在让我们修改例子程序来使用这些概念,如所示.

public class HelloWorld
{
    public static void main(String[] args)
    {
        Dog animal1 = new Dog();
        Cat animal2 = new Cat();
        Duck animal3 = new Duck();
        animal1.setMood(Animal.COMFORTED);
        System.out.println("A comforted dog says " +animal1.getHello());
        animal1.setMood(Animal.SCARED);
        System.out.println("A scared dog says " +animal1.getHello());
        System.out.println("Is a dog carnivorous? " +animal1.isCarnivorous());
        System.out.println("Is a dog a mammal? " +animal1.isCarnivorous());
        animal2.setMood(Animal.COMFORTED);
        System.out.println("A comforted cat says " +animal2.getHello());
        animal2.setMood(Animal.SCARED);
        System.out.println("A scared cat says " +animal2.getHello());
        System.out.println("Is a cat carnivorous? " +animal2.isCarnivorous());
        System.out.println("Is a cat a mammal? " +animal2.isCarnivorous());
        animal3.setMood(Animal.COMFORTED);
        System.out.println("A comforted duck says " +animal3.getHello());
        animal3.setMood(Animal.SCARED);
        System.out.println("A scared duck says " +animal3.getHello());
        System.out.println("Is a duck carnivorous? " +animal3.isCarnivorous());
        System.out.println("Is a duck a mammal? " +animal3.isCarnivorous());
    }
}

abstract class Animal
{
    // The two following fields are declared as public because they need to be
    // accessed by all clients
    public static final int SCARED = 1;
    public static final int COMFORTED = 2;
    // The following fields are declared as protected because they need to be
    // accessed only by descendant classes
    protected boolean mammal = false;
    protected boolean carnivorous = false;
    protected int mood = COMFORTED ;
    public boolean isMammal()
    {
        return(mammal);
    }

    public boolean isCarnivorous()
    {
        return(carnivorous);
    }

    abstract public String getHello();

    public void setMood(int newValue)
    {
        mood = newValue;
    }

    public int getMood()
    {
        return(mood);
    }
}

interface LandAnimal
{
    public int getNumberOfLegs();
    public boolean getTailFlag();
}

interface WaterAnimal
{
    public boolean getGillFlag();
    public boolean getLaysEggs();
}

class Dog extends Animal implements LandAnimal
{
    // The following fields are declared private because they do not need to be
    // access by any other classes besides this one.
    private int numberOfLegs = 4;
    private boolean tailFlag = true;
    // Default constructor to make sure our properties are set correctly
    public Dog()
    {
        mammal = true;
        carnivorous = true;
    }
    // methods that override superclass's implementation
    public String getHello()
    {
        switch (mood) {
            case SCARED:
                return("Growl");
            case COMFORTED:
                return("Bark");
        }
        return("Bark");
    }

    // Implementation of LandAnimal interface

    public int getNumberOfLegs()
    {
        return(numberOfLegs);
    }

    public boolean getTailFlag()
    {
        return(tailFlag);
    }
}

class Cat extends Animal implements LandAnimal
{
    // The following fields are declared private because they do not need to be
    // access by any other classes besides this one.
    private int numberOfLegs = 4;
    private boolean tailFlag = true;
    // Default constructor to make sure our properties are set correctly
    public Cat()
    {
        mammal = true;
        carnivorous = true;
    }
    // methods that override superclass's implementation
    public String getHello()
    {
        switch (mood) {
            case SCARED:
                return("Hiss");
            case COMFORTED:
                return("Purr");
        }
        return("Meow");
    }

    // Implementation of LandAnimal interface
    public int getNumberOfLegs()
    {
        return(numberOfLegs);
    }

    public boolean getTailFlag()
    {
        return(tailFlag);
    }
}

class Duck extends Animal implements LandAnimal, WaterAnimal
{
    // The following fields are declared private because they do not need to be
    // access by any other classes besides this one.
    private boolean gillFlag = false;
    private boolean laysEggs = true;
    private int numberOfLegs = 2;
    private boolean tailFlag = false;
    // Default constructor to make sure our properties are set correctly
    public Duck()
    {
        mammal = false;
        carnivorous = false;
    }
    // methods that override superclass's implementation
    public String getHello()
    {
        switch (mood) {
            case SCARED:
                return("Quack, Quack, Quack");
            case COMFORTED:
                return("Quack");
        }
        return("Quack");
    }

    // Implementation of WaterAnimal interface

    public boolean getGillFlag()
    {
        return(gillFlag);
    }

    public boolean getLaysEggs()
    {
        return(laysEggs);
    }

    // Implementation of LandAnimal interface

   
public int getNumberOfLegs()
    {
        return(numberOfLegs);
    }

    public boolean getTailFlag()
    {
        return(tailFlag);
    }
}

这个程序的输出结果应该如下:
A comforted dog says Bark
A scared dog says Growl
Is a dog carnivorous? true
Is a dog a mammal? true
A comforted cat says Purr
A scared cat says Hiss
Is a cat carnivorous? true
Is a cat a mammal? true
A comforted duck says Quack
A scared duck says Quack, Quack, Quack
Is a duck carnivorous? false
Is a duck a mammal? false

总结
使用数据隐藏/封装机制是一个控制对象数据和状态的强有力的方法.它允许一个对象决定是否要改变一个成员变量和如何改变一个成员变量.这使得一个对象的实现细节能够改变而暴露的对象界面得以维持.在我们的下一篇文章中,我们将进一步探讨Java提供的变量作用域规则并开始学习Java对象是如何构造和初始化的.

Java学习之神奇的初始化

java在初始化的时候也有很多讲究,因为java中出现了类,所以在初始化的时候就有可能使用到创建新对象,所以,对于初始化的顺序要求的比较严格,请看下面一个程序,是thinking in java中的一个程序,被我稍加改编,这样可以更好的说明几个初始化的要点:

class Cup

{

       Cup(int marker)

       {

              System.out.println("Cup(" + marker + ")");

       }

       void f(int marker)

       {

              System.out.println("f(" + marker + ")");

       }

}

 

class Cups

{

       static Cup c1=new Cup(1);

       Cup c3=new Cup(3);

       static Cup c2= new Cup(2);

       Cups()

       {

              System.out.println("Cups()");

       }

       Cup c4=new Cup(4);

}

 

public class ExplicitStatic

{

       Cups c=new Cups();

       {

              System.out.println("Hello");

       }

       public static void main(String[] args)

       {

              System.out.println("Inside main()");

              Cups.c1.f(99);

              ExplicitStatic x=new ExplicitStatic();

       }

       static Cups x = new Cups();

}

大家可以手动执行一下这个程序,考虑一下结果是什么,然后参照下面的答案对照一下,看看是否正确:

 

Cup(1)

Cup(2)

Cup(3)

Cup(4)

Cups()

Inside main()

f(99)

Cup(3)

Cup(4)

Cups()

Hello

我总结了四个初始化的要点,如下:

1、              如果有static,即静态成员定义,首先初始化static的变量,如,在类Cups中c3在c2前面,可是在输出的结果中,你可以发现,c2是在c3前执行的,这就是因为,所有的static都在第一时间被初始化。

2、              Static只初始化一次,在第二次创建类的对象的时候,就不会去执行static的语句,如,在第二次执行new Cups()的时候,就只输出了Cup(3)和Cup(4),显然,static的两个创建对象的语句没有做。

3、              变量的初始化在方法前。如,在Cups类中,方法Cups()在语句Cup c4=new Cup(4)之前,可是输出结果的时候,打印的Cups()却在Cup(4)之后。

4、              在含有main的类中执行顺序是先做static,然后就是main,而不是像其它类一样,除了static就按顺序做下来。如,在main函数中,如果去掉语句ExplicitStatic x=new ExplicitStatic(),则Cups c=new Cups()和System.out.println("hello")都不会执行。另外,留个小问题,如果去掉了System.out.println("hello")外的括号会怎么样呢?

【上篇】
【下篇】

抱歉!评论已关闭.