Java为什么不允许覆盖静态方法?


534

为什么无法覆盖静态方法?

如果可能,请使用示例。


3
大多数OOP语言都不允许这样做。
jmucchiello

7
@jmucchiello:看看我的答案。我和您的想法相同,但是后来了解了Ruby / Smalltalk的“类”方法,因此还有其他一些真正的OOP语言可以做到这一点。
凯文·布洛克

5
@jmucchiello大多数OOP语言不是真正的OOP语言(我认为是Smalltalk)
mathk 2010年


1
可能是因为Java在编译时解析了对静态方法的调用。因此,即使您已经编写了代码Parent p = new Child(),然后p.childOverriddenStaticMethod()编译器也会Parent.childOverriddenStaticMethod()通过查看引用类型来解决该问题。
Manoj

Answers:


494

覆盖取决于拥有类的实例。多态性的目的是可以对一个类进行子类化,并且实现那些子类的对象对于在超类中定义的相同方法(并在子类中被重写)将具有不同的行为。静态方法未与类的任何实例相关联,因此该概念不适用。

影响Java设计的因素有两个。一个是对性能的关注:Smalltalk对其速度太慢提出了很多批评(垃圾回收和多态调用是其中的一部分),Java的创建者决心避免这种情况。另一个决定是Java的目标受众是C ++开发人员。使静态方法按其实际方式工作对C ++程序员来说是很有益的,而且速度非常快,因为不需要等到运行时就确定要调用哪种方法。


18
...但是在Java中只有“正确”。例如,Scala等效于“静态类”(称为objects)允许方法的重载。

32
Objective-C还允许重写类方法。
理查德

11
有一个编译时类型层次结构和一个运行时类型层次结构。询问为什么在存在静态方法调用的情况下,静态方法调用本身不利用运行时类型层次结构是很有意义的。在Java中,当从对象(obj.staticMethod())调用静态方法时会发生这种情况-这是允许的,并使用编译时类型。当静态调用位于类的非静态方法中时,“当前”对象可以是该类的派生类型,但是不考虑在派生类型上定义的静态方法(它们在运行时类型中)等级)。
史蒂夫·鲍威尔

18
我应该明确指出:这个概念不适用正确
史蒂夫·鲍威尔

13
这个答案虽然正确,但更类似于“它是如何”,而不是应该如何或更准确地如何满足OP的期望,从而满足我本人和其他人的期望。除了“就是这样”之外,没有任何具体的理由禁止覆盖静态方法。我个人认为这是一个缺陷。
RichieHH 2014年

186

我个人认为这是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's像普通员工一样获得2%的奖金。但是,如果您删除了“静态”,那么SpecialEmployee将获得3%的奖金。

(诚​​然,此示例的编码风格很差,因为在现实生活中,您可能希望奖金乘数位于某个地方的数据库中,而不是硬编码。但这只是因为我不想过多地讨论该示例与此无关的代码)。

在我看来,您可能想使getBonusMultiplier静态化似乎很合理。也许您希望能够显示所有类别员工的奖金乘数,而无需在每个类别中都有一个员工实例。搜索此类示例实例的目的是什么?如果我们正在创建一个新的员工类别并且尚未分配任何员工该怎么办?从逻辑上讲,这是一个静态函数。

但这是行不通的。

是的,是的,我可以想到许多方法来重写上述代码以使其工作。我的意思不是说它会产生无法解决的问题,而是会给粗心的程序员带来陷阱,因为这种语言的行为不像我认为的合理人所期望的那样。

也许,如果我尝试为OOP语言编写编译器,我会很快理解为什么很难或不可能实现它以覆盖静态函数。

也许有一些很好的理由说明Java会采取这种方式。谁能指出这种行为的优势,某种行为会因此而变得更加容易?我的意思是,不仅要指向Java语言规范并说“看,这是有据可查的”。我知道。但是,为什么它应该以这种方式表现呢?(除了明显的“使其正常工作太难了” ...)

更新资料

