Java学习笔记之九 对象与类

面向对象程序设计概述

在20世纪70年代,流行“结构化”过程化程序设计开发技术。

在20世纪末,面向对象程序设计成为主流的程序设计范型。

面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。

程序中的许多对象来自于标准库,还有一些是自定义的。

是自己构造对象,还是从外界购买对象,完全取决于预算和时间。

从根本上说,只要对象能够满足要求,就不必关心其功能的具体实现过程。

结构化程序设计通过设计一系列的过程(算法)来求解问题。这些过程一旦被确定,就要考虑存储数据的方式。所以,Pascal语言设计者Niklaus Wirth说:算法+数据结构=程序。(他1975年发布的书名)。这里,算法是第一位的,数据结构是第二位的。

面向对象程序设计中,数据被放在了第一位,之后再考虑操作数据的算法。

类(class)是构造对象的模板或者蓝图。由类构造(constuct)对象的过程称为创建类的实例(instance)。

用Java编写的代码都位于某个类的内部。

封装(encapsulation,有时称为数据隐藏)是与对象有关的一个重要概念。对象中的数据称为实例域(instance fields),操纵数据的过程称为方法(method)。对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态(state)。无论何时,只要向对象发送一个消息,它的状态就有可能发生改变。

实现封装的关键,是绝对不能让类中的方法直接接触其他类的实例域。程序仅通过对象的方法域对象数据交互。

封装给予了对象“黑盒”特征。一个类可以全面地改变存储数据的方式,只要依旧使用同样的方法操作数据。其他对象不会知道也不介意所发生的变化。

面向对象的另一个原则,是类可以通过扩展另一个类来建立。

对一个已有的类扩展时,这个扩展后的新类,具有所扩展的类的全部属性和方法。在新类中,只需要提供仅适用于这个类的新方法和数据域即可。通过扩展一个类来建立另一个类的过程,称为继承(inheritance)。

对象有3个主要特性:

  • 对象的行为(behavior)。可以对对象施加哪些操作。
  • 对象的状态(state)。施加操作时,对象如何响应。
  • 对象标识(identity)。区分具有相同行为与状态的不同对象。

传统的过程化程序设计,必须从顶部的main函数开始编写程序。

面向对象的系统没有顶部。

定义类时,在分析问题的过程中寻找名词和动词。这是一种粗略的方法。具体创建类,完全取决于个人的开发经验。

类之间的关系,常见的有:

  • 依赖。use-a。
  • 聚合。has-a。
  • 继承。is-a。

应尽可能地将相互依赖地类减至最少。让类之间地耦合度最小。

使用现有类

在Java中,没有类就没法做任何事情。

并不是所有的类都具有面向对象特征。比如Math类。Math类只封装了功能,它没有数据。

要想使用对象,就必须先构造对象,并指定其初始状态。然后,对对象施加方法。

在Java语言中,使用构造器(constuctor)构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。

构造器的名字应该与类名相同。构造对象时,在构造器前面加上new操作符。

一个对象变量并没有实际包含一个对象,仅仅引用一个对象。

用户自定义类

要想创建一个完整的应用程序,应该将若干类组合在一起,其中只要一个类有main方法。

在一个源文件中,只能有一个公有类,但可以有任意数目的非公有类。

多个源文件的编译:

方法一:使用通配符调用Java编译器。

javac Some*.java

方法二:仅编译入口类。

Java编译器相当于内置了make功能,会自动查找对应的类的class文件,如何不存在,则会从源文件编译。

final实例域不是常量。

静态域与静态方法

如果将域定义为static,每个类中只能由一个这样的域。意思是,这个实际数据只有一份,而不是只能定义一个域为静态的。

静态域属于类,不属于任何独立的对象。

静态常量。静态常量是类的常量。所有类的实例仅此一份。

静态方法。静态方法是一种不能向对象实施操作的方法。不能在静态方法中访问实例域。静态方法可以访问自身类的静态域。

调用静态方法时,不需要使用对象。

main方法也是一个静态方法。main方法不对任何对象进行操作。

在启动程序时,还没有任何一个对象。静态的main方法将执行并创建程序所需要的对象。

每一个类可以由一个main方法。

方法参数

  • 一个方法不能修改一个基本数据类型的参数
  • 一个方法可以改变一个对象参数的状态
  • 一个方法不能实现让对象参数引用一个新的对象

对象构造

Java提供了多种编写构造器的方式。

重载。overloading。如果多个方法有相同的名字、不同的参数,便产生了重载。

Java允许重载任何方法。

要完整描述一个方法,需要指出方法名、参数类型。这叫做方法的签名,signature。

返回类型不是签名的一部分。

默认域初始化。如果在构造器中没有显示地给域赋予初值,会自动赋为默认值:数值型为0,布尔型为false,对象引用为null。

默认域初始化并不是一种良好地编程习惯。

默认构造器。指没有参数地构造器。

在编写类时如果没有编写构造器,系统会提供一个默认构造器。

仅当类没有提供任何构造器地时候,系统才会提供一个默认地构造器。

显式域初始化。这是一种良好地编程习惯。

对象析构。finalize方法。

Java有自动地废料回收器,不需要人工回收内存,所以Java不支持析构器。

