Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 继承是否违反了oops的基本法则。。?_Java_Oop_Inheritance - Fatal编程技术网

Java 继承是否违反了oops的基本法则。。?

Java 继承是否违反了oops的基本法则。。?,java,oop,inheritance,Java,Oop,Inheritance,让我先解释一下我到底想说什么 假设一个子类继承了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();

让我先解释一下我到底想说什么

假设一个子类继承了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());
    }
}
我知道创建子类对象也会调用超级类构造函数。 但构造函数的工作只是初始化字段,而不是将内存分配给对象


此外,对于不允许初始化的抽象类,我们仍然可以使用抽象类的实例变量

memory to instance变量将仅在创建实例时分配

我们如何在不创建实例的情况下使用实例字段呢。 这不是对oops概念的暴力吗


请帮忙。提前谢谢。

我想你对使用extends关键字时会发生什么感到困惑。这个关键词的意思是,Sub是一种更具体的Super。到目前为止,Super的所有属性也必须适用于Sub。这意味着Super的所有私有成员、方法和属性都存在于Sub的实例中。只是出于组织上的原因,Super的开发人员决定不希望任何派生类直接与Sub混淆

现在,这与内存分配有什么关系?对于Java,构造函数没有分配内存是正确的。它只是初始化字段。内存分配由运行时处理,它为整个画面分配足够的内存。记住,潜艇是超级潜艇,然后是超级潜艇。因此,它分配了足够的内存来保存从整个继承链到java.lang.Object的所有内容

事实上,抽象类可以被初始化,甚至可以强制其派生类初始化其成员。例如:

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字段,它可以自由声明自己的新id字段。这不会覆盖Super的字段。Super使用该字段的任何方法仍将使用Super中的方法。这可能有点令人困惑,所以最好的建议是不要这样想。通常,您希望覆盖方法而不是字段。

我认为您对使用extends关键字时会发生什么感到困惑。这个关键词的意思是,Sub是一种更具体的Super。到目前为止,Super的所有属性也必须适用于Sub。这意味着Super的所有私有成员、方法和属性都存在于Sub的实例中。只是出于组织上的原因,Super的开发人员决定不希望任何派生类直接与Sub混淆

现在,这与内存分配有什么关系?对于Java,构造函数没有分配内存是正确的。它只是初始化字段。内存分配由运行时处理,它为整个画面分配足够的内存。记住,潜艇是超级潜艇,然后是超级潜艇。因此,它分配了足够的内存来保存从整个继承链到java.lang.Object的所有内容

事实上,抽象类可以被初始化,甚至可以强制其派生类初始化其成员。例如:

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字段,它可以自由声明自己的新id字段。这不会覆盖Super的字段。Super使用该字段的任何方法仍将使用Super中的方法。这可能有点令人困惑,所以最好的建议是不要这样想。通常,您会希望覆盖方法而不是字段。

我完全同意Ian的答案。完全地关于你问题的题目,

继承是否违反了oops的基本法则

答案是视情况而定。有一种继承违反了封装原则:实现继承

每次通过扩展原语从未标记为抽象的类继承时,都使用实现继承。在这种情况下,要知道如何实现子类,您需要知道实现,也就是基类的方法代码。重写方法时,必须确切知道基类中该方法的行为。这种代码重用通常被称为白盒重用

引用GoF的书《设计模式:

父类通常至少定义其子类物理表示的一部分。因为继承将子类暴露于其父类实现的细节中,所以人们常说继承破坏了封装

因此,为了减少实现依赖性,您必须遵循可重用面向对象设计的原则之一,即:


我完全同意伊恩的答案。完全地关于你问题的题目,

是吗 N暴力违反了oops的基本法律

答案是视情况而定。有一种继承违反了封装原则:实现继承

每次通过扩展原语从未标记为抽象的类继承时,都使用实现继承。在这种情况下,要知道如何实现子类,您需要知道实现,也就是基类的方法代码。重写方法时,必须确切知道基类中该方法的行为。这种代码重用通常被称为白盒重用

引用GoF的书《设计模式:

父类通常至少定义其子类物理表示的一部分。因为继承将子类暴露于其父类实现的细节中,所以人们常说继承破坏了封装

因此,为了减少实现依赖性,您必须遵循可重用面向对象设计的原则之一,即:


