当前位置:首页 > Java技术 > 《 Java 编程思想》CH05 初始化与清理

《 Java 编程思想》CH05 初始化与清理

2022年08月05日 18:24:12Java技术8

《 Java 编程思想》CH05 初始化与清理

用构造器确保初始化

在 Java 中,通过提供构造器,类的设计者可确保每个对象都会得到初始化。Java 会保证初始化的进行。构造器采用与类相同的名称

  • 由于构造器的名称必须与类名完全相同,所以“每个方法首字母小写”的风格在这里不适用。
  • 构造器方法可以有参数,这样可以在初始化对象时提供实际参数。
  • 不接受任何参数的构造器叫做“默认构造器”。
  • 构造器一种特殊类型的方法,它没有返回值

方法重载

因为可以要用多种方式来创建并初始化一个对象,所以就需要多个构造器,而构造器的名称又需要和类名相同,所以必须允许方法名相同而形式参数不同的构造器存在,所以 Java 中有方法重载

class Rock {
	Rock() {
		// 默认构造器
	}
	Rock(int i) {
		// 带参数的构造器
		System.out.println("i=" + i);
	}
	void print() {
		System.out.println("i = null");
	}
	void print(int i) {
		System.out.println("i = " + i);
	}
}

// 初始化
Rock r1 = new Rock(); // 调用默认构造器
Rock r2 = new Rock(1); // 调用带参数的构造器
r2.print();
r2.print(1);

区分重载方法

方法签名是由方法名和参数类型列表构成的,所以用参数类型列表区分重载方法。不能用返回值区分重载方法

设计基本类型的重载

由于基本类型可能会从一个“较小”的类型自动提升为一个“较大”的类型,所以在重载方法中需要特别注意:

  • 常数值当作int处理
  • 实参的数据类型小于形参的数据类型时,会自动提升
  • 对于 char 类型,如果找不到以 char 作为形参的方法的话,会把 char 类型提升成 int 类型
  • 如果实参大于形参的话,需要显式的强制转换否则会报编译错误

默认构造器

  • 默认构造器是一个没有形式参数的构造器,其作用是创建一个“默认对象”
  • 如果类中没有构造器,编译器会自动创建一个默认构造器
  • 如果类中已经有了一个构造器了,编译器则不会自动创建默认构造器

this 关键字

由于同一类型的对象都可以调用相同的方法,为了在方法中区分不同的对象,会把对象的引用作为参数传递给方法,a.fun(1)在编译器内部会被翻译成ClassName.fun(a, 1),我们可以通过this关键字在方法中访问到对象的引用。

  • 在方法内部调用同个类的另一个方法不需要使用this,直接调用即可。
  • this 可以在方法内部作为参数传递给另一个方法,也可以作为返回值(可以构造出链式操作)

在构造器中调用构造器

  • 可以利用 this 来实现在构造器中调用构造器,这样可以避免重复代码。
  • this 在一个构造器中只能调用一次构造器
  • 必须将构造器置于最开始处,否则编译器会报错
public class Flower {
    int petalCount = 0;
    String s = "initial value";

    Flower(int petals) {
        petalCount = petals;
        System.out.println("int arg ,petalCount = " + petalCount);
    }

    Flower(String ss) {
        s = ss;
        System.out.println("string arg ,s = " + s);
    }

    Flower(String s, int petals) {
        this(petals);
        // this 只能调用一次构造器
        this.s = s;
        System.out.println("string & int arg, s = " + s + ", petalCount = " + petalCount);
    }

    Flower() {
        this("hello", 24);
    }

    public static void main(String[] args) {
        Flower flower = new Flower();
        System.out.println("flower.petalCount = " + flower.petalCount);
        System.out.println("flower.s = " + flower.s);
    }
}
// int arg ,petalCount = 24
// string & int arg, s = hello, petalCount = 24
// flower.petalCount = 24
// flower.s = hello

static 的含义

static 方法就是没有 this 的方法,在 static 中不能调用非静态方法,但是反过来可以。

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

  1. 对象可能不会被垃圾回收
  2. 垃圾回收不等于“析构”
    1. Java 未提供“析构函数”或相似的概念,要做类似的清理工作,必须手动创建一下执行清理工作的普通方法
  3. 垃圾回收只与内存有关
    1. 对与垃圾回收有关的任何行为来说(尤其是 finalize() 方法),它们必须同内存及其回收相关
    2. finalize() 的需求通常是用于一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间,这种情况主要发生在使用了“本地方法”的情况下,本地方法是一种在 Java 中调用非 Java 代码的方式。如在非 Java 代码中调用了 malloc,为了释放内存,我们需要在 finalize() 中调用对应的本地方法进行 free。
    3. finalize() 方法的执行机制:一旦垃圾回收器准备释放对象占用的存储空间,首先调用其 finalize() 方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

