当前位置: 首页 > 知识库问答 >
问题:

在Java的子类中绕过最终方法?

堵茂勋
2023-03-14

我目前正在学习继承,对于在超类中声明一个方法final应用于子类的限制,我有点困惑。假设我有一个具有取款方法的超类BankAccount,它需要用户密码和金额才能取款,并将帐户余额设置为(balance - amount)。我希望将该方法声明为final,这样其他子类就不会潜在地覆盖它,并允许客户端在不改变帐户余额的情况下取款。

public final void withdraw(double amount, String pass) {
    if (checkPassword(pass) && getBalance() >= amount;) {
        setBalance(getBalance() - amount);
    } else {
        System.out.println("Rejected.");
    }
}

我想避免这样的事情被允许:

public void withdraw(double amount, String pass) {

}

然而,有些银行账户允许透支,在提款时也必须考虑透支。现在,如果我有一个子类BankAccount透支,继承的提款方法是最终的,所以我不能改变它的任何部分。但是,我必须在子类中考虑透支限额?我该怎么做呢?

    public void withdraw(double amount, String pass) {
    if (checkPassword(pass) && getOverDraftLimit() + getBalance() >= amount) {
        setBalance(getBalance() - amount);
    } else {
        System.out.println("Rejected.");
    }
}

共有3个答案

宓英哲
2023-03-14

我假设你的例子只是为了说明设计问题。通常你不会用像字符串比较和API限制这样简单的东西来保护像银行账户这样的资源。还有许多其他问题,比如竞争条件。

您可以做的是将提款分成两种不同的方法。超级方法可以被标记为最终方法,您可以拥有第二个受保护的方法,该方法未标记为最终状态,用于实际提款。这迫使进行检查,但允许实际的提款行为是动态的。

public final void withdraw(double amount, String pass) {

    if (checkPassword(pass)) {
        withdraw(amount);
    } else {
        System.out.println("Rejected.");
    }
}

protected void withdraw(double amount) {

  if (getBalance() >= amount) {
    setBalance(getBalance() - amount);
  } else {
    System.out.println("Rejected.");
  }
}

在派生类中。。。

@Override
protected void withdraw(double amount) {

  if (getOverDraftLimit() + getBalance() >= amount) {
    setBalance(getBalance() - amount);
  } else {
    System.out.println("Rejected.");
  }
}

我应该补充一点,解决这个问题有很多不同的方法,解决方案取决于总体目标。继承并不总是添加动态行为的正确方式。上面的一个问题是,它需要添加一个新的派生类来改变撤销行为,这可能导致过于复杂的类层次结构。向类中添加透支额将允许同样的情况,并避免许多额外的继承问题。

邹宏峻
2023-03-14

其他人建议更改您的设计,但重要的是要了解为什么您的原始设计不起作用。

当您尝试透支时,您的银行帐户类会以某种方式运行。该行为是其隐式 API 的一部分。允许透支的类通过削弱余额不能为负的后置条件来破坏该 API。从某种意义上说,它不是银行帐户。因此,它不应该是银行帐户的子类。使类或方法成为最终的,是Java实现这一点的方式。

正如biziclop指出的,可以使用继承来表达一个可以透支的帐户和一个不能透支的帐户之间的关系。但是让其中一个成为另一个的父代违反了利斯科夫替代原理。相反,让这两个类实现或扩展一个不指定透支行为的公共接口或超类。也许超类根本不应该包含< code > retract 方法。避免编写一个双向工作的类。如果API的客户端必须测试对象的行为和能力,那么您就避开了强类型,这是Java最大的优势之一。

有一本很棒的书叫做《有效Java》,每个Java程序员都应该读。它讨论了继承如何破坏封装,以及每个允许继承的类如何必须非常仔细地设计。由于这些原因,我认为继承是编程入门课上首先教的内容之一,这对学生来说是一个极大的伤害。这应该是最后一次。

朱刚捷
2023-03-14

