Warum ist es nicht möglich, statische Methoden außer Kraft zu setzen?
Wenn möglich, verwenden Sie bitte ein Beispiel.
Warum ist es nicht möglich, statische Methoden außer Kraft zu setzen?
Wenn möglich, verwenden Sie bitte ein Beispiel.
Das Überschreiben hängt vom Vorhandensein einer Instanz einer Klasse ab. Der Sinn der Polymorphie besteht darin, dass man eine Klasse unterklassifizieren kann und die Objekte, die diese Unterklassen implementieren, unterschiedliche Verhaltensweisen für dieselben Methoden aufweisen, die in der Oberklasse definiert sind (und in den Unterklassen überschrieben werden). Eine statische Methode ist nicht mit einer Instanz einer Klasse verbunden, so dass das Konzept nicht anwendbar ist.
Bei der Entwicklung von Java gab es zwei Überlegungen, die sich hierauf auswirkten. Zum einen ging es um die Leistung: Smalltalk wurde häufig als zu langsam kritisiert (unter anderem wegen der Garbage Collection und polymorphen Aufrufen), und die Entwickler von Java wollten dies unbedingt vermeiden. Ein weiterer Grund war die Entscheidung, dass das Zielpublikum von Java C++-Entwickler sind. Die Funktionsweise statischer Methoden hatte den Vorteil, dass sie den C++-Programmierern vertraut war und außerdem sehr schnell war, da man nicht bis zur Laufzeit warten musste, um herauszufinden, welche Methode aufgerufen werden sollte.
...aber nur in Java "richtig". Zum Beispiel ist Scalas Äquivalent von "statischen Klassen" (die als objects
) erlauben das Überladen von Methoden.
Es gibt eine Typhierarchie zur Kompilierzeit und eine Typhierarchie zur Laufzeit. Es macht durchaus Sinn, sich zu fragen, warum ein statischer Methodenaufruf nicht von der Laufzeit-Typenhierarchie Gebrauch macht, wenn es eine solche gibt. In Java geschieht dies beim Aufruf einer statischen Methode aus einem Objekt ( obj.staticMethod()
) - was erlaubt ist und die Kompilierzeittypen verwendet. Wenn der statische Aufruf in einer nicht-statischen Methode einer Klasse erfolgt, könnte das "aktuelle" Objekt ein abgeleiteter Typ der Klasse sein - aber statische Methoden, die für abgeleitete Typen definiert sind, werden nicht berücksichtigt (sie befinden sich in der Laufzeit-Typenhierarchie).
Ich persönlich denke, dass dies ein Fehler im Design von Java ist. Ja, ja, ich verstehe, dass nicht-statische Methoden an eine Instanz gebunden sind, während statische Methoden an eine Klasse gebunden sind, usw. usw. Dennoch, betrachten Sie den folgenden Code:
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");
}
}
Dieser Code funktioniert nicht so, wie Sie vielleicht erwarten. SpecialEmployee's erhalten nämlich einen Bonus von 2 %, genau wie normale Mitarbeiter. Wenn Sie jedoch die "static "s entfernen, erhalten SpecialEmployee's einen Bonus von 3%.
(Zugegeben, dieses Beispiel ist insofern schlecht programmiert, als dass man im wirklichen Leben den Bonusmultiplikator wahrscheinlich eher in einer Datenbank als in einem fest programmierten Code haben möchte. Aber das liegt nur daran, dass ich das Beispiel nicht mit einer Menge Code überfrachten wollte, der für die Sache irrelevant ist).
Es erscheint mir durchaus plausibel, dass Sie getBonusMultiplier statisch machen wollen. Vielleicht möchten Sie in der Lage sein, den Bonusmultiplikator für alle Mitarbeiterkategorien anzuzeigen, ohne eine Instanz eines Mitarbeiters in jeder Kategorie zu benötigen. Welchen Sinn hätte es, nach solchen Beispielinstanzen zu suchen? Was ist, wenn wir eine neue Mitarbeiterkategorie erstellen und noch keine Mitarbeiter dieser Kategorie zugeordnet sind? Dies ist logischerweise eine statische Funktion.
Aber es funktioniert nicht.
Und ja, ja, ich kann mir jede Menge Möglichkeiten vorstellen, den obigen Code so umzuschreiben, dass er funktioniert. Mein Punkt ist nicht, dass es ein unlösbares Problem schafft, sondern dass es eine Falle für den unvorsichtigen Programmierer schafft, weil die Sprache sich nicht so verhält, wie ich denke, dass eine vernünftige Person es erwarten würde.
Wenn ich versuchen würde, einen Compiler für eine OOP-Sprache zu schreiben, würde ich vielleicht schnell erkennen, warum es schwierig oder unmöglich wäre, statische Funktionen zu überschreiben.
Vielleicht gibt es aber auch einen guten Grund, warum Java sich so verhält. Kann mir jemand einen Vorteil für dieses Verhalten nennen, irgendeine Problemkategorie, die dadurch einfacher wird? Ich meine, verweisen Sie mich nicht einfach auf die Java-Sprachenspezifikation und sagen Sie: "Sehen Sie, das ist dokumentiert, wie es sich verhält". Ich weiß das. Aber gibt es einen guten Grund, warum es sich so verhalten SOLLTE? (Außer dem offensichtlichen "es richtig zu machen war zu schwer"...)
Update
@VicKirk: Wenn Sie meinen, dass dies "schlechtes Design" ist, weil es nicht dazu passt, wie Java mit Statik umgeht, lautet meine Antwort: "Na klar, natürlich." Wie ich in meinem ursprünglichen Beitrag sagte, funktioniert es nicht. Wenn Sie aber meinen, dass es ein schlechtes Design in dem Sinne ist, dass etwas grundlegend falsch wäre mit einer Sprache, in der dies funktioniert, d.h. in der Statik genau wie virtuelle Funktionen überschrieben werden könnte, dass dies irgendwie eine Mehrdeutigkeit einführen würde oder es unmöglich wäre, es effizient zu implementieren oder ähnliches, antworte ich: "Warum? Was ist falsch an dem Konzept?"
Ich denke, das Beispiel, das ich gebe, ist eine ganz natürliche Sache, die man tun möchte. Ich habe eine Klasse, die eine Funktion hat, die nicht von Instanzdaten abhängt, und die ich vernünftigerweise unabhängig von einer Instanz aufrufen möchte, und die ich auch innerhalb einer Instanzmethode aufrufen möchte. Warum sollte das nicht funktionieren? Ich bin im Laufe der Jahre immer wieder mit dieser Situation konfrontiert worden. In der Praxis umgehe ich sie, indem ich die Funktion virtuell mache und dann eine statische Methode erstelle, deren einziger Lebenszweck darin besteht, eine statische Methode zu sein, die den Aufruf an die virtuelle Methode mit einer Dummy-Instanz weitergibt. Das scheint ein sehr umständlicher Weg zu sein, um ans Ziel zu kommen.
Obwohl dieser Code in Java funktioniert, ist es eine Eigenart von Java, die es erlaubt, statische Methoden auf einer Instanz aufzurufen. In C# würde es scheitern, weil C# es nicht erlaubt, statische Methoden als Instanzmethode aufzurufen, wie Sie es in der salary.multiply
Linie.
Dieses Beispiel ist einfach ein schlechtes Design und zeigt keinen "Fehler" bei der Implementierung statischer Methoden auf. Mir fällt kein Beispiel ein, in dem ich sie außer Kraft setzen möchte. Würden Sie wirklich Code wie bonus = (obj instanceof B) ? B.bonus() : A.bonus(). Was wäre, wenn sie bei Typ C überschrieben würde oder ein Dritter Typ D hinzufügt, dann wäre jeder Code, der die Methoden verwendet, kaputt. Zwar könnte man die obige Methode mit einer Instanzvariablen schreiben, aber dann wäre es eine Instanzmethode, da sie dann eine Instanz zur Auflösung der Methode benötigt!
BTW, wie die Dinge sind instance.staticMethod() ist wahrscheinlich mit IntanceClass.staticMethod() optimiert werden
Die kurze Antwort lautet: Es ist durchaus möglich, aber Java tut es nicht.
Der folgende Code veranschaulicht die aktueller Stand der Dinge in Java:
Datei 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();
}
}
Datei 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();
}
}
Wenn Sie dies ausführen (ich tat es auf einem Mac, von Eclipse aus, mit Java 1.6) erhalten Sie:
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.
Hier ist die nur Die Fälle, die überraschen könnten (und um die es in der Frage geht), sind offenbar die erste Fall:
"Der Laufzeittyp wird nicht verwendet, um zu bestimmen, welche statischen Methoden aufgerufen werden, selbst wenn sie mit einer Objektinstanz aufgerufen werden ( obj.staticMethod()
)."
und die zuletzt Fall:
"Wenn eine statische Methode aus einer Objektmethode einer Klasse heraus aufgerufen wird, wird die statische Methode gewählt, die von der Klasse selbst aus zugänglich ist und no von der Klasse, die den Laufzeittyp des Objekts definiert."
Der statische Aufruf wird zur Kompilierzeit aufgelöst, während ein nicht-statischer Methodenaufruf zur Laufzeit aufgelöst wird. Beachten Sie, dass statische Methoden zwar vererbt (von den Eltern) sind sie nicht außer Kraft gesetzt (von einem Kind). Dies könnte eine Überraschung sein, wenn Sie etwas anderes erwartet haben.
Objekt Methodenaufrufe werden über den Laufzeittyp aufgelöst, aber statische ( Klasse ) werden Methodenaufrufe anhand des zur Kompilierzeit deklarierten Typs aufgelöst.
Um diese Regeln zu ändern, so dass der letzte Aufruf im Beispiel Child.printValue()
In diesem Fall müssten statische Aufrufe zur Laufzeit mit einem Typ versehen werden, anstatt dass der Compiler den Aufruf zur Kompilierzeit mit der deklarierten Klasse des Objekts (oder Kontexts) auflöst. Statische Aufrufe könnten dann die (dynamische) Typenhierarchie verwenden, um den Aufruf aufzulösen, so wie dies heute bei Aufrufen von Objektmethoden der Fall ist.
Dies wäre leicht machbar (wenn wir Java ändern würden :-O), und ist keineswegs unvernünftig, aber es gibt einige interessante Überlegungen.
Die wichtigste Überlegung ist, dass wir entscheiden müssen die statische Methodenaufrufe sollten dies tun.
Zurzeit hat Java diese "Eigenart" in der Sprache, wodurch obj.staticMethod()
Aufrufe werden ersetzt durch ObjectClass.staticMethod()
Anrufe (normalerweise mit einer Warnung). [ Nota: ObjectClass
ist der Kompilierzeittyp von obj
.] Diese wären gute Kandidaten, um auf diese Weise überschrieben zu werden, wobei der Laufzeittyp von obj
.
Wenn wir das täten, würde es die Lesbarkeit von Methodenkörpern erschweren: statische Aufrufe in einer übergeordneten Klasse könnten möglicherweise als dynamisch "umgeleitet". Um dies zu vermeiden, müssten wir die statische Methode mit einem Klassennamen aufrufen - und dies macht die Aufrufe offensichtlicher mit der Kompilierzeit-Typenhierarchie aufgelöst (wie jetzt).
Die anderen Möglichkeiten, eine statische Methode aufzurufen, sind etwas komplizierter: this.staticMethod()
sollte dasselbe bedeuten wie obj.staticMethod()
und nimmt den Laufzeittyp von this
. Dies könnte jedoch bei bestehenden Programmen, die (offensichtlich lokale) statische Methoden ohne Dekoration aufrufen (was wohl gleichbedeutend ist mit this.method()
).
Und was ist mit ungeschminkten Anrufen? staticMethod()
? Ich schlage vor, dass sie das Gleiche tun wie heute, und den lokalen Klassenkontext nutzen, um zu entscheiden, was zu tun ist. Andernfalls würde es zu großer Verwirrung kommen. Das bedeutet natürlich, dass method()
würde bedeuten this.method()
si method
eine nicht-statische Methode war und ThisClass.method()
si method
waren eine statische Methode. Dies ist eine weitere Quelle der Verwirrung.
Wenn wir dieses Verhalten ändern (und statische Aufrufe potentiell dynamisch nicht-lokal machen), würden wir wahrscheinlich die Bedeutung von final
, private
y protected
als Qualifier auf static
Methoden einer Klasse. Wir müssten uns dann alle daran gewöhnen, dass private static
y public final
Methoden werden nicht überschrieben und können daher zur Kompilierzeit sicher aufgelöst werden und sind "sicher" als lokale Referenzen zu lesen.
"Wenn wir das täten, würde es die Lesbarkeit von Methodenkörpern erschweren: statische Aufrufe in einer übergeordneten Klasse könnten möglicherweise dynamisch "umgeleitet" werden." Stimmt, aber genau das passiert jetzt mit gewöhnlichen nicht-statischen Funktionsaufrufen. Dies wird routinemäßig als positives Merkmal für gewöhnliche virtuelle Funktionen angepriesen, nicht als Problem.
Eigentlich haben wir uns geirrt.
Obwohl es in Java nicht möglich ist, statische Methoden standardmäßig zu überschreiben, können Sie, wenn Sie die Dokumentation der Klassen und Methoden in Java gründlich durchlesen, dennoch einen Weg finden, das Überschreiben statischer Methoden zu emulieren, indem Sie folgenden Workaround anwenden:
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());
}
}
Resultierende Ausgabe:
0.02
0.02
0.02
0.03
was wir zu erreichen versuchten :)
Auch wenn wir die dritte Variable Carl als RegularEmployee deklarieren und ihr eine Instanz von SpecialEmployee zuweisen, wird im ersten Fall die Methode RegularEmployee und im zweiten Fall die Methode SpecialEmployee aufgerufen
RegularEmployee Carl = new SpecialEmployee();
System.out.println(Carl.calculateBonus());
System.out.println(Carl.calculateOverridenBonus());
sehen Sie sich einfach die Ausgabekonsole an:
0.02
0.03
;)
Statische Methoden werden von der JVM als global behandelt, sie sind nicht an eine Objektinstanz gebunden.
Es wäre konzeptionell möglich, wenn man statische Methoden von Klassenobjekten aufrufen könnte (wie in Sprachen wie Smalltalk), aber das ist in Java nicht der Fall.
EDITAR
Sie können Überlastung statische Methode, das ist in Ordnung. Aber Sie können nicht Überschreiben Sie eine statische Methode, denn Klassen sind keine Objekte erster Klasse. Sie können Reflection verwenden, um die Klasse eines Objekts zur Laufzeit zu ermitteln, aber das Objekt, das Sie erhalten, ist nicht parallel zur Klassenhierarchie.
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
Sie können über den Unterricht nachdenken, aber damit ist es vorbei. Sie rufen eine statische Methode nicht auf, indem Sie clazz1.staticMethod()
sondern mit MyClass.staticMethod()
. Eine statische Methode ist nicht an ein Objekt gebunden und daher gibt es keinen Begriff von this
noch super
in einer statischen Methode. Eine statische Methode ist eine globale Funktion; folglich gibt es auch keinen Begriff der Polymorphie, und daher macht das Überschreiben von Methoden keinen Sinn.
Dies könnte jedoch möglich sein, wenn MyClass
war ein Objekt zur Laufzeit, auf dem man eine Methode aufruft, wie in Smalltalk (oder vielleicht JRuby, wie ein Kommentar vorschlug, aber ich weiß nichts über JRuby).
Ach ja... noch eine Sache. Sie können eine statische Methode über ein Objekt aufrufen obj1.staticMethod()
aber das ist wirklich syntaktischer Zucker für MyClass.staticMethod()
und sollte vermieden werden. In modernen IDEs wird normalerweise eine Warnung ausgegeben. Ich weiß nicht, warum sie jemals diese Abkürzung erlaubt.
Sogar viele moderne Sprachen wie Ruby haben Klassenmethoden und erlauben es, diese zu überschreiben.
Klassen existieren in Java als Objekte. Siehe die Klasse "Class". Ich kann myObject.getClass() sagen und er wird mir eine Instanz des entsprechenden Klassenobjekts zurückgeben.
Sie erhalten nur eine "Beschreibung" der Klasse - nicht die Klasse selbst. Aber der Unterschied ist subtil.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.
3 Stimmen
Die meisten OOP-Sprachen lassen dies nicht zu.
11 Stimmen
@jmucchiello: siehe meine Antwort. Ich habe dasselbe gedacht wie Sie, aber dann habe ich von den Ruby/Smalltalk-"Klassen"-Methoden erfahren, und es gibt also auch andere echte OOP-Sprachen, die das tun.
6 Stimmen
@jmucchiello die meisten OOP-Sprachen sind keine echten OOP-Sprachen (ich denke an Smalltalk)
0 Stimmen
Siehe auch stackoverflow.com/q/370962/632951
0 Stimmen
Python erlaubt das Überschreiben einer statischen Methode. stackoverflow.com/questions/893015/
2 Stimmen
Kann daran liegen, dass Java Aufrufe zu statischen Methoden zur Kompilierzeit auflöst. Selbst wenn Sie also geschrieben haben
Parent p = new Child()
und dannp.childOverriddenStaticMethod()
löst der Compiler es auf inParent.childOverriddenStaticMethod()
indem Sie sich den Referenztyp ansehen.0 Stimmen
Huzzah, wir können jetzt Dinge schreiben wie hastebin.com/codajahati.java . Mit einem solchen Muster (vorzugsweise mit weniger plumpen Code) kann Java im Wesentlichen alle Ausnahmen aus der Kernbibliothek eliminieren (kann, nicht wird), oder wir können einfach Dinge verpacken.