Эквивалент #define в Java?
Я пишу библиотеку, которая должна иметь некоторый код, если включена определенная библиотека. Поскольку этот код разбросан по всему проекту, было бы неплохо, если бы пользователям не приходилось комментировать/раскомментировать все сами.
В C, это было бы достаточно легко с #define
в заголовке, а затем блоки кода, окруженные #ifdefs
. Конечно, Java не имеет препроцессора C...
чтобы уточнить-несколько внешних библиотек будут распространяться с шахта. Я не хочу включать их все, чтобы минимизировать размер исполняемого файла. Если разработчик включает библиотеку, я должен иметь возможность использовать ее, а если нет, то ее можно просто игнорировать.
каков наилучший способ сделать это на Java?
12 ответов
Как уже говорили другие, в Java нет такой вещи, как #define/#ifdef. Но что касается вашей проблемы наличия дополнительных внешних библиотек, которые вы бы использовали, если они есть, и не используете, если нет, использование прокси-классов может быть вариантом (если интерфейсы библиотеки не слишком велики).
мне пришлось сделать это один раз для конкретных расширений Mac OS X для AWT/Swing (найдено в com.яблоко.eawt.*). Классы, конечно, только на пути к классу, если приложение работает на Mac OS. Быть способный использовать их, но все же разрешающий использовать то же самое приложение на других платформах, я написал простые прокси-классы, которые просто предлагали те же методы, что и исходные классы EAWT. Внутренне прокси использовали некоторое отражение, чтобы определить, находятся ли реальные классы на пути к классу и будут проходить через все вызовы методов. Используя java.ленг.отражать.Прокси class, вы даже можете создавать и передавать объекты типа, определенного во внешней библиотеке, не имея его в время компиляции.
например, прокси для com.яблоко.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);
}
нет никакого способа сделать то, что вы хотите из Java. Вы можете предварительно обработать исходные файлы Java, но это выходит за рамки Java.
можете ли вы не абстрагировать различия, а затем варьировать реализацию?
основываясь на вашем разъяснении, похоже, вы сможете создать заводской метод, который вернет либо объект из одной из внешних библиотек, либо класс" заглушка", функции которого будут делать то, что вы сделали бы в " недоступном" условный код.
в Java можно использовать различные подходы для достижения такого же результата:
способ Java заключается в том, чтобы поместить поведение, которое изменяется в набор отдельных классов, абстрагированных через интерфейс, а затем подключить требуемый класс во время выполнения. Видеть также:
ну, синтаксис Java достаточно близок к C, что вы можете просто использовать препроцессор C, который обычно поставляется как отдельный исполняемый файл.
но Java на самом деле не о том, чтобы делать что-то во время компиляции. То, как я справлялся с подобными ситуациями раньше, - это отражение. В вашем случае, поскольку ваши вызовы возможно-отсутствующей библиотеки разбросаны по всему коду, я бы сделал класс-оболочку, заменил бы все вызовы библиотеки вызовами класса-оболочки, а затем используйте отражение внутри класса-оболочки для вызова библиотеки, если она присутствует.
использовать постоянный:
на этой неделе мы создаем некоторые константы которые имеют все преимущества использования возможности препроцессора C для определить константы времени компиляции и условно скомпилированный код.
Java избавилась от всего понятие текстового препроцессора (если вы принимаете Java как "потомка" C / C++). Мы можем, однако, получить лучшее преимущества по крайней мере некоторых из C возможности препроцессора в Java: константы и условная компиляция.
Я не верю, что это действительно так. Большинство истинных пользователей Java скажут вам, что это хорошо, и что полагаться на условную компиляцию следует избегать почти любой ценой.
Я не согласен с ними...
вы можете использовать константы, которые могут быть определены из строки компиляции, и что будет иметь некоторый эффект, но не все. (Например, вы не можете иметь вещи, которые не компилируются, но вы все равно хотите, внутри #if 0... (и нет, комментарии не всегда решают эту проблему, потому что вложенные комментарии могут быть сложными...)).
Я думаю, что большинство людей скажут вам, чтобы использовать наследование для этого, но это может быть очень некрасиво, а также, с большим количеством повторяющегося кода...
тем не менее, вы всегда можете просто настроить свою IDE, чтобы бросить java через предварительный процессор перед отправкой его в javac...
"чтобы минимизировать размер исполняемого файла"
Что вы подразумеваете под "исполняемым размером"?
Если вы имеете в виду количество кода, загруженного во время выполнения, то вы можете условно загружать классы через загрузчик классов. Таким образом, вы распространяете свой альтернативный код, несмотря ни на что, но он загружается только в том случае, если отсутствует библиотека, в которой он стоит. Вы можете использовать адаптер (или аналогичный) для инкапсуляции API, чтобы убедиться, что почти весь ваш код точно такой же способ, и один из двух классов обертки загружается в соответствии с вашим случаем. JAVA security SPI может дать вам некоторые идеи, как это может быть структурировано и реализовано.
Если вы имеете в виду размер .jar файл, то вы можете сделать выше, но скажите своим разработчикам, как удалить ненужные классы из банки, в случае, если они знают, что они не будут нужны.
используйте свойства, чтобы делать такие вещи.
использовать такие вещи, как класс.forName для идентификации класса.
Не используйте операторы if, когда вы можете тривиально перевести свойство непосредственно в класс.
в зависимости от того, что вы делаете (недостаточно информации), вы можете сделать что-то вроде этого:
interface Foo
{
void foo();
}
class FakeFoo
implements Foo
{
public void foo()
{
// do nothing
}
}
class RealFoo
{
public void foo()
{
// do something
}
}
а затем предоставить класс для абстракции экземпляра:
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);
}
}
затем запустите java с помощью-Dfoo.name=RealFoo / FakeFoo
проигнорировал обработка исключений в методе makeFoo и вы можете сделать это другими способами... но идея та же.
таким образом, вы компилируете обе версии подклассов Foo и позволяете разработчику выбирать runtime, который они хотят использовать.
Я вижу, что вы указываете две взаимоисключающие проблемы здесь (или, скорее, вы выбрали один, и я просто не понимаю, какой выбор вы сделали).
вам нужно сделать выбор: вы отправляете две версии исходного кода (одну, если библиотека существует, и одну, если она не существует), или вы отправляете одну версию и ожидаете, что она будет работать с библиотекой, если библиотека существует.
Если вы хотите, чтобы одна версия обнаружила существование библиотеки и используйте его, если он доступен, тогда у вас должен быть весь код для доступа к нему в вашем распределенном коде-вы не можете обрезать его. Поскольку вы приравниваете свою проблему к использованию #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_EXISTS ложна, она даже не должна загружаться во время выполнения (черт, она даже не должна быть скомпилирована, если JVM достаточно умен, поскольку он всегда защищен конечной константой.) но помните, что оболочка должна быть доступна во время компиляции в обоих случаях.
Если это поможет взглянуть на J2ME польский или использование директив препроцессора в плагине BlackBerry JDE для eclipse?
Это для мобильных телефонов приложение, но это не может быть повторно использован нет ?
у меня есть еще один лучший способ сказать.
вам нужна конечная переменная.
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. Любой из блоков будет присутствовать в исполняемом коде. Другие будут устранены во время компиляции