你需要问的问题是:透支账户的提款程序不同吗?不,不是真的,过程是一样的:你检查密码,你检查有足够的资金,你从账户中扣款。

不同的是检查有足够的资金步骤。所以你的抽象应该反映这一点,就像它对checkPassword()所做的那样。

public final void withdraw(double amount, String pass) {
 if (checkPassword(pass) && checkFundsAvailable(amount)) {
    setBalance(getBalance() - amount);
 } else {
    System.out.println("Rejected.");
 }
}

protected boolean checkFundsAvailable(double amount) {
  return amount <= getBalance();
}

当您可以透支时,您可以使用以下内容覆盖它:

protected boolean checkFundsAvailable(double amount) {
  return amount <= getBalance() + overdraftLimit;
}

这样,您的超类就不必知道透支限额或任何事情。您可以将锁定的帐户实现为它的子类,该帐户拒绝所有提款请求,或者您可以将任何其他逻辑放入checkFundsAvailable()中

P. s.:尽管有以上所述,还是有一个很好的理由不通过继承来解决这个问题(如果这是一个真正的问题,而不仅仅是一个练习),但是它更微妙。通过拥有一个BankAccount类和一个BankAcCountOverra子类,你也声称不允许透支的账户永远不会变成可透支的账户,反之亦然。但是真正的银行账户不是这样的,你可以从不允许透支开始,然后同意透支限额。继承没有办法表达这一点,你需要使用某种形式的组合。

 类似资料:
  • 我正在写一个程序,我得到这样的结果:“最后的局部变量asd不能被赋值,因为它是在一个封闭类型中定义的”。 我将提供一个示例,它不是来自我的代码,但这也会产生错误。我有一个arraylist,我想以后在按钮之外使用它(如果我在按钮内部定义它,以后就不能使用它了),但是按下按钮应该会给这个arraylist提供值。在本例中,它将“创建一个新的ArrayList”,这将导致同样的问题。

  • 问题内容: 在方法内部声明局部内部类时,为什么包含最终的静态String或int是合法的,而包含其他对象却不合法? 例如: 编译时,得到以下信息: 为什么要区分?是因为String是不可变的吗?如果是这样,Integer.valueOf()也无效吗? 问题答案: 这是因为前两个静态成员分配给原始类型或String类型的编译时常量。 根据Java语言规范的第8.1.3节: 8.1.3。内部类和封闭实

  • 问题内容: 我的问题很简单: 编译器是否将final类中的所有方法都视为final本身?将关键字添加到最终类中的方法是否有效果? 我知道最终方法更有可能被内联,这就是我要问的原因。 提前致谢。 问题答案: 没错,final类中的所有方法都隐式为final。 看这里: “请注意,您也可以将整个类声明为final。声明为final的类不能被子类化。例如,在创建不可变类(如String类)时,这特别有用

  • 这是密码 输出量 我的问题是: 为什么jackson在完成构建后修改了没有setter的私有final字段?如果这是有意的,我该如何关闭它 非常感谢。

  • 本文向大家介绍Java Thread类的最终boolean isDaemon()方法(带示例),包括了Java Thread类的最终boolean isDaemon()方法(带示例)的使用技巧和注意事项,需要的朋友参考一下 线程类最终布尔 软件包java.lang.Thread.isDaemon()中提供了此方法。 此方法用于检查当前线程是否是守护程序线程。 守护程序线程是在后台运行的线程。 此方

  • 我的手在头发上。 我正在使用Apache PDFBox,因为我想在JAVA中逐行读取pdf文件并稍后处理内容。但是我有以下问题...我在单独的java程序(在main方法中)中使用了下面的代码,它在那里工作正常。但是,当我将其与石英调度程序结合在我的tomcat服务器小程序中使用时,会出现问题,我无法找出原因。请记住,我将下面的行从工作的单独测试程序复制粘贴到我自己的更大项目中,因此它是完全相同的