posts - 12,  comments - 0,  trackbacks - 0
  2007年12月14日
方法调用的绑定:
将方法的调用连到方法本身被称为“绑定”。当绑定发生在程序运行之前时,被称作“前绑定”。
后绑定也称为“动态绑定”或“运行时绑定”,指程序运行的时候,根据对象的类型来决定该绑定哪个方法。如果语言实现了后绑定,那它就必须要有能在运行时判断对象类型,并且调用其合适方法的机制。也就是说编译器还是不知道对象的类型,但是方法的调用机制会找出,并且调用正确的方法。
除了static和final方法(private方法隐含有final的意思),java的所有的方法都采用后绑定。也就是说,通常情况下你不必考虑是不是应该采用后绑定--它是自动的。 为什么要声明final方法,上一章指出,这样可以禁止别人覆写这个方法,不过更重要的可能还是要“关闭”它的动态绑定,或者理确切的说,告诉编译器这里不需要使用后绑定。
shape为circle的基类,下面这句就是在“上传”:
Shape s = new Circle();
这里先创建了一个Circle对象,接着马上把它的引用赋给了Shape,看上去这像是一个错误(一种类型怎么能赋给另一种);但是由于Circle是由Shape派生出来的,Circle就是一种Shape,因此这种做法是非常正确的。假设你调用了一个基类的方法:s.draw();这里派生类里已经覆写了此方法,那么可能你会认为,这次应该总调用Shape的draw()了吧,因为毕竟这是Shape的引用,但是由于实现了后绑定(多态性),实际上它会调用Circle.draw().
posted @ 2008-01-05 19:18 仰望者 阅读(198) | 评论 (0)编辑 收藏
1、 float f=1.3;
    是不对的,编译时会出错,java认为1.3是double型的,所以定义时应写成:float f=1.3f,或float f= (float)1.3;
2、 byte b = 3;  b=b*3;
    是不对的,原因是在*运算过程中,java会做类型的提升,将b提升为int型,所以应改为:b=(byte)(b*3);
3、 while(1),if(1)
    是不对的,原因是java中布尔型只有true 和false两个值,这里与C语言不同,只能用while(true)..
4、 数组声明:int num[3];
    这是不对的,java中声明数组时不应对空间限定,正确的语法应是:
    int[] num = new int[3];
    或
    int[] num;
    num = new int[3];
5、数组初始化:int[] num;
               num {1,3,4,4};
   是不对的,应在定义的时候初始化。如:int[] num={1,3,4,4};
6、int[] num3 =new int[]{1,2,3};
   int[] num5 =new int[3]{1,2,3};
   int[] num3 =new int[]{1,2,3};是对的。
    int[] num5 =new int[3]{1,2,3};是错的。已经初始化的数组,不应再列明:[3]
       


posted @ 2007-12-23 23:01 仰望者 阅读(147) | 评论 (0)编辑 收藏
合成与继承
继承:
super关键字的使用:super使用在派生类中,如果派生类中重写了基类的方法,但在这个被重写的方法中仍然要调用基类的同名的方法,这就要用到super关键字,特别是在创建对象时,在带参数构造函数中调用基类构造函数的情况。
如:
class Cleanser {
  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); }
  public static void main(String[] args) {
    Cleanser x = new Cleanser();
    x.dilute(); x.apply(); x.scrub();
    x.print();
  }
}
public class Detergent extends Cleanser {
  // Change a method:
  public void scrub() {
    append(" Detergent.scrub()");
    super.scrub(); // Call base-class version
  }
// Add methods to the interface:
  public void foam() { append(" foam()"); }
  // Test the new class:
  public static void main(String[] args) {
    Detergent x = new Detergent();
    x.dilute();
    x.apply();
    x.scrub();
    x.foam();
    x.print();
    System.out.println("Testing base class:");
    Cleanser.main(args);
  }
} ///:~
可以看到基类Cleanser 中定义了scrub方法,但派生类Detergent 中对scrub方法进行了修改,并用在派生类Detergent 的scrub方法中,要调用基本的scrub方法,那么用super.scrub(); 

 基类的初始化:
         当你创建一个派生类的对象的时候,这个对象里面还有一个基类的子对象,这个子对象同基类自己创建的对象没什么两样,只是从外面看来,这个子对象被包裹在派生类的对象里面。
         基类子对象的正确初始化是非常重要的,而且只有一个办法能保证这一点:调用基类的构造函数来进行初始化,因为只有它才能掌握怎么样才能正确地进行初始化的信息和权限。java会让派生类的构造函数自动地调用基类的构造函数。
          示例:
 class Art {
  Art() {
    System.out.println("Art constructor");
  }
}

