Warum ist das Aufrufen einer statischen Methode über eine Instanz kein Fehler für den Java-Compiler?


76

Ich bin sicher, Sie alle kennen das Verhalten, das ich meine - Code wie:

Thread thread = new Thread();
int activeCount = thread.activeCount();

provoziert eine Compiler-Warnung. Warum ist es kein Fehler?

BEARBEITEN:

Um es klar zu sagen: Frage hat nichts mit Threads zu tun. Mir ist klar, dass Thread-Beispiele oft gegeben werden, wenn dies diskutiert wird, weil das Potenzial besteht, die Dinge wirklich durcheinander zu bringen. Aber das Problem ist wirklich, dass eine solche Verwendung immer Unsinn ist und man einen solchen Anruf nicht (kompetent) schreiben und meinen kann. Jedes Beispiel für diese Art von Methodenaufruf wäre barmy. Hier ist ein anderes:

String hello = "hello";
String number123AsString = hello.valueOf(123);

Dadurch sieht es so aus, als ob jede String-Instanz mit einer "String valueOf (int i)" - Methode geliefert wird.


15
Um Ihren Standpunkt zu erweitern, kann die redundante Instanz sogar null sein: String hello = null; hello.valueOf(123);funktioniert!
Thilo

Antworten:


79

Grundsätzlich glaube ich, dass die Java-Designer beim Entwerfen der Sprache einen Fehler gemacht haben, und es ist aufgrund der damit verbundenen Kompatibilitätsprobleme zu spät, ihn zu beheben. Ja, dies kann zu sehr irreführendem Code führen. Ja, du solltest es vermeiden. Ja, Sie sollten sicherstellen, dass Ihre IDE so konfiguriert ist, dass sie als Fehler behandelt wird, IMO. Sollten Sie jemals selbst eine Sprache entwerfen, denken Sie daran, wie Sie vermeiden sollten :)

Um auf den Punkt von DJClayworth zu antworten, ist in C # Folgendes erlaubt:

public class Foo
{
    public static void Bar()
    {
    }
}

public class Abc
{
    public void Test()
    {
        // Static methods in the same class and base classes
        // (and outer classes) are available, with no
        // qualification
        Def();

        // Static methods in other classes are available via
        // the class name
        Foo.Bar();

        Abc abc = new Abc();

        // This would *not* be legal. It being legal has no benefit,
        // and just allows misleading code
        // abc.Def();
    }

    public static void Def()
    {
    }
}

Warum halte ich es für irreführend? Denn wenn ich mir Code anschaue, someVariable.SomeMethod()erwarte ich, dass er den Wert von verwendetsomeVariable . Wenn SomeMethod()es sich um eine statische Methode handelt, ist diese Erwartung ungültig. Der Code betrügt mich. Wie kann das möglicherweise eine gute Sache sein?

Seltsamerweise lässt Java Sie keine potenziell nicht initialisierte Variable verwenden, um eine statische Methode aufzurufen, obwohl die einzige Information, die verwendet wird, der deklarierte Typ der Variablen ist. Es ist ein inkonsistentes und nicht hilfreiches Durcheinander. Warum es zulassen?

BEARBEITEN: Diese Bearbeitung ist eine Antwort auf Claytons Antwort, die behauptet, sie erlaube die Vererbung für statische Methoden. Das tut es nicht. Statische Methoden sind einfach nicht polymorph. Hier ist ein kurzes, aber vollständiges Programm, um dies zu demonstrieren:

class Base
{
    static void foo()
    {
        System.out.println("Base.foo()");
    }
}

class Derived extends Base
{
    static void foo()
    {
        System.out.println("Derived.foo()");
    }
}

public class Test
{
    public static void main(String[] args)
    {
        Base b = new Derived();
        b.foo(); // Prints "Base.foo()"
        b = null;
        b.foo(); // Still prints "Base.foo()"
    }
}

Wie Sie sehen können, wird der Ausführungszeitwert von bvollständig ignoriert.


Ich denke, Sie vermissen vielleicht etwas. Siehe meinen Beitrag.
DJClayworth

1
@ DJClayworth: Nein. Das ist in C # erlaubt, aber someInstanceOfMyClass.doComplexCalculation () wäre das nicht.
Jon Skeet

5
@ Jon: Ich habe einen ähnlichen Test durchgeführt und Ihre Ergebnisse bestätigt. Aufgrund dessen stimme ich zu, dass es für Java falsch ist, statische Methoden von einer Instanzvariablen aus aufrufen zu lassen. Es werden keine Fähigkeiten erworben (wie ich ursprünglich dachte), aber das Potenzial für Verwirrung oder Fehler wird erhöht.
Clayton

