继承作为java的三大特性之一,功能很强大,继承了父类的子类,就拥有了父类的全部特性(除final修饰的方法,变量以外),子类可以通过重写父类的方法,达到更具体的实现,更灵活的扩展。
但是继承也有双面性。有两个缺点:
1.破坏封装
子类在继承父类的方法并且实现时,需要不得不关注父类中对该方法的实现,看一下有没有破坏原有的结构。而父类在更改自身方法时也需要考虑子类,如果不考虑,也可能会影响到子类的实现。举个例子:
//父类
public class Student {
private static final int MAX=10;
private int [] arr=new int [MAX];
private int count;
public void add (int num){
System.out.println("父类add");
if(count<MAX){
arr[count++]=num;
}
}
public void addAll(int [] nums){
System.out.println("父类addAll");
for (int i =0;i<nums.length;i++){
add(nums[i]);
}
}
}
//子类
public class TestClass extends Student {
private long sum;
//添加求和功能
@Override
public void add (int num){
System.out.println("子类add");
super.add(num);
// sum+=num;
}
@Override
public void addAll(int [] nums){
System.out.println("子类addAll");
super.addAll(nums);
for (int i =0;i<nums.length;i++){
sum+=nums[i];
}
}
public long getSum(){
return sum;
}
//执行
public static void main(String[] args) {
TestClass testClass=new TestClass();
testClass.addAll(new int[]{1,2,3});
System.out.println(testClass.getSum());
}
期望为6,实际却为12, 我们只希望他在子类的addall 进行一个数字汇总,殊不知addall方法在父类中调用了add方法,而重写了add方法会优先调用子类的add方法,而子类的add方法同样进行了数据的汇总,所以进行了两次汇总,结果不尽人意。
此时我们删掉子类中的两个方法中任意一个汇总的代码即可。
如果子类不知道基类方法的实现细节,它就不能正确的进行扩展。对于子类而言,通过继承实现,是没有安全保障的,父类修改内部实现细节,它的功能就可能会被破坏,而对于基类而言,让子类继承和重写方法,就可能丧失随意修改内部实现的自由。
继承没有反映"is-a"关系
什么是is-a关系,is-a表名子类是父类的一种,父类的属性和行为也一定适用于子类。就像橙子和水果一样,水果有的属性橙子必须要有。
在“is-a”关系中,重写方法时,子类不应该改变父类预期的行为,但是这又是没办法约束的。
继承应该是被当做“is-a”来使用的,但java没有办法约束,父类的属性和行为,子类不一定都适用,子类甚至还可以重写和预期完全不一样的行为。如果是用父类引用操作子类对象时,会把其当做父类对象使用,但其方法实现的效果如果又和预期不一样,这样会造成混乱。
如何避免继承的双面性
1.父类不希望被继承的属性方法用final 修饰
2.不继承,将需要的对象以属性的方式写入类中
3.使用接口