4.8 继承与组合
Posted weststar
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了4.8 继承与组合相关的知识,希望对你有一定的参考价值。
继承是实现类复用的重要手段,但继承有一个大的坏处:破坏封装。相比之下,组合也是实现类复用的重要方式,且能提供更好的封装性。
一、使用继承的注意点
子类扩展父类,子类可以从父类继承得到成员变量和方法,如果访问权限允许,子类可以访问父类的成员变量和方法,相当于可以直接复用父类的成员变量和方法。继承却严重破坏了父类的封装性。在继承关系中,子类可以直接访问父类的成员变量(内部信息)和方法,从而造成子类和父类严重耦合。从这个角度看,父类的实现细节对子类不再透明,子类可以访问父类的成员变量和方法,并可以改变父类的实现细节(例如:通过方法重写来改变父类的方法实现),从而导致子类可以恶意篡改父类方法。
为了保证父类良好的封装性,不会被子类进行随意改变,设计父类通常应该遵循以下规则:
★尽量隐藏父类的内部数据。尽量把父类的所有成员变量都设置成private访问类型,不要让子类直接访问父类的成员变量。
★不要让子类可以随意访问、修改父类的方法。
父类中那些仅为辅助其他的工具方法,应该使用private访问控制符修饰,让子类无法直接访问该方法;
如果父类中的方法需要被外部类调用,则必须以public修饰,但又不希望子类重写该方法,可以使用final修饰符来修饰该方法;
如果希望父类中的某个方法被子类重写,但又不希望被其他类自由访问,则可以使用protected来修饰该方法。
★尽量不要在父类构造器中调用要被子类重写的方法。
class Base { //在构造器中调用方法 public Base() { test(); } public void test()//1号test()方法 { System.out.println("将被子类重写的方法"); } } public class Sub extends Base { private String name; public void test() //2号test()方法 { System.out.println("子类重写父类的方法,"+"其name字符串长度"+name.length()); } public static void main(String[] args) { //下面代码将会引发空指针异常 var s=new Sub(); } }
运行结果:
---------- 运行Java捕获输出窗 ---------- Exception in thread "main" java.lang.NullPointerException at Sub.test(Sub.java:19) at Base.<init>(Sub.java:6) at Sub.<init>(Sub.java:14) at Sub.main(Sub.java:24) 输出完成 (耗时 0 秒) - 正常终止
当系统试图创建Sub对象时,同样会先执行其父类的构造器,如果父类构造器调用了被其他子类重写的方法,则变成调用被子类重写后的方法。当创建Sub对象时,会先执行Base类中的Base构造器,而Base构造器中调用了test()方法——并不是1号test()方法,而是调用2号test()方法,此时Sub对象的name实例变量是null,因此将引发空指针异常。
何时需要从父类派生出新的子类?
★子类需要增加额外的成员变量,而并不仅仅是变量值的改变。
★子类需要增加自己独特的行为方式(包括增加新的方法或重写父类的方法)
二、利用组合实现复用
对于继承而言,子类可以直接获得父类public方法,程序使用子类时将可以直接访问从父类那里继承到的方法;而组合则是把旧类对象作为新类成员变量组合起来,用于实现新类功能,用户看到的是新类的方法,而不能看到被组合对象的方法。因此通常需要在新类里使用private修饰被组合的旧类对象。
仅从类复用的角度来看,父类的功能等同于被组合的类,都将自身的方法提供给新类使用;子类和组合关系里的整体类,都可以复用原有类的方法,用于实现自身的功能。
假设有三个类:Animal、Wolf、Bird,它们之间的继承树关系如图:
1 class Animal 2 { 3 private void beat() 4 { 5 System.out.println("心脏跳动..."); 6 } 7 public void breathe() 8 { 9 beat(); 10 System.out.println("吸气,吐气..."); 11 } 12 } 13 14 //继承Animal,直接复用父类的breathe()方法 15 class Bird extends Animal 16 { 17 public void fly() 18 { 19 System.out.println("我在天上自由飞翔..."); 20 } 21 } 22 23 //继承Animal,直接复用父类的breathe()方法 24 class Wolf extends Animal 25 { 26 public void run() 27 { 28 System.out.println("我在陆地上奔跑..."); 29 } 30 } 31 32 public class InheritTest 33 { 34 public static void main(String[] args) 35 { 36 var b=new Bird(); 37 b.breathe(); 38 b.fly(); 39 var w=new Wolf(); 40 w.breathe(); 41 w.run(); 42 } 43 } 44 ---------- 运行Java捕获输出窗 ---------- 45 心脏跳动... 46 吸气,吐气... 47 我在天上自由飞翔... 48 心脏跳动... 49 吸气,吐气... 50 我在陆地上奔跑... 51 52 输出完成 (耗时 0 秒) - 正常终止
上面程序可以改成以下方式也可以实现相同的复用。
1 class Animal 2 { 3 private void beat() 4 { 5 System.out.println("心脏跳动..."); 6 } 7 public void breathe() 8 { 9 beat(); 10 System.out.println("吸气呼气..."); 11 } 12 } 13 14 class Bird 15 { 16 //将原来的父类组合到子类,作为子类的一个组合部分 17 private Animal a; 18 public Bird(Animal a) 19 { 20 this.a=a; 21 } 22 //重新定义一个自己的breathe()方法 23 public void breathe() 24 { 25 a.breathe();//直接复用Animal提供的breathe()方法 26 } 27 public void fly() 28 { 29 System.out.println("鸟在天上飞"); 30 } 31 } 32 33 class Wolf 34 { 35 private Animal a; 36 public Wolf(Animal c) 37 { 38 this.a=c; 39 } 40 //重新定义一个自己的breathe()方法 41 public void breathe() 42 { 43 a.breathe();//直接复用Animal提供的breathe()方法 44 } 45 public void run() 46 { 47 System.out.println("狼在地上跑"); 48 } 49 50 } 51 52 public class CompositionTest 53 { 54 public static void main(String[] args) 55 { 56 //显示创建被组合的对象 57 var a1=new Animal(); 58 var b=new Bird(a1); 59 b.breathe(); 60 b.fly(); 61 62 var a2=new Animal(); 63 var w=new Wolf(a2); 64 w.breathe(); 65 w.run(); 66 } 67 } 68 ---------- 运行Java捕获输出窗 ---------- 69 心脏跳动... 70 吸气呼气... 71 鸟在天上飞 72 心脏跳动... 73 吸气呼气... 74 狼在地上跑 75 76 输出完成 (耗时 0 秒) - 正常终止
以上是关于4.8 继承与组合的主要内容,如果未能解决你的问题,请参考以下文章