Эквивалент #define в Java? - PullRequest
       53

Эквивалент #define в Java?

8 голосов
/ 22 декабря 2009

Я пишу библиотеку, которая должна иметь некоторый код, если включена конкретная библиотека. Поскольку этот код разбросан по всему проекту, было бы неплохо, если бы пользователям не приходилось комментировать / раскомментировать все самим.

В C это будет достаточно просто с #define в заголовке, а затем с кодовыми блоками, окруженными #ifdefs. Конечно, в Java нет препроцессора C ...

Для уточнения - несколько внешних библиотек будут распространяться вместе с моей. Я не хочу включать их все, чтобы минимизировать мой размер исполняемого файла. Если разработчик включает библиотеку, я должен иметь возможность использовать ее, а если нет, то ее можно просто проигнорировать.

Каков наилучший способ сделать это на Java?

Ответы [ 12 ]

11 голосов
/ 22 декабря 2009

Нет способа делать то, что вы хотите из Java. Вы можете предварительно обработать исходные файлы Java, но это выходит за рамки Java.

Не можете ли вы абстрагироваться от различий, а затем изменить реализацию?

Исходя из вашего пояснения, может показаться, что вы сможете создать фабричный метод, который будет возвращать либо объект из одной из внешних библиотек, либо класс "заглушки", функции которого будут делать то, что вы сделали бы в «недоступен» условный код.

6 голосов
/ 22 декабря 2009

Как уже говорили другие, в Java не существует такой вещи, как # define / # ifdef. Но что касается вашей проблемы наличия дополнительных внешних библиотек, которые вы бы использовали, если они есть, и не использовали бы, если бы не использовали, использование прокси-классов может быть вариантом (если интерфейсы библиотек не слишком велики).

Мне пришлось сделать это один раз для конкретных расширений Mac OS X для AWT / Swing (находится в com.apple.eawt. *). Классы, конечно, только на пути к классам, если приложение работает в Mac OS. Чтобы иметь возможность использовать их, но все же разрешить использовать одно и то же приложение на других платформах, я написал простые прокси-классы, которые просто предлагали те же методы, что и исходные классы EAWT. Внутренне прокси использовали некоторое отражение, чтобы определить, были ли реальные классы на пути к классам и будут ли проходить через все вызовы методов. Используя класс java.lang.reflect.Proxy , вы даже можете создавать и передавать объекты типа, определенного во внешней библиотеке, без его доступности во время компиляции.

Например, прокси для com.apple.eawt.ApplicationListener выглядел так:

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

}

Все это имеет смысл, только если вам нужно всего несколько классов из внешней библиотеки, потому что вы должны делать все с помощью отражения во время выполнения. Для больших библиотек вам, вероятно, понадобится какой-то способ автоматизации генерации прокси. Но затем, если вы действительно зависите от большой внешней библиотеки, вам просто нужно это сделать во время компиляции.

Комментарий Питера Лоури: (Извините, что отредактировал, очень сложно вставить код в комментарий)

Следующий пример является общим для метода, поэтому вам не нужно знать все используемые методы. Вы также можете сделать это универсальным для каждого класса, поэтому вам нужен только один класс InvocationHandler, закодированный для всех случаев.

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);
}
5 голосов
/ 22 декабря 2009

В Java можно использовать различные подходы для достижения того же результата:

Способ Java состоит в том, чтобы поместить поведение, которое изменяется в набор отдельных классов, абстрагированных через интерфейс, а затем подключить требуемый класс во время выполнения. Смотри также:

4 голосов
/ 22 декабря 2009

Ну, синтаксис Java достаточно близок к C, чтобы вы могли просто использовать препроцессор C, который обычно поставляется в виде отдельного исполняемого файла.

Но в любом случае Java на самом деле не работает во время компиляции. То, как я справлялся с подобными ситуациями раньше, - с помощью рефлексии. В вашем случае, поскольку ваши вызовы, возможно, отсутствующей библиотеки, разбросаны по всему коду, я бы создал класс-оболочку, заменил бы все вызовы библиотеки вызовами в класс-оболочку и затем использовал бы отражение внутри класса-оболочки вызвать библиотеку, если она есть.

2 голосов
/ 22 декабря 2009

Используйте константу :

На этой неделе мы создадим несколько констант которые имеют все преимущества использования возможности препроцессора C для определить константы времени компиляции и условно скомпилированный код.

Ява избавилась от всего Понятие текстового препроцессора (если вы принимаете Java как «потомок» C / C ++). Мы можем, однако, получить лучшее преимущества по крайней мере, некоторые из C Особенности препроцессора в Java: константы и условная компиляция.

