4.1 面向对象程序设计概述
面向对象程序设计(OOP)的核心是将数据放在第一位,然后再考虑操作数据的算法。
4.1.1 类
Java的类与C++的类其实极为相似,但是两者之间内的许多东西在名词形容,以及相关的属性上均有所不同,所以在此特别记录下Java的多种类中名词以及对应的C++名词,还有两者之间的辨析理解。
- 由类构造对象的过程被称为创建类的实例;
- 封装用于操作类中数据的隐藏手段,其中对象中的数据被称为实例域(C++中往往称为成员变量),操作数据的过程被称为方法(C++中往往称为函数);
- 每个类实例(对象)都有一组特定的实例域值,这些值的集合就是这个对象的当前状态,而一个类实例的状态往往需要随着运行而改变;
- Java的所有类都来源于Object类(这类似于Qt的QObject,封装了许多功能强大的方法)。
4.1.2 对象
每个对象都有一个唯一的身份(换句话说,就是实例化时候的变量名字)。
4.1.3 识别类
简单规则定义:分析问题的过程中寻找名词,而方法对应着动词。
4.1.4 类之间的关系
这里就是UML相关的一点知识:
- 依赖(use-a):通常表现为方法中的形参;
- 聚合(has-a):这里在UML中涉及到关联、聚合和组合三个关联关系的定义,通常表现为成员变量;
- 继承(is-a):通常表现为继承实现或接口实现。
4.2 使用预定义类
预定义类常指封装了大量静态方法的类,比如Math,在调用这种类时通常不会实例化他们,而是直接使用类中的静态方法(Math.random这样的调用,对应到C++中就是Maht::random这样的调用)。
4.2.1 对象与对象变量
Java使用构造器来构造新实例(C++中这个叫构造函数),同时这章也提到了Java的“=”传递过程都是值传递,换句话说,就是指针和数值的值传递。
4.2.2 Java中的LocalDate类
Java中的时间类有两个大类,一个是用来表示格林威治时间的Date类,一个是用来表示熟悉的日历表示法的LocalDate类。
4.2.3 更改器方法与访问器方法
更改器方法在调用后会改变对象的内部状态(即可以修改对象的成员变量),而访问器方法在调用后不会改变对象的内部状态。(在C++中,函数后面带有const的函数表示禁止修改对象的成员变量,但是Java中没有明显的标识)
4.3 用户自定义类
这节主要讲了一个Employee雇员类的实现,不过顺带也提了一些有意思的东西:
- Java中的构造器必然伴随着new操作符来实现,换句话说,就是Java的所有类对象都是需要在堆上构造的,这样就可以通过垃圾回收来处理种种自定义类。
- this参数依然存在并且与C++中的使用方式相同,不过Java在定义方法时需要保证所有的方法实现都在类的内部书写,而这种书写方式并不代表这些方法均为内联,一个方法是否内联是JVM的事(C++在类中直接定义则表示该方法内联,在类中声明类外定义则正常)
- 由于Java是值传递,而类对象在进行实例化时都是new形式(也就是说类对象变量其实都是指针形式),所以在进行访问器方法操作时最好不要直接返回实例域(因为这样值传递外部得到了内部封装实例域的指针),如果真的这样做了外部则可以直接修改类对象的实例域数据,直接导致了封装失效(也就是通过指针访问对应实例域的内存从而直接修改)。在返回时最好使用clone()方法,该方法说穿了就是重新new一个类对象然后复制数据后返回。
- 方法可以访问所属类的所有私有特性(包括作为形参时也可以),而不仅限于访问隐式参数的私有特性(this只能访问自己的public方法)。
- final用于变量前效果等同const,表示常量。
4.4 静态域与静态方法
4.4.1 静态域
static表示静态域,实际情况与C++完全相同,所有类实例共享一个类中静态域。
4.4.2 静态常量
static final表示静态常量域,与C++完全相同,比如Math.PI这样的定值。
4.4.3 静态方法
static …()方法表示静态方法,不能使用this参数与类中的实例域,只能使用类中的静态域以及其他静态方法。
4.4.4 工厂方法
存在的意义是根据实际需求命名不同名字,构造出对应结果类的子类。
4.4.5 main方法
main方法作为静态方法所以在类未被构建时也可以直接使用。
4.5 方法参数(按值传递)
这本书的这里讲的很差,如果没有C++基础很容易看晕。换句话说,如果用C++的形参、实参以及值传递与引用传递联合来讲其实效果更好,这里无非就是想告诉读者基本类型存储于栈且数值相同时调用的是同一个内存,其他的自定义类或者预定义类都是new出来的在堆上所以传递的是指针,在调用时根据指针情况与形参实参赋值情况进行分析。
4.6 对象构造
4.6.1 重载
相同的名字,不同的参数(所以也存在二义性问题)。
4.6.2 默认域初始化
如果构造器没有显示为域赋初值,那么Java会自动赋默认值,其中数值为0,布尔值为false,对象引用为null。
4.6.3 无参构造器
就是默认构造函数的感觉。
4.6.4 显示域初始化
就是直接在类中的实例域声明时加上定义,比如private int a = 1;
4.6.5 参数名
书里建议形参都写成实例域中的名字前面加一个"a"。
4.6.6 调用另一个构造器
通过this函数可以调用类中的另一个构造器。
4.6.7 初始化块
没有名字的块状结构体,在执行任何构造器时都会附带执行(实际上写到构造器中就完事了,这里估计是提升容错性
static表示静态初始化块,换句话说就是把静态域的初始化全部都写到一个前面有static关键字的初始化块中。
4.6.8 对象析构与finalize方法
Java不支持析构,但是可以通过添加finalize方法来做一些处理,该方法会在垃圾回收清除对象之前调用。
4.7 包
Java使用包来组织类,以便于共享使用。
4.7.1 类的导入
import语句+包名即可导入,其中使用import java.util.*这样的语句可以调用util前缀中所有的包。其特点在于简捷(类似于C++中的库有大量的命名空间,使用using来单独导入类或函数),可以只导入某个包中的某个类,而不是像C++一个include把所有的声明全部包含进来了。
4.7.2 静态导入
使用import+static+包名可以实现静态导入,此时不需要添加类名的前缀(有点类似命名空间) ,但是这么搞说实话不是主流。
4.7.3 将类放入包中
在类的定义上面加一行package+包名的代码,即可将此.java文件放置在对应的包中。
4.7.4 包作用域
类中的域(成员变量)如果没有写public这样的标识符,那么默认该成员变量为public类型的,所以这个域会被包中的所有类共同访问!这是非常危险的!
4.8 类路径
类文件也可以存储在JAR文件中,这里仅仅是讨论了类在JAR文件中的路径情况。
4.9 文档注释
/** 我是注释 */类型的注释,用于生成javadoc文件,很方便调用者的理解。
- 类注释需要在import语句之后,类定义之前,如:
import ...
/**
* This program demonstrates inheritance.
* @version 1.21 2004-02-21
* @author Cay Horstmann
*/
public class ManagerTest
{
...
}
- 方法注释中@param表示变量,@return表示返回值,@throws表示抛出异常
- 域注释时往往只对公有域(通常为静态常量)提供注释。
- 类中通用的注释有:@author表示作者姓名,@version表示版本,@since表示始于某某版本,@deprecated表示提示不再使用该方法或域,@see表示建议同时或者可以查看某类或方法(提供超链接)
- 如果想要提供包注释,最好在该包中单独建立一个用于对包整体注释的.java文件。
4.10 类设计技巧
这里描述了一些设计技巧:
- 一定要保证数据的私有:即尽量不要破坏类的封装性。
- 一定要对数据初始化:因为java会自己初始化未初始化的域,这样可能造成不必要的麻烦。
- 不要在类中使用过多的基本类型:换句话说,就是当基本类型过多时可以单独为这些类型建立一个新的类来便于书写与拓展。
- 不是所有的域都需要独立的域访问器和域更改器:有些域很私有,不需要外部修改。
- 将职责过多的类进行分解:根据类的作用尽可能的将其分为相对独立的个体,便于拓展与修改。
- 类名和方法名要能够体现它们的职责:就是取名时要实事求是,比如形容词或者动名词来修饰名词,以及对于更改器方法使用set,访问器方法使用get。
- 优先使用不可变的类:不可变的类可以相对安全的在多个线程中共享,而如果可变(比如多个线程要访问、修改或删除等等)往往会造成不可预料的问题