2009-12-21 21 views
8

Ich schreibe eine Bibliothek, die Code benötigt, wenn eine bestimmte Bibliothek enthalten ist. Da dieser Code überall im Projekt verstreut ist, wäre es schön, wenn Benutzer nicht alles selbst kommentieren/auskommentieren müssten.Entspricht #define in Java?

In C wäre dies einfach genug mit einem #define in einem Header, und dann Codeblöcke umgeben von #ifdefs. Natürlich hat Java nicht den C-Präprozessor ...

Um zu klären - mehrere externe Bibliotheken werden mit mir verteilt werden. Ich möchte sie nicht alle einbeziehen müssen, um meine ausführbare Dateigröße zu minimieren. Wenn ein Entwickler eine Bibliothek enthält, muss ich in der Lage sein, sie zu verwenden, und wenn nicht, kann sie einfach ignoriert werden.

Was ist der beste Weg, dies in Java zu tun?

Antwort

6

Wie andere schon gesagt haben, gibt es in Java keine Definition von # define/# ifdef. Aber in Bezug auf Ihr Problem mit optionalen externen Bibliotheken, die Sie verwenden würden, falls vorhanden, und nicht verwenden, wenn nicht, könnte die Verwendung von Proxy-Klassen eine Option sein (wenn die Bibliotheksschnittstellen nicht zu groß sind).

Ich musste dies einmal für die Mac OS X spezifischen Erweiterungen für AWT/Swing (in com.apple.eawt. *) Finden. Die Klassen befinden sich natürlich nur auf dem Klassenpfad, wenn die Anwendung unter Mac OS läuft. Um sie nutzen zu können und trotzdem die gleiche App auf anderen Plattformen zu verwenden, schrieb ich einfache Proxy-Klassen, die einfach die gleichen Methoden wie die ursprünglichen EAWT-Klassen anbieten. Intern verwendeten die Proxies einige Überlegungen, um festzustellen, ob die realen Klassen im Klassenpfad waren und alle Methodenaufrufe durchlaufen würden. Mit der Klasse java.lang.reflect.Proxy können Sie sogar Objekte eines in der externen Bibliothek definierten Typs erstellen und weitergeben, ohne sie zur Kompilierungszeit verfügbar zu haben.

Zum Beispiel sah die Proxy für com.apple.eawt.ApplicationListener wie folgt aus:

public class ApplicationListener { 

    private static Class<?> nativeClass; 

    static Class<?> getNativeClass() { 
     try { 
      if (ApplicationListener.nativeClass == null) { 
       ApplicationListener.nativeClass = Class.forName("com.apple.eawt.ApplicationListener"); 
      } 

      return ApplicationListener.nativeClass; 
     } catch (ClassNotFoundException ex) { 
      throw new RuntimeException("This system does not support the Apple EAWT!", ex); 
     } 
    } 

    private Object nativeObject; 

    public ApplicationListener() { 
     Class<?> nativeClass = ApplicationListener.getNativeClass(); 

     this.nativeObject = Proxy.newProxyInstance(nativeClass.getClassLoader(), new Class<?>[] { 
      nativeClass 
     }, new InvocationHandler() { 

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
       String methodName = method.getName(); 

       ApplicationEvent event = new ApplicationEvent(args[0]); 

       if (methodName.equals("handleReOpenApplication")) { 
        ApplicationListener.this.handleReOpenApplication(event); 
       } else if (methodName.equals("handleQuit")) { 
        ApplicationListener.this.handleQuit(event); 
       } else if (methodName.equals("handlePrintFile")) { 
        ApplicationListener.this.handlePrintFile(event); 
       } else if (methodName.equals("handlePreferences")) { 
        ApplicationListener.this.handlePreferences(event); 
       } else if (methodName.equals("handleOpenFile")) { 
        ApplicationListener.this.handleOpenFile(event); 
       } else if (methodName.equals("handleOpenApplication")) { 
        ApplicationListener.this.handleOpenApplication(event); 
       } else if (methodName.equals("handleAbout")) { 
        ApplicationListener.this.handleAbout(event); 
       } 

       return null; 
      } 

     }); 
    } 

    Object getNativeObject() { 
     return this.nativeObject; 
    } 

    // followed by abstract definitions of all handle...(ApplicationEvent) methods 

} 

dies nur alle Sinn macht, wenn man nur ein paar Klassen von einer externen Bibliothek benötigen, weil Sie müssen mach alles über Reflektion zur Laufzeit. Bei größeren Bibliotheken benötigen Sie wahrscheinlich eine Möglichkeit, die Generierung der Proxies zu automatisieren. Wenn Sie jedoch wirklich auf eine große externe Bibliothek angewiesen sind, sollten Sie sie nur zur Kompilierzeit benötigen.

Kommentar von Peter Lawrey ein: (zu bearbeiten, der sehr hart Code in einen Kommentar)

Das Beispiel folgen wird durch die Methode generisch, so dass Sie benötigen, um alle beteiligten Methoden nicht kennen. Sie können dies auch generisch nach Klasse machen, so dass Sie nur eine InvocationHandler-Klasse benötigen, die so codiert ist, dass sie alle Fälle abdeckt.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
    String methodName = method.getName(); 
    ApplicationEvent event = new ApplicationEvent(args[0]); 
    Method method = ApplicationListener.class.getMethod(methodName, ApplicationEvent.class); 
    return method.invoke(ApplicationListener.this, event); 
} 
2

Verwenden Sie ein constant:

Diese Woche stellen wir einige Konstanten , die alle die Vorteile der Verwendung der C-Präprozessor-Einrichtungen zu haben definieren Kompilierung-Konstanten und bedingt kompilierten Code.

Java hat Loswerden der gesamten Vorstellung eines Text Präprozessor bekommen (wenn Sie Java als „Nachkomme“ von C/C++ nehmen). Wir können jedoch die besten Vorteile von mindestens einigen der C Präprozessor Funktionen in Java: Konstanten und bedingte Kompilierung.

+0

Ich glaube nicht, dass das ist, was er sucht. Er möchte keine symbolischen Konstanten, sondern ein Äquivalent zur bedingten Kompilierung. Da ich Java nicht kenne, habe ich keine Ahnung ... – dmckee

+0

was ist mit der bedingten Kompilierung? Wie machst du das in Java? – Russell

+0

Der Artikel, den ich verlinkt habe, bespricht dies. –

0

Verwenden Eigenschaften diese Art der Sache zu tun.

Verwenden Sie Dinge wie Class.forName, um die Klasse zu identifizieren.

Verwenden Sie keine if-Anweisungen, wenn Sie eine Eigenschaft direkt in eine Klasse übersetzen können.

11

Es gibt keine Möglichkeit, was Sie von Java aus tun können. Sie könnten die Java-Quelldateien vorverarbeiten, dies liegt jedoch außerhalb des Java-Bereichs.

Können Sie die Unterschiede nicht abstrahieren und dann die Implementierung variieren?

Basierend auf Ihrer Klarstellung, es klingt wie Sie möglicherweise in der Lage, eine Factory-Methode, die entweder ein Objekt aus einer der externen Bibliotheken oder eine "Stub" -Klasse, deren Funktionen tun, was Sie in der tun würde "nicht verfügbarer" bedingter Code

+0

+1: Dieses Problem hört sich an, als könnte es möglich sein, das Lösungsarchitekturdesign zu modifizieren, um dabei zu helfen. – Russell

+0

Der Java-Compiler optimiert bedingte Prüfungen basierend auf konstanten Werten. So ist es definitiv möglich. –

+0

@Anon: Aber es kompiliert immer noch beide Zweige. es optimiert nur den unbenutzten Code, richtig? Auch eine Menge Zeit, Umstrukturierung Ihrer Architektur zur Beseitigung dieser Probleme kann wirklich unordentlich werden. Wenn Sie eine Menge zufälliger Orte haben, die Sie #ifdefed haben wollen, haben Sie am Ende eine Basisklasse, dann eine Reihe von Unterklassen mit Hilfsfunktionen, und es wird einfach ein Durcheinander. –

1

Ich glaube nicht, dass es so etwas wirklich gibt. Die meisten echten Java-Benutzer werden Ihnen sagen, dass dies eine gute Sache ist, und dass eine bedingte Kompilierung zu fast allen Kosten vermieden werden sollte.

Ich bin nicht wirklich mit ihnen einverstanden ...

