Java技术基础——Java编程思想学习小节(一)

来源:互联网 发布:蓝光2000主板端口说明 编辑:程序博客网 时间:2024/04/30 10:38

Java编程思想学习小节(一)

前言:由于最近在找工作,将《Java编程思想》这一经典书籍翻阅一番,回顾其中关键和精彩之处总结出来做个记录,并查阅相关资料进行扩展,供日后参考。如有理解错误,请不吝赐教。(从该书第5章开始)

第五章 初始化与清理

1. 函数重载

甚至参数的顺序不同,也足以区分两个方法。不过,一般情况下别这么做,因为这会使代码难以维护。
对于基本类型的重载,常数整数值会被当做int型处理;如果实际参数的数据类型小于形式参数的数据类型,实际数据类型就会被提升一级,对于char型会被直接提升到int型;如果传入的实际参数类型大于形式参数类型,就得通过类型转换来执行窄化(这个词翻译的有点别扭)转换,否则编译器会报错。

2. 构造器

如果你的类中没有写构造器,编译器会自动帮你创建一个默认构造器。
对于A a = new A(); a.fun(1); 编译器会暗自把所操作对象的引用作为第一个参数传递给func,像这样:A.fun(a, 1); this关键字只能在方法内部使用,表示对调用方法那个对象的引用。
this关键字的几个作用:作为返回值实现级联操作;传递给其他对象进行一些操作;在构造器中调用其他的构造器;区分参数名称和数据成员的名称。注意:不能用this调用两个构造器,必须将构造器调用置于最起始处。
static方法就是没有this参数的方法。在static方法内部不能调用非静态方法,反过来可以。提供static方法的一个原因是Java中禁止全局方法。

3. 清理和垃圾回收

finalize方法存在的原因:并非使用new获得了一块内存,由于垃圾回收器只知道释放那些经由new分配的内存,因此它不知道如何释放。对此,Java允许在类中定义一个finalize方法。其工作原理假定原书这么写的,为啥呢?待考,初步理解参照后面论述是这样:在垃圾回收器回收一个对象之前,调用finalize方法,在下一次垃圾回收时才会真正释放对象的内存。
注意:对象可能不被垃圾回收(一个对象引用被置为null时,参考资料3);垃圾回收不等于析构(因为析构可能做一些清理工作,而在Java中你需要在回收前自己去做)。
垃圾回收只与内存有关,对于与垃圾回收有关的任何行为来说(尤其是finalize方法)也必须同内存及其回收有关。
finalize方法的作用:Java支持本地方法,即调用非Java代码的方式。也许在非Java代码中调用了C函数malloc分配了内存,除非调用了free函数,否则这部分内存无法释放,这时可以在finalize方法中通过本地方法调用free函数。Java不保证finalize方法被完整地执行(再次参考资料3),在最终一定会被执行,要看JVM如何调度。
如何清理:如果希望进行除释放内存以外的清理工作,还是得明确调用某个恰当的Java方法。将执行清理的方法放入finally块中,防止由于出现异常而得不到调用。无论是垃圾回收还是finalize,都不保证一定会发生。如果JVM并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收的。虽然finalize不能保证调用,但可以最终来验证一些对象的终结条件,书上有一个例子:
  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");try {super.finalize();} catch (Throwable e) {e.printStackTrace();}}}public class CH5 {/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubBook novel = new Book(true);novel.checkIn();new Book(true);System.gc();}  }