你必须实施清理

Java 中没有用于释放对象的 delete,因为垃圾回收器会自动帮你释放存储空间,因此 Java 中没有析构函数。但是垃圾回收不能完全代替析构函数,如果希望进行除释放存储空间之外的清理工作,我们需要明确调用某个 Java 方法。例如某个类打开了一个文件,垃圾回收不能自动帮我们关闭这个文件。为什么这个工作不能有 finalize() 方法来完成呢,原因其实在上面已经说明了,对象可能不会被垃圾回收,也就是说 finalize() 方法可能永远都不会被调用。

如果 JVM 没有面临内存耗尽的情况,它是不会浪费时间去执行垃圾回收以恢复内存的。

终结条件

虽然我们不能用 finalize() 方法来进行“清理”,但是我们可以利用它验证某个对象的终结条件。还是刚才那个打开文件的例子,假设在文件没有关闭的时候,垃圾回收将对象回收了,这就会产生一些非常难找的 bug。而 finalize() 可以帮助我们发现这种 bug。

class Book {
    boolean checkedOut = false;
    Book(boolean checkOut) {
        checkedOut = checkOut;
    }
    void checkIn() {
        checkedOut = false;
    }

    protected void finalize() {
        if (checkedOut) {
            System.out.println("Error: checked out");
        }
        // super.finalize();
    }
}

public class TerminationCondition {
    public static void main(String[] args) {
        Book novel = new Book(true);
        novel.checkIn();
        new Book(true);
        System.gc();
    }
}
// Error: checked out

如上面这个例子,我们希望 Book 在被回收前已经 checkIn 了,所以我们在 finalize() 中写了一个条件语句来判断。

  • System.gc()强制 GC
  • 应该总是假设基类的 finalize() 也需要做某些时间,所以我们应该在 finalize() 函数的末尾加入 super.finalize();

垃圾回收器如何工作

垃圾回收器会提高对象在堆上创建的速度,这是因为 Java 的堆的实现与 C++ 的不同,其更像是一个传送带,每分配一个对象,它就往前移动一格,所以“堆指针” 只是简单的移动到尚未分配的空间,这意味 Java 中在堆上的分配速度非常快。当然,如果只是简单的像传送带一样工作的话,Java 的堆会占用大量的虚拟内存,进而导致频繁的页面调度,并可能会导致内存资源耗尽,因此需要有垃圾回收器的介入。垃圾回收会一边回收空间一边对堆进行“紧凑”操作。

几种常见的垃圾回收机制:

  • 引用计数:一种简单但比较慢的垃圾回收机制。
    • 每个对象都有一个引用计数器,当引用连接对象时,引用计数加一,当引用离开作用域或被置 null 时,引用计数减一。
    • 这种方法无法处理“循环引用”的情况。
  • 停止-复制(stop-and-copy):
    • 其依据的思想是:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区域之中。因此,可以从堆栈和静态存储区开始,遍历所有的引用,并递归查找该对象所包含的所有对象,即可找到所有“活”的对象。
    • 该机制会先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,然后更新引用
    • 当对象被复制到新的堆中时,没复制的则相当于被回收了,同时可以实现“紧凑”的目标。
    • 该机制会有以下两个缺点:
      • 需要两个堆,进而需要维护比之前大一倍的空间
      • 如果只有少量垃圾甚至没有垃圾,而这时如果进行垃圾回收的话,开销太大了。
  • 标记-清扫(mark-and-sweep):
    • 与 stop-and-copy 机制依据的思想是一样的也是,用同样的方式找到“活”的对象
    • 每当它找到一个“活”的对象,就会给该对象一个标记,这个过程中不会回收任何对象。只有当全部标记工作完成时,才会进行清理。
    • 清理的过程中,没有被标记的对象被释放,但不会做任何复制动作
    • 为了避免存储空间的“碎片化”,JVM 需要做紧凑操作

JVM 中采用的垃圾回收机制:

  • 一种结合的 stop-and-copy 和 mark-and-sweep 的自适应垃圾回收算法
  • 内存分配以较大的“快”作为单位,较大的对象可以独占一个块。每个块都有相应的代数(generation count)来记录它是否存活。
  • 垃圾回收会对上次回收操作之后新分配的快进行整理,这样有助于解决有大量短命对象的情况。
  • 垃圾回收机制会定期进行完整的清理——大型对象仍然不会被复制(只是其代数会增加),而那些含有大量小型对象的快会被复制并整理。
  • 当只有少量或没有垃圾产生时,则转为使用 mark-and-sweep 算法。

Java 中 JIT(Just-In-Time)技术:

这种技术可以把程序全部或部分翻译成本地机器码,而不是通过 JVM,进而提升程序的运行速度。

