Thinking in java 第五章 - 初始化与清理

Thinking in java 第五章 - 初始化与清理

        随着计算机革命的发展,“不安全”的编程方式已逐渐成为编程代价高昂的主因之一。

1 用构造器确保初始化

        构造器的作用就是确保了在你能操作对象之前,它已经被其当地初始化了。
请注意,由于构造器的名称必须与类名完全相同,所以“每个方法首字母小写”的编码风格并不适用构造器。

        从概念上来讲,初始化创建是彼此独立的,在代码中却找不到对initialize() 方法的明确调用。在Java中,初始化创建捆绑在一起,两者不能分离。

        构造器是一种特殊类型的方法,因为它没有返回值。这与返回值为空void明显不同。对于空返回值,尽管方法本身不会自动返回什么,但仍可选择让它返回别的东西。构造器则不会返回任何东西,你别无选择(new表达式确实返回了对新建对象的引用,但构造器本身并没有任何返回值)。假如构造器具有返回值,并且允许人们自行选择返回类型,那么势在必得让编译器知道该如何处理此返回值。

2 方法重载

        构造器是强制重载方法名的另一个原因。
        要是对明显相同的概念使用了不同的名字,那一定会让人很纳闷。好在有了方法重载,可以为两者使用相同的名字。

2.1 区分重载方法

        每个重载的方法都必须有一个独一无二的参数类型列表。

2.2 涉及基本类型的重载

        如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际数据类型就会被提升。char型略有不同,如果无法找到恰好接受char参数的方法,就会把char直接提升至int型。

        在这里,方法接受比较小的基本类型作为参数。如果过传入的实际参数过大,就得通过类型转换来执行窄化转换。如果不这样做,编译器就会报错。

2.3 以返回值区分重载方法

根绝方法的返回值来区分重载方法是行不通的。

3 默认构造器

        如果你写的类中没有构造器,则编译器会自动帮你创建一个默认构造器。
        如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器

4 this关键字

a.peel(1);
//内部的表现形式
Banana.peel(a,1);

        如果在方法内部调用一个类的另一个方法,就不必使用this,直接调用即可。当前方法中的this引用会自动应用于同一类中的去其他方法。
        当需要返回对当前对象的引用时,就常常在return后面写上this。

4.1 在构造器中调用构造器

        尽管可以用this调用一个构造器,但却不能调用两个。此外,必须将构造器调置于最起始处,否则编译器就会报错。

        由于参数s的名称和数据成员s的名字相同,所以会产生歧义。使用this.s来代表数据成员就能解决这个问题。
出构造器外,编译器禁止在其他任何方法中调用构造器。

4.2 static的含义

        static方法就是没有this的方法。

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

        假定你的对象(并非使用new)获得了一块“特殊”的内存资源,由于垃圾回收器只知道释放那些经由new分配的内存,所以他不知道该如何释放该对象的这块“特殊”内存。

        解决方法:再类中定义一个finalize()方法)
        工作原理(假定):一旦垃圾回收器准备好释放对象占用的内存,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。

  1. 对象可能不被垃圾回收。
  2. 垃圾回收并不等于“析构”
5.1 finalize()的用途何在

不该将finalize()作为通用的清理方法,那么fnalize()的真正用途是什么呢?
3. 垃圾回收只与内存有关
        使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是finalize() 方法),它们也必须同内存及其回收有关。
注意:
但是这是否意味着要是对象中含有其他对象,finalize() 就应该明确释放那些对象呢?

        不,无论对象是如何创建的,垃圾回收器都会负责释放对象占据的所有内存。这就将对finalize() 的需求限制到一种特殊情况,及通过某种创建对象方式以外的方式为对象分配了存储空间。不过,Java中一切皆为对象,那这种情况是怎么回事呢?
        看来之所以要使用finalize() ,是由于再分配内存时可能采用了类似C语言中的做法,而非Java中的通常做法。这种情况主要发生在使用“本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。(目前本地方法只支持C和C++,但他们可以调用其他任何语言写的代码,所以实际上可以调用任何代码。)

在非Java代码中,也许会调用C的malloc() 函数系列来分配存储空间,而且除非调用了free() 函数,否则存储空间将得不到释放,从而造成内存泄漏。当然,free() 是C和C++中的函数,所以需要在finalize() 中用本地方法调用它。

它确实不是进行普通的清理工作的合适场所。那么,普通的清理工作应该在哪里执行呢?

5.2 你必须实施清理

        Java不允许创建局部对象,必须使用new 创建对象。

        可以肤浅的认为由于垃圾回收器的存在,使得Java没有析构函数。但是,随着学习的深入,就会发现垃圾回收器并不能完全代替析构函数。(而且绝对不能直接使用finalize() ,所以这也不是一种解决方案。)如果希望进行除释放存储空间之外的清理工作,还是得明确调用某个恰当的Java方法。这就等同于使用析构函数了,只是没有它方便。

        记住,无论是“垃圾回收”还是“终结”,都不保证一定会发生。如果Java虚拟机(JVM)并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。

5.3 终结条件

        finalize() 一般只能用于程序员很难用到的一些晦涩用法里了。不过,finalize() 还有一个有趣的用法,它并不依赖于每次都要对finalize() 进行调用,这就是对象终结条件的验证。

