静音版 电台版
发新话题
打印

[Java] Notes of "Thinking in Java 3rd Edition" (三)

Notes of "Thinking in Java 3rd Edition" (三)

第6章 重复运用classes
  一.继承(inheritance)
   1. 在derived class中overriding某个函数时,只能覆写base class中的接口,即base class中的public或protected或friendly函数。如果试图overriding一个private函数,虽然编译通过,但实际上你只是在derived class中添加了一个函数。如
   class Cleanser{
   private void prt(){//(b)
   System.out.println("Cleanser.prt()");
   }
   }
   public class ExplicitStatic extends Cleanser{
   public void prt(){
   System.out.println("ExplicitStatic.prt()");
   }
   public static void main(String[] args){
   Cleanser x = new ExplicitStatic();
   x.prt();//(a)
   }
   }
   因为Cleanser中的prt()是private,所以不能在其derivedclass中被覆写。ExplicitStatic中的prt()只是ExplicitStatic中的一个函数,所以当试图在(a)处通过多态来调用prt()时,会发生错误。如果把(b)处的private去掉,则结果为
   ExplicitStatic.prt()
   2. Super的使用
   1)通过关键字super可以调用当前class的superclass(父类)。
   例6.1.1.1
   class Base{
   Base(){System.out.println("Base()");}
   public void scrub() { System.out.println(" Base.scrub()"); }
   }
   class Cleanser extends Base{
   private String s = new String("Cleanser");
   public void append(String a) { s+=a; }
   public void dilute() { append(" dilute()"); }
   public void apply() { append(" apply()"); }
   public void scrub() { append(" scrub()"); }
   public void print() { System.out.println(s); }
   Cleanser(){
   System.out.println("Cleanser(): " + s);
   }
   public static void testStatic(){
   System.out.println("testStatic()");
   }
   public static void main(String[] args){
   Cleanser x = new Cleanser();
   x.dilute(); x.apply(); x.scrub(); x.print();
   }
   }
   public class ExplicitStatic extends Cleanser{
   ExplicitStatic(){
   System.out.println("ExplicitStatic()");
   }
   public void scrub(){
   append(" Detergen.scrub()");
   super.testStatic();
   super.scrub();//调用的是Cleanser.scrub()
   }
   public void foam() { append(" foam()"); }
   public static void main(String[] args){
   ExplicitStatic x = new ExplicitStatic();
   x.dilute(); x.apply(); x.scrub(); x.foam();
   x.print(); System.out.println("Test base class:");
   Cleanser.main(args);
   testStatic();
   }
   }
   运行结果:
   Base()
   Cleanser(): Cleanser
   ExplicitStatic()
   testStatic()
   Cleanser dilute() apply() Detergen.scrub() scrub() foam()
   Test base class:
   Base()
   Cleanser(): Cleanser
   Cleanser dilute() apply() scrub()
   testStatic()
   2)通过super来调用superclass中的成员时,调用的是最近成员。
   例6.1.1.2
   class Base{
   protected String baseS = "Base";//(a)
   //private String baseS = "Base";
   Base(){System.out.println("Base()");}
   }
   class Cleanser extends Base{
   protected String baseS = "Cleanser";//(b)
   public String s = new String("Cleanser");
   Cleanser(){
   System.out.println("Cleanser(): " + s);
   }
   Cleanser(String a){
   System.out.println("Cleanser(" + a + "): s = " + s );
   }
   }
   public class ExplicitStatic extends Cleanser{
   String s2 = s;
   String baseS = super.baseS; //(c)
   ExplicitStatic(){
   super("ExplicitStatic");
   System.out.println("ExplicitStatic():s2 = " + s2 + ", baseS = "
   + baseS + "super.baseS = " + super.baseS);
   baseS = "ExplicitStatic";
   System.out.println("baseS = " + baseS + " , super.baseS = " + super.baseS);
   }
   public static void main(String[] args){
   ExplicitStatic x = new ExplicitStatic();
   }
   }
   结果1:
   Base()
   Cleanser(ExplicitStatic): s = Cleanser
   ExplicitStatic():s2 = Cleanser, baseS = Cleanser,super.baseS = Cleanser
   baseS = ExplicitStatic , super.baseS = Cleanser
   在上面例子中,在三个class中都存在String bases实例。在ExplicitStatic中如果直接调用baseS,则实际调用的是当前类ExplicitStatic中的baseS(即(c)处的成员);如果通过super.bases来调用baseS,则调用的是离当前类ExplicitStatic最近的baseS成员,即Cleanser class中的baseS实例(即(b)处),产生的结果如结果1所示。如果把(b)处语句注释掉,则将调用Base class中的baseS,结果如结果2所示。
   结果2:
   Base()
   Cleanser(ExplicitStatic): s = Cleanser
   ExplicitStatic():s2 = Cleanser, baseS = Base,super.baseS = Base
   baseS = ExplicitStatic , super.baseS = Base
   3. Base class的初始化
   2.1 当你产生derived class对象时,其中会包含base class子对象(subobject)。这个子对象就和你另外产生的base class对象一模一样。
   2.2 通过super()可调用base class的构造函数,但必须放在构造函数的第一行,并且只能在构造函数中运用。
   2.3 初始化顺序为:
   1) 加载代码(.class文件)
   2) 初始化class的静态成员,初始化顺序了“从里到外”,即从baseclass开始。
   3) 在derived class的构造函数中调用base class的构造函数。
   如果在derived class的构造函数中没有通过super()显式调用调用base class的构造函数,编译器会调用bass class的default构造函数并自动生成相应的调用语句,从而产生一个base class实例。如果在derived class的构造函数中通过super()显示调用了父类的构造函数,则调用所指定的构造函数。调用构造函数的调用顺序是“从里到外”。
   4) 调用derived class的构造函数。
   **:当base class没有default构造函数时,必须在derived class的构造函数中通过super显示调用base class的构造函数。
   例:下面代码的初始化过程为:
   1) 装载ExplicitStatic的代码(装载ExplicitStatic.class文件)。
   2) 发现ExplicitStatic有关键字extends,装载ExplicitStatic的baseclass的代码(装载Cleanser.class文件)。
   3) 发现Cleanser有关键字extends,装载Cleanser的baseclass的代码(装载Base.class文件)。
   4) 初始化Base class中的静态成员。
   5) 初始化Cleanser class中的静态成员。
   6) 初始化ExplicitStatic class中的静态成员。
   如果把(c)处的代码注释掉,那么初始化工作到此就结束了。
   7) 为ExplicitStatic对象分配存储空间,并把存储空间初始化为0。
   8) 在ExplicitStatic class的构造中调用super("ExplicitStatic")(在ExplicitStatic class的构造函数中显式调用父类的构造函数),试图产生一个Cleanser class实例。
   9) 为Cleanser对象分配存储空间,并把存储空间初始化为0。
   10) 由于Cleanser class又是继承自Base class,会在Cleanser class的构造函数中通过super()(由于没有显式调用父类的构造函数,所以自动调用父类的default构造函数)调用父类的构造函数,试图产生一个Cleanser class实例。
   11) 产生一个Base class实例。先初始化成员变量,再调用构造函数。
   12) 回到Cleanser class,产生一个实例。首先初始化Cleanser class中的成员数据,再执行构造函数Cleanser(String a)中的其余部分。
  13) 回到ExplicitStatic class,产生一个实例。首先初始化ExplicitStatic class中的成员数据,再执行构造函数ExplicitStatic ()中的其余部分(System.out.println(“ExplicitStatic()”))。
   class Base{
   static int s1 = prt("s1 initialized.", 11);
   int i1 = prt("i1 initialized.", 12);
   Base(){
   System.out.println("Base()");
   System.out.println("s1 = " + s1 + " ,i1 = " + i1);
   draw();//(d)
   }
   void draw(){
   System.out.println("base.draw:s1 = " + s1 + " ,i1 = " + i1);
   }
   static int prt(String s, int num) {
   System.out.println(s);
   return num;
   }
   }
   class Cleanser extends Base{
   static int s2 = prt("s2 initialized.", 21);
   int i2 = prt("i2 initialized.", 22);
   Cleanser(){
   System.out.println("Cleanser()");
   System.out.println("s2 = " + s2 + " ,i2 = " + i2);
   }
   Cleanser(String a){
   //super();(b)
   System.out.println("Cleanser(" + a + ")");
   System.out.println("s2 = " + s2 + " ,i2 = " + i2);
   }
   void draw(){
   System.out.println("Cleanser.draw:s2 = " + s2 + " ,i2 = " + i2);
   }
   }
   public class ExplicitStatic extends Cleanser{
   static int s3 = prt("s3 initialized.", 31);
   int i3 = prt("i3 initialized", 31);
   ExplicitStatic(){
   super("ExplicitStatic");//(a)
   System.out.println("ExplicitStatic()");
   System.out.println("s3 = " + s3 + " ,i3 = " + i3);
   }
   public static void main(String[] args){
   ExplicitStatic x = new ExplicitStatic();//(c)
   }
   }
   结果:
   s1 initialized.
   s2 initialized.
   s3 initialized.
   //如果把(c)处的代码注释掉,输出结果到此为止,不会输出下面结果
   i1 initialized.
   Base()
   s1 = 11 ,i1 = 12
   Cleanser.draw:s2 = 21 ,i2 = 0//(d)处结果
   i2 initialized.
   Cleanser(ExplicitStatic)//(a)处结果
   s2 = 21 ,i2 = 22
   i3 initialized
   ExplicitStatic()
   s3 = 31 ,i3 = 31
   由于在Base()中调用draw()时,Cleanser中的i2还未进行初始化,而在为Cleanser对象分配存储空间时,把存储空间初始化为0,所以此时i2为0。
   2.4 代码及结果中的(a)说明了是先产生当前class的base class的实例,否则在derived class中无法调用base class的成员。在调用Cleanser class的构造函数Cleanser(String a)时,在Cleanser(String a)中没有用super显式调用Base class的构造函数,所以系统会自动生成调用Base class的default构造函数的语句,如(b)。
   4. 组合与继承之间的快择
   . 1)继承表示的是一种“is-a(是一个)”的关系,如货车是汽车中的一种;组合表示的是一种“has-a(有一个)”的关系,如汽车有四个轮子。
   2)是否需要将新的class向上转型为base class。
   5. 在继承中的访问权限
   protect变量能被子孙类所调用。如Base class中的baseS能被Cleanser class和ExplicitStatic class调用。
   class Base{
   protected String baseS = "Base";
   //private String baseS = "Base";
   Base(){System.out.println("Base()");}
   }
   class Cleanser extends Base{
   protected String baseS = "Cleanser";
   public String s = new String("Cleanser");
   Cleanser(){
   System.out.println("Cleanser(): " + s);
   }
   Cleanser(String a){
   System.out.println("Cleanser(" + a + "): s = " + s );
   }
   }
   public class ExplicitStatic extends Cleanser{
   String s2 = s;
   String baseS = super.baseS;
   ExplicitStatic(){
   super("ExplicitStatic");
   System.out.println("ExplicitStatic():s2 = " + s2 + ", baseS = "
   + baseS + "super.baseS = " + super.baseS);
   baseS = "ExplicitStatic";
   System.out.println("baseS = " + baseS + " , super.baseS = " + super.baseS);
   }
   public static void main(String[] args){
   ExplicitStatic x = new ExplicitStatic();
   }
   }
   结果:
   Base()
   Cleanser(ExplicitStatic): s = Cleanser
   ExplicitStatic():s2 = Cleanser, baseS = Cleanser, super.baseS = Cleanser
   baseS = ExplicitStatic , super.baseS = Cleanser
   二.关键字final
   1.Final data
   1.1final data
   1)当基本型别被定义为final,表示它的数据值不能被改变。如
   final int i = 9;
   i++;//编译错误,不能改变I的值
   2) 当objectreference被定义为final时,不能改变的只是reference而不是对象本身。如
   class Value{
   int i = 1;
   }
   public class ExplicitStatic extends Cleanser{
   public static void main(String[] args){
   final Value v = new Value();//v.i = 1
   v.i++;//v.i = 2
   //v = new Value();
   }
   }
   由于v为final,所以不能通过new Value()使v重新指向一个对象;但是v所指向的对象的值是可以改变的(v.i++)。
   1.2blank finals
   我们可以将数据成员声明为final但不给予初值,这就是blankfinals。但blankfinals必须且只能在构造函数中进行初始化。
   public class ExplicitStatic {
   final int ib;
   final int i = 1;
   ExplicitStatic()
   {
   ib = 2;//(a)
   //i = 3;(b)
   System.out.println("i = " + i + ", ib = " + ib);
   }
   public static void main(String[] args){
   ExplicitStatic ex = new ExplicitStatic();
   }
   }
   ib为blankfinals,所以可以在构造函数中进行初始化。如果把(a)处的代码注释掉,则ib没有初值,编译出错。而i在定义处已进行了初始化,则不能改变i的值,(b)处的代码编译错误。
   **:非blankfinals成员即使在构造函数中也不能更改其值
   2.Finalmethods
   1)被声明为final的函数不能被覆写
   2)class中所有private函数自然而然会是final。
   3. Finalclasses
   1)当一个class被声明为final时,表示它不能被继承,但class的数据成员不是final,可以被改变。如
   class SmallBrain{}
   final class Dinosaur{
   int i = 7;
   int j = i;
   SmallBrain x = new SmallBrain();
   void f(){};
   }
   //不能继承final函数
   //class Further extends Dinosaur{}
   public class ExplicitStatic{
   public static void main(String[] args){
   Dinosaur n = new Dinosaur();
   n.f();
   n.i = 40;//final class中的non-final数据成员可以被改变
   n.j++;
   }
   }
   2)finalclass中的所有函数也都自然是final,因为没有人能够加以覆写。

        三、详解finalize函数
  finalize是位于Object类的一个方法,该方法的访问修饰符为protected,由于所有类为Object的子类,因此用户类很容易访问到这个方法。由于,finalize函数没有自动实现链式调用,我们必须手动的实现,因此finalize函数的最后一个语句通常是super.finalize()。通过这种方式,我们可以实现从下到上实现finalize的调用,即先释放自己的资源,然后再释放父类的资源。
  根据Java语言规范,JVM保证调用finalize函数之前,这个对象是不可达的,但是JVM不保证这个函数一定会被调用。另外,规范还保证finalize函数最多运行一次。
  很多Java初学者会认为这个方法类似与C++中的析构函数,将很多对象、资源的释放都放在这一函数里面。其实,这不是一种很好的方式。原因有三,其一,GC为了能够支持finalize函数,要对覆盖这个函数的对象作很多附加的工作。其二,在finalize运行完成之后,该对象可能变成可达的,GC还要再检查一次该对象是否是可达的。因此,使用finalize会降低GC的运行性能。其三,由于GC调用finalize的时间是不确定的,因此通过这种方式释放资源也是不确定的。
  通常,finalize用于一些不容易控制、并且非常重要资源的释放,例如一些I/O的操作,数据的连接。这些资源的释放对整个应用程序是非常关键的。在这种情况下,程序员应该以通过程序本身管理(包括释放)这些资源为主,以finalize函数释放资源方式为辅,形成一种双保险的管理机制,而不应该仅仅依靠finalize来释放资源。
  下面给出一个例子说明,finalize函数被调用以后,仍然可能是可达的,同时也可说明一个对象的finalize只可能运行一次。
class MyObject{
    Test main; //记录Test对象,在finalize中时用于恢复可达性
    public MyObject(Test t)
    {  
main=t; //保存Test 对象
    }
    protected void finalize()
    {
main.ref=this;// 恢复本对象,让本对象可达
System.out.println("This is finalize");//用于测试finalize只运行一次
    }
}
class Test {
MyObject ref;
  public static void main(String[] args) {
   Test test=new Test();
   test.ref=new MyObject(test);
   test.ref=null; //MyObject对象为不可达对象,finalize将被调用
   System.gc();
   if (test.ref!=null) System.out.println("My Object还活着");
}
}
  运行结果:
  This is finalize
  MyObject还活着
  此例子中,需要注意的是虽然MyObject对象在finalize中变成可达对象,但是下次回收时候,finalize却不再被调用,因为finalize函数最多只调用一次。
(--待续--)

[ 本帖最后由 kelven 于 2008-4-12 12:27 编辑 ]
小时候我以为自己长大后可以拯救整个世界,等长大后才发现整个世界都拯救不了我.......http://hi.baidu.com/kelven1986

TOP

发新话题