当需要装载某个类时(第一次创建这个类时),编译器会找到其.class 文件,然后将该类的字节码装入内存,此时有两种做法:

  • JIT 直接编译所有代码,但这个做法会有两个缺点:
    • 加载动作分散在整个程序中,累加起来要话更多时间
    • 可能会增加可执行代码的长度,进而导致页面调度
  • 惰性评估(lazy evaluation):,即 JIT 只在必要时才编译,这样不会执行的代码就不会被 JIT 所编译。

成员初始化

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

局部变量没有默认初始值,如有在未初始化前使用它会报错编译错误,而类变量则有默认初始值。

指定初始化

Java 允许在定义类成员变量的时候为其赋值进行初始化。非基本类型也可以,同时可以使用已经函数或已经初始化好的变量进行初始化,但要保证初始化顺序的正确。

public class InitialValues {

	boolean t = false;
	char c = 'a';
	byte b = 1;
	short s = 2;
	int i = func(s);
	long l = 4 + i;
	float f = (float)5.0; // 浮点数字面量是 double 类型的
	double d = 6.0;
	String reference = new String("hello world"); // 非基本类型也可以

    int func(short s) {
        return s*2;
    }
}

构造器初始化

  • 无法阻止自动初始化的进行,它将在构造器之前执行。
  • 类变量的定义顺序决定了初始化的顺序
  • 静态数据的默认值与类变量一致
  • 对于静态变量,Java 可以将多个初始化语句组合成一个静态块,其和静态变量初始化一样在类加载时执行。顺序与定义时的顺序相同
  • 对于非静态变量,Java 中也可以将多个初始化语句组成一个块,在实例初始化执行。
  • 对于以上两种块,既可以可以把它当成一条初始化语句来看待。
public class InitialValues {

	boolean t = false;
	char c = 'a';
	byte b = 1;
	short s = 2;
	int i = 3;
	long l = 4;
	float f;
	double d;
	String reference;

	{
		f = (float) 1.0;
		d = 2*f;
		reference = new String("hello");
		reference = reference + f + d;
	}

	static {
		System.out.println("hello");
	}
	static int a;
	static {
		System.out.println("A is " + a);
		a = 2;
	}
}

对象的创建过程:

  1. 第一次创建类或者访问其静态数据或方法,JVM 会加载其 .class 文件,此时执行所有静态初始化(按定义的顺序执行)。
  2. 当 new 该类时,首先会在堆上分配空间,因为堆在分配前被置零了,所以本类型的默认值都是 0,非基本类型的引用的默认值则是 null。
  3. 按顺序执行非静态的初始化
  4. 执行构造器

数组初始化

数组是同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。

定义方式:

int[] a; //建议使用这种
int a[]; //这样也可以, 但是不能指定数组的类型。`int a[3];` 这样是不允许的

int[] a;这样只是定义了一个数组的引用,我们可以使用new来创建一个数组,也可以直接初始化数组:

int[] a = new int[3]; // 使用 new 来创建一个数组,这时真实数据会分配在堆中,所以默认值都为“零”
int[] b = {1, 2, 3}; // 直接初始化一个长度为3的数组
Integer[] c = new Integer[3]; // 创建一个对象数组,保存引用,这时初始值都为 null
Random rand = new Random(2);
int len = rand.nextInt(20);
int[] c = new int[len]; // 长度不一定要是一个字面值,可以是变量

数组初始化的坑点:

InitialValues initialValues = new InitialValues();
initialValues.printInitialValues();
String[] stringArray = {"hello", "world"};
// initialValues.printStringArrary({"hello", "world"}); // 编译错误
initialValues.printStringArrary(stringArray);
initialValues.printStringArrary(new String[]{"hello", "world"}); //正确打开方式
int[] intArray = {1, 2, 3, 4};
// initialValues.printIntArray({1, 2, 3, 4}); // 编译错误
initialValues.printIntArray(intArray);
initialValues.printIntArray(new int[]{1, 2, 3, 4});

可变参数列表

在方法中,用ClassName... ArgName的形式可以定义可变参数列表,在方法中,ArgName 本质上是一个数组。在可变列表中可以使用任何类型,包括基础类型。这里传入基本类型时,没有依赖自动装包和解包,这意味着,ClassName 为 int 时,ArgName 是一个 int[],而不是 Integer。在重载方法时,应该只在一个方法中使用可变参数列表

static void printArray(Object... args) {
    for(Object arg: args) {
        System.out.print(arg + " ");
    }
    System.out.println();
}

static void f(int required, int... args) {
    System.out.println("Required: " + required);
    for(int i: args) {
        System.out.print(i + " ");
    }
    System.out.println();
}

