java_seven_net的专栏

记录我的成长历程! ~ bless seven&water better and better!~~

原创  3初始化与清理 收藏

1.             This关键字
1.1         引子
 
如果一个类Test的两个对象a和b,都调用类中的一个方法f(),f()如何知道是被a还是
被b调用?在这里编译器做了一些幕后操作,它暗自把 “所操作对象的引用”作为第一个参数传递给f()。所以,“a.f(1);”就变成了“Test.f(a,1);”。(说明:在类的存储中,静态变量为类所有,非静态变量在各个对象中都分配有存储空间,这些值用来标志各个对象本身,至于类中的方法,应该是只有一份方法列表,为各个对象所公用。)
       this关键字只能在方法内部使用,表示“调用方法的那个对象”的应用。只有当需要明确指出对当前对象的引用时,才需要使用this关键字,例如,当需要返回对当前对象的引用时,需要“return this;”。
 
1.2         this在构造器中的使用
 
       这种用法适合于一个类中有多个类构造器的情况。
       在构造器中,如果为this添加了参数列表,那么就有了不同的含义:这将产生对符合此参数列表的某个构造器的明确调用。这样就可以直接调用其他构造器。
例如:
public class Flower {
 int petalCount = 0; 
  String s = new String("null"); //类中有两个属性
 Flower(int petals) {            //构造器1
    petalCount = petals;
 }
 Flower(String ss) {            //构造器2
    s = ss;
 }
 Flower(String s, int petals) { //构造器3
    this(petals);                //this使用,必须在构造器的起始处
//!    this(s); // Can't call two!//不能调用两次
    this.s = s; // Another use of "this"//this的另一种用法
 }
 Flower() {                     //构造器4
    this("hi", 47);              
    }
 void print() {
//! this(11); // Not inside non-constructor! //不能在非构造器中使用
 }
 public static void main(String[] args) {
 }
} ///:~
说明:这种带参数列表的this只能在构造器中使用,而且只能调用一次,并且在构造器的起始处。
2.             Finalize()方法
Finalize()方法的工作原理:当垃圾回收器准备释放对象占用的存储空间时,它会首先调
用要释放对象的finalize() 方法。finalize() 方法定义在Object类中。
       finalize() 方法和c++中的析构函数截然不同:
l         垃圾收集不等于析构,析构函数是程序员主宰的,GC是系统主宰的。
l         对象可能不被垃圾回收
l         垃圾回收只与内存有关
例如:假设某个对象在创建过程中会将自己绘制到屏幕上,如果不是明确地从屏幕上将
其从屏幕上擦除,它可能永远得不到清理。如果在finalize() 方法中加入擦除功能,当“垃圾回收”时(不能保证一定会发生),finalize() 将得到调用,图像将会被擦除,如果“垃圾回收”没有发生,图像就会一直保留下来。
       如果程序结束,并且垃圾回收器一直没有释放你创建的任何对象的存储空间,随着程序的退出,那些资源也会全部交还给操作系统。这个策略是恰当的,因为GC本身也有开销,要是不使用它,那就不用支付这部分开销了。
       注意(编程技巧):在构造函数只做最基本的初始化,例如new一个对象等,所有有关内存之外的其他初始化都不要在构造函数中编写,应单独写,且名称要有含义,如open(),claose()。
3.             垃圾回收器
31 什么是垃圾
       引子:“引用记数”是一种简单但速度很慢的垃圾回收技术,它常用来说明垃圾回收器的工作方式,但似乎从未被应用于任何一种Java虚拟机中。每个对象都有一个引用记数器,当有引用连接至对象时,引用记数加1。当引用离开作用域或被置为null时,引用记数减1。当发现某个对象的引用记数为0时,就释放其占用的空间。这个方法有个缺陷:如果对象之间存在循环引用,可能会出现“对象应该被回收,但引用记数却不为零”的情况。
没有任何引用指向某个对象,那这个对象是垃圾;由堆栈和静态变量存储区的引用指向的对象一定不是垃圾,这句话是正确的,但不能包含所有情况。
       其实当有引用指向某个对象时,这个对象也可能是垃圾。如何来判断这种情况呢?
       如果是垃圾且有引用指向它,那这个垃圾一定被堆内存中一个对象中的引用所指向(而且,这个引用所属对象同样是垃圾)。但同样不能说:如果一个对象没有由堆栈或静态存储区中的引用指向,只有某对象中的引用指向,就说这个对象是垃圾。这种说法同样是片面的。因为如果一个对象不是垃圾,它包含的引用所指向的对象同样不是垃圾。
       最好的评判标准:从堆栈和静态存储区中的引用出发,它们所指向的对象不是垃圾,这些不是垃圾的对象中所引用的对象同样不是垃圾,以此类推,直到结束。这样就可以标识出所有不是垃圾的对象,剩余的就是垃圾了。
例1:
有两个类A和B(虽然它们的关系比较复杂,但并不属于交互引用的情况):
class A
{
 B b=new B();
}
class B
{
 A a=new A();
}
在main()函数中:
public static void main(String[] args)
{
 A aa=new A();
 B bb=new B();
 aa=null;
 bb=null;
}
 

&&&&
图释(分上图和下图):
上图是例1的情况,1是B对象,2是A对象,3是A对象,4是B对象,其中1和4不是同一个对象,2和3不是同一个对象。虽然情况比较复杂,但不属于互相引用的情况,所以即使使用“引用记数”的方式也能回收,只不过有一定的依赖关系,并不构成死循环,即2和4的回收得依赖于1和3先被回收。
下图是例2的情况,1是B对象,2是A对象,1和2相互引用,构成死循环,如果仅仅依靠“引用记数”的方式好象解决不了这个问题。
 