@VicKirk:如果您说这是“错误的设计”,因为它不适合Java处理静态的方式,我的回答是:“当然,嗯。” 正如我在原始帖子中所说,它不起作用。但是,如果您从某种意义上说这是一种错误的设计,那么某种语言在其中起作用就会产生根本性的错误,即像虚拟函数一样可以覆盖静态的语言,那么这将以某种方式引入歧义,或者不可能我会回答:“为什么?这个概念有什么问题?”

我认为我举的例子是很自然的事情。我有一个类,该类的功能不依赖于任何实例数据,并且我可能非常想独立于实例进行调用,并且希望从实例方法内进行调用。为什么这不起作用?多年来,我已经多次遇到这种情况。在实践中,我通过使函数虚拟化来解决它,然后创建一个静态方法,该方法的唯一目的是成为一个静态方法,该方法将调用与虚拟实例一起传递给虚拟方法。那似乎是到达那里的一种非常round回的方式。


11
@Bemrose:但这就是我的观点:为什么不应该允许我这样做?也许我对“静态”应该做什么的直观概念与您的想法不同,但是基本上我认为静态是一种可以是静态的方法,因为它不使用任何实例数据,而应该是静态的,因为您可能想要独立于实例调用它。静态显然与类相关:我希望将Integer.valueOf绑定到Integers,并且将Double.valueOf绑定到Doubles。
杰伊2010年

9
@ewernli&Bemrose:是的,就是这样。我没有在争论。由于示例中的代码不起作用,因此我当然不会尝试编写它。我的问题是为什么会这样。(我担心这会变成我们不交流的其中一种对话。“对不起,推销员先生,我可以用红色换成其中一种吗?”“否,它的价格为5美元。”“是的,我知道这会花费5美元,但是我能得到红色的吗?”“先生,我刚刚告诉过你它要花5美元。”“好吧,我知道价格,但是我在询问颜色。”“我已经告诉过你价格了!等等)
杰伊

6
我认为最终该代码令人困惑。考虑是否将实例作为参数传递。然后,您说运行时实例应规定要调用哪个静态方法。这基本上使整个独立的层次结构与现有实例平行。现在,如果子类将相同的方法签名定义为非静态的怎么办?我认为规则会使事情变得很复杂。Java试图避免的正是这些语言复杂性。
Yishai 2010年

6
@Yishai:RE“运行时实例指示要调用哪个静态方法”:确实如此。我不明白为什么您不能用静态方法做任何事情,却不能使用虚拟方法做任何事情。“单独的层次结构”:我想说,使其成为同一层次结构的一部分。为什么静态不包含在同一层次结构中?“子类定义了相同的非静态签名”:我认为这是非法的,就像让一个子类覆盖具有相同签名但返回类型不同的函数或不抛出所有异常一样是非法的父母抛出,或者范围缩小。
杰伊,2010年

28
我认为Jay有一点要说-当我发现静电不能被覆盖时,它也让我感到惊讶。部分原因是,如果我有一个带有方法的someStatic()A,而B扩展了A,然后B.someMethod() 绑定到A 中的方法。如果我随后将其添加someStatic()到B,则调用代码仍然会调用,A.someStatic()直到我重新编译调用代码为止。同样令我惊讶的是,它bInstance.someStatic()使用的是bInstance 的声明类型,而不是运行时类型,因为它在编译时未链接,因此A bInstance; ... bInstance.someStatic()如果B.someStatic()存在,则调用A.someStatic()。
劳伦斯·多尔

42

简短的答案是:完全有可能,但是Java没有做到这一点。

这是一些说明当前情况的代码 Java:

档案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();
  }
}

如果运行此程序(我是在Mac上,是使用Java 1.6在Eclipse上,在Mac上运行的),则会得到:

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()(通常带有警告)。[ 注意: ObjectClass是。的编译时类型obj。]采用。的运行时类型,这些将是以这种方式进行覆盖的不错的选择obj

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

调用静态方法的其他方式更为棘手:this.staticMethod()obj.staticMethod()与运行时类型的含义相同this。但是,这可能会给现有程序带来一些麻烦,这些现有程序会调用(显然是本地的)静态方法而不进行修饰(可以说等效于this.method())。

