think in java 第四章 初始化和清除 第四部分

think in java 第四章的学习

成员初始化,数组初始化:多维数组

 

一、成员初始化

Java尽自己的全力保证所有变量都能在使用前得到正确的初始化。若被定义成相对于一个方法的“局部”变量,这一保证就通过编译期的出错提示表现出来。因此,如果使用下述代码:

就会收到一条出错提示消息,告诉你i可能尚未初始化。

一个类的所有基本类型数据成员都会保证获得一个初始值。

在一个类的内部定义一个对象句柄时,如果不将其初始化成新对象,那个句柄就会获得一个空值。

我的理解:

为什么java要尽自己的全力保证所有变量都能在使用前得到正确的初始化,我觉得应该是因为怕程序员忘记初始化,然后又去使用这个对象,不存在的东西当然无法使用,所以需要报错。所以这种代码,int i;i++,一旦int没有给初始值。java就会变得很紧张,一边给这个int一个默认值,一边提醒你:是不是忘记了初始化呀?这种初始化方法应该是java为了减少初级错误的机制。

1.规定初始化

其实就是在初始化的时候给出初始值。

我的理解:

其实规定初始化就是在初始化的时候给出初始值。需要指定初始值,才可以使用对象,否则出现违例错误。编译器无法向前引用,初始化需要注意顺序。

2.构建器初始化

可考虑用构建器执行初始化进程。这样便可在编程时获得更大的灵活程度,因为我们可以在运行期调用方法和采取行动,从而“现场”决定初始化值。但要注意这样一件事情:不可妨碍自动初始化的进行,它在构建器进入之前就会发生。(这里需要注意构建器的使用顺序,在think in java 第四章 初始化和清除 第一部分中提到了)

这里虽然a在构建器下才声明,但是依然能正确初始化。这是因为类先初始化属性,再去调用构建函数。

这里的初始化顺序是怎样的?

不可妨碍自动初始化的进行,它在构建器进入之前就会发生。

所以i首先会初始化成零,然后变成7。

(1)初始化顺序

在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间,那些变量仍会在调用任何方法之前得到初始化——甚至在构建器调用之前。

我的理解:

先变量,再方法。

(2)静态数据的初始化

若数据是静态的(static),那么同样的事情就会发生;如果它属于一个基本类型(主类型),而且未对其初始化,就会自动获得自己的标准基本类型初始值;如果它是指向一个对象的句柄,那么除非新建一个对象,并将句柄同它连接起来,否则就会得到一个空值(NULL)。

如果想在定义的同时进行初始化,采取的方法与非静态值表面看起来是相同的。但由于static值只有一个存储区域,所以无论创建多少个对象,都必然会遇到何时对那个存储区域进行初始化的问题。

static初始化只有在必要的时候才会进行。如果不创建一个Table对象,而且永远都不引用Table.b1或Table.b2,那么b1和b2永远都不会创建。然而,只有在创建了第一个Table对象之后(或者发生了第一次static访问),它们才会创建。在那以后,static对象不会重新初始化。
初始化的顺序是首先static(如果它们尚未由前一次对象创建过程初始化),接着是非static对象。

对象的创建过程发生了什么?

对象的创建过程。请考虑一个名为Dog的类:
(1) 类型为Dog的一个对象首次创建时,或者Dog类的static方法/static字段首次访问时,Java解释器必须找到Dog.class(在事先设好的类路径里搜索)。
(2) 找到Dog.class后(它会创建一个Class对象,这将在后面学到),它的所有static初始化模块都会运行。因此,static初始化仅发生一次——在Class对象首次载入的时候。
(3) 创建一个new Dog()时,Dog对象的构建进程首先会在内存堆(Heap)里为一个Dog对象分配足够多的存储空间。
(4) 这种存储空间会清为零,将Dog中的所有基本类型设为它们的默认值(零用于数字,以及boolean和char的等价设定)。
(5) 进行字段定义时发生的所有初始化都会执行。
(6) 执行构建器。正如第6章将要讲到的那样,这实际可能要求进行相当多的操作,特别是在涉及继承的时候。

我的理解:

涉及到了重要的类的创建过程。当对象首次创建时,java解释器会去找到要创建对象的class,找到之后,就创建Class对象,然后初始化static模块之后,当new的时候,对象的创建进程会在堆内存中开辟足够的空间。将所有变量都初始化为初始值。之后进行变量的初始化。最后执行构建器最后急行初始化。

非常重要,需要理解。

(3)明确进行的静态初始化(static块)

Java允许我们将其他static初始化工作划分到类内一个特殊的“static构建从句”(有时也叫作“静态块”)里。它看起来象下面这个样子:

尽管看起来象个方法,但它实际只是一个static关键字,后面跟随一个方法主体。与其他static初始化一样,这段代码仅执行一次——首次生成那个类的一个对象时,或者首次访问属于那个类的一个static成员时(即便从未生成过那个类的对象)。

我的理解:

static代码块是在常见对象的步骤中,java解释器会去找到要创建对象的class,找到之后,就创建Class对象,然后初始化static模块。