例2:交互引用的情况
public class TestCycleRef {   
 
    public static void main(String[] args) {
        A aa = new A();
        B bb = new B();
       
        aa.setB(bb);
        bb.setA(aa);
       
        aa = null;
        bb = null;
        System.gc();
    }
 
}
 
class A {
 B b ;
 
public void finalize() {
System.out.println( " A is finalize" );
 
}
 
/**
 * @return Returns the b.
 */
public B getB() {
return this.b;
}
 
/**
 * @param b The b to set.
 */
public void setB(B b) {
this.b = b;
}
}
class B {
A a ;
public B() {
a = new A();
}
public void finalize() {
System.out.println( " B is finalize" );
 
}
/**
 * @return Returns the a.
 */
public A getA() {
return this.a;
}
/**
 * @param a The a to set.
 */
public void setA(A a) {
this.a = a;
}
}
 
3.2 C++Java堆内存处理方式简单比较
可以把C++里的堆想象成一个院子,里面每个对象都负责管理自己的地盘,一段时间后,对象可能被销毁,但地盘必须加以重用。我认为(因为没有找到相关资料):堆这部分内存都由程序员分配和销毁,当申请一块空间时,要查看每一块空余空间,看是否有大小合适的,如果要分配的空间比其他所有空间都大,编译器可能要整理堆内存,以满足分配。
在某些Java虚拟机中,堆的实现截然不同:它更象一个传送带,每分配一个新对象,它就往前移动一格。这意味着对象存储空间的分配速度非常快。Java的“堆指针”只是简单地移动到尚未分配的区域,其效率比得上C++在堆栈上分配空间的效率。当然,实际过程中在簿记工作方面还有少量额外开销,但比不上查找可用空间开销大。其实,Java中的堆未必完全像传送带那样工作,要是那样的话,势必会导致频繁的内存页面调度,这将极大影响性能,并最终耗尽资源。其中的秘密在于垃圾回收器的介入。当它工作的时候,将一边回收空间,一边使堆中的对象紧凑排列,这样“堆指针”就可以容易移动到更靠近传送带的开始处,也就尽量避免了页面错误。通过垃圾回收器对对象重新排列,实现了一种高速的、有无限空间可供分配的堆模型。
 
4.             成员初始化
4.1区分局部变量和类中的数据成员
 
l         局部变量:方法内部的变量是局部变量,java以编译时错误的形式来保证,在使用这个局部变量之前必须初始化。
l         类中的数据成员:有一个默认初始值,“0”。
 
4.2 初始化顺序
 
       初始化包括,自动初始化、定义初始化、构造器初始化和静态变量初始化。
       假设有个Dog的类:
l         当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法或静态字段首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。
l         然后载入Dog.class(这将创建一个Class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
l         当用new Dog()创建对象的时候,首先在堆上为Dog对象分配足够的存储空间。
l         这块存储空间会被清零,这就自动将Dog对象中的所有基本类型数据都设置成了缺省值(对数字来说是0,其他类型与此对应),而引用则被设置成null。
l         执行所有出现与字段定义处的初始化动作(定义初始化)。
l         执行构造器(这里未考虑继承问题)。
 
5.             数组初始化
5.1 length属性
 
       对于这个属性需要注意一点:length属性只能说明这个数组所能容纳的最多元素个数,对于数组元素而言有缺省值(“0”或null),其中有多少是通过自己赋值的元素是不得而知的。
 
5.2多维数组
       举例:
    int[][]  a={{1,2,3},{1,2}};
    说明:有2个最大元素是3的一维数组。即:int[2][3]。
   
 int[][][] b={
            {    {1,2,3,4},    {1,2}   ,{1,}  },
            {    {4,5,6,7},    {7,8}         }
};
  说明:有两个二维数组,每个二维数组最多包含三个一维数组,一维数组包含最多的元素为4,即:int[2][3][4]。
 
遍历程序:
for(int i=0;i<a.length;i++)
                     for(int j=0;j<a[i].length;j++)
                            System.out.println(a[i][j]);
 
5.3 实例说明数组初始化
 class A{} //定义一个类A
 public void test()
{
 A[] arrayObjectA;
 System.out.println(arrayObjectA); //编译会报错:局部变量没有初始化。arrayObjectA只是堆栈中的一个局部变量,此时没有指向任何对象,A[]”只是说明arrayObjectA可以指向A类型的数组,仅此而已。
 arrayObjectA=new A[3];
 System.out.println(arrayObjectA[i]); //编译不会报错。
 for(int i=0;i<arrayObjectA.length;i++)
{ System.out.println(arrayObjectA[i]);} //会打印出null。这相当于对局部变量arrayObjectA已经初始化,但arrayObjectA所指向的数组对象中的各个数组元素只有缺省初始化:null。所以,此时如果对数组arrayObjectA中的任何一个元素进行操作(除打印操作)的话,就会有空指针异常。
for(int i=0;i<arrayObjectA.length;i++)
 { arrayObjectA [i]=new A();}   //这才是对数组“有意义”的初始化,之后就可以对数组元素进行一些相关正确操作了。
}
 
 
      

发表于 @ 2006年05月13日 09:18:00 | 评论( loading... ) | 编辑| 举报| 收藏

旧一篇:2控制程序流程 | 新一篇:4隐藏具体实现

  • 发表评论
  • 评论内容:
  •  
Copyright © java_seven_net
Powered by CSDN Blog