可以为任何一个类添加finalize方法。finalize方法将在废料回收器清除对象之前调用。

在实际应用中,不要依赖于finalize方法回收任何短缺地资源,因为很难知道这个方法什么时候才能够调用。

Java允许使用包(package)将类组织起来。

借助包,可以方便地组织自己地代码,并将自己地代码与别人提供地代码库分开管理。

标准地java类库分布在多个包中。

标准地java包具有层次结构,可以嵌套层次组织包。

所有标准地java包都处于java和javax包层次中。

使用包地主要原因是确保类名地唯一性。

为了保证包地绝对唯一性,Sun公司建议将公司地因特网域名以逆序地形式作为包名,并且对于不同的项目使用不同的子包。

例如,horstmann.com是《Core Java》作者注册的郁闷。逆序形式为com.horstmann。这个包可以进一步划分子包,如com.horstmann.corejava。

从编译器的角度来看,嵌套的包之间没有任何关系。每一个包都拥有独立的类集合。

类的导入。一个包可以使用所属包中的所有类,以及其他包中的公有类(public class)。

采用两种方式访问另一个包中的公有类:

方式一:在每个类名之前添加完整的包名。

方式二:使用import语句。一旦使用了import语句,使用类时,就不必写出包的全名了。

可以使用impot语句导入一个特定的类或者整个包。

import语句应该位于源文件的顶部(但位于package语句的后面)。

如果导入包时,出现类名冲突,应使用包全称。

在包中定位类时编译器的工作。类文件中的字节码肯定使用完整的包名来引用其他类。

静态导入。从Java SE 5.0开始,import语句不仅可以导入类,还可以导入静态方法和静态域。

将类放入包中。将包的名字放在源文件的开头,包中定义类的代码之前。

package com.horstmann.corejava;

public class Employee
{
    ...
}

如果没有在源文件中放置package语句,这个源文件中的类会被放置在一个默认包(default package)中。默认包是一个没有名字的包。

将包中的文件放到与完整的包名匹配的子目录中。

编译:

javac com/mycompany/PayrollApp.java
java com.mycompany.PayrollApp

编译器对文件进行操作,java解释器加载类。

编译器在编译源文件时不检查目录结构。

包作用域。public的部分可以被任意类使用。private的部分只能被定义它们的类使用。如果没有指定public和private,这个部分(类、方法、变量)可以被同一个包中的所有方法访问。

类路径

类存储在文件系统的子目录中。类的路径必须与包名匹配。

类文件也可以存储在JAR(Java归档)文件中。在一个JAR文件中,可以包含多个压缩形式的类文件和子目录,这样可以节省空间、改善性能。

在程序中用到第三方(third-party)的库文件时,通常会给出一个或者多个需要包含的JAR文件。

JDK也提供了许多JAR文件。

JAR文件使用ZIP格式组织文件和子目录。

为了使类能被多个程序共享,可以:

  • 将类放在一个目录中。树状层次结构。
  • 将JAR文件放在一个目录中。
  • 设置类路径(class path)。类路径是所有包含类文件的路径的集合。

类路径包括:

  • 基目录
  • 当前目录
  • JAR文件

javac编译器总是在当前的目录中查找文件。

java虚拟机仅在类路径中有"."目录的时候才查看当前目录。

设置类路径:

  • 使用java 的-classpath或-cp选项指定类路径。所有指令应该写在一行中。
  • 也可以设置CLASSPATH环境变量。

文档注释

javadoc工具可以由源文件生成一个HTML文档。

类注释。类注释必须放在import语句之后,类定义之前。

方法注释。方法注释必须放在所描述的方法之前。

域注释。只需要对公有域(通常是静态常量)建立文档。

包与概述注释。

要想产生包注释,由两种选择。

一,提供一个package.html命名的HTML文件。<BODY></BODY>之间的所有文本会被抽取出来。

二,提供一个以package-info.java命名的java文件。这个文件必须包含一个初始的以/***/界定的javadoc注释,跟随在一个包语句之后。它不应该包含更多的代码或注释。

概述注释。overview.html文件中的<BODY></BODY>部分会被提取出来。

注释的抽取。

  • 切换到包含想要生成文档的源文件目录。对于嵌套的包,应切换到基目录。
  • 运行命令。

对于一个包:javadoc -d docDirectory nameOfPackage

对于多个包:javadoc -d docDiectory nameOfpackage1 nameOfPackage2

如果文件在默认包中,运行:javadoc -d docDirectory *.java

用link选项为标准类库添加超链接。

javadoc -link http://java.sun.com/javase/6/docs/api *.java

类设计技巧

一定将数据设计为私有。绝对不要破坏封装性。数据的表示形式很可能会改变,但它们的使用方式却不会经常发生变化。

一定要对数据初始化。

不要在类中使用过多的基本数据类型。用其他的类代替多个相关的基本数据类型的使用。

不是所有的域都需要独立的域访问器或域更改器。

使用标准格式进行类的定义。一定采用下面的形式书写类的内容:

  • 公有访问特性部分
  • 包作用域访问特性部分
  • 私有访问特性部分

在每一部分中,应该按照下列顺序列出:

  • 实例方法
  • 静态方法
  • 实例域
  • 静态域

对职责过多的类进行分解。

类名和方法名要能够体现它们的职责。