(4)非静态实例的初始化

针对每个对象的非静态变量的初始化,Java 1.1提供了一种类似的语法格式。

它看起来与静态初始化从句极其相似,只是static关键字从里面消失了。为支持对“匿名内部类”的初始化(参见第7章),必须采用这一语法格式。

我的理解:

其实我觉得,这样写:

和这样写:

有什么不同呢?我觉得区别在于延迟加载的思想。其实很多类,开销很大,而且并不是需要立刻使用的,完全可以在需要使用之前才进行初始化。这只是我的个人理解,不知道对不对…以后再回头修改吧。

3.数组初始化

一些数组初始化和多维数组的概念…

比较要注意的是:

所有数组都有一个本质成员(无论它们是对象数组还是基本类型数组),可对其进行查询——但不是改变,从而获知数组内包含了多少个元素。这个成员就是length。与C和C++类似,由于Java数组从元素0开始计数,所以能索引的最大元素编号是“length-1”。如超出边界,C和C++会“默默”地接受,并允许我们胡乱使用自己的内存,这正是许多程序错误的根源。然而,Java可保留我们这受这一问题的损害,方法是一旦超过边界,就生成一个运行期错误(即一个“违例”,这是第9章的主题)。当然,由于需要检查每个数组的访问,所以会消耗一定的时间和多余的代码量,而且没有办法把它关闭。这意味着数组访问可能成为程序效率低下的重要原因——如果它们在关键的场合进行。但考虑到因特网访问的安全,以及程序员的编程效率,Java设计人员还是应该把它看作是值得的。

我的理解:

java数组是java中很多集合类的基础,比如ArrayList说到底是由数组实现的。所以说,如果自身设计的算法没有问题,纯数组操作肯定是比包装之后的实现类操作更快。

为什么我有这个经验呢,就是之前要写个java脚本,对一系列大量的数据做解析,然后将数据按照一定的规律进行归类。这个规律还算是比较不好实现的,需要使用不少ArrayList和HashMap,在我写出可以使用的代码后,发现在我本机上跑数据大概在120s左右,在有固态硬盘的linux服务器上跑大概在20s左右。

当时我还以为这样的效率算是不错了,毕竟我没开多线程,而数据量又很大。后来dalao说他写的一样的脚本,处理这么多数据只需要4.5s左右。

当时我就觉得不对劲。我已经是优化过我数据结构的算法了,在这个脚本上我还是很有信心的。后来才知道,他是用java数组实现的。至于具体是怎么实现的,因为我没有拿到源码,所以不方便胡乱猜测,可以肯定的是使用了java数组做了处理,相当于自己封装了一个数据结构出来。

后来我问dalao思路,他表示因为他本来是写c的,从一开始就没考虑过使用java的集合来解决,而是从数组开始自己想算法。

对这件事我的感受很深。现在我都还没想出来是怎么实现的。反正我明白了,在正确使用的情况下,数组的表现也许比集合类要好很多。

现在想来,其实这个优势也是相对的…因为现在毕竟只是单线程,等到多线程的情况下,我可以换用线程安全的集合类,而数组这时候就显得不这么可靠了…而且在能开多个线程的情况下,性能差多少还不好说。而且在使用数组的时候cpu负载会异常的高,应该是执行效率太高了所导致的。这有时候对于服务器可能也是一种负担。

至于多维数组,其实也是在数组的基础上进一步实现的…

现在想来,要实现那个分类脚本,准确来说应该使用的是多维数组。用string特征值作为第一维,然后把对象/分类信息作为第二维,然后直接用来判断即可。感觉没有什么必要使用集合类…

实现似乎也不是很困难…只能怪自己c写少了,没有从最简单的基础去考虑,而是想要偷懒,按照以前的习惯选择了集合类,结果反而弄得复杂了…真的需要反省一下。

5.总结

看完了这章,我似乎稍微明白了java为什么要这样去解决问题。

这章从头至尾,都是为了让程序更加靠谱。构建器,让属性的初始化更直接明确了。过载给初始化提供了更多选择。默认构建器规范了java的类创建流程,如果不声明就会自带默认构建器。this和static满足了程序的特殊规范要求。垃圾清除让内存的回收更加简单,不需要特别手工回收。成员初始化保证属性有初始对象,防止出现初始化问题。数组的初始化有错误机制保证,防止出现越界等问题…

其实可以看出,java的这些特性,很多都针对c中容易出现的问题进行了改良。将一些直接操作内存的容易出错的部分自己封装然后捕获错误了,再把封装好的部分给我们使用。因为做了很多内部处理而不是直接操作内存,java可能实现更慢,但是写起来要简单不少。因为内存容易出问题的现象被错误机制掩埋了,使得程序员失去警觉,不一定知道错误发生的根源是什么…我感觉java和c应该是有利有弊,见仁见智。

但是现在说只要实现得好,java应用并不比c慢。我觉得这也足以证明java这个后起之秀的强力了…

发表评论

电子邮件地址不会被公开。 必填项已用*标注