【问题标题】:Java Constructor InheritanceJava构造函数继承
【发布时间】:2010-12-11 06:53:45
【问题描述】:

我想知道为什么java中的构造函数不被继承?你知道什么时候你有这样的课程:

public class Super {

  public Super(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC){
    this.serviceA = serviceA;
    //etc
  } 

}

稍后当你从Super 继承时,java 会抱怨没有定义默认构造函数。解决方案显然是这样的:

public class Son extends Super{

  public Son(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC){
    super(serviceA,serviceB,serviceC);
  }

}

这段代码是重复的,不是干的和无用的(恕我直言)......所以又带来了问题:

为什么java不支持构造函数继承?不允许这种继承有什么好处吗?

【问题讨论】:

  • 我同意Son 中的构造函数是重复的。正是出于这个原因,C++ 现在允许 派生类继承基构造函数(参见www2.research.att.com/~bs/C++0xFAQ.html#inheriting)。请注意,我强调“允许”,因为派生类必须明确声明它使用基类中的构造函数。
  • 与 C++ 一样,Java 也会受益于允许构造函数继承的语法。
  • 如果你在超类Super中有一个private final int foo;,你不能在Son的继承构造函数中为foo赋值,因为它在Super中是私有的。

标签: java inheritance constructor


【解决方案1】:

假设构造函数继承...那么因为每个类最终都派生自 Object,每个类最终都会有一个无参数的构造函数。这是个坏主意。你究竟会期待什么:

FileInputStream stream = new FileInputStream();

要做什么?

现在可能应该有一种方法可以轻松创建相当常见的“传递”构造函数,但我认为它不应该是默认值。构造子类所需的参数通常与超类所需的参数不同。

【讨论】:

  • 为什么不允许派生类像 C++ 一样选择性地继承其基构造函数(参见 www2.research.att.com/~bs/C++0xFAQ.html#继承)?
  • 一种有用的构造函数语法可能是允许派生构造函数继承基构造函数的参数并自动将这些参数转发给基构造函数,这样派生构造函数就不需要重复这些参数。
  • 是什么阻止您在不需要时将无参数构造函数声明为私有的?
  • @Mononofu:好吧,必须创建一个构造函数来有效地创建一个不可用的对象并且永远不会被调用,这肯定会很烦人,只是为了“删除”一些无论如何都不需要提供的东西.伊克。我宁愿语言像现在这样工作:)
  • @Mononofu 但是等等......为了正确而自信地使用它,您必须了解java对于构造函数和可见性的奇怪继承系统。这个世界还不够混乱吗? ;)
【解决方案2】:

当您从 Super 继承时,实际上会发生这种情况:

public class Son extends Super{

  // If you dont declare a constructor of any type, adefault one will appear.
  public Son(){
    // If you dont call any other constructor in the first line a call to super() will be placed instead.
    super();
  }

}

所以,这就是原因,因为您必须调用唯一的构造函数,因为“Super”没有默认构造函数。

现在,尝试猜测为什么 Java 不支持构造函数继承,可能是因为构造函数只有在谈论具体实例时才有意义,并且当您不知道时,您不应该创建某事物的实例它是如何定义的(通过多态性)。

