您的位置:首页 > 编程语言 > Java开发

Java基础之(十七)继承和组合

2016-08-08 14:29 330 查看
说明

继承是实现类重用的重要方法,但继承带来了一个最大的坏处:破坏封装。

使用继承的注意点

为了保证父类有良好的封装性,不会被子类随意改变,设计父类应当遵循以下规则:

尽量隐藏父类的内部数据。尽量把父类的所有的成员变量都设为private访问类型,不要让子类直接访问父类的成员变量。

不要让子类可以随意访问、修改父类的方法。父类中的工具方法应当使用private访问控制符修饰,让子类无法访问该方法;如果父类中的方法需要让外部类调用,则必须以public修饰,但又不希望子类重写该方法,可以使用final修饰符来修饰该方法;如果希望父类的某个方法被子类重写,但又不希望被其他类自由访问,则可以使用protected修饰该方法。

尽量不要在父类构造器中调用子类将要重写的方法。看如下程序:

class Base
{
public Base()
{
test();
}
public void test() ———————————————————— a
{
System.out.println("将被子类重写的方法");
}
}

public class Exercise7_5 extends Base{
private String name;
public void test() ———————————————————— b
{
System.out.println("子类重写父类的方法,其name字符串长度" + name.length());
}
public static void main(String[] args)
{
//下面代码会引发空指针异常
Exercise7_5 s = new Exercise7_5();
}
}


运行结果:
Exception in thread "main" java.lang.NullPointerException(空指针异常)


当系统试图创建sub对象的时候,同样会先执行其父类Base类中的构造器,而Base构造器中调用了test方法,并不是调用a处的test方法,而是调用b处的test方法,此时Sub对象的name Field是null,因此将引发空指针异常。

需要继承的条件

什么时候使用继承关系是一个难以把握的问题。这不仅需要保证子类是一种特殊的父类,而且需要具备以下两个条件之一。

子类需要额外增加属性,而不仅仅是属性值的改变。例如从Person类派生出Student子类,Person类里没有提供grade(年级)属性,而Student类需要grade属性来保存Student对象就读的年级,这种父类到子类的派生,就符合Java继承的前提。

子类需要增加自己独有的行为方式(包括增加新的方法和重写父类的方法)。例如从Person类派生出Teacher类,其中Teacher类需要增加一个teaching方法,该方法用于描述Teacher对象独有的行为方式:教学。

利用组合实现复用

如果只是出于方法复用的目的,并不一定非要使用继承,完全可以使用组合来实现。

如果需要复用一个类,除了把这个类当成基类来继承之外,还可以把该类当成另一个类的组合成分来实现,从而允许新类直接复用该类的public方法。

对于继承而言,子类可以直接获得父类的public方法,程序使用子类时,将可以直接访问该子类从父类中继承到的方法;而组合则是把旧类对象作为新类的Field嵌入,用以实现新类的功能,用户看到的是新类的方法,而不能看到被嵌入对象(旧类对象)的方法。因此通常需要在新类里使用private修饰被嵌入的旧类对象。

下面先看利用继承关系实现方法的复用:

class Animal
{
private void beat()
{
System.out.println("心脏跳动...");
}
public void breath()
{
beat();
System.out.println("吸一口气,吐一口气,呼吸中...");
}
}


//继承Animal,直接复用父类的breath方法
class Bird extends Animal
{
public void fly()
{
System.out.println("我在天空自在的飞翔...");
}
}


//继承Animal,直接复用父类的breath方法
class Wolf extends Animal
{
public void run()
{
System.out.println("我在陆地上的快速奔跑...");
}
}


public class TestInherit
{
public static void main(String[] args)
{
Bird b = new Bird();
b.breath();
b.fly();
Wolf w = new Wolf();
w.breath();
w.run();
}
}


输出结果:
心脏跳动...
吸一口气,吐一口气,呼吸中...
我在天空自在的飞翔...
心脏跳动...
吸一口气,吐一口气,呼吸中...
我在陆地上的快速奔跑...


通过让Bird类和Wolf类继承Animal类,从而允许Bird和Wolf获得Animal的方法,从而复用了Animal提供的breath方法。

如果仅仅从复用的角度来看,下面的方式也能实现复用的功能:

class Animal
{
private void beat()
{
System.out.println("心脏跳动...");
}
public void breath()
{
beat();
System.out.println("吸一口气,吐一口气,呼吸中...");
}
}
class Bird
{
//将原来的父类嵌入原来的子类,作为子类的一个组合成分
private Animal a;
public Bird(Animal a)
{
this.a = a;
}
//重新定义一个自己的breath方法
public void breath()
{
//直接复用Animal提供的breath方法来实现Bird的breath方法。
a.breath();
}

public void fly()
{
System.out.println("我在天空自在的飞翔...");
}
}
class Wolf
{
//将原来的父类嵌入原来的子类,作为子类的一个组合成分
private Animal a;
public Wolf(Animal a)
{
this.a = a;
}
//重新定义一个自己的breath方法
public void breath()
{
//直接复用Animal提供的breath方法来实现Bird的breath方法。
a.breath();
}
public void run()
{
System.out.println("我在陆地上的快速奔跑...");
}
}
public class TestComposite
{
public static void main(String[] args)
{
//此时需要显式创建被嵌入的对象
Animal a1 = new Animal();
Bird b = new Bird(a1);
b.breath();
b.fly();
//此时需要显式创建被嵌入的对象
Animal a2 = new Animal();
Wolf w = new Wolf(a2);
w.breath();
w.run();
}
}


输出结果:
心脏跳动...
吸一口气,吐一口气,呼吸中...
我在天空自在的飞翔...
心脏跳动...
吸一口气,吐一口气,呼吸中...
我在陆地上的快速奔跑...


此时的Wolf对象和Bird对象由Animal对象组合而成,因此在上面程序中创建Wolf对象和Bird对象之前先创建Animal对象,并利用这个Animal对象来创建Wolf对象和Bird对象。

在组合与继承之间选择

组合和继承都允许在新的类中放置子类(旧类)对象,组合是显式的这样做,继承是隐式的这样做。那么我们该如何选择?

继承是对已有的类做一番改造,以此获得一个特殊的版本。简而言之,就是将一个较为抽象的类,改造成能适应于某些需求的类。因此对于上面的Wolf类和Animal类的关系,使用继承更能表达出它们的现实关系。用一个动物来合成一匹狼是毫无意义的,因为狼并不是由动物组成的。反之如果两个类之间有明确的整体、部分关系,例如Person类需要复用Arm类的方法(Person对象由Arm对象组合而成),此时就应该使用组合关系来实现复用,把Arm作为Person的嵌入Field,借助了Arm的方法来实现Person的方法。

总之,狼是一种动物(is-a关系)。is-a的关系是用继承来表现的;而人类有一支军队(has-a关系)。has-a的关系是用组合来表达的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: