[关闭]
@7788999z 2014-05-09T08:32:54.000000Z 字数 3885 阅读 677

第4章 对象与类

Java核心技术卷1


4.1 面向对象程序设计概述

4.1.1 类

(class)是构造对象的模板或蓝图。

4.1.2 对象

对象的三个主要特性:

  • 对象的行为(behavior)——可以对对象施加哪些操作,或者说可以对对象施加哪些方法。
  • 对象的状态(state)——当施加那些方法时,对象如何响应?
  • 对象的标识(identity)——如何辨别具有相同行为与状态的不同对象?

4.1.3 识别类

识别类的简单规则是:在分析问题的过程中寻找名词,而方法对应着动词。
当然,所谓“名词与动词”原则只是一种粗略的方法,在创建类的时候,哪些名词和动词是重要的完全取决于个人的经验。

4.1.4 类之间的关系

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

  • 依赖(“uses-a”)
  • 聚合(“has-a”)
  • 继承(“is-a”)

依赖(dependence),即“use-a”的关系,是一种最明显的、最常见的关系。通常,如果一个类的方法操纵另一个类的对象,我们就说一个类依赖与另一个类。比如,类A的一个方法的参数中有类B的一个实例,我们就说类A依赖于类B。

应该尽可能地将相互依赖的类减至最少。如果类A不知道类B的存在,它就不会关心类B的任何改变(这意味着类B的改变不会导致类A产生任何bug)。用软件工程的术语来说,就是让类之间的耦合度最小。

聚合(aggregation),即“has-a”的关系,是一种具体且易于理解的关系。例如,一个Order对象包含一些Item对象。聚合关系意味着类A的对象包含着类B的对象。

表达类关系的UML符号

继承(inheritance),即“is-a”的关系,是一种用于表示特殊与一般关系的。

4.2 使用现有类

4.2.1 对象与对象变量

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

构造器的名字应该与类名相同。

对象变量仅仅引用了对象。实际上,对象变量如Date today = new Date()中,today为对象变量,today表示在内存中的栈里面的一个内存地址,这个内存地址为一个数值,这个数值指向了内存中堆里面的具体的对象new Date()。可以把对象变量想象成门牌号,对象则表示具体的内容。如果光有门牌号,而没有指向具体的内容,则我们不能对这个门牌号进行任何具体的操作(比如关门、开门等)。

4.2.2 Java类库中的GregorianCalendar类

4.2.3 更改器方法(set)和访问器方法(get)

4.3 用户自定义类

4.3.1 一个Employee类

4.3.2 多个源文件的使用

编译类A的时候,如果类A中使用了类B的话,会自动找到类B并编译它。更重要的是,如果类B的java版本比class版本新,Java编译器会重新编译类B.java。

4.3.3 解析Employee类

4.3.4 从构造器开始

4.3.5 隐式参数和显示参数

在每一个方法中,关键词this表示隐式参数。

4.3.6 封装的优点

  1. 可以改变内部实现,除了该类的方法外,不会影响其他代码。
  2. 更改器方法可以执行错误检查,然而直接对域进行赋值的话不会进行这些处理。

警告:注意不要编写返回引用可变对象的访问器方法。如:

  1. class Employee{
  2. private Date hireDay;
  3. public Date getHireDay(){
  4. return this.hireDay;
  5. }
  6. }
这样做会破坏封装性!请看下面这段代码:
  1. Employee harry = ...;
  2. Date d = harry.getHireDay();
  3. double tenYearsInMilliSeconds = 10 * 365 * 24 * 60 * 60 * 1000;
  4. d.setTime(d.getTime - (long)tenYearsInMilliSeconds);
出错的原因很微妙,d和harry.getHeriDay()引用了同一个对象。对d调用更改器方法就可以自动地改变这个雇员对象的私有状态!
如果需要返回一个可变对象的引用,应该首先对它克隆(clone)。如:
  1. class Employee{
  2. ...
  3. public Date getHeriDay(){
  4. return (Date) hireDay.clone();
  5. }
  6. ...
  7. }

4.3.7 基于类的访问权限

方法出了可以访问它的调用对象的私有数据,还可以访问所属类的所有对象的私有数据。例如:

  1. class Employee{
  2. ...
  3. boolean equals(Employee other){
  4. return this.name.equals(other.name);
  5. }
  6. }

4.3.8 私有方法

