您的位置:首页 > 其它

第4章 对象与类

2015-06-21 09:54 162 查看
4.1 面向对象程序设计概述
4.1.1 类

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

标准Java库提供几千个类供使用,但还是需要创建自己的类,以描述应用程序所对应的问题域中的对象。

封装(encapsulation,也称为数据隐藏),对对象的使用者隐藏了数据的实现方式。实现封装的关键就是绝不能让其他类的方法访问本类的实例域。封装给对象赋予了黑盒特性。这是提高重用性和可靠性的关键。

对象状态:每个特定对象都有一组特定的实例域(instance field)值,这些值的集合就是这个对象的当前状态。

4.1.2 对象

对象的三个主要特征

1)对象的行为(behavior)
2)对象的状态(state) ---对象状态的改变,必须通过调用方法实现
3)对象的标识(identity)

4.1.3 识别类

名次对应类

动词对应类的方法

4.1.4 类之间的关系

依赖 dependence(uses-a):如果一个类的方法操作另一个类的对象,我们就说一个类依赖于另一个类。

1)应尽可能将相互依赖的类减至最小。即类之前的耦合度最小。
2)如果类A不知道B的存在,B的改变不会引起A的错误。

聚合aggregation(has-a):类A的对象包含类B的对象。

1)继承inheritance(is-a):用于标识一种特殊与一般的关系。



4.2 使用预定义类
4.2.1 对象与对象变量

首先构造对象 -> 指定其初始状态 -> 对对象应用方法

使用构造器(constructor)构造新实例。用来构造和初始化对象。

new操作符返回的也是一个对象的引用。

方法中的局部变量不会自动地初始化为null,而必须通过调用new或将他们设置为null进行初始化。

4.2.2 Java类库中的Gregorian-Calendar类

标准Java类库分别包含了两个类:一个是用来表示时间点的Date类,另一个是用来表示日历表示法的GregorianCalendar

4.2.3 更改器方法和访问器方法

对实例域进行修改的方法称为更改器方法。

仅访问实例域而不进行修改的方法称为访问器方法。

4.3 用户自定义类

在一个Java源文件中,只能有一个public类,但可以有任意多个非公有类。源文件的文件名必须和public类的类名相同。

实例域通常标记为private

类通常包括类型属于某个类类型的实例域

构造器与类同名,并在构造类的对象时,将实例域初始化为希望的状态。

每个类可以有一个以上的构造器

构造器可以有0个、1个及1个以上的参数

构造器没有返回值

构造器总是伴随着new操作一起调用

在类的所有方法中(包括构造器),不要命名与实例域同名的变量,否则在方法内部,会屏蔽掉实例域。

隐式参数与显示参数

public void raiseSalary(double byPercent)
{
double raise = this.salary * byPercent / 100;
this.salary += raise;
}
//raiseSalary方法有两个参数。第一个参数为隐式(implicit)参数,表示该对象本身,第二个参数是在方法名后的参数列表中的数值,称为显示(explicit)参数。在一个方法中,关键字this表示隐式参数。使用this可以将实例域和局部变量明显的区分开来。

不要编写返回引用可变对象的访问器方法,否则会破坏封装性。

class Employee
{
private Date hireDay;
.....
public Date getHireDay(){
return hireDay;
}
}

如果需要返回一个可变对象的引用,应该首先对它进行克隆(clone),对象克隆是指存放在另一个位置上的对象副本。
class Employee
{
......
public Date getHireDay(){
return hireDay.clone();
}
}

基于类的访问权限

方法可以访问所调用对象的私有权限,一个方法可以访问所属类的所有对象的私有数据。
class Employee
{
..........
public boolean equals(Employee other){
return name.equals(other.name);
}
}

典型的调用方法,if(harry.equals(boss))....
这个方法访问了harry的私有域,而且还访问了boss的私有域,这是合法的,因为boss是Employee对象,而Employee类的方法可以访问Employee类的任何一个对象的私有域。