那么没有装饰的电话staticMethod()呢?我建议他们像今天一样做,并使用本地类上下文来决定要做什么。否则会引起极大的混乱。当然,method()这意味着this.method()如果method是非静态方法,并且ThisClass.method()如果method是静态方法,则意味着。这是造成混乱的另一个原因。

其他注意事项

如果我们更改了此行为(并使静态调用可能动态地非本地调用),则可能需要重新访问和的含义finalprivate并将其protected作为static类方法上的限定符。然后,我们所有人都将不得不适应一个事实,即private staticand public final方法不会被覆盖,因此可以在编译时安全地解决,并且作为本地引用是“安全的”。


“如果这样做的话,将使方法体难以阅读:父类中的静态调用可能会动态地“重新路由”。” 是的,但这正是现在普通的非静态函数调用会发生的情况。通常,这被吹捧为普通虚拟功能的一个积极特性,而不是问题。
周杰伦

25

其实我们错了。
尽管Java默认情况下不允许您覆盖静态方法,但是如果仔细查看Java中有关Class和Method类的文档,您仍然可以通过以下解决方法找到一种方法来模拟覆盖静态方法:

import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;

class RegularEmployee {

    private BigDecimal salary = BigDecimal.ONE;

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".02");
    }
    public BigDecimal calculateBonus() {
        return salary.multiply(this.getBonusMultiplier());
    }
    public BigDecimal calculateOverridenBonus() {
        try {
            // System.out.println(this.getClass().getDeclaredMethod(
            // "getBonusMultiplier").toString());
            try {
                return salary.multiply((BigDecimal) this.getClass()
                    .getDeclaredMethod("getBonusMultiplier").invoke(this));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
    // ... presumably lots of other code ...
}

final class SpecialEmployee extends RegularEmployee {

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

public class StaticTestCoolMain {

    static public void main(String[] args) {
        RegularEmployee Alan = new RegularEmployee();
        System.out.println(Alan.calculateBonus());
        System.out.println(Alan.calculateOverridenBonus());
        SpecialEmployee Bob = new SpecialEmployee();
        System.out.println(Bob.calculateBonus());
        System.out.println(Bob.calculateOverridenBonus());
    }
}

结果输出:

0.02
0.02
0.02
0.03

我们试图实现的目标:)

即使我们将第三个变量Carl声明为RegularEmployee并分配给它SpecialEmployee的实例,在第一种情况下我们仍将具有RegularEmployee方法的调用,在第二种情况下仍将具有SpecialEmployee方法的调用

RegularEmployee Carl = new SpecialEmployee();

System.out.println(Carl.calculateBonus());
System.out.println(Carl.calculateOverridenBonus());

只看输出控制台:

0.02
0.03

;)


9
是的,思考几乎是唯一可以做的一件事-但问题并不完全是-在这里有用

1
到目前为止,这个答案是我在所有Java主题中看到的最大的hack。仍然阅读它很有趣:)
Andrejs

19

静态方法被JVM视为全局方法,根本没有绑定到对象实例。

从概念上讲,如果可以从类对象中调用静态方法(例如在Smalltalk之类的语言中),则可能是可行的,但Java并非如此。

编辑

您可以重载静态方法,没关系。但是您不能覆盖静态方法,因为class不是一流的对象。您可以在运行时使用反射来获取对象的类,但是获取的对象与类层次结构不平行。

class MyClass { ... }
class MySubClass extends MyClass { ... }

MyClass obj1 = new MyClass();
MySubClass obj2 = new MySubClass();

ob2 instanceof MyClass --> true

Class clazz1 = obj1.getClass();
Class clazz2 = obj2.getClass();

clazz2 instanceof clazz1 --> false

您可以对这些类进行反思,但是到此为止。您不会使用来调用静态方法clazz1.staticMethod(),而是使用MyClass.staticMethod()。静态方法未绑定到对象,因此静态方法中this也没有或的概念super。静态方法是全局函数。结果,也没有多态性的概念,因此,方法覆盖是没有意义的。

但这可能是可能的,如果MyClass在运行时是一个对象,您可以在该对象上调用方法,如Smalltalk中一样(或者,正如评论中提到的JRuby,但我对JRuby一无所知)。

