Thinking in Java 读书笔记 第五章 初始化与清理

本文深入探讨Java中对象的初始化及清理机制,介绍构造器的作用与设计原则,解析方法重载及this关键字的使用场景,并详细阐述Java垃圾回收机制的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

章五 初始化和清理

前言

  在C语言中,大量的错误来自不正确的初始化;在C++中,大量的错误来自对new出来的对象没有正确的delete。Java吸取以上两者的教训,在使用了C++良好的构造器设计的同时,将所有对象都定义在堆上,并通过阻止直接对对象进行操作,使用垃圾回收器,自动回收内存,保证了对变量的正确初始化和清理。需要注意的是,对于对象的数据成员,即类的非static域,Java进行自动初始化,保证了对象的所有成员都得到了初始化。对于不是对象的变量,如局部基本类型变量和引用变量,则实施对使用未初始化变量进行报错和超出作用域后进行清理的初始化和清理错误。
  本章的内容从对对象的初始化开始,引导读者思考,自然而然的引入构造器的概念并引发对构造器起名的思考。再从名字复用的角度展开,讨论方法重载。中间对对象的清理和初始化进行详细讲解,最后则介绍了数组枚举类型

1 用构造器保证初始化

  构造器设计初衷:保证每个对象都会被初始化
  名称设计:采用C++的方案,与类同名。从而避免与方法重名。
  其他说明:
  1. 构造器没有返回值,不属于方法(可以看做是静态方法,但其本质上不属于方法),不采用方法首字母小写的编程风格(类名首字母大写)
  2. Java中,对象的初始化创建绑定在一起(对象本身无名,没法像C++一样先声明后创建)
  默认构造器:同C++一样,Java中没有参数的构造器被称为默认构造器。若类中未定义构造函数,则编译器自动创建默认构造器。但一旦定义任一构造器,则编译器不会创建默认构造器。

2 方法重载

  对于面向对象程序设计,可以把方法视为对象的行为。通过对行为命名,可以使对象方便的进行各种动作(行为),好的名字可以使得设计更易理解和修改。我们知道,一词可以多义,同样的方法名也可以对应不同的操作,这就是方法重载的由来。

  区分方法重载:每个重载方法都有一个独一无二的形参列表。包括:1.参数个数;2.参数类型;3.参数顺序。根据上面三个标准可知,仅返回值类型不同无法构成方法重载。如a.f();这行代码所示,这种写法在程序设计里是很常见的,若存在两个f(),仅返回值不同,则上述代码无法通过编译。所以不能仅以返回值类型区分方法重载

  当方法重载涉及到基本数据类型时,会有类型转换的问题。由第三章的内容可知,分为两种:窄化转换扩展转换。对于窄化转换,必须进行显示类型转换,不易出错,如下述代码所示:

   static void f(int l) {
        //
    }
   long arg = 55;
   f((int)arg); // explicit type conversion

  对于扩展转换,由于系统会自动提升类型,因此会发生一些意想不到的错误。下面给出将基本类型传递给重载参数的几个注意点:

  • 对于char类型,需要提升时,会直接提升至int型
  • 对于字面值常量,整数默认为int型,小数默认为float型
  • 对于byte,short,int,float逐级提升double能表示的范围最大。

3. this关键字

  对于OOP(面向对象程序设计),用OO的语法表示发送消息给对象时,比较自然的语法应该是object.doSomething()。但是这种表示方法无法反映当前状态object的状态。这里,定义了this关键字,用以表示对调用方法的当前对象的引用。需要说明的是,this只能在方法内部使用。this的几个常用场合:

  • 通过this返回对当前对象的引用; return this
  • 通过this将当前对象的引用传递给别的方法;transmit(this)
  • 使用this在构造器内调用同类的其他构造器,语法:this(a,b),该方法的注意事项:
    • 1. 调用方和被调用方必须都为构造器
    • 2. 构造器调用语句必须位于第一句
    • 3. 构造器调用语句只能有一句
staticthis

  static表示与具体对象无关,与this明显不能共存。通过于this对比,更够更好的理解static。在static方法内无法直接调用非static方法,因为static方法没有隐含的this

4. 清理:finalize()和垃圾回收器(GC)

  在C++中,清理工作内存回收都由析构函数来完成。在Java中,因为没有局部变量的概念,内存由垃圾收集器自动回收。因为GC并非实时回收内存,所以即使定义了清理工作,也没法保证其在指定位置被执行,因此Java取消了析构函数。对于确实需要执行清理工作的对象,Java工程师只能自行定义方法实现类似析构函数的操作。对于finalize()和GC,下面三点必须记住:

  • 1. 对象可能不被垃圾回收(并非实时处理,垃圾收集后统一处理)
  • 2. 垃圾回收不等于析构
  • 3. 垃圾回收只与内存有关

finalize()方法

   finalize()方法在Object类中即有定义如下:

protected void finalize() throws Throwable { }

   由于内存回收由GC负责,所以finalize()方法仅限处理以创建对象以外的方式为对象分配内存的情况,主要发生在使用本地方法的情形。

