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

sgo*_*les 519 java static static-methods overriding

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

如果可能,请举例说明.

Nat*_*hes 480

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

推动Java设计的两个因素影响了这一点.一个是对性能的担忧:有很多人批评Smalltalk关于它太慢(垃圾收集和多态调用是其中的一部分)而Java的创建者决心避免这种情况.另一个决定是Java的目标受众是C++开发人员.使静态方法按照它们的方式工作有利于熟悉C++程序员并且也非常快,因为没有必要等到运行时才能确定要调用哪种方法.

  • Objective-C还允许**重写**类方法. (31认同)
  • ...但在Java中只是"正确".例如,Scala相当于"静态类"(称为"对象")允许重载方法. (17认同)
  • 我应该说得很清楚:*这个概念不适用*是不正确的. (17认同)
  • 这个答案虽然正确,但更类似于"它是如何"而不是它应该如何或更准确地如何才能满足OP的期望,因此在这里,我自己和其他人.除了"就是这样"之外,没有具体的理由不允许覆盖静态方法.我认为这是个人缺陷. (11认同)
  • 有一个编译时类型层次结构和一个运行时类型层次结构.在没有静态方法调用的情况下,为什么静态方法调用不能利用运行时类型层次结构是完全合理的.在Java中,当从对象(`obj.staticMethod()`)调用静态方法时会发生这种情况 - 这是允许的并使用编译时类型.当静态调用在类的非静态方法中时,"当前"对象可以是类的派生类型 - 但是不考虑在派生类型上定义的静态方法(它们在运行时类型中)层次). (10认同)
  • @Steve:通过"概念不适用"我的意思是,尝试用OO或类方法来考虑静态方法是没有帮助的.至于*为什么*,它是那些性能攻击之一,比如拥有原始类型,Java的创建者试图避免代表像Smalltalk那样缓慢的代表.是否有助于修改这些方面的答案? (4认同)
  • `"静态方法不与类的任何实例相关联,因此该概念不适用."`.废话,它与类本身相关联,因此让IBox具有方法"public static IBox createBox"是有意义的,该方法被实现"IBox"的"Box"对象覆盖.如果没有这个,我们就不得不创建"​​不自然的"工厂类,或者在构造函数中失败(因为我们不能让静态方法返回对象,并依赖构造函数,它不能返回null,这会促进不必要的异常).如果Box可以覆盖createBox,它可以消除所有这些. (3认同)
  • 我认为这个答案没有回答这个问题——“为什么不呢?” 原因不明,而且这个概念在 Java 中并不适用。 (2认同)
  • @NathanHughes,这是一个后续问题:从你的答案和下面的其他问题来看,似乎无法"覆盖"静态方法的原因取决于Java中"覆盖"一词的定义.因为"覆盖"意味着"根据实际对象数据类型更改行为,而不管引用数据类型"和静态方法,根据定义,不要更改引用数据类型中的行为,然后不能使用匹配签名对静态方法进行SAY在子类型中覆盖超类型方法.因此,我们说它是"隐藏"超类型方法. (2认同)

Jay*_*Jay 181

我个人认为这是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");
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码无法正常工作.也就是说,SpecialEmployee就像普通员工一样获得2%的奖金.但如果你删除"静态",那么SpecialEmployee就会获得3%的奖励.

(不可否认,这个例子的编码风格很差,因为在现实生活中你可能希望奖金乘数在某个地方而不是硬编码.但那只是因为我不想让这个例子陷入困境与该点无关的代码.)

我觉得你可能想让getBonusMultiplier保持静态,这似乎很合理.也许您希望能够为所有类别的员工显示奖金乘数,而无需在每个类别中都有员工实例.搜索这样的示例实例有什么意义?如果我们正在创建一个新类别的员工并且还没有分配任何员工呢?这在逻辑上是一个静态函数.

但它不起作用.

是的,是的,我可以想到任何方法来重写上面的代码以使其工作.我的观点并不是它创造了一个无法解决的问题,而是它为那些粗心的程序员创造了一个陷阱,因为这种语言并不像我认为合理的人所期望的那样.

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