哦,是的。还有一件事。您可以通过一个对象调用一个静态方法,obj1.staticMethod()但是确实MyClass.staticMethod()要避免使用语法上的糖。通常在现代IDE中会发出警告。我不知道他们为什么允许这个快捷方式。


5
甚至像Ruby这样的许多现代语言都有类方法,并允许覆盖它们。
Chandra Sekar

3
在Java中,类确实作为对象存在。请参见“类”类。我可以说myObject.getClass(),它将为我返回适当类对象的实例。
杰伊,2010年

5
您只会得到该类的“描述”,而不是该类本身。但是区别是微妙的。
ewernli 2010年

您仍然有类,但是它隐藏在VM中(在类加载器附近),用户几乎无法访问它。
mathk 2010年

clazz2 instanceof clazz1正确使用class2.isAssignableFrom(clazz1),您可以改为使用,我相信在您的示例中该返回true。
西蒙·佛斯伯格

14

通过动态分派可以实现方法覆盖,这意味着对象的声明类型并不决定其行为,而是确定其运行时类型:

Animal lassie = new Dog();
lassie.speak(); // outputs "woof!"
Animal kermit = new Frog();
kermit.speak(); // outputs "ribbit!"

即使lassiekermit都被声明为type的对象Animal,它们的行为(方法.speak())也有所不同,因为动态调度只会在运行时(而不是在编译时)将方法调用绑定.speak()到实现。

现在,这里的static关键字开始变得有意义了:单词“ static”是“ dynamic”的反义词。因此,您不能覆盖静态方法的原因是因为静态成员上没有动态分派- 因为静态字面意思是“非动态”。如果它们是动态调度的(因此可以被覆盖),则该static关键字将不再有意义。


11

是。实际上,Java允许重写静态方法,从理论上讲,如果您不重写Java中的静态方法,那么它将可以编译和平稳运行,但是它将失去多态性,而后者是Java的基本属性。您将无处不在,无法尝试编译和运行。您将得到答案。例如,如果您有动物类和静态方法eat(),并且在其子类中覆盖了该静态方法,则将其称为Dog。然后,无论何时将Java对象的Dog对象分配给Animal Reference并调用eat(),都应该已经调用过Dog的eat(),但是在静态重写中,将会调用Animal的eat()。

class Animal {
    public static void eat() {
        System.out.println("Animal Eating");
    }
}

class Dog extends Animal{
    public static void eat() {
        System.out.println("Dog Eating");
    }
}

class Test {
    public static void main(String args[]) {
       Animal obj= new Dog();//Dog object in animal
       obj.eat(); //should call dog's eat but it didn't
    }
}


Output Animal Eating

根据Java的多态原则,输出应为Dog Eating
但是结果却有所不同,因为要支持多态,Java使用了后期绑定,这意味着仅在运行时才调用方法,而在静态方法中则不会。在静态方法中,编译器会在编译时而不是在运行时调用方法,因此我们根据引用而不是根据对象来获取方法,而引用是根据包含的,因此您可以说实际上它支持静态覆盖,但是从理论上讲,它不支持没错


3
从对象调用静态方法是一种不好的做法。
德米特里·扎戈鲁金

6

保留为实例成员保留以支持多态行为。静态类成员不属于特定实例。相反,静态成员属于该类,因此不支持覆盖,因为子类仅继承受保护的实例和公共实例成员,而不继承静态成员。您可能需要定义一个界面并研究工厂和/或策略设计模式,以评估替代方法。


1
您是否没有读过其他任何已经涵盖此问题的答案,并清楚表明这些还不足以在概念层面上消除压倒性的静态因素。我们知道它不起作用。这是完全“干净的欲望的静态方法的覆盖,实际上它在许多其他语言是可能的。
RichieHH

理查德,让我们假设一分钟,当我在4年前回答这个问题时,大多数答案都还没有发布,所以请不要做个笨蛋!无需断言我没有仔细阅读。此外,您是否未读到我们仅在讨论关于Java的覆盖。谁在乎其他语言的可能性。没关系 去巨魔其他地​​方。您的评论未对该线程添加任何有价值的东西。
雅典Holloway

6

