为什么Java不允许重写静态方法?

为什么不可能覆盖静态方法?

如果可能的话,请使用一个例子。


重写取决于有一个类的实例。 多态性的要点是,你可以继承一个类的子类,并且实现这些子类的对象对于在超类中定义的相同方法(并在子类中重写)将具有不同的行为。 静态方法不与任何类的任何实例关联,因此这个概念不适用。

推动Java设计的两个考虑因素影响了这一点。 其中一个问题是性能问题:Smalltalk对它的速度太慢了(垃圾回收和多态调用是其中的一部分),Java的创建者决心避免这种情况。 另一个决定是Java的目标受众是C ++开发人员。 使得静态方法的工作方式具有熟悉C ++程序员的好处,而且速度也非常快,因为不需要等到运行时才能确定要调用的方法。


我个人认为这是Java设计中的一个缺陷。 是的,是的,我了解非静态方法附加到实例,而静态方法附加到类等等。仍然,请考虑以下代码:

public class RegularEmployee {
    private BigDecimal salary;

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }

    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".02");
    }

    public BigDecimal calculateBonus() {
        return salary.multiply(getBonusMultiplier());
    }

    /* ... presumably lots of other code ... */
}

public class SpecialEmployee extends RegularEmployee {
    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".03");
    }
}

这段代码不会像你所期望的那样工作。 也就是说,SpecialEmployee像普通员工一样获得2%的奖金。 但是如果你删除了“静态”,那么SpecialEmployee就会获得3%的奖励。

(诚​​然,这个例子是糟糕的编码风格,因为在现实生活中,你可能希望奖金乘数在某个数据库中,而不是硬编码。但那是因为我不想让这个例子很多与该点无关的代码)。

我觉得你可能想让getBonusMultiplier是静态的,这似乎很合理。 也许你希望能够为所有类别的员工显示奖金乘数,而不需要在每个类别中都有一个员工实例。 搜索这种示例实例的重点是什么? 如果我们要创建一个新的员工类别并且没有任何员工分配给它,该怎么办? 这在逻辑上是一个静态函数。

但它不起作用。

是的,是的,我可以想出许多方法来重写上述代码以使其工作。 我的观点并不是它造成了一个无法解决的问题,而是它为粗心的程序员创造了一个陷阱,因为这种语言不像我认为合理的人会期望的那样行事。

也许如果我试图编写OOP语言的编译器,我很快就会明白为什么要实现它,以便可以重写静态函数将是困难的或不可能的。

或者,Java的行为方式可能有很好的理由。 任何人都可以指出这种行为的优势,某些类别的问题通过这种方式变得更容易? 我的意思是,不要把我指向Java语言规范,并说“看,这是记录它的行为”。 我知道。 但是,它有一个很好的理由说明它应该如此表现? (除了明显的“让它工作正确太难了......”)

更新

@VicKirk:如果你的意思是这是“糟糕的设计”,因为它不适合Java处理静态的问题,我的回答是,“呃,当然。” 正如我在我原来的帖子中所说的那样,它不起作用。 但是,如果你的意思是说这是一种糟糕的设计,从某种意义上讲,这种语言在某种程度上会出现根本性的错误,也就是说静态可以像虚拟功能一样被覆盖,这会以某种方式引入歧义,或者不可能有效地实施或者这样做,我回答说:“为什么?这个概念有什么问题?”

我想我给出的例子是一件很自然的事情,想要做。 我有一个具有不依赖于任何实例数据的函数的类,并且我可能非常合理地想独立于实例调用该函数,并且希望从实例方法中调用。 为什么这不适用? 多年来,我遇到过这种情况,相当多次。 在实践中,我通过使函数变成虚拟来解决它,然后创建一个静态方法,其唯一目的是将静态方法用虚拟实例传递给虚拟方法。 这似乎是一个非常迂回的方式去那里。


简短的答案是:完全可能,但Java不这样做。

以下是一些说明Java中当前状态的代码:

File Base.java

package sp.trial;
public class Base {
  static void printValue() {
    System.out.println("  Called static Base method.");
  }
  void nonStatPrintValue() {
    System.out.println("  Called non-static Base method.");
  }
  void nonLocalIndirectStatMethod() {
    System.out.println("  Non-static calls overridden(?) static:");
    System.out.print("  ");
    this.printValue();
  }
}

文件Child.java