只要方法是私有的,类的设计者可以确信,它不会被外部的其他类操作调用,可以将其删除。如果方法是公有的,就不能将其删除,因为其他的代码很可能已经依赖它了。

final实例域

可以将实例域定义为final,构建对象时必须初始化这样的域。即,必须确保在每个构造器执行完后,这个域的值被设置。且在后面的操作中不能再对它进行修改。
final修饰符大都应用于基本(primitive)类型域,或不可变类的域。用于可变的类,通常会引起混乱。

不可变(immutable)类的域:如果类中的每个方法都不会改变其对象,这种类就是不可变类。如String类就是不可变类。

4.4 静态域与静态方法
4.4.1 静态域

如果将域定义为static,每个类只有一个这样的域。而每个对象对于所有的实例域却都有自己的一份拷贝。

class Employee
{
private static int nextId = 1; //没这个类的所有实例所共享
private int id; //每一个雇员对象都有一个自己的id域

public setId(){
id = nextId;
nextId++;
}
}

4.4.2 静态常量

静态变量用的比较少,但静态常量用得比较多

public class Math{
......
public static final double PI = 3.1415926;
......
}
//程序中可以直接采用Math.PI的形式获得这个常量。

另一个经常使用的静态常量是System.out

public class System
{
public static final PrintStream out = ...;
}

4.4.3 静态方法

静态方法是一种不能向对象实施操作的方法。

静态方法没有隐式的参数。

可以认为静态方法是没有this参数的方法。

因为静态方法不能操作对象,所以不能在静态方法中访问实例域。但是静态方法可以访问自身类中的静态域。

public static int getNextId()
{
return nextid; //return static field
}
可以通过类名调用这个方法:
int n= Employee.getNextId();

可以使用对象调用静态方法,但不推荐这样做,容易引起混淆,建议使用类名来调用静态方法。

在下面两种情况下使用静态方法:

1)一个方法不需要访问对象状态,其所需参数都是通过显示参数提供。
2)一个方法只需要访问类的静态域。

4.4.4 工厂方法

静态方法还有一个常见的用途,使用工厂方法产生不同风格的格式对象。

4.4.5 main方法

main方法也是一个静态方法。main方法不对任何对象进行操作。事实上,在启动程序时,还没有任何一个对象。静态的main方法将执行并创建程序所需的对象。

每一个类都可以有一个main方法,这是一个常用于对类进行单元测试的技巧。

4.5 方法参数

Java总是采用按值调用,即,方法得到的是所有参数值的一个拷贝,特别的,方法不能修改传递给它的任何参数变量的内容。

4.6 对象构造
4.6.1 重载

重载(overloading):多个方法,有相同的名字,不同的参数,便产生了重载。

方法签名(signature):Java允许重载任何方法,而不只是构造器方法。因此完整地描述一个方法,需要指出方法名以及参数类型。这叫做方法签名。

返回类型不是方法签名的一部分,因此,不能有两个名字相同、参数类型也相同,但返回类型不同的方法。

4.6.2 默认域初始化

如果在构造器中,没有显示的给域赋初始值,那么就会被自动地赋予默认值:0、false、null,但这不是好的编程习惯。

但方法中的局部变量,则必须明确的进行初始化,不会像域变量那样被赋予初始值。否则会报错。

4.6.3 无参数的构造器

如果在编写一个类时,没有编写构造器,那么系统就会提供一个无参数的构造器。这个构造器将所有的实例域设置为默认值(0,false,null)。

如果在类中已经提供了至少一个非无参数的构造器,则系统不再自动为该类提供一个无参数的构造器。此时,就不能使用无参数构造器来构造对象。除非自己在类中手动显示地添加一个构造器。

public ClassName()
{

}
//上述构造器构造对象时,会将所有域赋予默认值(0,false,null)
4.6.4 显式域初始化

