2009-12-21 17 views
8

Estoy escribiendo una biblioteca que necesita tener algún código si se incluye una biblioteca en particular. Dado que este código está disperso por todo el proyecto, sería bueno que los usuarios no tuvieran que comentar/descomentar todo ellos mismos.¿Equivalente a #define en Java?

En C, esto sería bastante fácil con un #define en un encabezado, y luego bloques de código rodeados con #ifdefs. Por supuesto, Java no tiene el preprocesador C ...

Para aclarar: se distribuirán varias bibliotecas externas con la mía. No quiero tener que incluirlos a todos para minimizar mi tamaño de ejecutable. Si un desarrollador incluye una biblioteca, necesito poder usarla, y si no, puede ser ignorada.

¿Cuál es la mejor manera de hacer esto en Java?

Respuesta

6

Como han dicho otros, no existe tal cosa como # define/# ifdef en Java. Pero con respecto a su problema de tener bibliotecas externas opcionales, que usaría, si está presente, y no las usaría, usar clases proxy podría ser una opción (si las interfaces de la biblioteca no son demasiado grandes).

Tuve que hacer esto una vez para las extensiones específicas de Mac OS X para AWT/Swing (se encuentran en com.apple.eawt. *). Las clases son, por supuesto, solo en la ruta de clase si la aplicación se ejecuta en Mac OS. Para poder usarlos pero aún permitir el uso de la misma aplicación en otras plataformas, escribí clases de proxy simples, que solo ofrecían los mismos métodos que las clases originales de EAWT. Internamente, los proxies usaron alguna reflexión para determinar si las clases reales estaban en la ruta de clases y pasarían por todas las llamadas a métodos. Al utilizar la clase java.lang.reflect.Proxy, puede incluso crear y pasar objetos de un tipo definido en la biblioteca externa, sin tenerlo disponible en tiempo de compilación.

Por ejemplo, el proxy para com.apple.eawt.ApplicationListener era la siguiente:

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 

} 

Todo esto sólo tiene sentido, si necesita sólo unas pocas clases de una biblioteca externa, ya que hay que hacer todo a través de la reflexión en tiempo de ejecución. Para bibliotecas más grandes, probablemente necesite alguna forma de automatizar la generación de los proxies. Pero entonces, si realmente dependes de una gran biblioteca externa, solo deberías requerirla en tiempo de compilación.

El comentario de Peter Lawrey: (Lo siento editar, es muy difícil código para poner en un comentario)

El ejemplo sigue es genérica por el método de modo que no es necesario conocer todos los métodos involucrados. También puede hacer esto genérico por clase, por lo que solo necesita una clase InvocationHandler codificada para cubrir todos los casos.

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

Utilice un constant:

Esta semana crear algunas constantes que tienen todas las ventajas de usar las instalaciones del preprocesador C a definen las constantes de tiempo de compilación y código compilado de forma condicional.

Java ha librado de toda la noción de un preprocesador textual (si se toma Java como un "descendiente" de C/C++). Sin embargo, podemos obtener los mejores beneficios de al menos algunas de las características del preprocesador C en Java: constantes y compilación condicional.

+0

No creo que esto sea lo que está buscando. Él no quiere constantes simbólicas, sino un equivalente a la compilación condicional. Sin saber java, no tengo ni idea, aunque ... – dmckee

+0

¿qué pasa con la compilación condicional? ¿Cómo haces eso en Java? – Russell

+0

El artículo que he vinculado discute esto. –

0

Utilice propiedades para hacer este tipo de cosas.

Use cosas como Class.forName para identificar la clase.

No utilice declaraciones if cuando pueda traducir trivialmente una propiedad directamente a una clase.

11

No hay forma de hacer lo que quiera desde Java. Puede preprocesar los archivos fuente de Java, pero eso está fuera del alcance de Java.

¿No puede abstraer las diferencias y luego variar la implementación?

Según su aclaración, parece que podría crear un método de fábrica que devolverá un objeto de una de las bibliotecas externas o una clase "stub" cuyas funciones harán lo que usted hubiera hecho en el código condicional "no disponible".

+0

+1: Este problema parece que puede ser (podría) posible modificar el diseño de la arquitectura de la solución para ayudar con esto. – Russell

+0

El compilador de Java optimizará las comprobaciones condicionales de distancia basadas en valores constantes. Por lo tanto, es definitivamente posible. –

+0

@Anon: Pero aún compila ambas ramas. simplemente optimiza el código no utilizado, ¿verdad? Además, MUCHAS veces, la reestructuración de su arquitectura para eliminar estos problemas puede volverse realmente complicada. Si tienes un montón de lugares aleatorios que quieres #ifdefed ... terminas con una clase base, luego un grupo de subclases, con funciones de ayuda, y simplemente se convierte en un desastre. –

1

No creo que realmente exista tal cosa. La mayoría de los verdaderos usuarios de Java le dirán que esto es una buena cosa, y que confiar en la compilación condicional debe evitarse a casi todos los costos.

estoy realmente no estar de acuerdo con ellos ...

Puede utilizar las constantes que se pueden definir desde la línea de compilación, y que contará con algunos de los efectos, pero no realmente todo. (Por ejemplo, no puede tener cosas que no se compilan, pero aún desea, dentro de # si 0 ... (y no, los comentarios no siempre resuelven ese problema, porque anidar comentarios puede ser complicado ...))).

creo que la mayoría de la gente le dirá a utilizar algún tipo de herencia para ello, pero que puede ser muy feo, así, con una gran cantidad de código repetidos ...

Dicho esto, siempre puede configurar el IDE para lanzar su Java a través del procesador previo antes de enviarlo a javac ...

5

En Java se podría usar una variedad de enfoques para lograr el mismo resultado:

La forma Java es poner comportamiento que varía en un conjunto de clases separadas abstraídas a través de una interfaz, a continuación, enchufe la clase requerida en tiempo de ejecución. Consulte también:

+0

+1 para Inyección de dependencia. Imagine el cableado de sus objetos a través de un archivo XML (por ejemplo) y luego utilice un archivo XML diferente para sus diversas situaciones. –

1

"para reducir al mínimo el tamaño de mi ejecutable"

¿Qué quiere decir con "el tamaño del ejecutable" ?

Si se refiere a la cantidad de código cargado en el tiempo de ejecución, entonces puede cargar las clases de manera condicional a través del cargador de clases. Entonces distribuye su código alternativo sin importar qué, pero solo se carga realmente si falta la biblioteca en la que se encuentra. Puede usar un Adaptador (o similar) para encapsular la API, para asegurarse de que casi todo su código sea exactamente el mismo, y una de las dos clases contenedoras se carga según su caso. El SPI de seguridad de Java podría brindarle algunas ideas sobre cómo se puede estructurar e implementar.

Si se refiere al tamaño de su archivo .jar, puede hacer lo anterior, pero diga a sus desarrolladores cómo quitar las clases innecesarias del contenedor, en caso de que sepan que no van a ser necesario.

+0

La biblioteca se usará en aplicaciones de Android, por lo que no quiero incluir nada innecesario. – Justin

+0

Esto no cumple con los parámetros de su pregunta original. Si está enlazando en tiempo de ejecución, aún TIENE que tener el código que llama a la biblioteca compilada para que #ifdef no ayude de todos modos. Si no está enlazando en tiempo de ejecución pero compilando para diferentes objetivos, entonces si (CONSTANTE) será suficiente. ¿Cuál es el problema real? –

+0

No, la constante no será suficiente. Por ejemplo, 'if (false) {LibraryIDontHave.action();}' no se compilará. – Justin

4

Bueno, la sintaxis de Java está lo suficientemente cerca de C que podría simplemente usar el preprocesador C, que generalmente se envía como un ejecutable por separado.

Pero Java no se trata realmente de hacer cosas en tiempo de compilación de todos modos. La forma en que manejé situaciones similares antes es con reflexión. En su caso, dado que sus llamadas a la biblioteca posiblemente no presente están dispersas por todo el código, crearía una clase contenedora, reemplazaría todas las llamadas a la biblioteca con llamadas a la clase contenedora y luego usaría la reflexión dentro de la clase contenedora invocar en la biblioteca si está presente.

0

Dependiendo de lo que está haciendo (no del todo suficiente información) que podría hacer algo como esto:

interface Foo 
{ 
    void foo(); 
} 

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

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

y luego proporcionar una clase de abstraer la creación de instancias:

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); 
    } 
} 

A continuación, ejecute java con -Dfoo.name = RealFoo | FakeFoo

Ignorado el manejo de excepciones i n el método makeFoo y puedes hacerlo de otras maneras ... pero la idea es la misma.

De esta manera puede compilar ambas versiones de las subclases de Foo y dejar que el desarrollador elija en tiempo de ejecución las que desea utilizar.

0

Veo que especifica aquí dos problemas mutuamente excluyentes (o, más probablemente, ha elegido uno y no entiendo qué opción ha elegido).

Tienes que elegir: ¿Vas a enviar dos versiones de tu código fuente (una si existe la biblioteca y otra si no) o estás enviando una única versión y esperando que funcione con la biblioteca? si la biblioteca existe

Si desea que una versión individual detecte la existencia de la biblioteca y la utilice si está disponible, DEBE tener todo el código para acceder a ella en su código distribuido; no puede recortarla. Ya que está equiparando su problema con el uso de un #define, asumí que este no era su objetivo - desea enviar 2 versiones (La única forma en que #define puede funcionar)

Entonces, con 2 versiones, puede definir una bibliotecaInterfaz . Esto puede ser un objeto que envuelve su biblioteca y reenvía todas las llamadas a la biblioteca para usted o una interfaz; en cualquier caso, este objeto DEBE existir en el momento de la compilación para ambos modos.

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

Ahora, cuando se desea utilizar su biblioteca (casos en los que habría tenido un #ifdef en C) que tiene esto:

if(LIBRARY_EXISTS) 
    library.doFunc() 

Biblioteca es una interfaz que existe en ambos casos. Como siempre está protegido por LIBRARY_EXISTS, se compilará (nunca debería cargarse en el cargador de clases, pero eso depende de la implementación).

Si su biblioteca es una biblioteca preempaquetada proporcionada por un tercero, es posible que deba hacer que la biblioteca sea una clase contenedora que reenvía sus llamadas a su biblioteca. Como el contenedor de su biblioteca nunca se crea una instancia si LIBRARY_EXISTS es falso, ni siquiera debería cargarse en el tiempo de ejecución (Diablos, ni siquiera debería compilarse si la JVM es lo suficientemente inteligente ya que siempre está protegida por una constante final). que el contenedor DEBE estar disponible en tiempo de compilación en ambos casos.

0

Tengo una mejor manera de decirlo.

Lo que necesita es una variable final.

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

A continuación, dentro del código dicen que

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 
} 

Esto funcionará como #ifdef. Cualquiera de los bloques estará presente en el código ejecutable. Otro será eliminado en el tiempo de compilación en sí