在Java中(以及许多OOP语言,但我不能一概而论;有些根本没有静态语言),所有方法都具有固定的签名-参数和类型。在虚拟方法中,第一个参数是隐含的:对对象本身的引用,当从对象内部调用时,编译器会自动添加this

静态方法没有区别-它们仍然具有固定的签名。但是,通过将方法声明为静态,您已经明确声明了编译器在该签名的开头不得包含隐含的对象参数。因此,任何其他调用该代码的代码都不得尝试对堆栈上的对象进行引用。如果这样做的话,则方法执行将无法进行,因为参数在堆栈上的位置会错误(移位了一位)。

由于两者之间的差异;虚方法总是引用上下文对象(即this),因此可以引用堆中属于该对象实例的任何内容。但是对于静态方法,由于没有传递引用,因此该方法无法访问任何对象变量和方法,因为上下文是未知的。

如果您希望Java更改定义,以便为每种方法(静态或虚拟方法)传递对象上下文,那么实际上您将只有虚拟方法。

正如有人在对操作的评论中问到的那样-您想要此功能的原因和目的是什么?

我不太了解Ruby,正如OP提到的那样,我做了一些研究。我看到在Ruby中,类实际上是一种特殊的对象,并且可以创建(甚至动态地)创建新方法。类是Ruby中的完整类对象,而不是Java中的类。这只是使用Java(或C#)时必须接受的东西。这些不是动态语言,尽管C#添加了一些动态形式。实际上,据我所知,Ruby没有“静态”方法-在那种情况下,这些是单例类对象上的方法。然后,您可以使用新类覆盖此单例,并且上一个类对象中的方法将调用在新类中定义的方法(正确吗?)。因此,如果您在原始类的上下文中调用方法,则该方法仍只会执行原始静态数据,但是调用派生类中的方法,则会从父类或子类中调用方法。有趣的是,我可以看到一些价值。这需要不同的思维方式。

由于您使用Java进行工作,因此您将需要适应这种工作方式。他们为什么这样做?好吧,可能是基于现有的技术和知识来提高当时的性能。计算机语言在不断发展。回到足够远的地方,没有OOP之类的东西。将来,还会有其他新想法。

编辑:另一条评论。现在,我看到了这些差异,并且我自己是Java / C#开发人员,我可以理解,如果您来自Ruby之类的语言,那么从Java开发人员那里得到的答案为什么会造成混淆。Java static方法与Ruby class方法不同。相反,Java开发人员将很难理解这一点,相反,那些主要使用Ruby / Smalltalk之类的语言的人也将很难理解。我还看到Java还使用“类方法”作为谈论静态方法的另一种方式,但是Ruby用不同的方式使用了同一术语,这也将给我带来极大的困惑。Java没有Ruby样式类方法(很抱歉);Ruby没有Java样式的静态方法,这些方法实际上只是C中的旧程序样式函数。

顺便说一句-感谢您的提问!今天,我为我学到了一些有关类方法(Ruby风格)的新知识。


1
当然,Java没有理由不能将“ Class”对象作为隐藏参数传递给静态方法。它只是没有设计要这样做。
jmucchiello

6

好吧……如果您从Java中重写方法的行为角度考虑,答案是否定的。但是,如果您尝试覆盖静态方法,则不会出现任何编译器错误。这意味着,如果您尝试覆盖,Java不会阻止您这样做。但您肯定不会获得与非静态方法相同的效果。Java中的覆盖只是意味着将根据对象的运行时类型而不是对象的编译时类型来调用特定方法(对于覆盖的静态方法就是这种情况)。好吧...关于它们为何表现异常的任何猜测?由于它们是类方法,因此始终仅在编译时使用编译时类型信息来解决对它们的访问。

示例:让我们尝试看看如果重写静态方法会发生什么:

class SuperClass {
// ......
public static void staticMethod() {
    System.out.println("SuperClass: inside staticMethod");
}
// ......
}

public class SubClass extends SuperClass {
// ......
// overriding the static method
public static void staticMethod() {
    System.out.println("SubClass: inside staticMethod");
}

// ......
public static void main(String[] args) {
    // ......
    SuperClass superClassWithSuperCons = new SuperClass();
    SuperClass superClassWithSubCons = new SubClass();
    SubClass subClassWithSubCons = new SubClass();

    superClassWithSuperCons.staticMethod();
    superClassWithSubCons.staticMethod();
    subClassWithSubCons.staticMethod();
    // ...
}
}

输出:-
SuperClass: inside staticMethod
SuperClass: inside staticMethod
SubClass: inside staticMethod

注意输出的第二行。如果已覆盖staticMethod,则此行应该与第三行相同,因为我们在运行时类型的对象上以“ SubClass”而不是“ SuperClass”调用“ staticMethod()”。这确认了静态方法始终仅使用其编译时类型信息来解析。


5

通常,不允许“覆盖”静态方法是没有意义的,因为没有很好的方法来确定在运行时调用哪个方法。以Employee为例,如果我们调用RegularEmployee.getBonusMultiplier()-应该执行哪个方法?

就Java而言,可以想象一种语言定义,只要通过对象实例调用静态方法,就可以“覆盖”静态方法。但是,所有这些操作将是重新实现常规的类方法,从而在没有真正增加任何好处的情况下为该语言添加冗余。


2
凭直觉,我认为它应该像虚函数一样工作。如果B扩展了A并且A和B都具有名为doS​​tuff的虚函数,则编译器知道A的实例应使用A.doStuff,而B的实例应使用B.doStuff。为什么对静态函数不能做同样的事情?毕竟,编译器知道每个对象是哪个类的实例。
杰伊

Erm ... Jay,无需(通常不)在实例上调用静态方法……
优点

2
@meriton,但然后变得更加容易,不是吗?如果使用类名调用静态方法,则应使用适合该类的方法。
CPerkins'2

但是,最重要的是为您做的。如果调用A.doStuff(),则应使用在“ B扩展A”中覆盖的版本还是在“ C扩展A”中覆盖的版本。而且,如果您使用C或B,则无论如何都在调用这些版本...无需覆盖。
PSpeed 2010年

@meriton:的确,通常不使用实例调用静态方法,但是我认为这是因为鉴于当前的Java设计,此类调用没有任何用处!我建议替代设计可能是一个更好的主意。顺便说一句,在非常真实的意义上,静态函数通常是通过实例来调用的:从虚拟函数内部调用静态函数时。然后,您隐式获得this.function(),即当前实例。
杰伊,2010年

5

通过覆盖,我们可以根据对象类型创建多态性质。静态方法与对象无关。因此,java无法支持静态方法重写。


5

我喜欢Jay的评论(https://stackoverflow.com/a/2223803/1517187),并对其加倍。
我同意这是Java的不良设计。
正如我们在前面的评论中看到的,许多其他语言都支持重写静态方法。我觉得Jay也像我一样从Delphi来到Java。
Delphi(对象Pascal)是第一种实现OOP的语言。
显然,许多人都有使用该语言的经验,因为它是过去编写商业GUI产品的唯一语言。并且-是的,我们可以在Delphi中覆盖静态方法。实际上,Delphi中的静态方法称为“类方法”,而Delphi具有“ Delphi静态方法”的不同概念,即早期绑定的方法。要覆盖必须使用后期绑定的方法,请声明“虚拟”指令。因此,这非常方便且直观,我希望Java能够做到这一点。


3

重写静态方法有什么用处。您不能通过实例调用静态方法。

MyClass.static1()
MySubClass.static1()   // If you overrode, you have to call it through MySubClass anyway.

编辑:看来,通过对语言设计的不幸监督,您可以通过实例调用静态方法。通常没有人这样做。我的错。


8
“您不能通过实例调用静态方法”实际上,Java的一个怪癖是您可以通过实例调用静态方法,即使这是一个非常糟糕的主意。
Powerlord

1
实际上,Java确实允许通过实例访问静态成员:请参见Java中的静态变量
Richard JP Le Guen 2012年

但是,当您执行此操作时,适当的现代IDE会生成警告,因此至少可以捕获该警告,而Oracle可以保持向后兼容。
金比

1
能够通过实例调用静态方法在概念上没有错。这是为了争辩而争辩。为什么像Date实例之类的东西不调用其自己的静态方法,通过调用接口将实例数据传递给函数?
RichieHH 2014年

@RichieHH这不是他们所想的。问题是为什么允许调用variable.staticMethod()而不是在Class.staticMethod()哪里variable声明类型的变量Class。我同意这是错误的语言设计。
fishinear

3

Java中的重写只是意味着将根据对象的运行时类型而不是对象的编译时类型来调用特定方法(对于覆盖的静态方法就是这种情况)。由于静态方法是类方法,因此它们不是实例方法,因此它们与哪个引用指向哪个对象或实例无关,因为静态方法的性质使其属于特定类。您可以在子类中重新声明它,但是该子类对父类的静态方法一无所知,因为正如我所说,它仅特定于已声明其的类。使用对象引用访问它们只是Java设计人员的一种额外的自由,我们当然不应该只在他们限制更多细节和示例时才考虑停止这种实践。 http://faisalbhagat.blogspot.com/2014/09/method-overriding-and-method-hiding.html


3

通过覆盖,您可以实现动态多态。当说覆盖静态方法时,您要使用的单词是矛盾的。

静态说-编译时,覆盖用于动态多态。两者本质上是相反的,因此不能一起使用。

当程序员使用对象并访问实例方法时,就会出现动态多态行为。JRE将根据您使用的对象类型来映射不同类的不同实例方法。

当您说重写静态方法时,我们将使用类名来访问静态方法,该类名将在编译时链接,因此在运行时没有将方法与静态方法链接的概念。因此,术语“覆盖”静态方法本身没有任何意义。

注意:即使您使用对象访问类方法,java编译器仍然足够智能地找到它,并且将执行静态链接。


在很多情况下,在运行时实际上是从包含类的实例调用静态方法的情况下,情况并非如此,因此确定要调用函数的哪个实例是完全可行的。
RichieHH 2014年

静态并不意味着编译时间,静态意味着它是绑定到类而不是任何特定对象的。它比创建类工厂Box.createBox更有意义,除了静态比有意义BoxFactory.createBox,并且在需要进行错误检查构造而不会引发异常时(这是构造函数不能失败,它们只能杀死进程/抛出)是一种不可避免的模式。例外),而静态方法可以在失败时返回null,甚至接受成功/错误回调以编写类似hastebin.com/codajahati.java的内容
德米特里

2

这个问题的答案很简单,标记为static的方法或变量仅属于该类,因此static方法不能在子类中继承,因为它们仅属于超类。


1
嗨,G4uKu3_Gaurav。感谢您决定捐款。但是,我们通常期望比这更长,更详细的答案。
DJClayworth

@DJClayworth应按照此链接,详细的解答 geeksforgeeks.org/...
g1ji

感谢您的链接。实际上,我在这里是为了对网站的新手有所帮助,向不习惯它的人解释该网站的工作方式,不是因为我需要回答这个问题。
DJClayworth

1

简单的解决方案:使用单例实例。它将允许重写和继承。

在我的系统中,我具有SingletonsRegistry类,该类返回传递的Class的实例。如果找不到实例,则创建它。

Haxe语言课:

package rflib.common.utils;
import haxe.ds.ObjectMap;



class SingletonsRegistry
{
  public static var instances:Map<Class<Dynamic>, Dynamic>;

  static function __init__()
  {
    StaticsInitializer.addCallback(SingletonsRegistry, function()
    {
      instances = null;
    });

  } 

  public static function getInstance(cls:Class<Dynamic>, ?args:Array<Dynamic>)
  {
    if (instances == null) {
      instances = untyped new ObjectMap<Dynamic, Dynamic>();      
    }

    if (!instances.exists(cls)) 
    {
      if (args == null) args = [];
      instances.set(cls, Type.createInstance(cls, args));
    }

    return instances.get(cls);
  }


  public static function validate(inst:Dynamic, cls:Class<Dynamic>)
  {
    if (instances == null) return;

    var inst2 = instances[cls];
    if (inst2 != null && inst != inst2) throw "Can\'t create multiple instances of " + Type.getClassName(cls) + " - it's singleton!";
  }

}

非常酷,这是我第一次听说Haxe编程语言:)
cyc115