5.4 垃圾回收器如何工作

        其他语言在堆上分配对象的代价十分高昂,因此读者自然认为Java所有对象在堆上分配的方式也非常高昂。然而,垃圾回收器对于提高对象的创建速度,却具有明显的效果。听起来很奇怪——存储空间的释放竟然会影响存储空间的分配,但这确实是某些Java虚拟机的工作方式。这也意味着,Java从堆分配空间的速度,可以和其他语言从堆栈上分配空间的速度相媲美。

        在某些虚拟机中,堆的实现方式类似于栈分配内存的方式,只是简单地移动Java的堆指针。当然,实际过程中在簿记工作方面还有少量额外开销,但比上查找可用空间开销大。

垃圾回收器详细内容请移步深入理解Java虚拟机。

6 成员初始化

        Java尽力保证:所有变量在使用前都能够得到恰当的初始化。对于方法的局部变量,Java以编译时错误的形式来贯穿这种保证。

        局部变量必须进行初始化,而类的数据成员都会有一个初始值。

6.1 指定初始化

        有一种很直接的办法,就是在定义类成员变量的地方为其赋值(注意在C++里不能这样做,尽管C++的新手们总想这样做。)

        程序的正确性取决于初始化的顺序,而与其编译方式无关。所以,编译器恰当地对“向前引用”发出了警告。
这种初始化方法既简单又直观。但是有个限制:每个对象都会具有相同的初值。有时,这正是所希望的,但有时却需要更大的灵活性。

7 构造器初始化

初始化早已得到保证。
        无法阻止自动初始化的进行,它将在构造器被调用之前发生。

7.1 初始化顺序

        **在类的内部,变量定义的先后顺序决定了初始化的顺序。**即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。

7.2 静态数据的初始化

        无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。
        静态对象只有在第一个对象创建(或者第一次访问静态数据)的时候,它们才会被初始化。伺候,静态对象就不会再次被初始化。
        总结一下对象的创建:

  1. 即使没有显示地使用static关键字,构造器实际上也是静态方法。因此,当首次创建对象时,或者访问其静态方法/静态域首次被访问时Java解释器必须查找类路径,以定位Dog.class文件
  2. 然后载入Dog.class(后面会学到,这将创建一个Class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只会在Class对象首次加载的时候进行一次
  3. 当用new Dog()创建对象的时候,首先将在堆上为Dog对象分配足够的存储空间
  4. 这块存储空间被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值(对数字来说就是0,对布尔型和字符型也相同),而引用则被设置成了null
  5. 执行所有出现于字段定义出的初始化动作
  6. 执行构造器
7.3 显示的静态初始化

        Java允许将多个静态初始化动作组织成一个特殊的“静态子句”(有时也叫做“静态块”) 与其他静态初始化动作一样,这段代码仅执行一次:当首次生成这个类的一个对象时,或者首次访问属于那个类的静态数据成员时(即便从未生成过那个类的对象)

7.4 非静态实例初始化

        与静态初始化子句一模一样,只不过少了static关键字。实例初始化子句是在两个构造器之前执行的。

8 数组初始化

        编译器不允许指定数组的大小。现在拥有的只是对数组的一个引用(你已经为该引用分配了足够的存储空间),而且也没给数组对象本身分配任何空间。 为了给数组创建相应的存储空间,必须写初始化表达式。
        创建对象数组的时候,刚开始它只是一个引用数组。如果忘记了创建对象,并且视图使用数组中的空引用,就会在运行时产生异常。

8.1 可变参数列表

        可变参数列表的参数个数可以为0到n

        在Java SE5 之前,可变参数列表采用的都是传递Object[ ] 数组。
        Java SE5 新加特性,使用Object… 或者其他类型可变参数String…;有了可变参数,就再也不用显示地编写数组语法了,当你指定参数时,编译器实际上会为你去填充数组。 使用可变参数列表不依赖于自动包装机制,而实际上使用的是基本类型。请注意,你可以在单一的参数列表中将类型混合在一起,而自动包装机制将有选择地将int参数提升为Integer
        在每一种情况中,编译器都会使用自动包装机制来匹配重载的方法,然后调用最明确匹配的方法。你应该总是只在重载方法的一个版本上使用可变参数列表,或者压根就不是用它。

9 枚举类型

        在你创建enum时,编译器会自动添加一些有用的特性。tostring()方法显示输出;ordinal()方法表示某个特定的enum常量的而生声明顺序;static values()方法按照enum常量的声明顺序,产生由这些常量值构成的数组。

        很大程度上可以将enum当做任何其他的类来处理;事实上,enum确实是类,并且有自己的方法。
        由于switch是要在有限的可能值集合中进行选择,因此它与enum正是绝佳的组合。

10 总结

        初始化在Java中占有至关重要的地位。 构造器能保证正确的初始化和清理(没有正确的构造器调用,编译器就不允许创建对象),所以有了完全的控制,也很安全。
        有些垃圾回收器甚至能清理其他资源,比如图形化和文件句柄。 然而,垃圾回收器确实也增加了运行时的开销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值