或者也许有一些很好的理由说明Java的行为方式.任何人都可以指出这种行为的优势,某些类别的问题由此变得更容易吗?我的意思是,不要只指向Java语言规范并说"看,这是记录它的行为".我知道.但它有一个很好的理由为什么它应该这样做?(除了显而易见的"让它正常工作太难了"......)

更新

@VicKirk:如果你的意思是"糟糕的设计",因为它不适合Java处理静态的方式,我的回答是,"嗯,当然,呃." 正如我在原帖中所说,它不起作用.但是,如果你的意思是设计不好,那么这种语言会有一些根本性的错误,即静态可以像虚函数一样被覆盖,这会以某种方式引入歧义或者不可能我回答说:"为什么?这个概念出了什么问题?"

我认为我给出的例子是一件很自然的事情.我有一个类,它有一个不依赖于任何实例数据的函数,我可能非常合理地想要独立于实例调用,以及想要在实例方法中调用.为什么这不起作用?这些年来,我经常遇到这种情况.在实践中,我通过使函数虚拟化来创建静态方法,然后创建一个静态方法,其生命中唯一的目的是将静态方法传递给具有虚拟实例的虚方法.这似乎是一个非常迂回的方式到达那里.

  • 我认为周杰伦有一点 - 当我发现静态不能被覆盖时,它也让我感到惊讶.部分因为如果我有一个方法`someStatic()`和B扩展A,那么`B.someMethod()`*绑定*到A中的方法.如果我随后将`someStatic()`添加到B,调用代码仍调用`A.someStatic()`直到我重新编译调用代码.另外让我感到惊讶的是`bInstance.someStatic()`使用*声明的*类型的bInstance,而不是运行时类型,因为它在编译而不是链接时绑定,所以`A bInstance; ...如果B.someStatic()存在,则bInstance.someStatic()`调用A.someStatic(). (27认同)
  • @Bemrose:但这就是我的观点:为什么不允许我这样做?也许我对"静态"应该做什么的直观概念与你的不同,但基本上我认为静态作为一种可以是静态的方法,因为它不使用任何实例数据,并且它应该是静态的,因为你可能想要独立于实例调用它.静态显然与一个类绑定:我希望Integer.valueOf与Integers绑定,Double.valueOf与双打绑定. (10认同)
  • @ewernli&Bemrose:是的,就是这样.我不是在辩论.由于我的示例中的代码不起作用,当然我不会尝试编写它.我的问题是为什么会这样.(我担心这会转变为我们之间没有沟通的那些谈话之一."对不起,勒克斯曼先生,我可以把它们中的一个换成红色吗?""不,它的价格是5美元.""是的,我知道它的成本5美元,但我可以得到一个红色的吗?""先生,我刚刚告诉你这个价格是5美元.""好的,我知道价格,但我问的颜色.""我已经告诉你价格!"等等.) (8认同)
  • 我认为最终这段代码令人困惑.考虑实例是否作为参数传递.然后你说运行时实例应该指定调用哪个静态方法.这基本上使整个单独的层次结构与现有实例1并行.现在,如果子类定义与非静态相同的方法签名,该怎么办?我认为这些规则会使事情变得相当复杂.正是这些语言并发症是Java试图避免的. (5认同)
  • @Yishai:RE"运行时实例指示调用哪个静态方法":完全正确.我不明白为什么你不能用虚拟的静态做任何事情."单独的层次结构":我会说,让它成为同一层次结构的一部分.为什么静态不包含在同一层次结构中?"Subsclass定义了相同的非静态签名":我认为这是非法的,就像它有一个子类覆盖具有相同签名但不同返回类型的函数是非法的,或者不抛出所有异常父母抛出,或者缩小范围. (5认同)
  • PS我不想永远绕过争论.如果我们不同意,在某些时候我们只能说,"好吧,我们不同意,也不会说服对方." 但如果你们两个想继续交易评论,我就是游戏. (4认同)
  • @Jay:老实说,你不应该.我不知道为什么Java的开发人员决定这样做.它基本上是一个编译技巧,正如vickirk暗示的那样. (3认同)
  • 但最终,我发现自己同意vickirk:"当然,如果你需要一个对象实例来解决调用它的静态方法,那就不是一个静态方法".我希望Java从未允许通过实例引用进行静态调用,我希望他们现在可以添加一个编译器lint开关来为它发出警告. (3认同)
  • @SoftwareMonkey:我们回到"是"与"应该是".是的,你说的是Java是怎样的.我不是在争论.但我没有理由不选择基于运行时类型的静态方法,就像它根据运行时类型选择虚方法一样.为什么静态方法不能与虚拟方法一起包含在指针表中?是的,这意味着有两种方法可以达到目的.但那又怎么样? (3认同)
  • 在Scala中引入一些功能(特别是当它使得阅读代码时更加棘手但提供更多功能)并且没有人会引起注意.问为什么它不是Java,突然间每个人都会失去理智,告诉你什么是糟糕的设计. (3认同)
  • 我把真正的逻辑放在虚拟中并用虚拟对象创建一个静态来调用它,而不是把真正的逻辑放在虚拟中,因为重点是我需要覆盖子类中的逻辑,Java不会我允许我覆盖静态. (2认同)
  • "我认为我给出的例子是一件非常自然的事情.我有一个具有不依赖于任何实例数据的函数的类" - 但它取决于对象实例的类型.如果你不知道它是什么类型的对象,多态性不是由对象状态驱动的,而不是实例展示的行为,你怎么可能调用正确的方法. (2认同)
  • 如果我想在没有实例的情况下调用静态函数,那么是的,当然我必须指定类.对我来说,我应该能够通过类名调用静态函数,并通过对象实例调用静态或虚函数.为什么不? (2认同)
  • 好吧,我试图创建一个简单的示例,是的,在这种情况下,您可以通过创建一个变量来保存乘数来解决问题。但可以肯定,不难想象现实世界中的案例并不那么简单。比如,对于推销员来说,奖金是基于他们本季度的销售额,对于经理来说,是基于他们部门的收入,对于文员来说,奖金是他们工资的一个百分比。关键是是否有办法完成这个特定的例子,但是否有办法在更复杂的现实世界案例中实现这个想法。 (2认同)
  • 没有阅读之前的69条评论,但我全心全意地给你一个+1 - _yes - 想要覆盖一个静态方法是一个非常有效的**要求.问题不是没有经验的程序员btw--主要是经验丰富的程序员 - 由于无法轻松优雅地定义所有子类的共同行为而感到沮丧 - "熟悉C++程序员的好处",duh (2认同)
  • @Jay如果你用`try {Method m = this.getClass().getMethod("getBonusMultiplier")替换`return salary.multiply(getBonusMultiplier());`; return salary.multiply((BigDecimal)m.invoke(null)); } catch(NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex){Logger.getLogger(Parent.class.getName()).log(Level.SEVERE,null,ex); 它确实有效.让我想知道:为什么Java不支持隐式反射:`return salary.multiply(origin.getBonusMultiplier());` (2认同)

Ste*_*ell 40

简短的回答是:它完全有可能,但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();
  }
}
Run Code Online (Sandbox Code Playgroud)

档案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();
  }
}
Run Code Online (Sandbox Code Playgroud)

如果你运行它(我在Mac上,从Eclipse,使用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.
Run Code Online (Sandbox Code Playgroud)

在这里,唯一可能是惊喜(以及问题所在)的案例似乎是第一种情况:

"运行时类型不用于确定调用哪些静态方法,即使使用对象实例(obj.staticMethod())调用也是如此."

去年的情况:

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

使用对象实例调用

静态调用在编译时解析,而非静态方法调用在运行时解析.请注意,虽然静态方法是从父级继承的,但它们不会被重写(由子级).如果你不这么想的话,这可能是一个惊喜.

从对象方法中调用

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

改变规则

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

这很容易实现(如果我们改变了Java:-O),并且根本不合理,但它有一些有趣的考虑因素.

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

目前,Java在语言中有这种"怪癖",obj.staticMethod()呼叫被呼叫取代ObjectClass.staticMethod()(通常带有警告).[ 注意: ObjectClass是编译时类型obj.]这些是以这种方式覆盖的好候选者,采用运行时类型obj.

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

调用静态方法的其他方法更棘手:this.staticMethod()应该obj.staticMethod()与运行时类型相同this.然而,这可能会导致现有程序出现一些令人头疼的问题,这些程序调用(显然是本地的)静态方法而没有装饰(可以说相当于this.method()).

那么简单的电话staticMethod()怎么样?我建议他们和今天一样,并使用本地类上下文来决定做什么.否则会产生很大的混乱.当然,method()这意味着this.method()如果method是非静态方法,并且ThisClass.method()如果method是静态方法则意味着.这是混乱的另一个原因.

其他考虑

如果我们改变了这种行为(并使静态调用可能是动态非本地的),我们可能想要重新审视它的含义final,privateprotected作为static类方法的限定符.那么我们都必须习惯这样的事实private staticpublic final方法没有被重载,因此可以在编译时安全地解决,是"安全"改为局部引用.


Pat*_*tus 23

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

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

结果输出:

0.02
0.02
0.02
0.03
Run Code Online (Sandbox Code Playgroud)

我们想要实现的目标:)

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

RegularEmployee Carl = new SpecialEmployee();

System.out.println(Carl.calculateBonus());
System.out.println(Carl.calculateOverridenBonus());
Run Code Online (Sandbox Code Playgroud)

只看输出控制台:

0.02
0.03
Run Code Online (Sandbox Code Playgroud)

;)

  • 是的,反思几乎是人们唯一可以做的事情 - 但问题并不完全是这样 - 尽管如此有用 (8认同)
  • 不错的黑客,但不太可能有人会在实际生产中使用它.... (6认同)
  • 这个答案是迄今为止我在所有 Java 主题中看到的最大的 hack。阅读它仍然很有趣:) (2认同)

ewe*_*nli 18

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

从概念上讲,如果你可以从类对象中调用静态方法(比如像Smalltalk这样的语言)就可以实现,但在Java中并非如此.

编辑

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

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
Run Code Online (Sandbox Code Playgroud)

你可以反思课程,但它会停在那里.您不是通过使用clazz1.staticMethod()而是使用来调用静态方法MyClass.staticMethod().静态方法没有绑定到一个对象,并有因此没有的概念this,也没有super在一个静态方法.静态方法是一个全局函数; 因此,也没有多态性的概念,因此,方法覆盖是没有意义的.

但是如果MyClass是一个在运行时调用方法的对象,就像在Smalltalk中那样(或者可能是JRuby作为一个注释建议,但我对JRuby一无所知).

哦,是的......还有一件事.你可以通过一个对象调用静态方法,obj1.staticMethod()但是MyClass.staticMethod()应该避免使用真正的语法糖.它通常会在现代IDE中引发警告.我不知道为什么他们允许这个捷径.

  • 甚至像Ruby这样的许多现代语言都有类方法并允许覆盖它们. (4认同)
  • 你只得到一个类的"描述" - 而不是类本身.但差别很微妙. (4认同)
  • 类在Java中作为对象存在.请参阅"类"类.我可以说myObject.getClass(),它将返回一个相应类对象的实例. (2认同)

Ric*_*uen 13

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

Animal lassie = new Dog();
lassie.speak(); // outputs "woof!"
Animal kermit = new Frog();
kermit.speak(); // outputs "ribbit!"
Run Code Online (Sandbox Code Playgroud)

尽管两个lassiekermit被声明为类型的对象Animal,他们的行为(方法.speak())变化,因为动态调度只能绑定方法调用.speak(),以实现在运行时-而不是在编译时.

现在,static关键字开始有意义:"静态"一词是"动态"的反义词.所以你不能覆盖静态方法的原因是因为静态成员没有动态调度 - 因为静态字面意思是"不动态".如果他们动态调度(因此可以覆盖)static关键字就再也没有意义了.


Shu*_*oni 10

是.实际上Java允许重写静态方法,理论上如果你在Java中覆盖静态方法然后它将编译并运行顺利,但它将失去多态性,这是Java的基本属性.您将在任何地方阅读,无法尝试自己编译和运行.你会得到你的答案.例如,如果你有类动物和静态方法eat(),你覆盖它的子类中的静态方法让它叫做Dog.然后当你将Dog对象分配给Animal Reference并根据Java Dog调用eat()时,应该调用eat(),但是在静态覆盖动物'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
Run Code Online (Sandbox Code Playgroud)

根据Java的多态原理,输出应该是Dog Eating.
但结果不同,因为支持Polymorphism Java使用Late Binding,这意味着只在运行时调用方法,而在静态方法的情况下不调用.在静态方法中,编译器在编译时调用方法而不是运行时,所以我们根据引用获取方法而不是根据对象引用包含这就是为什么你可以说实际上它支持静态覆盖,但理论上,它没有"T.

  • 从对象调用静态方法是不好的做法。 (4认同)

Ath*_*way 5

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


Lar*_*ars 5

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

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

  • 直觉上,我认为它应该像虚函数一样工作。如果 B 扩展 A 并且 A 和 B 都有名为 doStuff 的虚函数,则编译器知道 A 的实例应该使用 A.doStuff,而 B 的实例应该使用 B.doStuff。为什么不能对静态函数做同样的事情?毕竟,编译器知道每个对象是哪个类的实例。 (2认同)
  • @meriton,但那就更容易了,不是吗?如果使用类名调用静态方法,则应使用适合该类的方法。 (2认同)

Kev*_*ock 5

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

静态方法没有区别 - 它们仍然具有固定的签名.但是,通过声明方法static,您已明确声明编译器不得在该签名的开头包含隐含的object参数.因此,调用它的任何其他代码必须不得尝试在堆栈上引用对象.如果它确实这样做了,那么方法执行将不起作用,因为参数将在堆栈上的错误位置 - 移位1.

因为这两者之间存在差异; 虚方法始终具有对上下文对象的引用(即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风格).


Rup*_*dav 5

嗯......如果你从一个重写方法应该如何在Java中运行的角度思考,答案是否定的.但是,如果尝试覆盖静态方法,则不会出现任何编译器错误.这意味着,如果你试图覆盖,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();
    // ...
}
}
Run Code Online (Sandbox Code Playgroud)

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

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


Pat*_*tus 5

我喜欢并加倍 Jay 的评论(/sf/answers/155666241/)。
我同意这是 Java 的糟糕设计。
正如我们在之前的评论中看到的那样,许多其他语言支持覆盖静态方法。我觉得 Jay 和我一样也是从 Delphi 来到 Java 的。
Delphi (Object Pascal) 是 Java 之前实现 OOP 的语言之一,也是最早用于商业应用程序开发的语言之一。
很明显,很多人都使用过这种语言,因为它过去是编写商业 GUI 产品的唯一语言。而且 - 是的,我们可以在 Delphi 中覆盖静态方法。实际上,Delphi 中的静态方法被称为“类方法”,而 Delphi 有“Delphi 静态方法”的不同概念,它们是早期绑定的方法。要覆盖必须使用后期绑定的方法,请声明“虚拟”指令。所以它非常方便和直观,我希望在 Java 中实现这一点。


Dev*_*nab 5

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


use*_*551 5

通过重写,您可以实现动态多态性。当您说重写静态方法时,您尝试使用的词语是矛盾的。

静态表示-编译时,重写用于动态多态性。两者性质相反,不能同时使用。

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

当你说重写静态方法时,静态方法我们会通过类名来访问,类名会在编译时链接,所以不存在运行时与静态方法链接的概念。因此,术语“重写”静态方法本身没有任何意义。

注意:即使你用一个对象访问一个类方法,java编译器仍然有足够的智能来找到它,并且会做静态链接。