Java编程基础:面向对象编程

389 阅读12分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

抽象编程

我们将问题空间中的元素以及它们在方案空间的表示物称作“对象”(Object)。当然,还有一些在问题空间没有对应体的其他对象。通过添加新的对象类型,程序可进行灵活的调整,以便与特定的问题配合。与现实世界的“对象”或者“物体”相比,编程“对 象”与它们也存在共通的地方:它们都有自己的状态(state)和行为(behavior)。比如,狗的状态有名字、颜色等,狗的行为有叫唤、摇尾等。

软件世界中的对象和现实世界中的对象类似,对象存储状态在字段(field)里,而通过方法(methods)暴露其行为。方法对对象的内部状态进行操作,并作为对象与对象之间通信主要机制。隐藏对象内部状态,通过方法进行所有的交互,这个面向对象编程的一个基本原则——数据封装(data encapsulation)。

所有东西都是对象。可将对象想象成一种新型变量;它保存着数据,但可要求它对自身进行操作。理论上讲,可从要解决的问题身上提出所有概念性的组件,然后在程序中将其表达为一个对象。

程序是一大堆对象的组合;通过消息传递,各对象知道自己该做些什么。为了向对象发出请求,需向那个对象“发送一条消息”。更具体地讲,可将消息想象为一个调用请求,它调用的是从属于目标对象的一个子例程或函数。

每个对象都有自己的存储空间,可容纳其他对象。或者说,通过封装现有对象,可制作出新型对象。所以,尽管对象的概念非常简单,但在程序中却可达到任意高的复杂程度。

每个对象都有一种类型。根据语法,每个对象都是某个“类”的一个“实例”。其中,“类”(Class)是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?”。

同一类所有对象都能接收相同的消息。由于类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,所以一个圆完全能接收形状消息。这意味着可让程序代码统一指挥“形状”,令其自动控制所有符合“形状”描述的对象,其中自然包括 “圆”。这一特性称为对象的“可替换性”,是 OOP 最重要的概念之一。

类(Class)

在现实世界中,你经常会发现许多单个对象是同类。有可能是存在其他成千上万的自行车,都是一样的品牌和型号。每个自行车都由相同的一组设计图纸而建成,并因此包含相同的组件。在面向对象的术语,我们说你的自行车被称为自行车对象类(class of objects)的实例(instance)。类就是创建单个对象的设计图纸。

下面是一个 Bicycle (自行车)类的实现:

class Bicycle {

    int cadence = 0;
    int speed = 0;
    int gear = 1;

    void changeCadence(int newValue) {
         cadence = newValue;
    }

    void changeGear(int newValue) {
         gear = newValue;
    }

    void speedUp(int increment) {
         speed = speed + increment;   
    }

    void applyBrakes(int decrement) {
         speed = speed - decrement;
    }

    void printStates() {
         System.out.println("cadence:" +
             cadence + " speed:" + 
             speed + " gear:" + gear);
    }
}

字段 cadence, speed, 和 gear 是对象的状态,方法 changeCadence, changeGear, speedUp 定义了与外界的交互。

你可能已经注意到,Bicycle 类不包含一个 main 方法。这是因为它不是一个完整的应用程序。这是自行车的设计图纸,可能会在应用程序中使用。创建和使用新的 Bicycle对象是应用程序中的其他类的责任。

下面是 BicycleDemo 类,创建两个单独的 Bicycle 对象,并调用其方法:

class BicycleDemo {
    /**
     * @param args
     */
    public static void main(String[] args) {
         // Create two different 
        // Bicycle objects
        Bicycle bike1 = new Bicycle();
        Bicycle bike2 = new Bicycle();

        // Invoke methods on 
        // those objects
        bike1.changeCadence(50);
        bike1.speedUp(10);
        bike1.changeGear(2);
        bike1.printStates();

        bike2.changeCadence(50);
        bike2.speedUp(10);
        bike2.changeGear(2);
        bike2.changeCadence(40);
        bike2.speedUp(10);
        bike2.changeGear(3);
        bike2.printStates();

    }

}

执行程序,输出为:

cadence:50 speed:10 gear:2
cadence:40 speed:20 gear:3

接口(Interface)

所有对象——尽管各有特色——都属于某一系列对象的一部分,这些对象具有通用的特征和行为。

每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的等价或对应关系是面向对象程序设计的基础。

Light lt = new Light();
lt.on();

在这个例子中,类型/类的名称是 Light,Light 对象的名称是 lt ,可向 Light 对象发出的请求包括打开(on)、关闭(off)、变得更明亮(brighten )或者变得更暗淡(dim)。

通过简单地定义一个“引用(reference)”(lt),我们创建了一个 Light 对象,并用 new 关键字来请求那个类新建的对象。为了向对象发送一条消息,我们列出对象名(lt),再用一个句点符号(.)把它同消息名称(on)连接起来。从中可以看出,使用一些预先定义好的类时,我们在程序里采用的代码是非常简单和直观的。

对应自行车的行为,可以定义如下接口:

interface Bicycle {

    // wheel revolutions per minute
    void changeCadence(int newValue);

    void changeGear(int newValue);

    void speedUp(int increment);

    void applyBrakes(int decrement);
}

