当前位置: 首页 > news >正文

OOP丨《Java编程思想》阅读笔记Chapter 5 : 初始化与清理

《Java编程思想》Chapter 5 : 初始化与清理

Initialization and Cleanup(初始化和清理)
编程方式中的设计安全的两个重要问题
本章就将讨论这两个重要的问题

  • 1. 基础概念
  • 2. 用构造器确保初始化
    • 2.1. 构造器的命名来源
    • 2.2. 构造器的定义
  • 3. 方法重载
    • 3.1. 为什么我们需要方法重载?
    • 3.2. 方法重载的基础实例
    • 3.3. 区分重载方法
  • 4. this关键字
    • 4.1. this的一般用法
    • 4.2. static含义
  • 5. 清理:终结处理和垃圾回收
    • 5.1. 为什么?
    • 5.2. finalize()的用途何在?
    • 5.3. 终结条件
    • 5.4. 垃圾回收器如何工作
  • 6. 成员初始化
    • 6.1. 指定初始化
  • 7. 构造器初始化
    • 7.1. 初始化顺序
    • 7.2. 静态数据的初始化
  • 8. 数组初始化
    • 8.1. 数组引用的定义
    • 8.2. 数组的初始化方法
  • 9. 可变参数列表
    • 9.1. 介绍
    • 9.2. 注意点
  • 10. 枚举类型
    • 10.1. 枚举的使用
    • 10.2. 枚举的特性
    • 10.3. 枚举的理解以及常用使用场景

1. 基础概念

  • Constructor(构造器)
    C++引入的概念,在对象被创建时自动调用的特殊方法
    Java同样采用了构造器的概念

  • 垃圾回收器
    Java提供的内存资源回收机制,自动回收不再使用的内存

2. 用构造器确保初始化

假如没有构造器,我们会如何初始化对象呢?
一个比较常规的想法就是为每个Class定义一个initialize()方法
然后在每个对象创建时调用这个方法
而在之后创建Object时,我们必须记得调用此方法

但通过提供构造器,Class的设计者可以确保每个Object都得到正确的初始化

2.1. 构造器的命名来源

问题:既然我们需要一个方法作为构造器,如何取名?

考虑点:

  1. 防止与任何其他方法冲突
  2. 构造器由编译器负责,需要让编译器能够自动知道调用哪个方法

C++采用了与Class同名的方法作为构造器
Java也继承了这种方式

2.2. 构造器的定义

当我们定义了一个Class,如果我们没有定义构造器,编译器会自动为我们生成一个默认构造器
但如果我们定义了一个构造器,编译器就不会再生成默认构造器

因此如果我们定义了一个带参数的构造器
那么我们之后创建Object时,就必须传入参数

3. 方法重载

3.1. 为什么我们需要方法重载?

一般来说当我们定义了一个方法
它所能接受的参数和处理的对象、逻辑都是固定的
这就要求我们如果要处理不同的对象,就必须定义不同的方法
按照一般的思路(例如C语言),我们就需要为这些对象分别分配不同的标识符

但是对于我们实际生活中,例如“清洗”这个操作
我们一般只会使用“清晰”+“对象”这两个信息来确定我们要进行的操作
而不会使用“清洗类1的方法清洗”+“对象1”这种方式

因此我们产生了一个想法就是:
我们可以使用某一个通用的标识符,定义多个不同的方法,来处理不同的对象等
这就是方法重载概念的主要来源

3.2. 方法重载的基础实例

Constructor就是一个重要例子

C++和Java中,构造器是强制重载方法名的另一个原因
因为Constructor的名字已经与Class名绑定
如果没有重载,我们就只能拥有一种构造方法了
这显然是不满足我们的需求的

3.3. 区分重载方法

由于我们重载的需求,此前用于区分方法的标识符失去了唯一性
那我们需要一些别的部分来区分这些方法

根据参数区分重载方法:

  • 参数顺序
    实际上只有参数的顺序不同的方法,也可以被认为是不同的方法
    但是不建议这样做
  • 参数类型
    重载方法的参数类型不同,此时方法可以被区分
    注意:传入的参数类型满足以下转换规则
    存在对应时,直接取对应;存在更大类型时,向更大类型转化;仅存在类型转换时,必须传入时就进行显式转换
  • 参数数量

或许我们会想通过返回值来区分方法
但是我们需要回想,即使方法定义返回值,我们也不一定会使用这个返回值
这时候我们如何区分只有返回值不同的方法呢?
因此Java不允许通过返回值来区分方法

4. this关键字

class有多个对象
当调用class中的方法时,我们如何知道是哪个对象调用?
Java的解决方法是让编译器自动传入操作对象的引用
但是因为是隐式传入,我们没有标识符来表示这个引用
因此Java提供了 this关键字 来表示这个引用

但我们同样发现,一般来说
我们在调用同一class的其他方法或实例变量时,我们一般不会使用this关键字
这是因为编译器会帮我们自动添加