public static void main(String[] args) {
    printArray(1, 2, 3, 4, 5);
    f(1);
    f(1, 2, 3);
    Integ
}

枚举类型

  • 按照命名习惯,枚举值一般用全大写字母
  • 为了使用 enum,需要创建一个该类型的引用
  • enum 会自动创建一些实用的函数,如toString()显示其名称,ordinal()表示声明顺序
  • enum 适合与 switch 一起使用
enum EnumDemo {
	HELLO, WORLD, 
};

EnumDemo e1 = EnumDemo.HELLO;
System.out.println(e1); // 自动调用toString()
System.out.println(e1.ordinal());

for(EnumDemo e: EnumDemo.values()) {
    System.out.println("EnumDemo: " + e + " ordinal " + e.ordinal());
}
// HELLO
// 0
// EnumDemo: HELLO ordinal 0
// EnumDemo: WORLD ordinal 1

首发与Code & Fun

作者:wuxiaobai24
来源链接:https://www.cnblogs.com/wuxiaobai24/p/12302173.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/17818.html

分享给朋友:

“《 Java 编程思想》CH05 初始化与清理” 的相关文章

读Java编程思想随笔の数组

  数组与其他种类的容器之间区别有三:效率、类型和保存基本类型的能力。在Java中,数组是一种最高的存储和随机访问对象引用序列的方式。数组就是一个简单的线性序列,这使得元素访问非常快速。但是为这种速度所付出的代价是数组对象的大小被固定,并且在其生命周期中不可改变。你可以能会建议使用Ar...

Java编程思想 4th 第2章 一切都是对象

Java编程思想 4th 第2章 一切都是对象

Java是基于C++的,但Java是一种更纯粹的面向对象程序设计语言,和C++不同的是,Java只支持面向对象编程,因此Java的编程风格也是纯OOP风格的,即一切都是类,所有事情通过类对象协作来完成。 在Java中,使用引用来操纵对象,在Java编程思想的第四版中,使用的术语是...

Java编程思想学习(五)----第5章:初始化与清理

随着计算机革命的发展,“不安全”的编程方式已逐渐成为编程代价高昂的主因之一。 C++引入了构造嚣(constructor)的概念,这是一个在创建对象时被自动调用的特殊方法。Java中也采用了构造器,并额外提供了“垃圾回收器”。对于不再使用的内存资源,垃圾回收器能自动将其释放。...

Java编程思想 学习笔记5

五、初始化与清理   1.用构造器确保初始化      在Java中,通过提供构造器,类的设计者可确保每个对象都会得到初始化。创建对象时,如果其类具有构造器,Java就会在用户有能力操作对象之前自动调用相应的构造器,从而保证了初始化的进行。构造器的...

回顾Java编程思想篇(一)

本文主要介绍Java中对象的理解。 很久以前看过Java编程思想这本书,当时看得不是很懂,重新拿起这本书,感觉非常陌生,于是产生了重新研究的念头,并做一些读书笔记。   一、一切都是对象 1、Java与...

[JAVA]java编程思想-第一章-对象入门

第1章 对象入门 “为什么面向对象的编程会在软件开发领域造成如此震憾的影响?” 面向对象编程(OOP)具有多方面的吸引力。对管理人员,它实现了更快和更廉价的开发与维护过程。对分析与设计人员,建模处理变得更加简单,能生成清晰、易于维护的设计方案。对程序员,对象模...

Java编程思想第四版勘误

  坊间传说这本书翻译得很烂,我倒觉得还好。虽然看原文更准确,但是如果在具备一定编程思维和基础、能够看出来疑问的情况下,还是看中文更快一些,而且这本书本身也不适合初学者看。当然,错误和不通顺还是有的,而且官方和网上居然没有一份公开的勘误表,至少我没有搜到,搜索“Java编程思想第四版勘...

Java编程思想【温故知新】

第一章:对象导论 1. 抽象过程(类与对象的关系)   类是一类对象的共同行为(成员函数)与状态(成员变量),对象是具体类的实例化。(Eg.人类是一个类,共同的行为:吃,状态:名字。)   【类创建者需要考虑这件事情,回头看看这个概念四个字醍醐灌顶,每次创建这...

java异常捕获案例,此题的出处是《Java编程思想》一书

转自:https://blog.csdn.net/jackfrued/article/details/44921941   class Annoyance extends Exception {} class Sneeze extends Annoyan...

重学Java(一):与《Java编程思想》的不解之缘

  说起来非常惭愧,我在 2008 年的时候就接触了 Java,但一直到现在(2018 年 10 月 10 日),基础知识依然非常薄弱。用一句话自嘲就是:十年 IT 老兵,Java 菜鸡一枚。 于是,我想,不如静下心来,重新读一遍那些经典的 Java 技术书,并且...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。