4.3.9 Final实例域

构建对象时必须初始化这样的域,并且在后面的操作中,都不能被修改。

4.4 静态域和静态方法

static修饰静态域和静态方法。

4.4.1 静态域

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

4.4.2 静态常量

4.4.3 静态方法

4.4.4 Factory方法

4.4.5 main方法

4.5 方法参数

程序设计语言中有关参数传递给方法的一些专业术语:

  • 值调用(call by value),表示方法接收的是调用者提供的值。
  • 引用调用(call by reference),表示方法接受的是调用者提供的变量地址。

一个方法可以修改传递引用调用所对应的变量值,而不能修改传递值调用所对应的变量值。

Java程序设计语言总是采用值调用。也就是说,方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。对于基础数据类型(如int、double)是值不改变,而对于对象类型是引用对象不改变(引用对象的域可改变)。

例如:

  1. public static void tripleValue(double x){
  2. x = 3 * x;
  3. }
  4. public static void main(String[] args){
  5. double percent = 10;
  6. tripleValue(percent);
  7. System.out.println(percent);
  8. }

可以看到,无论怎样,调用这个方法之后,percent的值还是10。下面看一下具体的执行过程:

  1. x被初始化为percent值的一个拷贝(10)。
  2. x被乘以3后等于30.但是percent仍然是10.
  3. 这个方法结束后,参数变量x不再使用。

再看一个对象引用的例子,可以改变对象引用的域,如下,将一个雇员的薪金提高两倍的操作:

  1. public static void tripleSalary(Employee x){
  2. x.raiseSalary(200);
  3. }
  4. public static void main(String[] args){
  5. harry = new Employee(...);
  6. tripleSalary(harry);
  7. }

具体的执行过程为:

  1. x被初始化为harry值的拷贝,这里是一个对象的引用。
  2. raiseSalary方法应用于这个对象的引用。x和harry同时引用的那个Employee对象的薪金提高两倍。
  3. 方法结束后,参数变量x不再使用。当然,对象变量harry继续引用那个薪金增至3倍的雇员对象。

对象作为参数

通过上面的例子,有些程序员可能会认为Java对对象参数采用的是引用调用,实际上是误解。再看一个反例:

  1. public static void swap(Employee x,Employee y){
  2. Employee temp = x;
  3. x = y;
  4. y = temp;
  5. }
  6. public static void main(String[] args){
  7. Employee a = new Employee("bob",...);
  8. Employee b = new Employee("jim",..,);
  9. swap(a,b);
  10. System.out.println(a.getName());// bob
  11. System.out.println(b.getName());// jim
  12. }

如果是引用传递的话,a和b应该进行了置换,而实际上并没有,a和b的引用没有发生改变,因为x和y只是a和b的引用的一个拷贝,在方法结束后就丢弃了。

总结,在Java中,方法参数的使用情况:

  • 一个方法不能修改一个基本数据类型的参数(即数值型和布尔类型)。
  • 一个方法可以改变一个参数对象的状态(域)。
  • 一个方法不能实现让对象参数引用一个新的对象。

4.6 对象构造

4.6.1 重载

如果一个类有多个方法有相同的名字,不同的参数(包含个数和顺序的不同),便产生了方法重载。

4.6.2 默认域初始化

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

4.6.3 默认构造器

如果不显示地定义构造器的话,系统会提供一个不带参数的默认构造器。

4.6.4 显示域初始化

初始值不一定是常量,可以调用方法对域进行初始化。

4.6.5 参数名

命名有意义。

4.6.6 调用另一个构造器

  1. public Employee(double s){
  2. this("jim",s);
  3. }

4.6.7 初始化块

初始化数据域的方法:

  • 在构造器中赋值。
  • 在声明中赋值。
  • 初始化块
  1. class Employee{
  2. private static int nextId;
  3. private int id;
  4. private String name;
  5. private double salary;
  6. {
  7. id = nextId;
  8. nextId++;
  9. }
  10. }

只要构造类的对象,这些初始化块就会执行。相应的,有静态初始化块static{}

4.6.8 对象析构与finalize方法

finalize方法将在垃圾回收器清除对象之前调用。在实际应用中,由于不知道垃圾回收器在何时清除对象,也就不知道何时会调用finalize方法,所以不要依赖与finalize方法回收任何短缺的资源。

4.7 包

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注