程序到接口,而不是实现继承只关心完成了什么和如何完成,而不是承诺了什么。如果违反了基类的承诺,会发生什么?是否有任何保证确保其兼容-即使您的编译器也不会理解这个错误,您的代码中也会出现错误。例如:

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;
    }
}
我只是用DataHashSet类重新实现HashSet,以便跟踪插入。事实上,DataHashSet继承并是HashSet的一个子类型。我们可以代替HashSet,只传递DataHashSetin-java是可能的。此外,我确实重写了基类的一些方法。这是否符合Liskov替代原则?由于我没有对基类的行为进行任何更改,只是添加了一个轨迹来插入操作,所以它看起来是完全合法的。但是,我认为这显然是一个有风险的继承和错误代码。首先,我们应该看看add方法到底做了什么。将一个单元添加到相关属性并调用父类方法。有一个叫溜溜球的问题。看看addAll方法,首先,它将集合大小添加到相关属性中,然后在父级中调用addAll,但父级addAll究竟做什么呢?它将在集合上多次调用add方法,将调用哪个add?外接程序将添加到当前类中,因此,计数的大小将添加两次。一次调用addAll,第二次父类调用子类中的add方法,这就是为什么我们称之为yo-yo问题。再举一个例子,想象一下:

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方法中看到的,将调用哪个条?第二个是B级的酒吧。但是,这里的问题是什么?问题是类A中的foo方法不知道类B中bar方法的重写,那么您的不变量可能会被违反。因为foo可能期望bar方法的唯一行为是在自己的类中,而不是被重写。这个问题被称为脆弱基类问题。

继承只关心完成了什么以及如何完成,而不是承诺了什么。如果违反了基类的承诺,会发生什么?是否有任何保证确保其兼容-即使您的编译器也不会理解这个错误,您的代码中也会出现错误。例如:

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;
    }
}
我只是用DataHashSet类重新实现HashSet,以便跟踪插入。事实上,DataHashSet继承并是HashSet的一个子类型。我们可以代替HashSet,只传递DataHashSetin-java是可能的。此外,我确实重写了基类的一些方法。这是否符合Liskov替代原则?由于我没有对基类的行为进行任何更改,只是添加了一个轨迹来插入操作,所以它看起来是完全合法的。但是,我认为这显然是一个有风险的继承和错误代码。首先,我们应该看看add方法到底做了什么。将一个单元添加到相关属性并调用父类方法。有一个叫溜溜球的问题。看看addAll方法,首先,它将集合大小添加到相关属性中,然后在父级中调用addAll,但父级addAll究竟做什么呢?它将在集合上多次调用add方法,将调用哪个add?外接程序将添加到当前类中,因此,计数的大小将添加两次。一次调用addAll,第二次父类调用子类中的add方法,这就是为什么我们称之为yo-yo问题。再举一个例子,想象一下:

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方法中看到的,将调用哪个条?第二个是B级的酒吧。但是,这里的问题是什么?问题是f
类A中的oo方法将不知道关于类B中的bar方法重写的任何信息,那么您的不变量可能会被违反。因为foo可能期望bar方法的唯一行为是在自己的类中,而不是被重写。这个问题称为脆弱的基类问题。

子类的所有实例都是超级类的实例。创建Sub实例时,该对象还具有Super中的所有字段。这是extendsin的定义,如果抽象类的初始化不允许出错:抽象基类中的字段可以初始化,通过声明中的赋值或构造函数。我认为Super的私有成员是不可继承的,不能是subshow的实例字段。如何在不实例化的情况下初始化类的实例字段?如果AbstractSuper是一个抽象类,Sub是一个扩展它的具体类,那么在实例化新Sub时,新对象也是AbstractSuper的实例。参见Ian的第一条评论。因此,它还有您为AbstractSuper定义的实例字段。因此,在某种程度上,您已经实例化了AbstractSuper,也就是说,您已经创建了一个作为AbstractSuper实例的对象。你只需要通过一个具体的子类就可以了。@sandiee,是的,现在有两个同名的不同字段,但这没关系,因为它们在不同的名称空间或作用域中。两个名字没有冲突。Super不知道Subs的成员,Sub也看不到Super,所以编译器不会混淆哪个是哪个。Sub的所有实例也是Super的实例。创建Sub实例时,该对象还具有Super中的所有字段。这是extendsin的定义,如果抽象类的初始化不允许出错:抽象基类中的字段可以初始化,通过声明中的赋值或构造函数。我认为Super的私有成员是不可继承的,不能是subshow的实例字段。如何在不实例化的情况下初始化类的实例字段?如果AbstractSuper是一个抽象类,Sub是一个扩展它的具体类,那么在实例化新Sub时,新对象也是AbstractSuper的实例。参见Ian的第一条评论。因此,它还有您为AbstractSuper定义的实例字段。因此,在某种程度上,您已经实例化了AbstractSuper,也就是说,您已经创建了一个作为AbstractSuper实例的对象。你只需要通过一个具体的子类就可以了。@sandiee,是的,现在有两个同名的不同字段,但这没关系,因为它们在不同的名称空间或作用域中。两个名字没有冲突。Super不知道Subs成员,Sub也看不到Super,因此编译器不会混淆哪个是哪个。@sandiee Ian在代码注释中说,不在超类中调用构造函数会导致编译器错误-正如他所说,抽象类实际上可以初始化,甚至强制其派生类初始化其成员。@sandiee Ian在代码注释中说,不在超类中调用构造函数会导致编译器错误——正如他所说,抽象类实际上可以初始化,甚至强制其派生类初始化其成员。