package sp.trial;
public class Child extends Base {
  static void printValue() {
    System.out.println("  Called static Child method.");
  }
  void nonStatPrintValue() {
    System.out.println("  Called non-static Child method.");
  }
  void localIndirectStatMethod() {
    System.out.println("  Non-static calls own static:");
    System.out.print("  ");
    printValue();
  }
  public static void main(String[] args) {
    System.out.println("Object: static type Base; runtime type Child:");
    Base base = new Child();
    base.printValue();
    base.nonStatPrintValue();
    System.out.println("Object: static type Child; runtime type Child:");
    Child child = new Child();
    child.printValue();
    child.nonStatPrintValue();
    System.out.println("Class: Child static call:");
    Child.printValue();
    System.out.println("Class: Base static call:");
    Base.printValue();
    System.out.println("Object: static/runtime type Child -- call static from non-static method of Child:");
    child.localIndirectStatMethod();
    System.out.println("Object: static/runtime type Child -- call static from non-static method of Base:");
    child.nonLocalIndirectStatMethod();
  }
}

如果你运行这个(我使用Java 1.6在Mac上完成,使用Java 1.6),你会得到:

Object: static type Base; runtime type Child.
  Called static Base method.
  Called non-static Child method.
Object: static type Child; runtime type Child.
  Called static Child method.
  Called non-static Child method.
Class: Child static call.
  Called static Child method.
Class: Base static call.
  Called static Base method.
Object: static/runtime type Child -- call static from non-static method of Child.
  Non-static calls own static.
    Called static Child method.
Object: static/runtime type Child -- call static from non-static method of Base.
  Non-static calls overridden(?) static.
    Called static Base method.

在这里,唯一可能出乎意料的问题(以及问题的关键)似乎是第一个案例:

“运行时类型不用于确定调用哪个静态方法,即使使用对象实例( obj.staticMethod() )调用。”

和最后一种情况:

“从类的对象方法中调用静态方法时,所选的静态方法是可从类本身访问的方法,而不是定义对象的运行时类型的类。”

调用一个对象实例

静态调用在编译时解析,而非静态方法调用在运行时解析。 请注意,虽然静态方法是从父类继承的,但它们不会被子类覆盖。 如果您有其他预期,这可能会让您感到意外。

从对象方法中调用

对象方法调用使用运行时类型来解析,但静态(类)方法调用使用编译时(声明)类型来解析。

改变规则

为了改变这些规则,以便在示例中的最后一个调用叫做Child.printValue() ,静态调用必须在运行时提供一个类型,而不是编译器在声明的类中解析调用的对象(或上下文)。 然后,静态调用可以使用(动态)类型层次来解析调用,就像对象方法调用所做的那样。

这很容易实现(如果我们改变了Java:-O),并不是毫无道理的,但是它有一些有趣的考虑。

主要考虑的是我们需要决定哪些静态方法调用应该这样做。

目前,Java在obj.staticMethod()调用被ObjectClass.staticMethod()调用取代(通常带有警告)的语言中有这种“怪癖”。 [注意: ObjectClassobj的编译时类型]这些将是以这种方式重写的好候选对象,采用运行时类型的obj

如果我们这样做了,它会使方法体更难阅读:父类中的静态调用可能会动态“重新路由”。 为了避免这种情况,我们必须使用类名调用静态方法 - 这使得调用更明显地通过编译时类型层次结构解决(如现在)。

调用静态方法的其他方面都比较棘手: this.staticMethod()应该意味着相同obj.staticMethod()取的运行时类型this 。 然而,这可能会导致现有程序的一些头痛,这些程序调用(显然是本地的)静态方法而没有装饰(这可以等价于this.method() )。

那么staticMethod()调用staticMethod()呢? 我建议他们和今天一样,并使用当地的上下文来决定做什么。 否则会产生很大的混乱。 当然,这意味着如果method是非静态方法,则method()将意味着this.method() ,而ThisClass.method() if method是静态方法。 这是混淆的另一个来源。

其他考虑

如果我们改变了这种行为(并且使得静态调用可能是动态的非本地的),我们可能希望重新审视finalprivateprotected的含义作为类的static方法的限定符。 然后,我们都必须习惯这样一个事实: private staticpublic final方法不会被覆盖,因此可以在编译时安全地解决,并且作为本地引用来读取是“安全的”。

链接地址: http://www.djcxy.com/p/13823.html

上一篇: Why doesn't Java allow overriding of static methods?

下一篇: What is method hiding in Java? Even the JavaDoc explanation is confusing