在面向对象编程语言中,继承和组合是两个很关键的问题,二者在语法和形式上有很多的相似之处。此处,仅对二者的用法做一个小结[1]。
一、组合语法
组合仅需将对象引用置于新类中即可,比如:
// SprinklerSystem.java
class WaterSource
{
private String s;
WaterSource()
{
System.out.println("WaterSource() Constructor !");
s="Constructed";
}
public String toString(){return s;}
}
public class SprinklerSystem
{
private String val1,val2,val3,val4;
private WaterSource source=new WaterSource();
private float f;
private int i;
public String toString()
{
return "value1= "+val1+" "+"value2= "+val2+" "+
"value3= "+val3+" "+"value4= "+val4+"/n"+
"i= "+i+" "+"f= "+f+" "+
"source= "+source;
}
public static void main(String[] args)
{
SprinklerSystem sprinklers=new SprinklerSystem();
System.out.println(sprinklers);
}
}
[讨论]:上述代码段中定义有两个成员函数toString(),每一个非基本类型的对象都有一个toString()函数。当编译器需要一个字符串string,而当前又只有一个对象时,则toString()会被自动调用。
按照规定,一方面类域为基本类型时会被自动初始化为0,而对象引用则会被自动初始化为null;另一方面,类中的所有变量会在所有函数调用之前按照定义的顺序(若同时存在static和非static型变量时,遵循先static后非static)得到合理的初始化[2]。
新类SprinklerSystem 中含有WaterSource类对象source的组合。main()中System.out.println()需要参数string,而实际情况是给予一个对象引用sprinklers,此时类SprinklerSystem中的toString就会被自动调用。巧合的是,"source= "+source重复了讨论当中遇到的情况,因此,类WaterSource同样也会调用属于它的哪个toString()函数。因此,运行上述代码得到的结果应该是:
WaterSource() Constructor !
Value1=null Value2=null Value3=null Value4=null
I=0 f=0.0 source= Constructed
二、继承语法
Java中继承以关键字extends实现,即BaseClass extends SubClass{ }的形式实现导出类由BaseClass导出子类SubClass,子类可以在其接口中自动获取基类的方法而不论这些方法是否在子类中显示定义过;一方面,在子类中使用基类中的方法及对其进行修改都是可行的。需要特别说明的是,若要使用子类与基类中共有的属于基类方法时,则需要加关键字super表示当前类是从超类继承来的;
另一方面,并非一定得使用基类中的方法,在导出类中添加新的方法也是可以的。比如说:
class Cleanser
{
private String s="Cleanser ";
public void append(String s){this.s=s;System.out.println(this.s);}
public void dilute(){append("dilute()");}
public void apply(){append("apply()");}
public void scrub(){append("scrub()");}
public String toString(){return s;}
public static void main(String[] args)
{
Cleanser x=new Cleanser();
x.dilute();
x.apply();
x.scrub();
}
}
public class Detergent extends Cleanser
{
public void scrub()
{
append("Detergent.scrub()");
super.scrub();
}
public void form(){append("form()");}
public static void main(String[] args)
{
Detergent x=new Detergent();
x.dilute();
x.apply();
x.scrub();
x.form();
System.out.println(x);
System.out.println("Now,test the base class: ");
Cleanser.main(args);
}
}
[分析]:根据上述原理,代码段执行顺序应该是:
Detergent x=new Detergent()调用Detergent的构造函数(由于Detergent是继承类,因此调用它的构造函数之前应先调用其基类的构造函数);然后执行子类从基类继承来的方法dilute()、apply(),然后执行子类修改的方法scrub()及新定义的方法form();最后,再调用基类Cleanser的main()函数。因此,上述结果应该是:
// Detergent x=new Detergent()
…
// x.dilute()
dilute()
// x.apply()
apply()
// x.scrub()调用子类修改后的scrub()
Detergent.scrub()
scrub()//调用超类的scrub()
//
form()
// System.out.println(x),因为此处需要字符串参数,而实际给的是对象引用x,则类//Detergent的toString()将被自动调用(而此方法又继承自基类,返回s=”form()”)
form()
// System.out.println("Now,test the base class: ");
Now,test the base class:
dilute()
apply()
scrub()
三、继承中带参数的构造器
当基类中没有默认的构造器或者想调用其中一个不带参数的构造器时,就必须使用关键字super显示地编写调用基类构造器的语句,并且配以适当的参数列表;注意,子类中的构造函数应该在基类的构造函数之后调用,比如:
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 !");
}
}
class Custom
{
Custom(int i){System.out.println("Custom() Constructor !");}
}
public class PlaceSetting extends Custom
{
private Spoon sp;
private Fork fk;
private Knife kn;
private DinnerPlate pl;
public PlaceSetting(int i)
{
super(i);
sp=new Spoon(i+2);
fk=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);
}
}
[分析]:上述代码根据先前的要求,其运行结果应为:
// super(i);
Custom() Constructor !
// sp=new Spoon(i+2);
Utensil() Constructor !
Spoon() Constructor !
// fk=new Fork(i+3);
Utensil() Constructor !
Fork() Constructor !
// kn=new Knife(i+4)
Utensil() Constructor !
Knife() Constructor !
// pl=new DinnerPlate(i+5)
Plate() Constructor !
DinnerPlate() Constructor !
// System.out.println("PlaceSetting() Constructor !")
PlaceSetting() Constructor !
四、继承机制中的名字屏蔽效应
在java之中,其子类的方法定义可以重载基类中的方法,并且这并不会屏蔽掉基类该方法在子类中的继承,这与C++是截然不同的两种机制。比如说:
class Home
{
char f(char c)
{
System.out.println("f(char)");
return 'b';
}
float f(float f)
{
System.out.println("f(float)");
return 1.0f;
}
}
class Family{}
class Hank extends Home
{
void f(Family m)
{
System.out.println("f(Family)");
}
}
public class Hide
{
public static void main(String[] args)
{
Hank h=new Hank();
h.f(1);
h.f('x');
h.f(new Family());
}
}
[分析]:由于子类重载基类的方法不会因为重载而使基类的方法失去效用,因此,对象会根据参数列表选择是选择子类经过重载的方法还是基类中的方法。基于此,上述结果为:
f(float)
f(char)
f(Family)
Reference:
[1]. Bruce Eckel.<
[2]. Hank.<