Java Name Hiding: Der harte Weg

96

Ich habe ein Problem mit dem Verstecken von Namen, das extrem schwer zu lösen ist. Hier ist eine vereinfachte Version, die das Problem erklärt:

Es gibt eine Klasse: org.A

package org;
public class A{
     public class X{...}
     ...
     protected int net;
}

Dann gibt es eine Klasse net.foo.X

package net.foo;
public class X{
     public static void doSomething();
}

Und jetzt ist hier die problematische Klasse, die von erbt Aund anrufen möchtenet.foo.X.doSomething()

package com.bar;
class B extends A {

    public void doSomething(){
        net.foo.X.doSomething(); // doesn't work; package net is hidden by inherited field
        X.doSomething(); // doesn't work; type net.foo.X is hidden by inherited X
    }
}

Wie Sie sehen, ist dies nicht möglich. Ich kann den einfachen Namen nicht verwenden, Xda er von einem geerbten Typ ausgeblendet wird. Ich kann den vollständig qualifizierten Namen nicht verwenden net.foo.X, da er netdurch ein geerbtes Feld ausgeblendet wird.

Nur die Klasse Bbefindet sich in meiner Codebasis. die Klassen net.foo.Xund org.Asind Bibliotheksklassen, also kann ich sie nicht ändern!

Meine einzige Lösung sieht so aus: Ich könnte eine andere Klasse aufrufen, die wiederum aufruft X.doSomething(); aber diese Klasse würde nur wegen des Namenskonflikts existieren, der sehr chaotisch erscheint! Gibt es keine Lösung , in der ich direkt anrufen kann X.doSomething()aus B.doSomething()?

In einer Sprache, in der der globale Namespace angegeben werden kann, z. B. global::in C # oder ::C ++, könnte ich einfach netdieses globale Präfix voranstellen, aber Java lässt dies nicht zu.

Gexizid
quelle
Ein Quickfix könnte dies sein: public void help(net.foo.X x) { x.doSomething(); }und mithelp(null);
Absurd-Mind
@ Absurd-Mind: Nun, das Aufrufen einer statischen Methode über ein nicht existierendes Objekt scheint noch chaotischer als meine Lösung :). Ich werde sogar eine Compiler-Warnung erhalten. Aber wahr, dies wäre eine weitere hackige Lösung zusätzlich zu meiner.
Gexizid
@ JamesB: Dies würde das falsche X instanziieren! net.foo.Xhat die Methode nicht org.A.X!
Gexizid
3
Haben Sie haben zu vererben A? Vererbung kann so böse sein, wie Sie gefunden haben ...
Donal Fellows
2
I could call another class that in turn calls X.doSomething(); but this class would only exist because of the name clash, which seems very messy+1 für die Einstellung von sauberem Code. Aber für mich scheint dies eine Situation zu sein, in der Sie einen Kompromiss eingehen sollten. Tun Sie dies einfach und geben Sie einen schönen langen Kommentar darüber ab, warum Sie es tun mussten (wahrscheinlich mit einem Link zu dieser Frage).
Sampathsris

Antworten:

84

Sie können ein nullin den Typ umwandeln und dann die Methode darauf aufrufen (was funktioniert, da das Zielobjekt nicht am Aufruf statischer Methoden beteiligt ist).

((net.foo.X) null).doSomething();

Dies hat die Vorteile von

  • frei von Nebenwirkungen sein (ein Problem beim Instanziieren net.foo.X),
  • Sie müssen nichts umbenennen (damit Sie die Methode in Bdem Namen angeben können, den Sie möchten; deshalb import staticfunktioniert a in Ihrem genauen Fall nicht).
  • keine Einführung einer Delegiertenklasse erforderlich (obwohl dies eine gute Idee sein könnte…), und
  • Der Aufwand oder die Komplexität der Arbeit mit der Reflection-API sind nicht erforderlich.

Der Nachteil ist, dass dieser Code wirklich schrecklich ist! Für mich erzeugt es eine Warnung, und das ist im Allgemeinen eine gute Sache. Aber da es sich um ein Problem handelt, das ansonsten völlig unpraktisch ist, fügen Sie a hinzu

@SuppressWarnings("static-access")

An einem geeigneten (minimalen!) Einschlusspunkt wird der Compiler heruntergefahren.

Donal Fellows
quelle
1
Warum nicht? Sie würden einfach das Richtige tun und den Code umgestalten.
Gimby
1
Statische Importe sind nicht viel klarer als diese Lösung und funktionieren nicht in allen Fällen (Sie sind verrückt, wenn es doSomethingirgendwo in Ihrer Hierarchie eine Methode gibt ), also yep beste Lösung.
Voo
@Voo Ich gebe frei zu, dass ich tatsächlich alle Optionen ausprobiert habe, die andere als Antworten aufgeführt hatten, nachdem ich zuerst genau reproduziert hatte, was das ursprüngliche Problem war, und ich war erschrocken, wie schwer es war, das Problem zu umgehen. Die ursprüngliche Frage schließt alle anderen, schöneren Optionen ordentlich ab.
Donal Fellows
1
Stimmen Sie für Kreativität ab, aber ich würde dies niemals im Produktionscode tun. Ich glaube, Indirektion ist die Antwort auf dieses Problem (ist es nicht für alle Probleme?).
Ethanfar
Danke Donal für diese Lösung. Tatsächlich hebt diese Lösung die Stellen hervor, an denen ein "Typ" verwendet werden kann und eine Variable nicht verwendet werden kann. In einer "Besetzung" würde der Compiler also den "Typ" wählen, selbst wenn es eine Variable mit demselben Namen gibt. Für solche interessanten Fälle würde ich 2 "Java Pitfalls" -Bücher empfehlen: books.google.co.in/books/about/… books.google.co.in/books/about/…
RRM
38

Der wahrscheinlich einfachste (nicht unbedingt einfachste) Weg, dies zu verwalten, wäre eine Delegatenklasse:

import net.foo.X;
class C {
    static void doSomething() {
         X.doSomething();
    }
}

und dann ...

class B extends A {
    void doX(){
        C.doSomething();
    }
}

Dies ist etwas ausführlich, aber sehr flexibel - Sie können dafür sorgen, dass es sich so verhält, wie Sie es möchten. Außerdem funktioniert es sowohl mit staticMethoden als auch mit instanziierten Objekten ähnlich

Weitere Informationen zum Delegieren von Objekten finden Sie hier: http://en.wikipedia.org/wiki/Delegation_pattern

blgt
quelle
Wahrscheinlich die sauberste Lösung. Ich würde das wahrscheinlich benutzen, wenn ich dieses Problem hätte.
LordOfThePigs
37

Sie können einen statischen Import verwenden:

import static net.foo.X.doSomething;

class B extends A {
    void doX(){
        doSomething();
    }
}

Beachten Sie dies Bund Aenthalten Sie keine benannten MethodendoSomething

Absurd-Mind
quelle
Hat net.foo.X.doSomething nicht nur Paketzugriff? Das heißt, Sie können nicht über das Paket com.bar darauf zugreifen
JamesB
2
@JamesB Ja, aber dann würde die vollständige Frage keinen Sinn ergeben, da die Methode in keiner Weise zugänglich wäre. Ich denke, dies ist ein Fehler bei der Vereinfachung des Beispiels
Absurd-Mind
1
Aber die ursprüngliche Frage scheint aktiv doSomethingals Name der Methode in B...
Donal Fellows
3
@DonalFellows: Du hast recht; Diese Lösung funktioniert nicht, wenn mein Code so bleiben musste, wie ich ihn gepostet habe. Zum Glück kann ich die Methode umbenennen. Stellen Sie sich jedoch einen Fall vor, in dem meine Methode eine andere Methode überschreibt, dann kann ich sie nicht umbenennen. In diesem Fall würde diese Antwort das Problem tatsächlich nicht lösen. Aber das wäre noch zufälliger als mein Problem bereits ist, also wird es vielleicht nie einen Menschen geben, der auf ein solches Problem mit einem dreifachen Namenskonflikt stößt :).
Gexizid
4
Um es allen anderen klar zu machen: Diese Lösung funktioniert nicht, sobald Sie doSomethingirgendwo in der Vererbungshierarchie sind (aufgrund der Auflösung von Methodenaufrufzielen). Knuth hilft Ihnen also, wenn Sie eine toStringMethode aufrufen möchten . Die von Donal vorgeschlagene Lösung ist die in Blochs Java Puzzlers-Buch (ich glaube, ich habe sie nicht nachgeschlagen), daher können wir sie wirklich als maßgebliche Antwort betrachten :-)
Voo
16

Die richtige Vorgehensweise wäre der statische Import. Im schlimmsten Fall KÖNNTEN Sie jedoch eine Instanz der Klasse mithilfe von Reflection erstellen, wenn Sie den vollständig qualifizierten Namen kennen.

Java: newInstance einer Klasse ohne Standardkonstruktor

Rufen Sie dann die Methode für die Instanz auf.

Oder rufen Sie einfach die Methode selbst mit Reflektion auf: Rufen Sie eine statische Methode mit Reflektion auf

Class<?> clazz = Class.forName("net.foo.X");
Method method = clazz.getMethod("doSomething");
Object o = method.invoke(null);

Dies sind natürlich die letzten Mittel.

EpicPandaForce
quelle
2
Diese Antwort ist bei weitem der größte Overkill bis jetzt - Daumen hoch :)
Gexizid
3
Für diese Antwort sollten Sie ein Abschlussabzeichen erhalten. Sie haben die Antwort für diese einzigartige arme Person gegeben, die eine alte Version von Java verwenden und genau dieses Problem lösen muss.
Gimby
Ich habe eigentlich nicht darüber nachgedacht, dass die static importFunktion nur in hinzugefügt wurde Java 1.5. Ich beneide keine Leute, die sich für 1.4 oder weniger entwickeln müssen, ich musste es einmal und es war schrecklich!
EpicPandaForce
1
@ Gimby: Nun, es gibt immer noch die ((net.foo.X)null).doSomething()Lösung, die in altem Java funktioniert. Aber wenn der Typ Aauch einen inneren Typen enthält net, DANN ist dies die einzige Antwort , die gültig :) bleibt.
Gexizid
6

Nicht wirklich DIE Antwort, aber Sie könnten eine Instanz von X erstellen und die statische Methode darauf aufrufen. Das wäre eine Möglichkeit (schmutzig, gebe ich zu), Ihre Methode aufzurufen.

(new net.foo.X()).doSomething();
Michael Laffargue
quelle
3
Das Umwandeln nullin den Typ und das anschließende Aufrufen der Methode wäre besser, da das Erstellen eines Objekts Nebenwirkungen haben kann, die ich nicht möchte.
Gexizid
@gexicide Diese Idee des Castings nullscheint eine der saubereren Möglichkeiten zu sein, dies zu tun. Braucht eine @SuppressWarnings("static-access")Warnung frei zu sein ...
Donal Fellows
Vielen Dank für die Präzision null, aber Casting nullwar für mich immer ein Herzensbrecher.
Michael Laffargue
4

Es ist nicht erforderlich, seltsame Warnungen auszuführen oder zu unterdrücken oder redundante Instanzen zu erstellen. Nur ein Trick mit der Tatsache, dass Sie statische Methoden der übergeordneten Klasse über die Unterklasse aufrufen können. (Ähnlich wie bei meiner hackigen Lösung hier .)

Erstellen Sie einfach eine Klasse wie diese

public final class XX extends X {
    private XX(){
    }
}

(Der private Konstruktor in dieser letzten Klasse stellt sicher, dass niemand versehentlich eine Instanz dieser Klasse erstellen kann.)

Dann können Sie darüber anrufen X.doSomething():

    public class B extends A {

        public void doSomething() {
            XX.doSomething();
        }
billc.cn
quelle
Interessant, noch ein anderer Ansatz. Die Klasse mit der statischen Methode ist jedoch eine endgültige Dienstprogrammklasse.
Gexizid
Ja, ich kann diesen Trick in diesem Fall nicht anwenden. Ein weiterer Grund, warum die statische Methode ein Sprachfehler ist und Scalas Objektansatz gewählt werden sollte.
billc.cn
Wenn Sie trotzdem eine andere Klasse erstellen möchten, ist die Verwendung einer Delegatenklasse, wie in blgt answer beschrieben, wahrscheinlich die beste.
LordOfThePigs
@LordOfThePigs Dies vermeidet jedoch den zusätzlichen Methodenaufruf und die zukünftige Refactoring-Belastung. Die neue Klasse dient grundsätzlich als Alias.
billc.cn
2

Was ist, wenn Sie versuchen, den Gobalnamespace abzurufen, da sich alle Dateien im selben Ordner befinden? ( http://www.beanshell.org/javadoc/bsh/class-use/NameSpace.html )

    package com.bar;
      class B extends A {

       public void doSomething(){
         com.bar.getGlobal().net.foo.X.doSomething(); // drill down from the top...

         }
     }
Vectoria
quelle
Wie funktioniert das? AFAIK getGlobal()ist keine Standard-Java-Methode für Pakete ... (Ich denke, Pakete können keine Methoden in Java haben ...)
siegi
1

Dies ist einer der Gründe, warum die Zusammensetzung der Vererbung vorzuziehen ist.

package com.bar;
import java.util.concurrent.Callable;
public class C implements Callable<org.A>
{
    private class B extends org.A{
    public void doSomething(){
        C.this.doSomething();
    }
    }

    private void doSomething(){
    net.foo.X.doSomething();
    }

    public org.A call(){
    return new B();
    }
}
Emory
quelle
0

Ich würde das Strategiemuster verwenden.

public interface SomethingStrategy {

   void doSomething();
}

public class XSomethingStrategy implements SomethingStrategy {

    import net.foo.X;

    @Override
    void doSomething(){
        X.doSomething();
    }
}

class B extends A {

    private final SomethingStrategy strategy;

    public B(final SomethingStrategy strategy){
       this.strategy = strategy;
    }

    public void doSomething(){

        strategy.doSomething();
    }
}

Jetzt haben Sie auch Ihre Abhängigkeit entkoppelt, sodass Ihre Komponententests einfacher zu schreiben sind.

Erik Madsen
quelle
Sie haben vergessen, implements SomethingStrategyder XSomethingStrategyKlassendeklaration eine hinzuzufügen .
Ricardo Souza
@rcdmk Danke. Sollte jetzt behoben sein.
Erik Madsen