Java 继承是否违反了oops的基本法则。。?
让我先解释一下我到底想说什么 假设一个子类继承了Super类。 现在我们可以在Sub类中超过Super类的所有非私有成员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();
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在代码注释中说,不在超类中调用构造函数会导致编译器错误——正如他所说,抽象类实际上可以初始化,甚至强制其派生类初始化其成员。