2007年12月20日
方法调用的绑定:
将方法的调用连到方法本身被称为“绑定”。当绑定发生在程序运行之前时,被称作“前绑定”。
后绑定也称为“动态绑定”或“运行时绑定”,指程序运行的时候,根据对象的类型来决定该绑定哪个方法。如果语言实现了后绑定,那它就必须要有能在运行时判断对象类型,并且调用其合适方法的机制。也就是说编译器还是不知道对象的类型,但是方法的调用机制会找出,并且调用正确的方法。
除了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 仰望者 阅读(207) |
评论 (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 仰望者 阅读(154) |
评论 (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 仰望者 阅读(229) |
评论 (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 仰望者 阅读(233) |
评论 (0) |
编辑 收藏