由于类的构造方法可以重载,所以可以采用多种形式设置类的实例域的初始状态。确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值。

可以在类定义中,直接将一个值赋给任何域。

class Employee
{
private string name = "";
...........
}
//在构造器之前,先执行赋值操作

当一个类的所有构造器,都希望将一个特定的值赋予某个特定的实例域时,这种方式特别有用。

4.6.5 参数名
1) 构造器的参数名用单个字符命名
public Employee(String n, double s)
{
name = n;
salary = s;
}
//这种方式的缺点是,参数的可读性不佳
2) 构造器的参数名在域名称的基础上加上一个前缀 a
public Employee(String aName,double aSalary)
{
name = aName;
salary = aSalary;
}
3) 构造器的参数名和域名称完全一样
public Employee(String name,double salary)
{
this.name = name;
this.salary = salary;
}
//这种方式会使参数将实例域在构造器内部屏蔽起来,但可以采用this.salary的形式访问实例域。this指示的是方法调用的隐式对象,也就是被构造的对象。

4.6.6 调用另一个构造器

如果构造器的第一个语句形如this(...),这个构造器将调用同一个类的另一个构造器。

public Employee(double salary)
{
this("emp" + nextId, salary);
nextId++;
}

一般都是参数个少的构造器调用参数个数多的构造器。

采用这种方式非常有用,这样对公共的构造器代码编写一次即可。

4.6.7 初始化块

第三种初始化域的机制,初始化块(initialization block),在一个类声明中,可以包含多个代码块,只要构造类的对象,这些块就会被执行。

class Employee
{
private static int nextId;

private int id;
private String name;
private int salary;

{
id = nextId;
nextId++;
}

public Employee(String n, double s)
{
name = n;
salary =s;
}

public Employee()
{
name = "";
salary = 0;
}
}
//无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。首先运行初始化块,然后再运行构造器的主体部分。

4.6.8 对象析构与finalize方法

由于Java有自动的垃圾回收器,不需要人工回收内存,所以Java不支持析构器。

但,某些对象使用了内存之外的其他资源,如文件或系统资源的另一个对象的句柄。在这种情况下,当资源不再需要时,将其回收或再利用显得十分的重要。

可以为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象之前调用。在实际应用中,不要依赖于使用finalize方法回收任何短缺的资源。这是因为很难知道这个方法什么时候才能够调用。

如果某个资源需要在使用完毕后立刻被关闭,那么就需要由人工来管理。对象用完时,可以应用close方法来完成相应的清理操作。

4.7 包

Java允许使用包(package)将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。

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

使用包的主要原因是确保类名的唯一性。Sun公司建议将公司的英特网域名(显然是独一无二的)以逆序的形式作为包名,并且对于不同的项目使用不同的子包。

从编译器的角度来看,嵌套的包之前没有任何关系。如:java.util和java.util.jar包毫无关系,每一个都拥有独立的类集合。

4.7.1 类的导入

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

import语句是一种引用包含在包中的类的简明描述。一旦使用了import语句,在使用类时,就不必写出包的全名了。

4.7.2 静态导入

import语句不仅可以导入类,还增加了导入静态方法和静态域的功能。

4.7.3 将类放入包中

想将一个类放入包中,就必须将包的名字放在源文件的开头,包中定义类的代码之前。

package com.horstmann.corejava;

public class Employee
{
.....
}

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

将包中的源文件放到与完整的包名匹配的子目录中。例如,com.horstmann.corejava包中的所有源文件应该被放置到com/horstmann.corejava目录中。编译器将class类文件也放在相同的目录结构中。

4.7.4 包作用域

在默认情况下,包不是一个封闭的实体。也就是说,任何人都可以向包中添加更多的类。

可以通过包密封(package sealing)机制将一个包密封起来,就不能向这个包添加类了。

制作包含密封包的JAR文件的方法。