Sie KÖNNEN Konstanten verwenden, die von der Kompilierung Linie definiert werden können, und das wird etwas von der Wirkung haben, aber nicht wirklich. (Zum Beispiel können Sie keine Dinge haben, die nicht kompilieren, aber Sie wollen immer noch, in #if 0 ... (und nein, Kommentare lösen nicht immer dieses Problem, weil das Verschachteln von Kommentaren knifflig sein kann ...))).

Ich denke, dass die meisten Leute werden Ihnen sagen, eine gewisse Form der Vererbung zu verwenden, um dies zu tun, aber das kann auch sehr hässlich sein, mit vielen wiederholten Code ...

Das heißt, können Sie immer nur richten sie Ihre IDE Ihre Java durch den Pre-Prozessor zu werfen, bevor es zu javac ...

5

In Java sendet man eine Vielzahl von Ansätzen verwenden könnte das gleiche Ergebnis zu erzielen:

Die Java Art und Weise ist das Verhalten zu setzen, die, stecken Sie dann die gewünschte Klasse zur Laufzeit abstrahiert über eine Schnittstelle in eine Reihe von separaten Klassen variieren. Siehe auch:

+0

+1 für Dependency Injection. Stellen Sie sich vor, Sie verbinden Ihre Objekte über eine XML-Datei (als Beispiel) und verwenden dann eine andere XML-Datei für Ihre verschiedenen Situationen. –

1

"meine Größe der ausführbaren Datei zu minimieren" Was meinst du mit "Größe der ausführbaren Datei" ?

Wenn Sie die Menge an Code zur Laufzeit geladen meinen, können Sie Klassen über den Classloader bedingt laden. Sie verteilen also Ihren alternativen Code, egal was passiert, aber er wird nur geladen, wenn die Bibliothek, für die er steht, fehlt. Sie können einen Adapter (oder einen ähnlichen Adapter) verwenden, um die API zu kapseln, um sicherzustellen, dass fast Ihr gesamter Code genau gleich ist, und je nach Fall eine von zwei Wrapper-Klassen geladen wird. Der Java-Sicherheits-SPI könnte Ihnen einige Ideen geben, wie dies strukturiert und implementiert werden kann.

Wenn Sie die Größe Ihrer .jar-Datei meinen, können Sie das oben beschriebene tun, aber sagen Sie Ihren Entwicklern, wie Sie die unnötigen Klassen aus dem Jar entfernen, für den Fall, dass sie es nicht wissen erforderlich.

+0

Die Bibliothek wird in Android-Anwendungen verwendet, daher möchte ich nichts unnötiges hinzufügen. – Justin

+0

Dies entspricht nicht den Parametern Ihrer ursprünglichen Frage. Wenn Sie zur Laufzeit verlinken, MÜSSEN Sie immer noch den Code haben, der die Bibliothek aufruft, die kompiliert wird, damit #ifdef nicht helfen würde. Wenn Sie nicht zur Laufzeit verknüpfen, sondern für verschiedene Ziele kompilieren, dann wird if (CONSTANT) ausreichen. Welches ist das eigentliche Problem? –

+0

Nein, die Konstante wird nicht ausreichen. Zum Beispiel wird 'if (false) {LibraryIDontHave.action();}' nicht kompiliert. – Justin

4

Nun, Java-Syntax ist nahe genug an C, dass Sie einfach den C-Präprozessor verwenden könnten, der normalerweise als separate ausführbare Datei ausgeliefert wird.

Aber Java ist nicht wirklich Dinge zur Kompilierzeit sowieso zu tun. Die Art, wie ich zuvor ähnliche Situationen behandelt habe, ist die Reflexion. In Ihrem Fall würde ich, da Ihre Aufrufe der möglicherweise nicht vorhandenen Bibliothek im Code verstreut sind, eine Wrapper-Klasse erstellen, alle Aufrufe der Bibliothek durch Aufrufe der Wrapper-Klasse ersetzen und dann die Reflektion in der Wrapper-Klasse verwenden in der Bibliothek aufrufen, wenn sie vorhanden ist.

0

Je nachdem, was Sie (nicht genug Informationen) machst du so etwas tun könnte:

interface Foo 
{ 
    void foo(); 
} 

class FakeFoo 
    implements Foo 
{ 
    public void foo() 
    { 
     // do nothing 
    } 
} 

class RealFoo 
{ 
    public void foo() 
    { 
     // do something 
    } 
} 

und bieten dann eine Klasse zu abstrahieren die Instanziierung:

class FooFactory 
{ 
    public static Foo makeFoo() 
    { 
     final String name; 
     final FooClass fooClass; 
     final Foo  foo; 

     name  = System.getProperty("foo.class"); 
     fooClass = Class.forName(name); 
     foo  = (Foo)fooClass.newInstance(); 

     return (foo); 
    } 
} 

Führen Sie dann Java mit -Dfoo.name = RealFoo | FakeFoo

Ignoriert die Ausnahmebehandlung i n die makeFoo-Methode und Sie können es anders machen ... aber die Idee ist die gleiche.

Auf diese Weise kompilieren Sie beide Versionen der Foo-Unterklassen und lassen Sie den Entwickler zur Laufzeit auswählen, die sie verwenden möchten.

0

Ich sehe Sie hier zwei einander ausschließende Probleme angeben (oder, wahrscheinlicher, haben Sie eine gewählt und ich verstehe nur nicht, welche Wahl Sie getroffen haben).

Sie müssen wählen: Versenden Sie zwei Versionen Ihres Quellcodes (einen, wenn die Bibliothek existiert, und einen, wenn dies nicht der Fall ist), oder versenden Sie eine einzelne Version und erwarten, dass sie mit der Bibliothek funktioniert wenn die Bibliothek existiert.

Wenn Sie möchten, dass eine einzelne Version die Existenz der Bibliothek erkennt und diese, falls verfügbar, verwendet, dann MÜSSEN Sie den gesamten Code für den Zugriff auf Ihren verteilten Code haben - Sie können ihn nicht ausschneiden. Da Sie Ihr Problem mit der Verwendung einer #define gleichsetzen, nahm ich an, dass dies nicht Ihr Ziel war - Sie möchten 2 Versionen versenden (Die einzige Möglichkeit, #define kann funktionieren)

Also, mit 2 Versionen können Sie eine Bibliothek definieren . Dies kann entweder ein Objekt sein, das Ihre Bibliothek umschließt und alle Aufrufe an die Bibliothek für Sie oder eine Schnittstelle weiterleitet - in jedem Fall MUSS dieses Objekt zur Kompilierzeit für beide Modi vorhanden sein.

public LibraryInterface getLibrary() 
{ 
    if(LIBRARY_EXISTS) // final boolean 
    { 
     // Instantiate your wrapper class or reflectively create an instance    
     return library; 
    } 
    return null; 
} 

Nun, wenn Sie Ihre Bibliothek verwenden möchten (Fälle, in denen Sie ein #ifdef in C hätte) haben Sie diese:

if(LIBRARY_EXISTS) 
    library.doFunc() 

Bibliothek ist eine Schnittstelle, die in beiden Fällen existiert. Da es immer durch LIBRARY_EXISTS geschützt ist, wird es kompiliert (sollte niemals in Ihren Klassenlader geladen werden - aber das ist implementierungsabhängig).

Wenn Ihre Bibliothek eine vordefinierte Bibliothek ist, die von einem Drittanbieter bereitgestellt wird, müssen Sie möglicherweise Library eine Wrapper-Klasse erstellen, die ihre Aufrufe an Ihre Bibliothek weiterleitet. Da Ihr Bibliothekswrapper niemals instanziiert wird, wenn LIBRARY_EXISTS false ist, sollte er nicht einmal zur Laufzeit geladen werden (Heck, es sollte nicht einmal kompiliert werden, wenn die JVM schlau genug ist, da sie immer durch eine letzte Konstante geschützt ist.), Aber denken Sie daran dass der Wrapper in beiden Fällen zur Kompilierzeit verfügbar sein muss.

0

Ich habe eine weitere beste Möglichkeit zu sagen.

Was Sie brauchen, ist eine endgültige Variable.

public static final boolean LibraryIncluded= false; //or true - manually set this 

dann in den Code sagen, wie

if(LibraryIncluded){ 
    //do what you want to do if library is included 
} 
else 
{ 
    //do if you want anything to do if the library is not included 
} 

Dies als #ifdef arbeiten. Jeder der Blöcke wird im ausführbaren Code vorhanden sein. Andere werden in der Kompilierzeit selbst beseitigt