Der Ausführungszeitwert von b wird vollständig ignoriert, da Sie eine statische Methode aufrufen, die nur von der Klasse von b abhängt, nicht von ihrem Wert. Alle Werte vom Typ Base haben die gleiche Methode. Sie haben Base b deklariert, also ist b eine Base, keine abgeleitete. Wenn Sie Derived b deklariert hätten, hätte es "Derived.foo ()" gedruckt.
Matthew

@ Matthew: Ja, ich weiß ... Ich bin nicht sicher, welchen Punkt Sie versuchen zu machen. (Ich würde nicht persönlich sagen, dass "alle Werte vom Typ Base die gleiche Methode haben" - eine Instanz "hat" überhaupt keine Methode ... aber ich glaube, ich weiß, was Sie meinen.)
Jon Skeet

15

Warum sollte es ein Fehler sein? Die Instanz hat Zugriff auf alle statischen Methoden. Die statischen Methoden können den Status der Instanz nicht ändern (der Versuch ist ein Kompilierungsfehler).

Das Problem mit dem bekannten Beispiel, das Sie angeben, ist sehr spezifisch für Threads und nicht für statische Methodenaufrufe. Es sieht so aus, als würden Sie die activeCount()für den Thread erhalten, auf den verwiesen wird thread, aber Sie erhalten wirklich die Anzahl für den aufrufenden Thread. Dies ist ein logischer Fehler, den Sie als Programmierer machen. In diesem Fall ist es für den Compiler angemessen, eine Warnung auszugeben. Es liegt an Ihnen, die Warnung zu beachten und Ihren Code zu korrigieren.

EDIT: Mir ist klar, dass die Syntax der Sprache es Ihnen ermöglicht, irreführenden Code zu schreiben, aber denken Sie daran, dass der Compiler und seine Warnungen auch Teil der Sprache sind. Die Sprache ermöglicht es Ihnen, etwas zu tun, das der Compiler für zweifelhaft hält, aber es gibt Ihnen die Warnung, um sicherzustellen, dass Sie sich bewusst sind, dass es Probleme verursachen könnte.


9
Es ist für die Instanz irrelevant . Das einzige ist der deklarierte Typ der Variablen. Es ist eine schreckliche, schreckliche Fehlfunktion und ein Fehler der Sprachdesigner IMO.
Jon Skeet

1
Ja, es ist für die Instanz irrelevant, aber ich denke nicht, dass das Aufrufen einer statischen Methode von einer Instanz ein vollwertiger Fehler sein sollte. Es ist einfach ein wirklich schlechter Stil. IMHO sollte eine Klasse entweder alle statischen Methoden sein oder die statischen Methoden sollten privat sein, aber ich möchte nicht, dass der Compiler dies erzwingt.
Bill the Lizard

1
@ Bill: Ich glaube nicht, dass es in den frühen Tagen eine Warnung gegeben hat. Die Warnung wurde hinzugefügt, weil sie Menschen irreführte. Was ist der Vorteil, wenn man es zulässt?
Jon Skeet

4
@ Bill: Sagen wir es so - wenn Java es nicht zulässt und jemand vorschlägt , dass es es zulässt, wie würden Sie reagieren? Würden Sie wirklich dafür argumentieren? Oder würden Sie (wie ich) argumentieren, dass es sich um eine sinnlose Funktion handelt, die unnötigerweise irreführenden Code zulässt?
Jon Skeet

2
Ich kaufe den Grund für die "Kompilierungszeit" nicht - es ist offensichtlich, dass Sie die Methode über eine Referenz nachschlagen. Wie lange dauert es dann, um zu überprüfen, ob es statisch ist oder nicht? Ich denke, es ist einfacher, es einfach als Sprachdesignfehler zu betrachten.
Jon Skeet

9

Sie können es nicht mehr zu einem Fehler machen, da der gesamte Code bereits vorhanden ist.

Ich bin bei Ihnen, dass es ein Fehler sein sollte. Möglicherweise sollte es eine Option / ein Profil für den Compiler geben, um einige Warnungen auf Fehler zu aktualisieren.

Update: Als sie das Schlüsselwort assert in 1.4 einführten , das ähnliche potenzielle Kompatibilitätsprobleme mit altem Code aufweist, haben sie es nur verfügbar gemacht, wenn Sie den Quellmodus explizit auf "1.4" gesetzt haben . Ich nehme an, man könnte es in einem neuen Quellmodus "Java 7" zu einem Fehler machen. Aber ich bezweifle, dass sie es tun würden, wenn man bedenkt, dass all der Ärger, den es verursachen würde. Wie andere bereits betont haben, ist es nicht unbedingt erforderlich, Sie daran zu hindern, verwirrenden Code zu schreiben. Und Sprachänderungen an Java sollten an dieser Stelle auf das unbedingt Notwendige beschränkt sein.


Es gibt eine solche Option im Eclipse-Compiler (indirektStatic, help.eclipse.org/help33/index.jsp?topic=/… )
Peter Štibraný

1
Dies haben sie jedoch getan, als sie das Schlüsselwort assert in Java 1.4 eingeführt haben. Warum können sie es nicht wieder auf die gleiche Weise tun?
Hosam Aly

Du hast recht. Für Assert müssen Sie die Compiler-Ebene speziell auf 1.4 setzen, andernfalls wird sie immer noch ohne Assert kompiliert. Ich nehme an, 1,5 Anmerkungen und Generika funktionieren genauso. Man könnte also eine Java 7-Kompilierungsstufe haben, die es zu einem Fehler machen würde.
Thilo


2

Wahrscheinlich für dieselbe Logik, die dies nicht zu einem Fehler macht:

public class X
{
    public static void foo()
    {
    }

    public void bar()
    {
        foo(); // no need to do X.foo();
    }
}

2

Aus Sicht des Compilers ist es wirklich wichtig, dass Symbole aufgelöst werden können. Bei einer statischen Methode muss sie wissen, in welcher Klasse sie suchen soll, da sie keinem bestimmten Objekt zugeordnet ist. Die Designer von Java haben offensichtlich entschieden, dass sie, da sie die Klasse eines Objekts bestimmen können, auch die Klasse einer beliebigen statischen Methode für dieses Objekt aus einer beliebigen Instanz des Objekts auflösen können. Sie beschließen, dies zuzulassen - möglicherweise beeinflusst durch die Beobachtung von @ TofuBeer -, um dem Programmierer etwas Bequemlichkeit zu geben. Andere Sprachdesigner haben andere Entscheidungen getroffen. Ich wäre wahrscheinlich in das letztere Lager gefallen, aber für mich ist das keine große Sache. Ich würde wahrscheinlich die Verwendung erlauben, die @TofuBeer erwähnt,


2

Es ist kein Fehler, weil es Teil der Spezifikation ist, aber Sie fragen offensichtlich nach der Begründung, die wir alle erraten können.

Ich vermute, dass die Quelle dafür tatsächlich darin besteht, einer Methode in einer Klasse zu erlauben, eine statische Methode in derselben Klasse ohne Probleme aufzurufen. Da das Aufrufen von x () legal ist (auch ohne den Namen der Selbstklasse), sollte das Aufrufen von this.x () ebenfalls legal sein, und daher wurde das Aufrufen über ein beliebiges Objekt ebenfalls legalisiert.

Dies hilft auch Benutzern, private Funktionen in statische zu verwandeln, wenn sie den Status nicht ändern.

Außerdem versuchen Compiler im Allgemeinen, das Deklarieren von Fehlern zu vermeiden, wenn dies nicht zu einem direkten Fehler führen kann. Da eine statische Methode den Status oder die Pflege des aufrufenden Objekts nicht ändert, verursacht sie keinen tatsächlichen Fehler (nur Verwirrung), um dies zuzulassen. Eine Warnung reicht aus.


2
@Uri: Es ist einfach genug, es innerhalb der aktuellen Klasse verfügbar zu machen, ohne den Klassennamen anzugeben. C # hat es geschafft, und Java-Compiler können den Unterschied erkennen (da in diesem Fall keine Warnung angezeigt wird). Warum sollte es also so angegeben werden?
Jon Skeet

2

Der Zweck der Instanzvariablenreferenz besteht nur darin, den Typ anzugeben, der die Statik einschließt. Wenn Sie sich den Bytecode ansehen, der eine statische Methode über instance.staticMethod oder EnclosingClass.staticMethod aufruft, wird der gleiche aufgerufene statische Methodenbytecode erzeugt. Es wird kein Verweis auf die Instanz angezeigt.

Die Antwort auch, warum es dort drin ist, nun, es ist einfach so. Solange Sie die Klasse verwenden. und nicht über eine Instanz helfen Sie, Verwirrung in Zukunft zu vermeiden.


1

Wahrscheinlich können Sie es in Ihrer IDE ändern (in Eclipse-Einstellungen -> Java -> Compiler -> Fehler / Warnungen)


0