finalize中检查如果有book没有checkIn,则报错。finalize最终会发现这样的问题。
垃圾回收工作原理:垃圾回收可以提高对象创建的速度。Java的堆指针只是简单地移动到尚未分配的区域,其效率比得上C++在栈上分配空间的效率。垃圾回收器一面回收空间,一面使堆中对象紧凑排列,实现了一种高速的、有无限空间可供分配的堆模型。
引用计数器:垃圾回收器会在含有全部对象的列表上进行遍历,发现某个对象的引用计数为0时,就是释放对象空间。缺陷是如果对象之间存在循环引用,则无法回收。这种方法很少使用在JVM中。
追溯引用:思想是对任何正在使用的对象,一定能追溯到其存活在堆栈或静态存储区之中的引用。如果从堆栈或静态存储区开始,遍历所有的引用,就能找到所有正在使用的对象,对于每个发现的引用,必须继续追踪此对象包含的所有引用,直到所有能访问到的引用都被访问为止。循环引用但没有使用的对象不会被发现,也就被回收了。
复制式回收:停止-拷贝,先暂停程序,将所有活对象从当前堆复制到另一个堆,没有复制的被回收。新堆里对象紧密排列,指向这些对象的引用要被修正。这种机制效率很低:两个堆的空间复杂度较高;垃圾很少时,复制是浪费时间。解决后者的方案:使用标记-清扫技术。
标记-清扫:这是一种比较自然的方式。遍历所有引用时,不做任何清扫,只是将对象做个标记。然后将未标记的对象释放,没有复制过程。缺点是剩下的空间是不连续的,分配效率较低。垃圾回收器可以选择性地整理剩余空间。标记-清扫也需暂停程序。
自适应分代技术:作为前面技术的结合,有一种自适应技术。内存分配以较大的块为单位。对于停止-复制来说,可以向废弃的块中复制对象,而不是都复制到新的堆中。块的代数是指经历垃圾回收的次数,代数越多,在内存中的时间越长。根据参考资料3的说法,堆被分为三部分:年轻代、成熟代和永久代。其中永久代部分存放的是class对象,这些对象永远不会被垃圾回收。前两者中的对象会被垃圾回收。年轻代又进一步分为三部分:伊甸园区、from区和to区。伊甸园区存放上次垃圾收集后新建的对象;From和To区就对应停止-拷贝中的旧堆和新堆。当新建对象很多导致伊甸园区装满时,触发停止-拷贝过程,将伊甸园区和from区中的不可达对象拷贝到to区,同时清空伊甸园和from区,此时from区变为to区,而to区则成为新的from区。如果to区装满,则将一部分年代较久的对象放入成熟代,代数足够久远的对象也会被放入成熟代。如果连成熟代也装满,这是对象都较为稳定,就触发标记-清扫机制,对成熟代的对象进行回收。这就是所谓的自适应的过程。
即时编译技术的优化:即时编译技术可以把程序全部或部分翻译成本地机器码。当将某个类的字节码装入内存后,可以即时编译所有代码,但这样做很耗时,并且增加了可执行代码的长度,导致页面调度,从而降低程序速度。因此,可以采用惰性评估(lazy evaluation)法,即时编译器只在必要时才编译代码。
关于分代回收,《疯狂Java》一书4.3节有如下论述:垃圾回收器会根据不同代的特点采用不同的回收算法,从而充分利用各种回收算法的优点。分代回收策略基于两个事实:1. 绝大数对象不会被长期引用,这些对象在年轻时就被回收。2. 很旧的对象和很新的对象之间很少存在相互引用的情况。由于第一点,垃圾回收时年轻代中处于可达的对象不会很多,复制成本就不会很大,因此对年轻代采用停止-复制算法。对于一些较大的对象,可直接放入成熟代。由于成熟代中的对象都存活很长时间,因此垃圾回收的频率无需太高,且随着时间流逝,成熟代中的对象会越来越多。这是采用标记-清除算法可以避免大量对象复制,也不太可能产生大量碎片。
当年轻代内存快用完时,垃圾回收机制会高频地对其进行垃圾回收,称为次要回收(minor collection);当成熟代内存快要用完时,会触发全回收,即两种代的内存都要回收,称为主要回收(major collection)。

4. 初始化

Java尽量保证成员在使用前被初始化,方法中的变量如果不初始化,会报编译错误。而类的数据成员会指定一个默认的初始值。
构造器初始化的动机是使不同对象可以有不同的初始值。
在类的内部,变量定义的顺序决定了初始化的顺序。初始化在构造器执行之前就进行。
静态数据的初始值与普通数据无区别,但不能用于局部变量,只能用于定义字段数据。
静态对象只有在第一次创建对象(构造器其实也是静态方法)或者第一次访问静态数据时才会初始化且只有一次(静态块也是如此)。初始化的顺序是先静态对象,后非静态对象。
数组声明时这两种写法等效:int[] a1 和 int a1[]。编译器不允许指定数组的大小。数组的创建是运行时进行的。
利用花括号初始化数组时有一个细节,最后一个元素后面的逗号是可选的,不一定要去掉,目的是维护长列表更容易。
Java支持方法的可变参数列表,如:
static void printArray(Object ... args){}
对args参数可以进行foreach式的访问,像一个数组那样。实际上,编译器会将传入的参数转换为数组的一个元素。如果传入的参数本身就是一个数组,也不会出错,不需要转换罢了。另外,可以传入0个参数(args的length为0),且参数列表类型可以是基本类型,这表明可变参数列表不依赖于自动包装机制。
注意,应该总是在重载方法的一个版本上使用可变参数列表,或者压根不用。参考105页程序举例。
枚举对象可以在switch中使用。

参考资料:

1. Java编程思想第5章——初始化与清理
2. 疯狂Java
3. Java进阶10 内存管理与垃圾回收
4. Java的垃圾回收机制浅析