实现该接口的类 ACMEBicycle,使用 implements 关键字:

class ACMEBicycle implements Bicycle {

    int cadence = 0;
    int speed = 0;
    int gear = 1;

    @Override
    public void changeCadence(int newValue) {
        // TODO Auto-generated method stub

    }

    @Override
    public void changeGear(int newValue) {
        // TODO Auto-generated method stub

    }
    
    @Override
    public void speedUp(int increment) {
        // TODO Auto-generated method stub

    }

    @Override
    public void applyBrakes(int decrement) {
        // TODO Auto-generated method stub

    }

注: 接口的实现方法前必须添加 public 关键字。

包(Package)

包是组织相关的类和接口的命名空间。从概念上讲,类似于计算机上的文件夹,用来将各种文件进行分类。

Java 平台提供了一个巨大的类库(包的集合),该库被称为“应用程序接口”,或简称为“API”。其包代表最常见的与通用编程相关的任务。

例如,一个 String 对象包含了字符串的状态和行为;File 对象允许程序员轻松地创建,删除,检查,比较,或者修改文件系统中的文件;Socket 对象允许创建和使用网络套接字;各种 GUI 对象创建图形用户界面。从字面上有数以千计的课程可供选择。作为开发人员只需要专注于特定的应用程序的设计即可,而不是从基础设施建设开始。

对象提供服务

当设计一个程序时,需要将对象想象成一个服务的供应商。对象提供服务给用户,解决不同的问题。

比如,在设计一个图书管理软件,你可能设想一些对象包含了哪些预定义输入,其他对象可能用于图书的统计,一个对象用于打印的校验等。这都需要将一个问题分解成一组对象。

将对象的思考作为服务供应商有一个额外的好处:它有助于改善对象的凝聚力。高内聚(High cohesion) 是软件设计的基本质量:这意味着,一个软件组件的各方面(如对象,尽管这也可以适用于一个方法或一个对象的库)“结合在一起”。在设计对象时经常出现的问题是将太多的功能合并到一个对象里面。例如,在您的支票打印模块,你可以决定你需要知道所有有关格式和打印的对象。你可能会发现,这对于一个对象来说有太多的内容了,那你需要三个或三个以上的对象。一个对象用于查询有关如何打印一张支票的信息目录。一个对象或一组对象可以是知道所有不同类型的打印机的通用打印接口。第三个对象可以使用其他两个对象的服务来完成任务。因此,每个对象都有一套它提供的有凝聚力的服务。良好的面向对象设计,每个对象做好一件事,但不会尝试做太多。

将对象作为服务供应商是一个伟大的简化工具。这不仅在设计过程中是非常有用的,也在当别人试图理解你的代码或重用的对象。如果能看到根据它提供什么样的服务获得对象的值,它可以更容易适应它到设计中。

封装

封装是面向对象的特征之一,是对象和类概念的主要特性。

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

封装的优点

  • 将变化隔离
  • 便于使用
  • 提高重用性
  • 提高安全性

封装的缺点:

将变量等使用private修饰,或者封装进方法内,使其不能直接被访问,增加了访问步骤与难度!

封装的实现形式

  • A、使用访问权限修饰符private 在定义JavaBean时对于成员变量使用private进行修饰,同时对外提供set、get方法 使用了private修饰的成员在其他类中不能直接访问,此时需要使用set、get方法进行。
  • B、定义一个Java类与Java的方法就是最简单最常见的面向对象的封装操作,这些操作符合隐藏实现细节,提供访问方式的思路。

继承

面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

通过继承创建的新类称为“子类”或“派生类”。

被继承的类称为“基类”、“父类”或“超类”。

继承的过程,就是从一般到特殊的过程。

要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。

在Java语言中,一个类只能单继承,可以实现多个接口。继承就是子类继承父类的特征和行为,使得子类对象具有父类的非private属性和方法。

类的继承格式

通过extends关键字申明一个类继承另一个类,如:

  • class父类{}

  • class子类extends父类{}

为什么需要继承?

  • 减少代码重复、臃肿,提高代码可维护性。

继承的特性

  • 子类拥有父类非private的属性和方法;
  • 子类可以拥有完全属于自己的属性和方法(对父类扩展);
  • Java是单继承(每个子类只能继承一个父类);但是Java可以是多重继承(如A继承B,B继承C)。

Super和this关键字:

  • Super关键字:我们可以通过super关键字来实现子类对父类成员的访问,引用当前实例对象的父类。

  • This关键字:指向实例对象自己的引用。

多态

面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。

多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

多态的作用:消除类型之间的耦合关系。

现实中,关于多态的例子不胜枚举。比方说按下 F1 键这个动作,如果当前在 Word 下弹出的就是 Word 帮助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。

多态存在的三个必要条件:

  • 要有继承
  • 要有重写
  • 父类引用指向子类对象。

多态的好处:

  1. 可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆 Circle 类工作,对其他任何圆形几何体,如圆环,也同样工作。
  2. 可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
  3. 接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。图中超类 Shape 规定了两个实现多态的接口方法,computeArea() 以及 computeVolume()。子类,如 Circle 和 Sphere 为了实现多态,完善或者覆盖这两个接口方法。
  4. 灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
  5. 简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。