本地方法:在Java中调用非Java代码的方式,一般为C/C++

   finalize()的另一种用法,用于验证对象终结条件。使用System.gc()可强制执行没有对象引用的对象的finalize()方法。

GC工作原理

  垃圾回收机制主要有两种算法:引用计数法可达分析算法。其中引用计数法存在循环引用的问题,在Java中,主要应用可达分析算法,思想如下:对的对象,一定能最终追溯到其存活在堆栈或静态存储期之中的引用。对于发现的每个引用,追踪其所引用的对象,再追踪此对象包含的所有引用,以此反复,直至访问空间内形成的所有引用网络。
   在上述思路下,Java采用一种自适应的垃圾回收技术。交替使用停止-复制标记-清扫模式。

停止-复制:先暂停程序的运行(不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的都是垃圾。被复制到新堆后,对象在新堆中紧凑排列,避免页面错误。
缺点:会降低效率。一是对象的所有引用需要被修正,需要维护比实际多一倍的空间。二是在程序稳定后,垃圾较少,反复复制浪费极大。

标记-清扫:采用可达算法分析,对每个找到的存活对象,进行标记,此过程不回收任何对象。当全部标记完成,开始清理,释放所有的未标记对象,并不进行复制动作。
缺点:速度较慢,剩下的堆空间不连续。

   下面将说明Java的自适应垃圾回收机制:

Java虚拟机进行监视,若所有对象都很稳定,垃圾回收器效率较低,则切换到标记-清扫模式;同样,JVM跟踪标记-清扫的效果,要是堆空间出现很多碎片,便切回停止-复制方式;可以称为自适应的、分代的、停止-复制、标记-清扫式垃圾回收器

5. 成员初始化

   本节主要讲自动初始化指定初始化
   Java尽力保证所有变量在使用前都能被初始化。对于局部变量(局部基本类型变量和对象引用),若未经初始化即使用,则编译器报错。对于类的数据成员则实行自动初始化,具体结果如下:

Data Typebooleancharbyteshortintlongfloatdoublereference
Initial Valuefalse[ ]00000.00.0null

   若不想使用Java对的自动初始化值,可以在定义域时对其进行指定初始化。对于非static域,类的对象的每个实例的该字段的值相同,地址不同。

6.构造器初始化

   本节主要讲构造器初始化。需要注意的是,构造器初始化在自动初始化指定初始化之后进行。其中,自动初始化必然被执行,类的域必然存在初值。
   初始化顺序:字段定义的顺序
   静态变量初始化:static关键字不能应用于局部变量,只能作用域静态域在类对象被第一次创建或其被第一次访问时初始化,且只初始化一次。
   static块初始化:优先级同静态域,与静态域按定义的顺序初始化,初始化条件也一样。主要用于静态域初始化
   非static实例初始化:用以初始化非静态变量,等价于指定初始化

下面以Dog类为例,总结对象创建过程

  • 1. 当首次创建Dog类对象,或首次访问Dog类的静态方法/域时,Java解释器查找类路径以定位Dog.class文件。若存在继承,则有必要时顺着继承树一一定位。
  • 2. 载入Dog.class,按先父类后子类,同类中按定义顺序,对静态域static块进行初始化。只执行一次。
  • 3. 当用new Dog()创建对象时,在堆上为该对象分配足够的存储空间,并将这片空间清零——基础类型为0,引用类型为null。
  • 4. 对父类进行初始化
  • 5. 执行指定初始化非static实例初始化
  • 6. 执行构造器。

7. 数组初始化

  数组相同类型,用一个标识符封装到一起的一个对象序列基本类型序列。定义方式:
  1. int[] arr,     2.int arr[];
  按照规范,采用定义1的方式。编译器不允许指定数组的大小。以上仅定义了对数组的引用,必须初始化以创建相应的存储空间。通过在没有数组时定义数组引用,方便数组之间的赋值——传引用。数组有一个固定成员——length,存储数组元素个数,可以用以监督下标越界。
  数组有三种初始化方式:

     //Approach 1: initialize one by one
    Integer[] a = new Integer[rand.nextInt(20)];
    for(int i = 0; i < a.length; i++)
      a[i] = rand.nextInt(500); 
    //Approach 2: initialize all at one
    Integer[] a = { new Integer(1), new Integer(2),3, };
    //Approach 3: can be used to return a reference to an array
    Integer[] b = new Integer[]{new Integer(1), new Integer(2),3, };
可变参数列表

  定义方式: printArray(Object ... args)
  可变参数列表本质上是数组,可以用以存储基本数据类型,参数个数可以为0个。

8.枚举类型

  定义方式:采用enum关键字,如下:

public enum Spiciness {
  NOT, MILD, MEDIUM, HOT, FLAMING
} ///:~

  两个方法:

  • ordinal():返回某个特定enum常量在enum中声明顺序;
  • static values():返回按enum常量声明顺序组成的对应数组。

  枚举类型用于switch语句:

    Spiciness degree = new Spiceness();
    switch(degree) {
      case NOT:    System.out.println("not spicy at all.");
                   break;
      case MILD:
      case MEDIUM: System.out.println("a little hot.");
                   break;
      case HOT:
      case FLAMING:
      default:     System.out.println("maybe too hot.");
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值