1
用Java作为类本身的静态方法可以更好地实现这一点。Singleton.get()。注册表只是样板操作,它排除了类上的GC。
劳伦斯·多尔

您是对的,这是一个经典的解决方案。我不完全记得我为什么选择注册表,可能有某种思想框架导致了这一结果。
Raivo Fishmeister 2014年

1

静态方法,变量,块或嵌套类属于整个类而不是对象。

Java中的方法用于公开对象/类的行为。在这里,由于方法是静态的(即,静态方法仅用于表示类的行为。)更改/覆盖整个类的行为将违反面向对象编程的基本支柱之一即高内聚性的现象。。(请记住,构造函数是Java中的一种特殊方法。)

高凝聚力 -一堂课只能发挥一种作用。例如:汽车类应仅生产汽车对象,而不生产自行车,卡车,飞机等。但是汽车类可能具有仅属于自身的某些功能(行为)。

因此,在设计Java编程语言时。语言设计师认为,仅通过使方法本质上是静态的,允许开发人员将类的某些行为保留给自己。


下面的代码尝试覆盖静态方法,但不会遇到任何编译错误。

public class Vehicle {
static int VIN;

public static int getVehileNumber() {
    return VIN;
}}

class Car extends Vehicle {
static int carNumber;

public static int getVehileNumber() {
    return carNumber;
}}