4.1. this的一般用法

  1. 返回调用对象的引用
    当我们需要返回调用对象的引用时,我们必须使用this关键字
    一个相关使用场景:一条语句中对同一个对象进行多次操作
  2. 为了将自身传递给外部方法
    也许某个工具方法在对象所属class之外
    为了将调用对象传递至外部方法,需要使用this关键字
  3. 在构造器中调用构造器
    一个class中定义了多个构造器
    我们可以在一个构造器的开头调用另一个构造器
    注意:1.只能在开头;2.只能调用一个
  4. 区分同名的方法传入参数与调用对象的实例变量
    当我们需要传入参数与调用对象的实例变量同名时
    我们可以使用this关键字来区分

4.2. static含义

当我们了解了this关键字
我们就可以更好理解static关键字的含义了
借助我们上面的知识,我们可以如下说:
static(静态)方法就是没有this的方法

static方法的特性:

  1. 可以在没有创建object的情况下,直接通过class调用
    这就有点类似于全局函数的概念了
  2. 不能调用非static方法和实例变量
    因为非static方法和实例变量需要this关键字来调用
    但可以通过传入对象引用来调用或者在static方法中创建一个object来使用,但为什么不改成非static方法呢?

5. 清理:终结处理和垃圾回收

Java中的垃圾回收机制是Java的一个重要特性,也是Java的一个优势
它负责回收无用对象占据的内存
但是它一直有用吗?
也存在特殊情况,即对象(并非使用new)获得了特殊的内存资源,它无法被垃圾回收机制回收
因为垃圾回收机制只能回收由new创建的对象
为了应对这种情况,Java允许在class中定义一个finalize()方法

而我们可以同样分析一下C++采用的内存管理方式:
C++中存在析构函数的概念,是在销毁对象必须使用函数
在一般情况下,C++程序(没有缺陷的理想情况下)中的对象一定回归销毁
但Java中,对象并非总是被垃圾回收

可以总结为以下两点:

  1. 对象可能不被垃圾回收
  2. 垃圾回收并不等于“析构”

5.1. 为什么?

很有可能,只要程序没有面临存储空间使用殆尽的情况,对象所占用的空间就总是得不到释放
若这种情况下程序执行完毕,垃圾会抽起一直没有起作用,则会随着程序的退出,直接将资源全部交还操作系统
这个策略的目的是节省垃圾回收本身造成的开销
只要我不用,那就不会产生开销

5.2. finalize()的用途何在?

显然,通过上述分析,我们知道finalize()方法不应该作为通用的清理方法

这与第三点有关:

3. 垃圾回收只与内存有关

即,垃圾回收器被使用的唯一原因就是为了回收程序不再使用的内存
只要是通过Java通常做法来分配内存创建对象,那么它就可以被垃圾回收器释放
因此特殊情况就来自于非通常做法的内存分配
而这种情况主要发生在本地方法
此处不多赘述

但不管是“垃圾回收”还是“终结”,都不一定会发生
如果JVM未发生内存耗尽的情形,就不会浪费时间取执行垃圾回收

5.3. 终结条件

来自于finalize()方法的一个有趣用法 :
用于最终发现对象中是否存在没有被适当清理的部分

即,当对象被清理时,其应该处于某种状态,使得其内存可以被安全释放(例如打开文件的对象应该关闭文件)
人如果没有妥善处理,程序就会存在很隐晦的缺陷
finalize()方法中设置检验和提醒可以用于检测这种情况
尽管它并不总是被执行
但是一旦实行,就可能据此找出问题所在!

5.4. 垃圾回收器如何工作

  • TODO: 此处暂时还难以理解

主要介绍了Java垃圾回收器以及其堆内存上的一些浅显的工作原理以及优化思想

6. 成员初始化

Java尽力保证:所有变量在使用前都能得到恰当的初始化

Java程序中
创建局部变量时,若未进行初始化就进行使用,会以编译时错误报告
编译器自然可以为其赋上一个默认值,但实际上往往是程序员疏忽导致的未初始化
要求强制提供一个初始值,更利于找出程序缺陷

示例:

public static void main(String[] args)
{int i ;i ++ ;
}
错误: 可能尚未初始化变量ii ++ ;^
1 个错误

但与局部变量不同
class的实例变量,即使没有人为的初始化,也会保证具有一个初始值
如下:

Data TypeInitial Value
booleanfalse
char‘\u0000’
byte0
short0
int0
long0L
float0.0f
double0.0d
referencenull

6.1. 指定初始化

如果想为某个变量赋初值
一个直接的办法就是在定义类实例变量时就进行赋值
对于非基本类型的实例变量,也同样适用
注意:C++中这个操作是不合法的

class InitialValues
{boolean t = true ;char c = 'x' ;byte b = 47 ;short s = 0xff ;int i = 999 ;long l = 1 ;float f = 3.14f ;double d = 3.14159 ;ExampleClass e = new ExampleClass() ;
}

此处,我们甚至可以通过调用某方法来提供初值
不过需要注意初始化的顺序

以上所述的方法,简单而直观
但是限制也很明显,每个对象所具有的初值都是固定的
当我们需要更大的灵活性时,可能需要另作考虑

7. 构造器初始化

构造器初始化即适用构造器进行object中实例变量的初始化
但值得注意的时,构造器初始化无法组织自动初始化的进行
自动初始化将在构造器被调用之前发生