1 голос
/ 22 декабря 2009

«чтобы минимизировать мой исполняемый размер»

Что вы подразумеваете под "исполняемым размером"?

Если вы имеете в виду объем кода, загруженного во время выполнения, вы можете условно загрузить классы через загрузчик классов. Таким образом, вы распространяете свой альтернативный код, несмотря ни на что, но он фактически загружается только в том случае, если отсутствует библиотека, в которой он стоит. Вы можете использовать Адаптер (или аналогичный) для инкапсуляции API, чтобы убедиться, что почти весь ваш код в любом случае одинаков, и один из двух классов-оболочек загружается в соответствии с вашим случаем. SPI безопасности Java может дать вам некоторые идеи, как это можно структурировать и реализовать.

Если вы имеете в виду размер вашего файла .jar, то вы можете сделать это выше, но рассказать своим разработчикам, как убрать ненужные классы из банки, в случае, если они знают, что они не понадобятся .

1 голос
/ 22 декабря 2009

Я не верю, что действительно есть такая вещь. Большинство истинных пользователей Java скажут вам, что это хорошо, и что следует избегать использования условной компиляции практически любой ценой.

Я не совсем согласен с ними ...

Вы МОЖЕТЕ использовать константы, которые могут быть определены из строки компиляции, и это будет иметь некоторый эффект, но не все. (Например, вы не можете иметь вещи, которые не компилируются, но вы все еще хотите, внутри #if 0 ... (и нет, комментарии не всегда решают эту проблему, потому что вложенные комментарии могут быть сложными ... )).

Я думаю, что большинство людей скажут вам использовать некоторую форму наследования для этого, но это может быть и очень уродливо, с большим количеством повторяющегося кода ...

Тем не менее, вы МОЖЕТЕ всегда просто настроить свою IDE, чтобы она пропускала Java через препроцессор перед отправкой его в javac ...

0 голосов
/ 23 января 2015

У меня есть еще один лучший способ сказать.

Вам нужна последняя переменная.

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

Тогда внутри кода произнесите как

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
}

Это будет работать как #ifdef. Любой из блоков будет присутствовать в исполняемом коде. Другое будет устранено во время компиляции

0 голосов
/ 20 января 2010

Если это поможет, взгляните на j2me polish или Использование директив препроцессора в плагине BlackBerry JDE для затмения?

это для мобильного приложения, но его можно использовать повторно?

0 голосов
/ 22 декабря 2009

Я вижу, вы указали здесь две взаимоисключающие проблемы (или, скорее, вы выбрали одну, и я просто не понимаю, какой выбор вы сделали).

Вы должны сделать выбор: отправляете ли вы две версии вашего исходного кода (одну, если библиотека существует, и другую, если ее нет), или вы отправляете одну версию и ожидаете, что она будет работать с библиотекой, если библиотека существует.

Если вы хотите, чтобы одна версия обнаруживала существование библиотеки и использовала ее, если она доступна, вы ДОЛЖНЫ иметь весь код для доступа к ней в своем распределенном коде - вы не можете ее обрезать. Поскольку вы приравниваете свою проблему к использованию #define, я предположил, что это не ваша цель - вы хотите отправить 2 версии (единственный способ, которым может работать #define)

Итак, с 2 версиями вы можете определить libraryInterface. Это может быть либо объект, который упаковывает вашу библиотеку и перенаправляет все вызовы библиотеки для вас, либо интерфейс - в любом случае этот объект ДОЛЖЕН существовать во время компиляции для обоих режимов.

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

Теперь, когда вы хотите ИСПОЛЬЗОВАТЬ свою библиотеку (в случаях, когда у вас был бы #ifdef в C), у вас есть это:

if(LIBRARY_EXISTS)
    library.doFunc()

Библиотека - это интерфейс, который существует в обоих случаях. Поскольку он всегда защищен LIBRARY_EXISTS, он скомпилируется (даже не должен загружаться в ваш загрузчик классов - но это зависит от реализации).

Если ваша библиотека является предварительно упакованной библиотекой, предоставленной третьей стороной, вам, возможно, придется сделать Library класс-оболочку, который перенаправляет ее вызовы в вашу библиотеку. Поскольку обертка вашей библиотеки никогда не создается, если LIBRARY_EXISTS имеет значение false, ее даже не следует загружать во время выполнения (черт возьми, ее даже не нужно компилировать, если JVM достаточно умна, поскольку всегда защищена конечной константой.) что оболочка ДОЛЖНА быть доступна во время компиляции в обоих случаях.

...