Java核心卷一之对象与类详解

目录

4.1 面向对象程序设计概述

4 . 1.1 类

4 . 1.2 对 象

4 . 1.3 识 别 类

4.1.4 类之间的关系

4.2 使用预定义类

4 . 2.1 对象与对象变量

4.2.2Java 类库中的 LocalDate 类

4.3 用户自定义类

4.3.9 final 实例域

4.4 静态域与静态方法

4 . 4.1 静态域

4.4.2 静态常量

4.4.3 静态方法

4.4.4 工厂方法

4 . 6 对 象 构 造

4.6.1 重载

4.6.7 初始化块

4.6.8 对象析构与 finalize 方法

4 . 7 包

4 . 7.2 静态导入

4 . 7.3 将类放入包中

4.9 文档注释

4.9.1 注释的插入

4.9.2 类注释

4.9.3 方法注释

4.10 类设计技巧


面向对象程序设计概述
使用预定义类
用户自定义类
静态域与静态方法
方法参数
对象构造
类路径
文档注释
类设计技巧

4.1 面向对象程序设计概述

面向对象程序设计 简称 OOP ) 是当今主流的程序设计范型 它已经取代了 20 世纪 70
年代的 结构化 过程化程序设计开发技术 Java 是完全面向对象的 必须熟悉 OOP 才能
够编写 Java 程序。在 OOP 中, 不必关心对象的具体实现,只要能够满足用户的需求即可。
对于一些规模较小的问题 将其分解为过程的开发方式比较理想。而面向对象更加适用 于解决规模较大的问题

4 . 1.1

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

正如前面所看到的 Java 编写的所有代码都位于某个类的内部 标准的 Java 库提供
了几千个类 可以用于用户界面设计 日期 日历和网络程序设计 尽管如此 还是需要在
Java 程序中创建一些自己的类 以便描述应用程序所对应的问题域中的对象

封装 encapsulation , 有时称为数据隐藏 是与对象有关的一个重要概念 从形式上看

封装不过是将数据和行为组合在一个包中并对对象的使用者隐藏了数据的实现方式。

对象
中的数据称为实例域 instance field ) , 操纵数据的过程称为方法 method ) 对于每个特定的
类实例 对象 都有一组特定的实例域值

实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域。程序仅通过对 象的方法与对象数据进行交互。封装给对象赋予了“ 黑盒” 特征, 这是提高重用性和可靠性的关键。

OOP 的另一个原则会让用户自定义 Java 类变得轻而易举 这就是 可以通过扩展一个
类来建立另外一个新的类 事实上 Java 所有的类都源自于一个 神通广大的超类

它就是 Object。

在扩展一个已有的类时 这个扩展后的新类具有所扩展的类的全部属性和方法 在新类
只需提供适用于这个新类的新方法和数据域就可以了 通过扩展一个类来建立另外一个
类的过程称为继承 inheritance )

4 . 1.2 对 象

要想使用 OOP ,—定要清楚对象的三个主要特性
对象的行为 behavior)—可以对对象施加哪些操作,或可以对对象施加哪些方法?
对象的状态 state )—当施加那些方法时,对象如何响应?
对象标识 identity )—  如何辨别具有相同行为与状态的不同对象?

同一个类的所有对象实例 由于支持相同的行为而具有家族式的相似性 对象的行为是
用可调用的方法定义的
此外 每个对象都保存着描述当前特征的信息 这就是对象的状态 对象的状态可能会
随着时间而发生改变 但这种改变不会是自发的 对象状态的改变必须通过调用方法实现
( 如果不经过方法调用就可以改变对象状态 只能说明封装性遭到了破坏

对象的状态并不能完全描述一个对象 每个对象都有一个唯一的身份 identity )
在一个订单处理系统中 任何两个订单都存在着不同之处 即使所订购的货物完全相同也是
如此 需要注意 作为一个类的实例, 每个对象的标识永远是不同的,状态常常也存在着差异。

4 . 1.3 识 别 类

传统的过程化程序设计 必须从顶部的 main 函数开始编写程序 在面向对象程序设计
时没有所谓的 顶部 对于学习 OOP 的初学者来说常常会感觉无从下手 答案是 首先从
设计类开始 然后再往每个类中添加方法
识别类的简单规则是在分析问题的过程中寻找名词 而方法对应着动词
例如 在订单处理系统中 有这样一些名词

接下来 查看动词 商品被添加到订单中 订单被发送或取消 订单货款被支付 对于
每一个动词如
添加
发送
取消 以及 支付 都要标识出主要负责完成相应动作
的对象 例如 当一个新的商品添加到订单中时 那个订单对象就是被指定的对象 因为它
知道如何存储商品以及如何对商品进行排序 也就是说 add 应该是 Order 类的一个方法
Item 对象是一个参数

4.1.4 类之间的关系

在类之间 最常见的关系有
•依赖(“ uses - a
聚合 (“ has - a
•继承( is - a

依赖 dependence ) , uses - a 关系 是一种最明显的 最常见的关系 例如 Order
类使用 Account 类是因为 Order 对象需要访问 Account 对象查看信用状态 但是 Item 类不依
赖于 Account 这是因为 Item 对象与客户账户无关 因此 如果一个类的方法操纵另一个
类的对象 我们就说一个类依赖于另一个类
应该尽可能地将相互依赖的类减至最少 如果类 A 不知道 B 的存在 它就不会关心 B
的任何改变 这意味着 B 的改变不会导致 A 产生任何 bug ) 用软件工程的术语来说 就是
让类之间的耦合度最小
聚合 aggregation ) , has - a 关系 是一种具体且易于理解的关系 例如 一个

Order 对象包含一些 Item 对象聚合关系意味着类 A 的对象包含类 B 的对象

继承 inheritance ) , is - a 关系 是一种用于表示特殊与一般关系的 例如 Rush
Ordei 类由 Order 类继承而来 在具有特殊性的 RushOrder 类中包含了一些用于优先处理的
特殊方法 以及一个计算运费的不同方法 而其他的方法 如添加商品 生成账单等都是从
Order 类继承来的 一般而言 如果类 A 扩展类 B , A 不但包含从类 B 继承的方法 还会
拥有一些额外的功能

4.2 使用预定义类

Java 没有类就无法做任何事情 我们前面曾经接触过几个类 然而 并不是所有
的类都具有面向对象特征 例如 Math 在程序中 可以使用 Math 类的方法 Math
,
random , 并只需要知道方法名和参数 如果有的话 ) 而不必了解它的具体实现过程 这正是
封装的关键所在 当然所有类都是这样 但遗憾的是 Math 类只封装了功能 它不需要也不
必隐藏数据 由于没有数据 因此也不必担心生成对象以及初始化实例域: 下一节将会给出一个更典型的类— Date 从中可以看到如何构造对象, 以及如何调 用类的方法。

4 . 2.1 对象与对象变量

要想使用对象 就必须首先构造对象 并指定其初始状态 然后 Xt 对象应用方法
Java 程序设计语言中 使用构造器 constructor ) 构造新实例。构造器是一种特殊的方法 用来构造并初始化对象
构造器的名字应该与类名相同 因此 Date 类的构造器名为 Date 要想构造一个 Date
需要在构造器前面加上 new 操作符 如下所示:new Date ( )
这个表达式构造了一个新对象 这个对象被初始化为当前的日期和时间
如果需要的话 也可以将这个对象传递给一个方法
System . out . printTn ( new DateO ) ;
在这两个例子中 构造的对象仅使用了一次 通常 希望构造的对象可以多次使用
需要将对象存放在一个变量中

        Date birthday = new Date();

一定要认识到 一个对象变量并没有实际包含一个对象 而仅仅引用一个对象
Java 任何对象变量的值都是对存储在另外一个地方的一个对象的引用 new 操作
符的返回值也是一个引用 下列语句
Date deadline = new Date ( ) ;
有两个部分 表达式 new Date ( ) 构造了一个 Date 类型的对象 并且它的值是对新创建对象的
引用 这个引用存储在变量 deadline

4.2.2Java 类库中的 LocalDate

在前面的例子中 已经使用了 Java 标准类库中的 Date Date 类的实例有一个状态
即特定的时间点
尽管在使用 Date 类时不必知道这一点 但时间是用距离一个固定时间点的毫秒数 可正
可负 表示的 这个点就是所谓的纪元 epoch ) , 它 是 UTC 时间 1970 1 1 00 : 00 : 00

4.3 用户自定义类

4.3.9 final 实例域