4.8 类路径--目的是让Java程序在运行时,JVM能够顺利找到各个.class类文件

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

类文件也可以存储在JAR(Java归档)文件中,在一个JAR文件中,可以包含多个压缩形式的类文件和子目录。在程序中用到第三方(third-party)库文件时,通常给出一个或多个需要包含的JAR文件。

JDK也提供了许多JAR文件。在jre/lib/rt.jar中包含数千个类库文件。

为了使类能被多个程序共享,需要做到下面几点:

1)把类放到一个目录中,如home/user/classdir,这个目录是包树状结构的基目录
2)将JAR文件放在一个目录中,例如:/home/user/archives
3)设置类路径,类路径是所有包含类文件的路径的集合。
UNIX环境(用冒号分割):
/home/user/classdir:.:/home/user/archives/archive.jar
Windows环境(用分号分割):
c:\classdir;.;c:\archives\archive.jar
上述,句点(.)表示当前目录

可以在JAR文件目录中,指定通配符:表示在归档目录中的所有JAR文件(不包含.class文件)都包含在类路径中。

/home/user/classdir:.:/home/user/archives/'*'
c:\classdir;.;c:\archives\*

由于运行时库文件(rt.jar和在jre/lib与jre/lib/ext目录下的一些其他的JAR文件)会被自动搜索,所以不必将他们显示的列在类路径中。

javac编译器总是在当前目录中查找文件,但Java虚拟机仅在类路径中有“.”时才查看当前目录。如果没有设置类路径,那也不会产生什么问题,默认的类路径包含“.”目录。但如果设置了类路径,却忘记包含“.”目录,则程序任然可以编译通过,但不能运行。

类路径所列出的目录和归档文件是搜索类的起点。假设虚拟机要寻找某个类com.hostmann.corejava.Employee类文件:

1)首先查看存储在jre/lib和jre/lib/ext目录下的归档文件中所存放的系统类文件。若没找到,则再依次查看类路径:
2)/home/user/classdir/com/hostmann/corejava/Employee.class
3)com/hostmann/corejava/Employee.class 从当前目录开始
4)com/hostmann/corejava/Employee.class inside /home/user/archives/archive.jar

设置类路径

1)最好采用-classpath(或-cp)选项指定类路径:
java -classpath /home/user/classdir:.:/home/user/archives/archive.jar MyProg
java -classpath c:\classidr;.;c:\archives\archive.jar MyProg
2)也可以通过设置classpath环境变量完成这个操作
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
set CLASSPATH=c:\classidr;.;c:\archives\archive.jar
知道shell退出,类路径都有效。

4.9 文档注释

JDK提供了javadoc工具,用于有源文件生成一个HTML文档。

如果在源文件中添加了以专用的定界符/**开始的注释,就可以很容易的生产一个专业的文档。

4.9.1 注释的插入

javadoc工具从下面几个特性中抽取信息

1)包
2)公有类和接口
3)公有的和受保护的构造器和方法
4)公有的和受保护的域

注释以/**开始,以*/结束

每个/**.....*/文档注释在标记之后紧跟着自由格式文本,标记由@开始,如@author 或 @param

4.9.2 类注释

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

4.9.3 方法注释

必须放在所描述的方法之前,除了通用的标记外,还可以使用如下标记

1)@param 变量描述
2)@return 描述
3)@throws 类描述
4.9.4 域注释

只需要对公有域(通常指的是静态常量)建立文档

4.9.5 通用注释

@author姓名

@version文本

@since文本

@deprecated文本

@see 引用

4.9.6 包与概述注释
4.9.7 注释的抽取

4.10 类的设计技巧

一定要保证数据私有,绝对不要破坏封装性

一定要对数据初始化

不要在类中使用过多的基本类型

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

将职责过多的类进行分解

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

类名命名的良好习惯:采用一个名词(Order),前面有形容词修饰的名次(RushOrder),或动名词修饰的名次(BuildingAddress)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: