对象与类《Java核心技术 SE8》
面向对象程序设计概述
Java是完全面向对象的,面向对象的程序是由对象组成的,每个对象包含对用户公开的特定功能部分和隐藏的实现部分。
在OOP中,必须关心对象的具体实现,只要能满足用户的需求即可,是选择自己构造对象还是使用Java库中的对象取决于项目的预算和时间。
传统的结构化程序设计通过设计一系列的过程(算法)来求解问题,先确定过程,再考虑数据存储方式,偏向于解决规模较小的问题。而OOP将数据放在第一位,然后再考虑操作数据的算法,更适合解决规模较大的问题。

类
类class是构造对象的模板,由类构造对象的过程称为创建类的实例。
封装(数据隐藏)是隐藏数据的实现方式,对象中的数据称为实例域,操纵数据的过程称为方法。实现封装的关键在于绝对不能让类中的方法直接访问其他类的实例域,程序只能通过调用对象的方法才能进行数据交互,封装给对象赋予了”黑盒“特征,这是提高重用性和可靠性的关键,这意味着一个类可以全面的改变存储数据的方式,只要使用同样的方法操作数据,其他对象就不会知道或介意所发生的变化。
OOP可以轻松实现继承,通过扩展一个类来建立另外一个新类,实际上Java所有的类都源于Object超类。在拓展一个已有的类时,新类具有所拓展类的全部数据和方法,在新类在只需要提供适用于这个新类的数据域和新方法就可以了。
识别类
首先从设计类开始,然后再往每个类中添加方法。
识别类的简单规则是在分析问题的过程中寻找名词,而方法对应着动词。
类之间的关系
类之间最常见得关系有:
依赖(”uses-a"):订单类依赖账户类因为订单是用户所属的,因此一个类的方法操纵另一个类的对象,我们就说一个类依赖另一个类,应尽可能地将相互依赖地类减至最少,降低修改被依赖类带给依赖类的bug。
聚合(“has-a"):类A的对象包含类B的对象,jpa中的oneToOne或者OneToMany。
继承(”is-a"):类A扩展类B,类A不但包括类B继承的属性和方法,还会拥有一些额外的功能。
UML(Unified Modeling Language统一建模语言)描述类图,用来描述类之间的关系。

使用预定义类
并不是所有的类都具有面向对象特征,例如Math类,只需要知道方法名和参数,而不必了解其具体实现过程。
要使用对象,须先使用构造器构造对象,并指定初始状态,然后调用对象方法。
构造器的名字与类名相同。
一个对象变量并没有实际包含一个对象,而仅仅是引用了一个对象。在Java中任何对象变量的值都是对存储在另外一个地方对象的引用。
Date是用来表示时间点的Date类,另一个LocalDate类用来表示日历。
只访问对象而不修改对象方法:访问器方法get方法,localDate.getYear();
调用方法后会改变对象的状态:更改器方法:set方法,localDate.plusDays(1);
用户自定义类
构造器
- 构造器与类同名;
- 每个类可以有一个以上的构造器;
- 构造器可以有N个参数
- 构造器没有返回值
- 构造器总是伴随new操作符的执行被调用,而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的。
隐式参数与显示参数
- 隐式参数用关键字this表示,所构造的对象;
- 显示参数为方法参数或局部变量。
封装
- 一个私有的数据域;
- 一个公有的域访问器方法;
- 一个公有的域更改器方法;
如果需要返回一个可变对象的引用,需要对其进行克隆(Date对象是可变的,String对象是不可变的)。
基于类的访问权限
方法可以访问所调用对象的私有数据;
一个方法可以访问所属类的所有对象的私有数据。
私有方法
实现一个类时,由于公有数据非常危险,所以应该将所有的数据域都设置为私有的。
辅助方法应设计为私有方法。
final实例域
final实例域在构建对象时必须初始化,后续不能再进行修改。
final修饰符大都应用于基本类型域或不可变类的域,对于可变的类,final修饰符会造成混乱。
静态域与静态方法
静态域
static修饰的域,每个类只有一个,则每个对象对于静态域都有自己的一份拷贝。
静态常量因为被声明为final,所有可以设计为public,不用担心被对象修改。
public static final double PI = 3.1415926;
静态方法
静态方法是一种不能向对象实时操作的方法。但静态方法可以访问自身类中的静态域。
//计算x的a次方
Math.pow(x, a);
以下两种情况建议使用静态方法:
- 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供;
- 一个方法只需要方法类的静态域;
静态方法的另一种常见用途:工厂方法
NumberFormat currencyInstance = NumberFormat.getCurrencyInstance();
NumberFormat percentInstance = NumberFormat.getPercentInstance();
double x = 0.1;
//¥0.10
System.out.println(currencyInstance.format(x));
//10%
System.out.println(percentInstance.format(x));
使用静态工厂方法的原因
-
无法命名构造器,因为构造器的名字必须与类名相同,但希望得到的实例采用不同的名字;
-
当使用构造器时,无法改变所构造对象的对象类型,但构建过程方法能够返回子类。
main方法不对任何对象进行操作,在启动程序时还没有任何一个对象,静态的main方法将执行并创建程序所需要的对象。
方法参数
- 按值调用:方法接收得是调用者提供的值;
- 按引用调用:方法接收的是调用者提供的变量地址。
Java采用按值调用,方法得到的是所有参数的拷贝,也就是方法不能修改参数的内容。
以基本数据类型验证:一个方法不可能修改一个基本数据类型的参数。
当参数是对象就不一样了,方法得到是对象引用的拷贝,对象引用和拷贝引用的是同一个对象。
// a还是a,如果是按引用调用a应该是b
public static void swap(Student a, Student b){
Student temp = a;
a = b;
b = temp;
}
总结:
- 一个方法不能修改一个基本数据类型的参数;
- 一个方法可以改变一个对象的状态;
- 一个方法不能让对象参数引用一个新的对象。
对象构造
重载
如果多个方法有相同的名字、不同的参数便是重载。
类通过重载可提供多个构造器。
方法签名:方法名+参数列表(类型+顺序)。
为区分对象内部的变量和参数列表变量,构造器参数命名可使用a开头或者使用this隐式参数。
默认域初始化
构造器中没有显式地给域赋予初值,就会自动赋为默认值。
数值为0,布尔值为false,对象域为null;建议初始化,不然调用对象域会增加空指针的风险。
类至少提供一个构造器,当没有提供任何构造器时,系统才会提高一个无参构造器,默认域初始化;自定义无参构造器器时方法体可进行初始化赋值。
显示域初始化
通常会直接把初始化代码放在构造器中,使用代码块初始化不常见。
当一个类的所有构造器都希望把相同的值赋予实例域时,可使用代码块进行显示域初始化,这样在执行构造器之前会先执行赋值操作。
调用另一个构造器
构造器第一行this(...);会调用同一个类的另一个构造器,这样对公共的构造器代码部分只需要编写一次即可。
类在第一次加载的时候,会进行静态域的初始化。所有的静态初始化语句以及静态初始化块都将按照类定义的顺序执行。
调用构造器的具体处理步骤:
- 所有数据域被初始化(0,false,null);
- 按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块;
- 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体;
- 执行当前构造器主体。
对象析构与finalize方法
Java有自动的垃圾回收器,不需要人工回收内存,所有不支持析构器。
finalize方法将在垃圾回收器清除对象之前调用,在实际中不要依赖使用finalize方法回收任何短缺的资源,因为很难确切什么时候才调用。
如果某个资源在使用完毕后应该立刻关闭,就应该调用close方法来完成相应的清理操作。
包
Java使用包能够确保类名唯一,管理类文件的层级,区分自己的代码和代码库。
//首行是包名,默认包是一个没有名字的包
package com.example.demo;
// *,只能导入一个包,语法简单,对代码的大小也没有任何负面影响
import java.util.*;
// 明确指出所导入的类,能提高可读性
import java.util.Date;
//当两个Date类需要同时使用时,第二个类名前加上完整的包名
java.sql.Date date = new java.sql.Date(...);
// 静态导入:在源文件顶部添加下述指令,就可以使用System类的静态方法和静态域,而不必加类名前缀
import static java.lang.System.*;
// 静态导入具体的方法活域
import static java.lang.Math.abs;
包作用域
-
public:可以被任意类使用;
-
private:只能被所在类使用;
-
protected:同一个包和其子类;
-
没有修饰符:只能被所在包使用;
类路径
为了能够使类被多个程序共享:
-
把类放在一个目录下,例如:/home/user/classdir/
-
添加com.horstmann.corejave.Employee类,对应得Employee.class类文件就位于/home/user/classdir/com/horstmann/corejave
-
添加archive.jar包,对应的路径/home/user/archives/archive.jar
-
设置类路径,在 UNIX 中,禁止使用 * 以防止 shell 命令进一步扩展
-
UNIX环境,不同项目使用冒号分割:
/home/user/classdir:.:/home/use r/archives/archive.jar -
WINDOW环境,使用分号分割:
C:\classdir;.;c:\archies\archive.jar
-
设置类路径
最好使用-classpath(或-cp)选项指定类路径
java -classpath /home/user/classdir:.:/home/user/archives/archive.jar MyProg
java -classpath C:\classdir;.;c:\archies\archive.jar MyProg
设置环境变量
# 直到退出shell为止,类路径设置均有效
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
set CLASSPATH=C:\classdir;.;c:\archies\archive.jar
注释
javaDoc实用程序从包、公有类与接口、公有的和受保护的构造器及方法和域抽取信息。
注释以/**开始。*/结束,标记由@开始,之后的自由格式文本中可以使用HTML修饰符。
第一句应是一个概要性的句子。
类注释必须放在import之后,类定义之前;
- @author 姓名。可以使用多个@author
- @version 当前版本描述
方法注释:放在所描述的方法之前
- @param 变量描述,所有@param必须放在一起;
- @return 添加return描述,可以跨越多行,使用HTML标记;
- @throws描述,表示由可能抛出的异常。
只需要对静态常量公有域建立文档。
通用标记:
- @since 文本,对引入特性的版本描述,比如JDK版本@since version 1.7.1
- @deprecated文本,给出替代建议
- @see和@link可以使用超链接
包注释,需要在每个包目录中添加一个单独的文件。
- 提供一个package.html命名的HTML文件,抽取body标签中的所有文本;
- 提供一个package-info.java命名的Java文件,抽取包语句下的文档注释,它不应再包含其它内容。
注释的抽取
javadoc -d docDirectory *.java
javadoc -d docDirectory nameOfPackage1 nameOfPackage2
IDEA——Tools——Generate JavaDoc
类设计技巧
-
一定要保证数据私有;
绝对不要破坏封装性,数据的表示形式很可能会改变,但使用方式却不会经常发生变化,当数据私有时,表示形式的变化不会对类的使用者产生影响,也易于检测出bug;
-
一定要对数据初始化;
Java会对对象的实例域进行初始化,但不会对局部变量进行初始化,应显式地初始化所有数据;
-
用类代替多个相关基本类型的使用
把类中相关实例域可抽取为一个类,这样易于理解且易于修改。
-
不是所有的域都需要独立的域访问器和域更改器
对不会或不希望别人获得或修改的实例域,就不需要添加独立的访问器和更改器。
-
将职责过多的类进行分解
-
类名和方法名能够体现职责
类名使用名称(Order订单),有修饰的名词(RushOrder急单),动名词修饰的名词(BillingAddress账单地址);
方法名:访问器get开头,更改器set开头。
-
优先使用不可变的类
Date可变,java.time包中的类都是不可变的,没有方法能够改变对象的状态,如LocalDate,plusDays是返回已修改的新对象,而不是更改对象。
类是不可变的,就可以安全地在多个线程间共享其对象。

浙公网安备 33010602011771号