class Drawing extends Art {
  Drawing() {
    System.out.println("Drawing constructor");
  }
}

public class Cartoon extends Drawing {
  Cartoon() {
    System.out.println("Cartoon constructor");
  }
  public static void main(String[] args) {
    Cartoon x = new Cartoon();
  }
} ///:~
输出结果为:
Art constructor
Drawing constructor
Cartoon constructor
一看结果便一目了然了。

上面的示例是不带任何参数的情况,如果构造函数中带有参数的话,那这里又要用到super的特性了。与上面super的使用涵意一样,super在这里用作:派生的带参数构造函数中调用基类的带参构造函数,只是这里不象上面那样super.scrub();这里只使用super(i);即可。
        
class Game {
  Game(int i) {
    System.out.println("Game constructor");
  }
}

class BoardGame extends Game {
  BoardGame(int i) {
    super(i);
    System.out.println("BoardGame constructor");
  }
}

public class Chess extends BoardGame {
  Chess() {
    super(11);
    System.out.println("Chess constructor");
  }
  public static void main(String[] args) {
    Chess x = new Chess();
  }
} ///:~
输出结果是:
Game constructor
BoardGame constructor
Chess constructor

合成和继承一起使用,实现类的复用:

class Plate {
  Plate(int i) {
    System.out.println("Plate constructor");
  }
}

class DinnerPlate extends Plate {
  DinnerPlate(int i) {
    super(i);
    System.out.println(
      "DinnerPlate constructor");
  }
}

class Utensil {
  Utensil(int i) {
    System.out.println("Utensil constructor");
  }
}

class Spoon extends Utensil {
  Spoon(int i) {
    super(i);
    System.out.println("Spoon constructor");
  }
}

class Fork extends Utensil {
  Fork(int i) {
    super(i);
    System.out.println("Fork constructor");
  }
}

class Knife extends Utensil {
  Knife(int i) {
    super(i);
    System.out.println("Knife constructor");
  }
}

// A cultural way of doing something:
class Custom {
  Custom(int i) {
    System.out.println("Custom constructor");
  }
}

public class PlaceSetting extends Custom {
  Spoon sp;
  Fork frk;
  Knife kn;
  DinnerPlate pl;
  PlaceSetting(int i) {//把初始化工作都放在构造函数中
    super(i + 1);
    sp = new Spoon(i + 2);
    frk = new Fork(i + 3);
    kn = new Knife(i + 4);
    pl = new DinnerPlate(i + 5);
    System.out.println(
      "PlaceSetting constructor");
  }
  public static void main(String[] args) {
    PlaceSetting x = new PlaceSetting(9);
  }
} ///:~
        尽管编译器会强迫我们对基础类进行初始化,并要求我们在构建器最开头做这一工作,但它并不会监视我们是否正确初始化了成员对象。所以对此必须特别加以留意。
FINAL关键字:
        FINAL关键字指“那样东西是不允许改动”,你可能会出于两点考虑不想让别人作改动:设计和效率。由于这两个原因差别很大,所以很可能会误用final关键字。
final的三种用途:数据(Data)、方法(method)、类(class)。
 很多语言通知编译器:“这段常量(constant)数据”的手段。常量能用下列两种情况出现:
        1、可以是“编译时的常量”,这样以后就不能改了;
        2、也可以是运行时初始化的值,这个值以后就不想再改了。
        如果是编译时的常量,编译器会把常量放到算式里面;这样编译的时候就能进行计算,因此也就降低了运行时的开销。在Java 中这种常量必须是primitive 型的,而且要用final 关键词表示。这种常量的赋值必须在定义的时候进行。
        一个既是static 又是final 的数据成员会只占据一段内存,并且不可修改。
        当final 不是指primitive,而是用于对象的reference 的时候,意思就有点不一样了。对primitive 来说,final 会将这个值定义成常量,但是对于对象的reference 而言,final 的意思则是这个reference 是常量。初始化的时候,一旦将reference 连到了某个对象,那么它就再也不能指别的对象了。但是这个对象本身是可以修改的;Java 没有提供将某个对象作成常量的方法。
        (但是你可以自己写一个类,这样就能把类当做常量了)
        这种局限性也体现在数组上,因为它也是一个对象。
