首先让我解释一下我想说的话
假设一个Sub类继承了Super类。 现在我们可以在Sub类中超出所有类Super的私人成员。现在假设案例
class Super{
private int id;
public int getId()
{
return id;
}
}
class Sub extends Super {
public static void main(String args[]){
Sub sub = new Sub();
System.out.println(sub.getId());
}
}
我知道创建Sub类对象也会调用Super类构造函数。 但是构造函数的工作只是初始化字段 - 而不是将内存分配给对象。
此外,在不允许初始化的抽象类的情况下,我们仍然可以使用抽象类的实例变量。
仅在实例创建时分配内存到实例变量。
我们如何在不创建实例的情况下使用实例字段。 它不会违反oops概念.. ??
请帮忙解决这个问题。并提前感谢。
答案 0 :(得分:4)
我认为您对使用extends
关键字时会发生什么感到困惑。该关键字的含义是Sub
是一种更具体的类 Super
。根据{{3}},Super
的所有属性也必须包含Sub
。这意味着所有Super
的私有成员(方法和属性)都存在于Sub
的实例中。只是出于组织原因,Super
的开发人员决定他们不希望任何派生类直接搞乱它。
现在,这与内存分配有什么关系?对于Java,您是正确的,构造函数不分配内存。它只是初始化字段。内存分配由运行时处理,并为整个画面分配足够的内存。请注意,Sub
是Super
,然后是。因此它分配了足够的内存来保存从整个继承链到java.lang.Object
的所有内容。
abstract
类可以初始化,甚至强制它们的派生类初始化它们的成员。例如:
public abstract class Super {
private int id;
public Super(int id) {
this.id = id;
}
public int getId() { return this.id; }
}
public class Sub extends Super {
public Sub() {
super(5); // failure to call this constructor is a compiler error
}
}
现在,由于Sub
无法看到Super
的私有id
字段,因此可以自由声明其自己的Super
字段。此不会覆盖Super
的字段。使用该字段的任何Super
方法仍将使用{{1}}中的方法。这可能有点令人困惑,所以最好的建议是不要那么想。通常,您需要覆盖方法而不是字段。
答案 1 :(得分:1)
我完全赞同伊恩的回答。完全。关于你问题的标题,
继承是否违反了oops的基本规律..?
答案取决于。有一种违反封装原则的继承:实现继承。
每次从未标记为extends
的类继承(通过abstract
原语)时,您都会使用实现继承。在这种情况下,要知道如何实现子类,您需要知道基类方法的实现(a.k.a.代码)。覆盖方法时,必须确切知道基类中该方法的行为。这种代码重用通常称为白盒重用。
引用GoF的书“设计模式:
”父类通常至少定义其子类的一部分'物理代表。因为继承将子类暴露给其父实现的细节,所以它经常说"继承打破了封装"。
因此,为了减少实现依赖性,您必须遵循可重用的面向对象设计的原则之一,即:
编程到接口,而不是实现
答案 2 :(得分:0)
class DoubleEndedQueue {
void insertFront(Node node){
// ...
// insert node infornt of queue
}
void insertEnd(Node node){
// ...
// insert a node at the end of queue
}
void deleteFront(Node node){
// ...
// delete the node infront of queue
}
void deleteEnd(Node node){
// ...
// delete the node at the end of queue
}
}
class Stack extends DoubleEndedQueue {
// ...
}
如果该类想使用继承以达到代码重用的目的,则它可以继承违反其主体的行为,例如insertFront
。我们还要看另一个代码示例:
public class DataHashSet extends HashSet {
private int addCount = 0;
public function DataHashSet(Collection collection) {
super(collection);
}
public function DataHashSet(int initCapacity, float loadFactor) {
super(initCapacity, loadFactor);
}
public boolean function add(Object object) {
addCount++;
return super.add(object);
}
public boolean function addAll(Collection collection) {
addCount += collection.size();
return super.addAll(collection);
}
public int function getAddCount(Object object) {
return addCount;
}
}
我只是用HashSet
类重新实现了DataHashSet
以便跟踪插入。实际上,DataHashSet
继承并且是HashSet
的子类型。我们可以通过HashSet
代替DataHashSet
(在Java中是可能的)。另外,我确实重写了基类的某些方法。根据李斯科夫替代原则,这合法吗?由于我没有对基类的行为进行任何更改,因此只需添加一条插入动作的轨道即可,这似乎是完全合法的。但是,我认为这显然是危险的继承和错误代码。首先,我们应该了解add
方法的确切作用。将一个单位添加到相关属性并调用父类方法。名为yo-yo
的问题。看一下addAll
方法,首先,它将集合大小添加到相关属性中,然后在父级中调用addAll
,但是父级addAll
到底能做什么?它将多次调用add
方法(遍历集合),哪个add
将被调用?当前类中的add
,因此count的大小将被添加两次。一次调用addAll,第二次调用父类将在子类中调用add方法,这就是为什么我们将其称为溜溜球问题。再举一个例子,想象一下:
class A {
void foo(){
...
this.bar();
...
}
void bar(){
...
}
}
class B extends A {
//override bar
void bar(){
...
}
}
class C {
void bazz(){
B b = new B();
// which bar would be called?
B.foo();
}
}
您在bazz
方法中看到哪个bar
将被调用?第二个将调用类B中的bar
。但是,这是什么问题呢?问题是类A中的foo
方法对类B中的bar
方法的覆盖一无所知,那么您的不变式可能会受到侵犯。因为foo可能期望bar方法的唯一行为是在自己的类中,所以不会覆盖某些内容。此问题称为脆弱的基类问题。