Условная компиляция Java: как предотвратить компиляцию фрагментов кода? - PullRequest
47 голосов
/ 24 декабря 2010

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

Подробности: У меня есть служебный класс с именем OS, который инкапсулирует все специфические для ОС вещи. У него есть метод

public static void openFile(java.io.File file) throws java.io.IOException {
  // open the file using java.awt.Desktop
  ...
}

для открытия файлов, например, двойным щелчком мыши (start команда Windows или open эквивалент Mac OS X). Поскольку его нельзя скомпилировать с помощью Java 1.5, я хочу исключить его во время компиляции и заменить другим методом, который вызывает run32dll для Windows или open для Mac OS X с использованием Runtime.exec.

Вопрос: Как я могу это сделать? Могут ли здесь помочь аннотации?

Примечание. Я использую ant и могу создать два java-файла OS4J5.java и OS4J6.java, которые будут содержать класс OS с требуемым кодом для Java 1.5 и 1.6, и скопировать один из них в OS.java до компиляция (или уродливый способ - заменить содержимое OS.java условно в зависимости от версии Java), но я не хочу этого делать, если есть другой способ.

Более подробно: в C я мог бы использовать ifdef, ifndef, в Python нет компиляции, и я мог проверить функцию, используя hasattr или что-то еще, в Common Lisp я мог бы использовать #+feature. Есть ли что-то подобное для Java?

Найден этот пост , но он не кажется полезным.

Любая помощь очень ценится. кч.

Ответы [ 8 ]

42 голосов
/ 24 декабря 2010

Нет, в Java нет поддержки условной компиляции.

Обычный план состоит в том, чтобы скрыть специфичные для ОС биты вашего приложения за Interface, а затем определить тип ОС во время выполнения и загрузить реализацию, используя Class.forName(String).

В вашем случае нет причин, по которым вы не можете скомпилировать оба OS* (и повлиять на все ваше приложение), используя Java 1.6 с -source 1.5 -target 1.5, а затем в фабричный метод для получения классов OS ( теперь будет интерфейсом) определит, что класс java.awt.Desktop доступен, и загрузит правильную версию.

Что-то вроде:

 public interface OS {
     void openFile(java.io.File file) throws java.io.IOException;
 }

 public class OSFactory {
     public static OS create(){
         try{
             Class.forName("java.awt.Desktop");
             return new OSJ6();
         }catch(Exception e){
             //fall back
             return new OSJ5();
         }
     }
 }
17 голосов
/ 24 декабря 2010

Скрытие двух классов реализации за интерфейсом, подобным предложенному Гаретом, вероятно, является наилучшим способом.

Тем не менее, вы можете ввести условную компиляцию, используя задачу замены в сценариях сборки ant.Хитрость заключается в том, чтобы использовать комментарии в вашем коде, которые открываются / закрываются текстовой заменой непосредственно перед компиляцией исходного кода, например:

/*{{ Block visible when compiling for Java 6: IFDEF6

public static void openFile(java.io.File file) throws java.io.IOException {
  // open the file using java.awt.Desktop
  ...

/*}} end of Java 6 code. */

/*{{ Block visible when compiling for Java 5: IFDEF5

  // open the file using alternative methods
  ...

/*}} end of Java 5 code. */

теперь в ant, когда вы компилируете для Java 6, замените «IFDEF6»с "* /", давая:

/*{{ Block visible when compiling for Java 6: */

public static void openFile(java.io.File file) throws java.io.IOException {
  // open the file using java.awt.Desktop
  ...

/*}} end of Java 6 code. */

/*{{ Block visible when compiling for Java 5, IFDEF5

public static void openFile(java.io.File file) throws java.io.IOException {
  // open the file using alternative methods
  ...

/*}} end of Java 5 code. */

и при компиляции для Java 5, заменить "IFDEF5".Обратите внимание, что вы должны быть осторожны при использовании // comments внутри блоков /*{{, /*}}.

7 голосов
/ 27 декабря 2013

Представленный ниже скрипт Ant дает хороший и чистый трюк.

ссылка: https://weblogs.java.net/blog/schaefa/archive/2005/01/how_to_do_condi.html

, например,

//[ifdef]
public byte[] getBytes(String parameterName)
        throws SQLException {
    ...
}
//[enddef]

со скриптом Ant

        <filterset begintoken="//[" endtoken="]">
            <filter token="ifdef" value="${ifdef.token}"/>
            <filter token="enddef" value="${enddef.token}"/>
        </filterset>

перейдите по ссылке выше для более подробной информации.

5 голосов
/ 20 июня 2017

В java 9 можно создавать файлы jar с несколькими выпусками. По сути это означает, что вы делаете несколько версий одного и того же Java-файла.

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

+ com
  + mypackage
    + Main.class
    + Utils.class
+ META-INF
  + versions
    + 9
      + com
        + mypackage
          + Utils.class

В приведенном выше примере основная часть кода скомпилирована в Java 8, но для Java 9 есть дополнительная (но другая) версия класса Utils.

Когда вы запускаете этот код на java 8 JVM, он даже не проверяет классы в папке META-INF. Но в java 9 он найдет и использует более свежую версию класса.

5 голосов
/ 28 мая 2016

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

http://www.javapractices.com/topic/TopicAction.do?Id=64

Цитирование сущности:

Практика условной компиляции используется для необязательного удаления кусков кода из скомпилированной версии класса,Он использует тот факт, что компиляторы будут игнорировать любые недоступные ветви кода.Для реализации условной компиляции

  • определяет статическое окончательное логическое значение как не приватный член некоторого кода места
  • , который должен быть условно скомпилирован в блоке if, который оцениваетboolean
  • установить значение логического значения в false, чтобы компилятор игнорировал блок if;в противном случае оставьте значение true

Конечно, это позволяет нам «компилировать» фрагменты кода внутри любого метода.Чтобы удалить членов класса, методы или даже целые классы (возможно, оставив только заглушку), вам все равно потребуется предварительный процессор.

5 голосов
/ 24 декабря 2010

Вы можете совершать вызовы, используя отражение, и компилировать код с помощью Java 5.

, например

Class clazz = Class.forName("java.package.ClassNotFoundInJavav5");
Method method = clazz.getMethod("methodNotFoundInJava5", Class1.class);
method.invoke(args1);

Вы можете перехватить любые исключения и вернуться к чему-то, что работает на Java 5.

4 голосов
/ 21 марта 2015

Если вы не хотите, чтобы в вашем приложении были условно разрешенные блоки кода, тогда препроцессор - единственный способ, вы можете взглянуть на java-comment-preprocessor , который можно использовать как для проектов maven, так и для ant.
ps
также я привел некоторый пример того, как использовать предварительную обработку с Maven для создания многоверсионного JAR-файла JEP-238 без дублирования источников

0 голосов
/ 08 апреля 2019

Генератор специализаций примитивов Java поддерживает условную компиляцию:

   /* if Windows compilingFor */
   start();
   /* elif Mac compilingFor */
   open();
   /* endif */

Этот инструмент имеет плагины Maven и Gradle.

...