注意,通常约定,被初始化为常量值的final static 的primitive 的名字全都用大写,词与词之间用下
划线分开,如VAL_ONE
Final 方法
使用final 方法的目的有二:
        第一,为方法上“锁”,禁止派生类进行修改。这是出于设计考虑。当你希望某个方法的功能,能在继承过程中被保留下来,并且不被覆写,就可以使用这个方法。
        第二个原因就是效率。如果方法是final 的,那么编译器就会把调用转换成“内联的(inline)”。它会用方法本身的拷贝来代替方法的调用
final 和private
        private 方法都隐含有final 的意思。由于你不能访问private 的方法,因此你也不能覆写它。你可以给private 方法加一个final 修饰符,但是这样做什么意义也没有。
        这个问题有可能会造成混乱,因为即使你覆写了一个private 方法(它隐含有final 的意思),看上去它还是可以运行的,而且编译器也不会报错:
        class WithFinals {
            // Identical to "private" alone:
            private final void f() {
                    System.out.println("WithFinals.f()");
                                          }
            / / Also automatically "final":
           private void g() {
                    System.out.println("WithFinals.g()");
                                 }
        }
        class OverridingPrivate extends WithFinals {
                private final void f() {
                        System.out.println("OverridingPrivate.f()");
                                                  }
                private void g() {
                        System.out.println("OverridingPrivate.g()");
                                                  }
         }
只有是基类接口里的东西才能被“覆写”,如果方法是private 的,那它就不属于基类的接口。它只能算是被类隐藏起来的,正好有着相同的名字的代码。如果你在派生类里创建了同名的public 或protected,或package 权限的方法,那么它们同基类中可能同名的方法,没有任何联系。你并没有覆写那个方法,你只是创建了一个新的方法。由于private 方法是无法访问的,实际上是看不见的,因此这么作除了会影响类的代码结构,其它什么意义都没有。
Final 类
把整个类都定义成final 的(把final 关键词放到类的定义部分的前面)就等于在宣布,你不会去继承这个类,你也不允许别人去继承这个类。换言之,出于类的设计考虑,它再也不需要作修改了,或者从安全角度出发,你不希望它再生出子类。
final class Dinosaur{}
注意,final 类的数据可以是final 的,也可以不是final 的,这要由你来决定。无论类是不是final 的,这一条都适用于“将final 用于数据的”场合。但是,由于final 类禁止了继承,覆写方法已经不可能了,因
此所有的方法都隐含地变成final 了。你可以为final 类的方法加一个final 修饰符,但是这一样没什么意义。
posted @ 2007-12-20 17:33 仰望者 阅读(217) | 评论 (0)编辑 收藏
访问控制符:public 、private、protected、friendly
public包内包外均可访问。
private只有本类可访问。
protected针对继承而使用的:1、包内继承,因为在包内,声明为protected不影响它本来的friendly权限。
                           2、包外继承,必须声明为protected。
派生类可以访问基类的protected成员。
注意不可将类设成private(那样会使除类之外的其他东西都不能访问它),也不能设成protected。因此,我们现在对于类的访问只有两个选择:“友好的”或者public。若不愿其他任何人访问那个类,可将所有构建器设为private,这样除你之外,没有可以用类创建的了。而你可以使用static成员创建对象。
package com.access.external;

class Soup{
    private Soup(){//构造函数声明为private,其它类不能用此构造函数创建对象;
        System.out.println("sffewe");
    }
    public static Soup makSoup(){//其它类可通过makSoup来创建对象;
        return new Soup();
    }
    private static Soup ps1 = new Soup();//自己创建对象;
    public static Soup access(){//返回对象的引用。
        return ps1;
    }
    public void f(){}
}

class Sandwich{
    void f(){
        new Lunch();
    }
}

public class Lunch {
    void test(){
        //Soup priv1 = new Soup();
        Soup priv2 = Soup.makSoup();
        Sandwich f1 = new Sandwich();
        Soup.access().f();//不创建对象,但通过Soup中返回的对象引用调用其方法。
    }

}


该方法返回一个句柄,它指向类Soup的一个对象。
Soup类向我们展示出如何通过将所有构建器都设为private,从而防止直接创建一个类。请记住,假若不明确地至少创建一个构建器,就会自动创建默认构建器(没有自变量)。若自己编写默认构建器,它就不会自动创建。把它变成private后,就没人能为那个类创建一个对象。但别人怎样使用这个类呢?上面的例子为我们揭示出了两个选择。第一个选择,我们可创建一个static方法,再通过它创建一个新的Soup,然后返回指向它的一个句柄。如果想在返回之前对Soup进行一些额外的操作,或者想了解准备创建多少个Soup对象(可能是为了限制它们的个数),这种方案无疑是特别有用的。
第二个选择是采用“设计方案”(Design Pattern)技术,本书后面会对此进行详细介绍。通常方案叫作“独子”,因为它仅允许创建一个对象。类Soup的对象被创建成Soup的一个static private成员,所以有一个而且只能有一个。除非通过public方法access(),否则根本无法访问它。