这是因为,在这里我们不覆盖方法,而只是在重新声明它。Java允许重新声明方法(静态/非静态)。

从Car类的getVehileNumber()方法中删除static关键字将导致编译错误,因为我们正在尝试更改仅属于Vehicle类的static方法的功能。

同样,如果将getVehileNumber()声明为final,则代码将无法编译,因为final关键字限制了程序员重新声明该方法。

public static final int getVehileNumber() {
return VIN;     }

总体而言,这取决于软件设计人员在哪里使用静态方法。我个人更喜欢使用静态方法来执行某些操作,而无需创建类的任何实例。其次,要隐藏外界的课堂行为。


1

这是一个简单的解释。静态方法与类相关联,而实例方法与特定对象相关联。覆盖允许调用与特定对象关联的覆盖方法的不同实现。因此,覆盖甚至不与对象关联而是首先与类本身关联的静态方法是违反直觉的。因此,不能基于调用该对象的对象来覆盖静态方法,它将始终与创建该对象的类相关联。


拥有public abstract IBox createBox();内部IBox界面如何反直观?Box可以实现IBox来覆盖createBox,并在有效的IBox中创建对象,否则返回null。构造函数无法返回“ null”,因此您被迫(1)使用异常EVERYWHERE(我们现在要做什么),或(2)创建工厂类来实现我之前所说的内容,但对新手或专家都没有意义Java(现在我们也这样做)。静态的未实现方法可以解决此问题。
德米特里

