《Thinking in Java》狗血的学习笔记-3初始化

”C++的发明人Bjarne Stroustrup在设计C++期间,在针对C语言的生产效率所进行的最初调查中发现,大量编程错误都源于不正确的初始化。这种错误很难发现,并且不恰当的清理也会导致类似问题。构造器能保证正确的初始化和清理(没有正确的构造器调用,编译器就不允许创建对象),所以有了完全的控制,也很安全。“

——《Thinking In Java》第三版,第5章-初始化与清理

一、初始化


1. 局部变量的初始化


初始化你妹,局部变量可不是亲儿子,得自己初始化,否则直接就是一个” Error“。

2. 类的初始化


类里面有两种变量,一种是成员变量(字段),一种是静态变量(静态域)。
初始化的方式也有很多,包括C++的 构造器初始化(非强制初始化)以及 定义时初始化(对于C++来说好爽),就算你什么也没有干,由于类是亲儿子的缘故,所以会给 默认初始化
定义时初始化就是在定义的时候就进行初始化,与局部变量的定义语句类似。
默认初始化就是在一开始申请空间的时候,内存自动清零。
构造器初始化严格来说就是在函数中进行赋值而已,这并不是强制性的和必须的。
中进行初始化的时机与定义时初始化相同。
初始化的顺序较为复杂,但是有一点是: 自上而下,与语句顺序相同。
初始化的时机十分有趣。JAVA为了性能考虑,类的静态变量初始化在首次加载类(比如调用了其静态方法、构造对象)就是那个.class文件时才进行初始化(这与动态加载如出一辙),而成员变量的初始化则是在new的时候进行。
我自己的见解:
将整个class看做一个大函数,里面的块、函数都是函数中的块和函数(和JavaScript一样)。那么构造一个类也好,构造一个对象也好,无非是执行函数中的一些语句罢了,自外而内、自上而下的执行,只执行自己需要的部分。
下面是一个图解:
拿一个例子来讲:
class MyGirlFriend {
	// 构造时输出顺序
	MyGirlFriend(String info) {
		System.out.println(info);
	}
}

public class Main {
	// 以下是成员变量
	// 这是mgf3的初始化化块
	{
		mgf3 = new MyGirlFriend("mgf3");
	}
	// 第一个成员变量,默认初始化
	MyGirlFriend mgf1;
	// 第二个成员变量,定义时初始化
	MyGirlFriend mgf2 = new MyGirlFriend("mgf2");
	// 第三个成员变量,用块,注意初始化块在前面!
	MyGirlFriend mgf3;
	
	// 以下是静态变量
	// 第一个静态变量,默认初始化
	static MyGirlFriend smgf1;
	// 第二个静态变量,定义时初始化
	static MyGirlFriend smgf2 = new MyGirlFriend("smgf2");
	// 第三个静态变量,用静态块,注意静态块在后面
	static MyGirlFriend smgf3;
	static {
		smgf3 = new MyGirlFriend("smgf3");
		// 在这里可以执行任何语句,包括变量定义
		new MyGirlFriend("在static中");	// 变量定义
		System.out.print("smgf1的默认值为");
		System.out.println(smgf1);
	}
	
	// 构造函数
	Main() {
		// 看看第一个有没有初始化
		System.out.println(mgf1);
		// 重复初始化第一个,发现定义时初始化也进行了
		mgf2 = new MyGirlFriend("重复初始化mgf2");
	}
	
	public static void main(String[] args) {
		System.out.println("\nIn Main.main()");
		// 创建一个Main
		System.out.println("\n创建一个Main对象");
		new Main();
	}
} /* Output
smgf2
smgf3
在static中
smgf1的默认值为null

In Main.main()

创建一个Main对象
mgf3
mgf2
null
重复初始化mgf2
*///:~
初始化顺序看了上述代码就一目了然了。

3. 数组的初始化


一切都是对象,数组也不例外。因此定义任何数组都是定义一个引用,直到通过new来进行初始化或者在定义时初始化,才会为它在堆中分配空间。如果是对象数据,那么数组中的数据(即堆空间的数据)则为对象的引用,想要真正的对象还得继续为对象初始化(二级跳)。那么显然多维数组不再是一个连续空间,而是一个N级映射表。
数组的初始化方式可以在定义时进行,用的语法如下:
Object[] arr = { new Object(), new Object, .....} 
也可以在任何时候初始化
Object[] arr = new Object[] { new Object()....} (就和一般对象重新为引用绑定对象一样)。
特别的,在函数参数列表中可以用 Object...的方式或者 Object[]的方式声明为可变参数列表(本质上就是数组)。

二、清理


1.垃圾回收


Java没有万恶的析构函数了,一切对象的销毁都是由垃圾回收器进行。
当一个堆对象没有任何引用来引用它时,他就 可能(我次奥,竟然是可能!)被销毁。因为当Java虚拟机内存空间充裕时,回收对象显然是一件没有意义而浪费性能的事情(其实懒人模式是最易行也极为高效的^_^,很多不同种类系统采用)。当然用户可以显示调用垃圾回收函数来进行强制回收(一般没有什么意义)。
因为垃圾回收的时机是不确定的,所以让垃圾回收来做一些必须事务类似于析构函数一样,显然是不安全的。
进行垃圾回收首先一个问题是确定哪些是垃圾。
一种显而易见的方式就是用一个计数器来为每个对象标注。首先一个问题是性能,因为这是 ,这样做效率会低(我有一点不明白,为什么引用拥有对象的地址还要顺序访问?疑有误)。然后很重要的问题是,如果两个对象彼此之间引用(就是成员变量都有对方),那么每个对象计数器都不会是0,永远不会回收。所以这种方式不可取。
另一种方式可以将 ”引用“-”对象“看做一个 ”二部图“。垃圾回收时,从引用部进行 连通图遍历,但凡连通的对象节点保留(说明它被某个引用直接或间接引用着),剩下的非连通节点则被回收。
第二个问题是如何回收
垃圾回收的目的是为了也只是为了释放内存。释放内存的结果尽可能应该是一块连续内存。于是一个显而易见的方式是每次垃圾回收后,将所有的对象挪到一起,但是这种复制显然是低效的。于是将内存分块以减少复制的量( 这不同于虚拟内存的分页),因为每一次复制的单位是块,而不是整个内存。
但是这还不够,如果只有少量的垃圾,进行复制的代价可比留下这些碎片的代价大得多!因此干脆标记上哪些是垃圾不动声色好了。等到垃圾量大了,再把所有标记的垃圾和新的垃圾一起回收掉,然后整理内存。

2.finalize()

这个函数是垃圾回收前要执行的一个函数,可以看做是垃圾回收前先发一个通牒:我要回收你了,调用你的finalize()特此通知。这个函数的用处呢,一个是服务于C中maloc分配内存产生的蛋疼对象,显然是要程序员自己调用。还有一种是为了检测一些错误,比如在销毁一个对象前,这个对象的某些功能没有履行(比如解雇一个员工前没有退户口档案云者)。

三、枚举

和C++,枚举有着本质变化。它是类!除了用的时候更人性化外,比如默认提供了一些使用函数,没有什么改进了。他不像某些语言,允许enum完全按照类来处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值