《 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

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

  1. 《Java编程思想》——初始化与清理(一)读书笔记

    第一次写这个,这一章都用word写的,结果复制过来没图片....只能上传word文档了.以后改用markdown比较好 word文档地址:<Java编程思想>--初始化与清理(一)读书笔记

  2. Java编程思想之五初始化与清理

    随着计算机革命的发展,"不安全"的编程方式已经逐渐称为编程代价高昂的主因之一. 初始化和清理正是涉及安全的两个问题. 5.1 用构造器确保初始化 通过提供构造器,类的设计者可确保每 ...

  3. 《java编程思想》 初始化与清理

    1.初始化与清理的重要性: 1.许多C程序的错误都源于程序员忘记初始化变量,特别是使用程序库时,如果不知道如何初始化库的构件更容易出错 2.当使用完一个元素时,这个元素就不会有什么影响了,所以很容易就 ...

  4. (六)《Java编程思想》——初始化及类的加载顺序

    package chapter7; /** * 初始化及类的加载顺序:顺序如下 * 1.基类的static变量 * 2.导出类的static变量 * 3.基类的变量 * 4.基类的构造函数 * 5.导 ...

  5. Java编程思想——初始化与清理

    PS:最近一直忙于项目开发..所以一直没有写博客..趁着空闲期间来一发.. 学习内容: 1.初始化 2.清理 1.初始化   虽然自己的Java基础还是比较良好的..但是在解读编程思想的时候还是发现了 ...

  6. 《java编程思想》读书笔记(一)开篇&第五章(1)

    2017 ---新篇章  今天终于找到阅读<java编程思想>这本书方法了,表示打开了一个新世界. 第一章:对象导论 内容不多但也有20页,主要是对整本书的一个概括.因为已经有过完整JAV ...

  7. Java编程思想(11~17)

    [注:此博客旨在从<Java编程思想>这本书的目录结构上来检验自己的Java基础知识,只为笔记之用] 第十一章 持有对象 11.1 泛型和类型安全的容器>eg: List<St ...

  8. Java编程思想 (1~10)

    [注:此博客旨在从<Java编程思想>这本书的目录结构上来检验自己的Java基础知识,只为笔记之用] 第一章 对象导论 1.万物皆对象2.程序就是对象的集合3.每个对象都是由其它对象所构成 ...

  9. Java编程思想(前十章)

    Java编程思想 有C++编程基础的条件下, 前10章可以快速过一下,都是基本语法,不需要花太多时间. 着重中后段的一些章节,类型信息.泛型.容器.IO.并发等. 中文翻译版 阅读地址 对于一个架构师 ...

随机推荐

  1. Revealjs网页版PPT让你复制粘贴另类装逼,简洁优雅又低调,不懂编程也看过来

    Revealjs网页版PPT让你复制粘贴另类装逼,简洁优雅又低调,不懂编程也看过来 要了解一个新知识我们可以从三个方面入手:是什么,有什么用,怎么用.下面我们就从这三个方面进行讲解Reveal.js噢 ...

  2. llinux重启、用户切换、注销命令

    一.指令 shutdown命令 shutdown -h now //立即关机 shutdown -h 2 //分钟后关机 shutdown -r now //立即重启 shutdown -r 1 // ...

  3. 推荐中的多任务学习-YouTube视频推荐

    本文将介绍Google发表在RecSys'19 的论文<Recommending What Video to Watch Next: A Multitask Ranking System> ...

  4. 【转】ArcGIS 10.1 for Server 架构

    前一段时间在博客中公布了我们的计划,我们采用博客的形式将对ArcGIS10.1 for Server进行全面介绍.但这种形式有一定的遗憾:缺少互动的空间,所以我们希望广大爱好者能将自己感兴趣的话题在博 ...

  5. JVM系列七(JIT 即时编译器).

    一.概述 即时编译器(Just In Time Compiler),也称为 JIT 编译器,它的主要工作是把热点代码编译成与本地平台相关的机器码,并进行各种层次的优化,从而提高代码执行的效率. 那么什 ...

  6. Netty之缓冲区ByteBuf解读(一)

    Netty 在数据传输过程中,会使用缓冲区设计来提高传输效率.虽然,Java 在 NIO 编程中已提供 ByteBuffer 类进行使用,但是在使用过程中,其编码方式相对来说不太友好,也存在一定的不足 ...

  7. Elasticsearch系列---实战零停机重建索引

    前言 我们使用Elasticsearch索引文档时,最理想的情况是文档JSON结构是确定的,数据源源不断地灌进来即可,但实际情况中,没人能够阻拦需求的变更,在项目的某个版本,可能会对原有的文档结构造成 ...

  8. map + 前缀和

    B. Balanced Substring time limit per test 1 second memory limit per test 256 megabytes input standar ...

  9. CheckStyle报错的常见问题及解决方式

    CheckStyle报错的常见问题及解决方式 声明: 本文摘自百度文库.希望这篇文章提到的规范能对大家编程起到好的效果,此文不定期更新,将推出更加详尽的编程规范. 1   提示:Type is mis ...

  10. Spring Cloud Eureka------详解

    一 Eureka服务治理体系 1.1 服务治理 服务治理是微服务架构中最为核心和基础的模块,它主要用来实现各个微服务实例的自动化注册和发现. Spring Cloud Eureka是Spring Cl ...