-1

现在看到上面的答案,每个人都知道我们不能覆盖静态方法,但是不应误解从子类访问静态方法概念

如果子类中定义的新静态方法尚未隐藏该静态方法,则可以使用子类引用访问父类的静态方法。

例如,请参见下面的代码:-

public class StaticMethodsHiding {
    public static void main(String[] args) {
        SubClass.hello();
    }
}


class SuperClass {
    static void hello(){
        System.out.println("SuperClass saying Hello");
    }
}


class SubClass extends SuperClass {
    // static void hello() {
    // System.out.println("SubClass Hello");
    // }
}

输出:-

SuperClass saying Hello

Java的甲骨文文档和搜索在子类中,你可以做有关的子类的静态方法隐藏的细节。

谢谢


-3

以下代码显示了可能的情况:

class OverridenStaticMeth {   

static void printValue() {   
System.out.println("Overriden Meth");   
}   

}   

public class OverrideStaticMeth extends OverridenStaticMeth {   

static void printValue() {   
System.out.println("Overriding Meth");   
}   

public static void main(String[] args) {   
OverridenStaticMeth osm = new OverrideStaticMeth();   
osm.printValue();   

System.out.println("now, from main");
printValue();

}   

} 

1
不,不是。静态声明的类型osmOverridenStaticMethOverrideStaticMeth
劳伦斯·多尔

2
另外,在编程<big grin>时,我将尽量避免使用过多的Meth。
劳伦斯·多尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.