Es gibt keine Option dafür. In Java (wie in vielen anderen Sprachen) können Sie über den Klassennamen oder das Instanzobjekt dieser Klasse auf alle statischen Mitglieder einer Klasse zugreifen. Es liegt an Ihnen und Ihrer Fall- und Softwarelösung, welche Sie verwenden sollten, um die Lesbarkeit zu verbessern.


0

Es ist ein ziemlich altes Thema, aber immer noch auf dem neuesten Stand und bringt heutzutage überraschenderweise eine größere Wirkung. Wie Jon erwähnte, könnte es nur ein Fehler sein, den Javas Designer am Anfang gemacht haben. Aber ich würde es mir nicht vorstellen, bevor es Auswirkungen auf die Sicherheit haben kann.

Viele Programmierer kennen Apache Velocity, eine flexible und leistungsstarke Template-Engine. Es ist so leistungsfähig, dass es die Vorlage von Vorlagen mit einer Reihe benannter Objekte ermöglicht - streng genommen als Objekte aus der Programmiersprache (ursprünglich Java). Auf diese Objekte kann wie in der Programmiersprache innerhalb einer Vorlage zugegriffen werden, sodass beispielsweise die String-Instanz von Java mit all ihren öffentlichen Feldern, Eigenschaften und Methoden verwendet werden kann

$input.isEmpty()

Dabei ist die Eingabe ein String , läuft direkt über JVM und gibt true oder false an die Ausgabe des Velocity-Parsers zurück. So weit, ist es gut.

In Java erben jedoch alle Objekte von Object, sodass unsere Endbenutzer dies auch in die Vorlage einfügen können

$input.getClass()

um eine Instanz von String Class zu erhalten.

Und mit dieser Referenz können sie auch einen Aufruf statische Methode forName (String) auf diese

$input.getClass().forName("java.io.FileDescriptor")

Verwenden Sie einen beliebigen Klassennamen und verwenden Sie ihn für jedes Konto des Webservers (Verunstalten, DB-Inhalt stehlen, Konfigurationsdateien überprüfen, ...).

Dieser Exploit wird hier (in einem bestimmten Kontext) beschrieben: https://github.com/veracode-research/solr-injection#7-cve-2019-17558-rce-via-velocity-template-by-_s00py

Es wäre nicht möglich, wenn das Aufrufen statischer Methoden aus der Referenz auf die Instanz der Klasse verboten wäre.

Ich sage nicht, dass ein bestimmtes Programmierframework besser ist als das andere oder so, aber ich möchte nur einen Vergleich anstellen. Es gibt eine Portierung von Apache Velocity für .NET. In C # ist es nicht möglich, statische Methoden nur aus der Referenz der Instanz aufzurufen, was einen solchen Exploit nutzlos macht:

$input.GetType().GetType("System.IO.FileStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")

-9

Ich denke nur an Folgendes:

instanceVar.staticMethod();

dafür eine Abkürzung sein:

instanceVar.getClass().staticMethod();

Wenn Sie dies immer tun müssten:

SomeClass.staticMethod();

Dann könnten Sie die Vererbung nicht für statische Methoden nutzen.

Das heißt, wenn Sie die statische Methode über die Instanz aufrufen, müssen Sie nicht wissen, welche konkrete Klasse die Instanz zur Kompilierungszeit ist, sondern nur, dass sie staticMethod () irgendwo entlang der Vererbungskette implementiert.

EDIT: Diese Antwort ist falsch. Siehe Kommentare für Details.


1
Gut, das ist ein weiterer Grund, warum es nicht erlaubt sein sollte - weil Sie auch irregeführt werden. Es verhält sich nicht so, wie Sie es wollen. In Ihrem Beispiel wird nur der deklarierte Typ "instanceVar" verwendet. Heck, der Wert von instanceVar kann sogar null sein.
Jon Skeet

1
@Clayton: Ja, aber getClass () wird nicht aufgerufen. Ich werde meine Antwort mit einem Beispiel bearbeiten.
Jon Skeet

3
(Und wenn es tut Anruf getClass (), könnte es nicht so nennen Static (), denn das ist keine Methode auf Klasse <T>.)
Jon Skeet

5
@ Jon: Ich werde es hier lassen. Ich lerne so viel aus Fehlern wie alles andere, daher könnte es jemand anderem helfen, dies zu erkennen. Danke für Ihre Hilfe.
Clayton

3
@Clayton: Vielleicht möchten Sie dieses Community-Wiki erstellen, damit wir es ablehnen können, ohne Sie zu bestrafen.
Bill the Lizard
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.