posted @ 2007-12-20 11:09 仰望者 阅读(223) | 评论 (0)编辑 收藏
Eclipse提供了很好的工具:
1、实时运算薄页面(java scrapbook page)具体的说就是一小段代码,比如一个for循环,就可以在里面执行,无须写出main函数等。
操作如下:new->other->java->java run/debug->scrapbook page
创建页面后,输入代码:
for (int i = 0; i < 10; i++) {
 System.out.println(Integer.toString(i));
}
选择代码,右键excute即可看到结果。。很方便。。。。。

2、程序代码产生模板
window->prefrences->java->editor->Templates
    添加:name:Sys
                context:java
                Description:shortcut for System.out.println
                pattern:System.out.println(${cursor});
确定后,在程序中输入s或Sys时再按alt+/会提示语句。。。接着按enter键吧。。。


3、产生 getter 与 setter Java 编辑器可以为编译单元内的类型字段,产生存取元(accessors,也就是getter和setter的method)。 I. 「Source」→「Generate Getter and Setter...」 (或是在Java编辑器按右键,「Source」→「Generate Getter and Setter...」)
挑选哪些需要建立getter和setter的method ;
选择method要建立的地方 ;
排序的方式;
选择Access modifier ;
选择是否需要建立批注;
按OK;

4、建立新的 JAR 档案 如果要在工作台中建立新 JAR 档,请执行下列动作: I. 在「Package Explorer」中,可以选择性地预选一或多个要汇出的 Java 元素。(在步骤IV中,这些会在JAR Package Specification精灵页面中自动选出。) II. 从快速菜单或从菜单列的File菜单,选取Export。 III. 选取JAR file,然后按一下Next。
IV. 在JAR Package Specification页面的Select the resources to export字段中,选取要汇出的资源。 V. 选取适当的勾选框,以指出想Export generated class files and resourcess或Export java source files and resources。附注:这两种情况皆会汇出所选的资源。 VI. 在Select the export destination字段中,输入或按一下Browse以选取 JAR 文件的位置。 VII. 选取或清除Compress the contents of the JAR file勾选框。 VIII. 选取或清除Overwrite existing files without warning勾选框。如果清除这个勾选框,则会提示确认是否要更换每一个将被改写的档案。 IX. 附注:在撰写 JAR 檔、JAR 说明与 Manifest 档时,会套用改写选项。 X. 有两项选择: 􀂅 按一下Finish来立即建立 JAR 檔。 􀂅 按一下Next,使用「JAR 套装选项」页面,以设定进阶选项,建立 JAR 说明,或变更预设 manifest。
posted @ 2007-12-18 15:53 仰望者 阅读(158) | 评论 (0)编辑 收藏
     摘要: 菜单 功能 热键 说明 Edit Add Block Comment Ctrl+Shift+/ Editing in Structured Text Editors ...  阅读全文
posted @ 2007-12-18 15:36 仰望者 阅读(300) | 评论 (0)编辑 收藏

JDK SRC中注解:

基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

此实现假定哈希函数将元素正确分布在各桶之间,可为基本操作(getput)提供稳定的性能。迭代集合视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)的和成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。

HashMap 的实例有两个参数影响其性能:初始容量加载因子容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用 rehash 方法将容量翻倍。

通常,默认加载因子 (.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 getput 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地降低 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。

注意,此实现不是同步的。如果多个线程同时访问此映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的不同步访问,如下所示:

 Map m = Collections.synchronizedMap(new HashMap(...));

由所有此类的“集合视图方法”所返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器自身的 removeadd 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间任意发生不确定行为的风险。

注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。


Hashtable和HashMap的区别:

1.Hashtable是Dictionary的子类,HashMap是Map接口的一个实现类;
2.Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。即是说,在多线程应用程序中,不用专门的操作就安全地可以使用Hashtable 了;而对于HashMap,则需要额外的同步机制。但HashMap的同步问题可通过Collections的一个静态方法得到解决:
Map Collections.synchronizedMap(Map m)
这个方法返回一个同步的Map,这个Map封装了底层的HashMap的所有方法,使得底层的HashMap即使是在多线程的环境中也是安全的。
3. 在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
posted @ 2007-12-14 12:06 仰望者 阅读(415) | 评论 (0)编辑 收藏