7.1. 初始化顺序

class内,实例变量的定义先后,决定了初始化的顺序
此外,即使实例变量的定义散布于方法之间(即不一定位于class开头),也会在任何方法被调用之前得到初始化

7.2. 静态数据的初始化

先前所属
static关键字,不止可用于修饰方法
同样可以用于修饰实例变量
当然,不能应用于局部变量

静态初始化只有在必要的时刻才会进行
即当第一个对象被创建第一次静态数据被访问时才会初始化静态实例变量
注,上述后者包括静态实例变量和静态方法的使用

顺序:
在一个尚未初始化的class
初始化的顺序是先静态数据,后“非静态”数据

显式的静态初始化:

public class Spoon
{static int i ;static {i = 1 ;}
}

以上第二个static的内容就是静态初始化块
它会保证在class第一次加载时被执行,且只会执行这一次

注:以上显式的静态初始化的方法也可以用于非静态实例的初始化
static去掉即可
其可以保证不管调用哪个显式构造器,其中的操作都会发生

8. 数组初始化

8.1. 数组引用的定义

与普通对象引用的定义类似
需要注意的是,数组引用的定义,[]的位置有两种风格

int[] a1 ;
int a2[] ;

第一种更为直观,比较推荐
第二种是适应C/C++的风格

8.2. 数组的初始化方法

  1. 定义数组处进行指定初始化

    int[] a = { 1, 2, 3, 4, 5} ;
    

    编译器不允许指定数组的大小,但在这种方法中
    存储空间的分配(等于使用new)将有编译器负责

  2. 使用new在数组里创建元素

    int[] a ;
    a = new int[5] ;
    // or
    int[] b = new int[5] ;  // when possible, more recommended
    

    值得一提的是,首先数组的大小可以是变量
    其次如果我们创建的是非基本类型的数组,

9. 可变参数列表

示例代码
VarArgs.java

9.1. 介绍

可变参数列表,一种方便的语法用来创建对象并调用
可以应用于参数个数或类型为止的场合

语法:

type... name

优点:
因为可变参数的引入,不再需要显式地编写数组语法
指定参数时,编译器实际上回我为我们填充数组

9.2. 注意点

允许参数范围:

  1. 可以为空
    具有可选的尾随参数时,很有效
  2. 可以有一个或多个
  3. 可以直接传入个体参数,也可以传入数组

可变参数对重载的影响:
根据大致理解,带来的问题主要是可能的歧义导致的编译错误
应该总是只在重载方法的第一个版本上使用可变参数列表

10. 枚举类型

enum关键字,于Java SE5中被引入
定义示例:

public enum Spiciness
{NOT, MILD, MEDIUM, HOT, FLAMING
}

10.1. 枚举的使用

enum的用法:
创建类型引用,赋值给某个实例

10.2. 枚举的特性

enum`被创建时,编译器会自动添加一些有用的特性:

  1. toString()方法
    方便用于显示某个enum实例的名字
  2. ordinal()方法
    返回一个int值,表示enum实例在声明时的次序
    从0开始
  3. static values()方法
    返回一个包含所有enum实例的数组
    顺序与enum实例的次序相同

使用示例:
EnumTest.java

10.3. 枚举的理解以及常用使用场景

枚举的理解:
相较于C中的类似于宏定义的枚举,Java的enum更为强大
在java中,enum可以理解为一种特殊的class
因此他当初还可以拥有方法

相关文章:

  • python爬虫降低IP封禁,python爬虫除了使用代理IP和降低请求频率,还有哪些方法可以应对IP封禁?
  • Cursor入门教程-JetBrains过度向
  • ReportLab 导出 PDF(页面布局)
  • 【Windows】安装或者点击OneDrive没有任何反应的解决方案
  • C++零基础实践教程 函数 数组、字符串与 Vector
  • 【文献笔记】SatLM: Satisfiability-Aided Language Models Using Declarative Prompting
  • STM32-FreeRTOS的详细配置
  • STM32基础教程——DMA
  • 深入解析Java日志框架Logback:从原理到最佳实践
  • 医院 VMware 替代实践合集|以国产虚拟化和超融合替代 vSphere 和 vSAN
  • 随机IP的重要性:解锁网络世界的无限可能
  • 数据库脱裤
  • Spring Boot管理Spring MVC
  • 【CRF系列】第7篇:CRF实战——经典工具与Python库应用
  • SPA 收入支出/技师提成自动统计系统——仙盟共创平台——未来之窗
  • 21源码剖析——初始化——加载核心配置文件
  • 基于YOLOv9的课堂行为检测系统
  • ubuntu开机自启动
  • Godot学习-创建简单动画
  • 开始学习USB——第一步
  • 建立网站的英文怎么说/seo外链推广工具下载
  • 自己怎么做网站赚钱/淘宝关键词top排行榜
  • 南阳专业做网站公司/seo相关岗位
  • 德州做网站公司/nba西部排名
  • 网站开发是无形资产/网站建设优化推广系统
  • 城市旅游网站开发/怎么在网上推广产品