可以将实例域定义为 final 构建对象时必须初始化这样的域 也就是说 必须确保在每
一个构造器执行之后 这个域的值被设置 并且在后面的操作中 不能够再对它进行修改
例如 可以将 Employee 类中的 name 域声明为 final , 因为在对象构建之后 这个值不会再
被修改 即没有 setName 方法

final 修饰符大都应用于基本 primitive ) 类型域 或不可变 immutable ) 类的域 如果类
中的每个方法都不会改变其对象 这种类就是不可变的类 例如 String 类就是一个不可变
的类 )

4.4 静态域与静态方法

4 . 4.1 静态域

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

现在 每一个雇员对象都有一个自己的 id 但这个类的所有实例将共享一个 iiextld
换句话说 如果有 1000 Employee 类的对象, 则有 1000 个实例域 id 但是 只有一
个静态域 nextld 即使没有一个雇员对象 静态域 nextld 也存在 它属于类 而不属于任何
独立的对象

4.4.2 静态常量

静态变量使用得比较少 但静态常量却使用得比较多 例如 Math 类中定义了一个
静态常量:

另一个多次使用的静态常量是 System . out 它在 System 类中声明:
前面曾经提到过 由于每个类对象都可以对公有域进行修改 所以 最好不要将域设计
public 然而 公有常量 final 却没问题 因为 out 被声明为 final , 所以 不允许
再将其他打印流陚给它

4.4.3 静态方法

可以使用对象调用静态方法。, 如果 harry 是一个 Employee 对象, 可以用harry.getNextId( ) 代替 Employee.getNextId( 。) 不过,这种方式很容易造成混淆,其原因是 getNextld 方法计算的结果与 harry 毫无关系。我们建议使用类名, 而不是对象来调用静态方法。

4.4.4 工厂方法

静态方法还有另外一种常见的用途 类似 LocalDate NumberFormat 的类使用静态工
厂方法 ( factory methocO 来构造对象 你已经见过工厂方法 LocalDate . now LocalDate . of
NumberFormat 类如下使用工厂方法生成不同风格的格式化对象

4 . 6 对 象 构 造

4.6.1 重载

有些类有多个构造器 例如 可以如下构造一个空的 StringBuilder 对象
StringBuilder messages = new StringBuilderO ;
或者 可以指定一个初始字符串
StringBuilder todoList = new StringBuilderC ' To do : \ n "
这种特征叫做重载 overloading ) 如果多个方法 比如 StringBuilder 构造器方法
相同的名字 不同的参数 便产生了重载 编译器必须挑选出具体执行哪个方法 它通过用
各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法
果编译器找不到匹配的参数 就会产生编译时错误 因为根本不存在匹配 或者没有一个比
其他的更好 这个过程被称为重载解析 overloading resolution ) 。)

Java 允许重载任何方法 而不只是构造器方法 因此 要完整地描述一个方法
需要指出方法名以及参数类型 这叫做方法的签名 signature ) 例如 String 类有 4
称为 indexOf 的公有方法 它们的签名是

返回类型不是方法签名的一部分 也就是说 不能有两个名字相同 参数类型也相
同却返回不同类型值的方法

4.6.7 初始化块

前面已经讲过两种初始化数据域的方法
在构造器中设置值
•在声明中赋值
实际上 Java 还有第三种机制 称为初始化块 initializationblock ) 在一个类的声明中
可以包含多个代码块 只要构造类的对象 这些块就会被执行 例如

在这个示例中 无论使用哪个构造器构造对象 id 域都在对象初始化块中被初始化
先运行初始化块 然后才运行构造器的主体部分

4.6.8 对象析构与 finalize 方法

        有些面向对象的程序设计语言, 特别是 C + + , 有显式的析构器方法 其中放置一些当对
象不再使用时需要执行的清理代码 在析构器中 最常见的操作是回收分配给对象的存储空
由于 Java 有自动的垃圾回收器 不需要人工回收内存 所以 Java 不支持析构器
        当然, 某些对象使用了内存之外的其他资源 例如 文件或使用了系统资源的另一个对
象的句柄 在这种情况下 当资源不再需要时 将其回收和再利用将显得十分重要
可以为任何一个类添加 finalize 方法 finalize 方法将在垃圾回收器清除对象之前调用
        在实际应用中, 不要依赖于使用 finalize 方法回收任何短缺的资源 这是因为很难知道这个
方法什么时候才能够调用

4 . 7

        Java 允许使用包 package > 将类组织起来 借助于包可以方便地组织自己的代码 并将