【讨论】:

    【解决方案3】:

    因为构建子类对象的方式可能与构建超类的方式不同。您可能不希望子类的客户端能够调用超类中可用的某些构造函数。

    一个愚蠢的例子:

    class Super {
        protected final Number value;
        public Super(Number value){
            this.value = value;
        }
    }
    
    class Sub {
        public Sub(){ super(Integer.valueOf(0)); }
        void doSomeStuff(){
            // We know this.value is an Integer, so it's safe to cast.
            doSomethingWithAnInteger((Integer)this.value);
        }
    }
    
    // Client code:
    Sub s = new Sub(Long.valueOf(666L)): // Devilish invocation of Super constructor!
    s.doSomeStuff(); // throws ClassCastException
    

    甚至更简单:

    class Super {
        private final String msg;
        Super(String msg){
            if (msg == null) throw new NullPointerException();
            this.msg = msg;
        }
    }
    class Sub {
        private final String detail;
        Sub(String msg, String detail){
            super(msg);
            if (detail == null) throw new NullPointerException();
            this.detail = detail;
        }
        void print(){
            // detail is never null, so this method won't fail
            System.out.println(detail.concat(": ").concat(msg));
        }
    }
    // Client code:
    Sub s = new Sub("message"); // Calling Super constructor - detail is never initialized!
    s.print(); // throws NullPointerException
    

    从这个例子中,你看到你需要某种方式来声明“我想继承这些构造函数”或“我想继承除这些之外的所有构造函数”,然后你还必须指定一个默认构造函数继承首选项以防万一有人在超类中添加新构造函数......或者如果你想“继承”它们,你可以只要求你重复超类中的构造函数,这可以说是更明显的方法这样做.

    【讨论】:

    • 你的例子不能说服我。仅仅因为您故意编写错误代码,所以它不是反对构造函数继承的有效论据。如果你搞砸了(有时即使你没有搞砸),NullPointerExceptions 是完全正常的。
    • @TheincredibleJan 重点是构造函数的用途之一是强制执行类不变量。隐式构造函数继承会使为类的实例提供任何保证变得更加复杂(即使您认为可以将构造函数添加到超类中也是不可能的)。
    • 这不是一个很好的例子。您的问题是您将非整数数字转换为整数。它与继承无关。
    • @LowKeyEnergy 不,问题是能够绕过构造函数将剥夺你必须强制执行类不变量的唯一方法。但是,我承认我的例子可能更简洁——在接受的答案中提出的观点更重要;一切都会有一个默认的构造函数。
    【解决方案4】:

    因为构造函数是一个实现细节——它们根本不是接口/超类的用户可以实际调用的东西。当他们得到一个实例时,它已经构建好了;反之亦然,在您构造一个对象时,根据定义,它当前没有分配给它的变量。

    想想强制所有子类都具有继承的构造函数意味着什么。我认为直接传递变量比让类“神奇地”拥有一个带有一定数量参数的构造函数更清楚,因为它是父级。

    【讨论】:

      【解决方案5】:

      构造函数不是多态的。
      在处理已构建的类时,您可能正在处理对象的声明类型或其任何子类。这就是继承的用处。
      构造函数总是在特定类型上调用,例如new String()。假设的子类在其中没有任何作用。

      【讨论】:

      • OP 可能问的是“为什么我必须进行超级调用,而编译器完全有能力为我生成它们?”
      • ... 例如,当超类具有默认构造函数而子类未指定任何构造函数时,它会这样做。
      【解决方案6】:

      大卫的回答是正确的。我想补充一点,你可能会从上帝那里得到一个信号,表明你的设计是一团糟,并且“Son”不应该是“Super”的子类,但相反,Super 有一些实现细节最好的表达拥有 Son 提供的功能,作为某种策略。

      编辑:Jon Skeet 的回答非常棒。

      【讨论】:

      • Super 实际上是我的服务层的一个 LayerSupertype (martinfowler.com/eaaCatalog/layerSupertype.html)。而且我需要在构造函数中注入的依赖项来支持 DI。我的设计没有搞砸
      • 嗯,这是见仁见智的问题!在我看来,如果你的层次结构被扭曲以适应流行语框架的要求,那它本身可能是来自上方的标志。
      • 请解释为什么你说我的层次结构是扭曲的。而且我从来没有提到过流行语框架,实际上我只是在使用 Guice + Plain old servlet。
      • 我不知道你的设计是不是变形了,也不是设计的不好。我只是说,如果您的子类与它的超类通过提供一些策略上的差异而有所不同,那么可能可以更好地表达为超类而不是。然后我说,如果是这样,但你仍然必须将你的软件设计为父子关系,以支持某些框架的需求(无论是 DI 还是其他),那就是“扭曲”!但我不知道你们的软件是如何设计的。
      【解决方案7】:

      因为(超)类必须完全控制它的构造方式。如果程序员认为提供默认(无 args)构造函数作为类契约的一部分没有意义,则编译器不应提供。

      【讨论】:

        【解决方案8】:

        您实际上确实继承了构造函数,因为您可以在适当的时候简单地调用 super ,只是由于其他人提到的原因,如果默认情况下发生这种情况,它很容易出错。编译器无法推测什么时候合适,什么时候不合适。

        编译器的工作是提供尽可能多的灵活性,同时降低复杂性和意外副作用的风险。

        【讨论】:

          【解决方案9】:

          我不知道子类继承构造函数的任何语言(但是,我不太会编程)。

          Here's 讨论有关 C# 的同一问题。普遍的共识似乎是它会使语言复杂化,给基类的变化带来潜在的不良副作用,并且通常在良好的设计中不是必需的。

          【讨论】:

          • C++ 现在允许派生类继承基构造函数(参见 www2.research.att.com/~bs/C++0xFAQ.html#inheriting)。这避免了派生类中常见的习惯用法,派生构造函数只是声明与基构造函数中相同的参数并将这些参数转发给基构造函数。
          • Python 会:stackoverflow.com/a/6536027/2112722。 Python 还将“构造函数”阶段分为__new__()__init__() 方法,并且对多态方法有非常不同的方法。见pythonconquerstheuniverse.wordpress.com/2010/03/17/…
          • 任何非类型化语言都允许这样做
          【解决方案10】:

          派生类与其基类不同,您可能关心也可能不关心基类的任何成员是否在构造派生类时被初始化。这是程序员而不是编译器做出的决定。

          【讨论】:

            猜你喜欢
            • 2013-03-21
            • 2011-09-09
            • 1970-01-01
            • 2020-08-07
            • 1970-01-01
            • 2018-10-30
            • 1970-01-01
            • 1970-01-01
            • 2023-04-04
            相关资源
            最近更新 更多