自己的代码与别人提供的代码库分开管理
        标准的 Java 类库分布在多个包中 包括 java . lang java . util java . net 标准的 Java
包具有一个层次结构 如同硬盘的目录嵌套一样 也可以使用嵌套层次组织包 所有标准的
Java 包都处于 java javax 包层次中
        使用包的主要原因是确保类名的唯一性。 假如两个程序员不约而同地建立了 Employee
只要将这些类放置在不同的包中 就不会产生冲突 事实上 为了保证包名的绝对
唯一性 Sun 公司建议将公司的因特网域名 这显然是独一无二的 以逆序的形式作为包
并且对于不同的项目使用不同的子包 例如 horstmann . com 是本书作者之一注册的域
逆序形式为 com . horstmann 这个包还可以被进一步地划分成子包 com . horstmann .
corejava

4 . 7.2 静态导入

import 语句不仅可以导人类 还增加了导人静态方法和静态域的功能
例如 如果在源文件的顶部 添加一条指令

import static java . lang .System. * ;

就可以使用 System 类的静态方法和静态域 而不必加类名前缀

4 . 7.3 将类放入包中

要想将一个类放人包中 就必须将包的名字放在源文件的开头 包中定义类的代码之
例如 程序清单 4 - 7 中的文件 Employee . java 开头是这样的

4.9 文档注释

        JDK 包含一个很有用的工具, 叫做 javadoc , 它可以由源文件生成一个 HTML 文档
实上 在第 3 章讲述的联机 API 文档就是通过对标准 Java 类库的源代码运行 javadoc
成的
        如果在源代码中添加以专用的定界符 / * * 开始的注释 那么可以很容易地生成一个看上
去具有专业水准的文档 这是一种很好的方式 因为这种方式可以将代码与注释保存在一个
地方 如果将文档存人一个独立的文件中 就有可能会随着时间的推移 出现代码和注释不
一致的问题 然而 由于文档注释与源代码在同一个文件中 在修改源代码的同时 重新运
javadoc 就可以轻而易举地保持两者的一致性

4.9.1 注释的插入

javadoc 实用程序 utility ) 从下面几个特性中抽取信息

公有类与接口
•公有的和受保护的构造器及方法
公有的和受保护的域

        应该为上面几部分编写注释、 注释应该放置在所描述特性的前面 注释以 / ** 开始
* / 结束
        每个 / ** . . . * / 文档注释在标记之后紧跟着自由格式文本 free - form text ) 标记由 @
@ author @ param
        

4.9.2 类注释

类注释必须放在 import 语句之后 类定义之前。下面是一个类注释的例子

4.9.3 方法注释

每一个方法注释必须放在所描述的方法之前 除了通用标记之外 还可以使用下面的标记
@ param 变量描述
这个标记将对当前方法的 param 参数 部分添加一个条目 这个描述可以占据多
并可以使用 HTML 标记 一个方法的所有 @ param 标记必须放在一起
@ return 描述
这个标记将对当前方法添加 return 返回 部分 这个描述可以跨越多行 并可以
使用 HTML 标记
© throws 类描述
这个标记将添加一个注释 用于表示这个方法有可能抛出异常 有关异常的详细内容

4.10 类设计技巧

我们不会面面俱到 也不希望过于沉闷 所以这一章结束之前 简单地介绍几点技巧
应用这些技巧可以使得设计出来的类更具有 OOP 的专业水准 \
1. 一定要保证数据私有
        这是最重要的; 绝对不要破坏封装性 有时候 需要编写一个访问器方法或更改器方法
但是最好还是保持实例域的私有性 很多惨痛的经验告诉我们 数据的表示形式很可能会改
但它们的使用方式却不会经常发生变化 当数据保持私有时 它们的表示形式的变化不
会对类的使用者产生影响 即使出现 bug 也易于检测

2. 一定要对数据初始化
        Java 不对局部变量进行初始化 但是会对对象的实例域进行初始化 最好不要依赖于系
统的默认值 而是应该显式地初始化所有的数据 具体的初始化方式可以是提供默认值
可以是在所有构造器中设置默认值

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

就是说 用其他的类代替多个相关的基本类型的使用

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

5.将职责过多的类进行分解

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

7.优先使用不可变的类

猜你喜欢

转载自blog.csdn.net/weixin_47277897/article/details/121030371
今日推荐