【问题标题】:Equivalent of #define in Java?相当于Java中的#define?
【发布时间】:2010-12-28 22:03:35
【问题描述】:

我正在编写一个库,如果包含特定库,则该库需要一些代码。由于此代码分散在整个项目中,如果用户不必自己评论/取消评论所有内容,那就太好了。

在 C 中,在标头中使用 #define,然后用 #ifdefs 包围代码块就很容易了。当然,Java 没有 C 预处理器……

澄清一下 - 几个外部库将与我一起分发。我不想将它们全部包含在内以最小化我的可执行文件大小。如果开发人员确实包含了一个库,我需要能够使用它,如果没有,则可以忽略它。

在 Java 中最好的方法是什么?

【问题讨论】:

标签: java preprocessor c-preprocessor


【解决方案1】:

没有办法在 Java 中做你想做的事。您可以预处理 Java 源文件,但这超出了 Java 的范围。

你不能抽象出差异然后改变实现吗?

根据您的说明,听起来您可能能够创建一个工厂方法,该方法将返回来自外部库之一的对象或“存根”类,其函数将执行您在“不可用”条件代码。

【讨论】:

  • +1:这个问题听起来可以(可能)修改解决方案架构设计来帮助解决这个问题。
  • Java 编译器将优化掉基于常量值的条件检查。因此,这绝对是可能的。
  • @Anon:但它仍然编译两个分支。它只是优化了未使用的代码,对吗?此外,很多时候,重新构建架构以消除这些问题可能会变得非常混乱。如果你有一堆你想要的随机位置#ifdefed...你最终会得到一个基类,然后是一堆子类和辅助函数,它就会变得一团糟。
  • 你有一个类,没有辅助函数,还有一堆符号常量。我没有看到需要对任何东西进行子类化。
  • 我们需要更多细节来判断是否存在设计解决方案。
【解决方案2】:

正如其他人所说,Java 中没有#define/#ifdef 这样的东西。但是关于您拥有可选外部库的问题,如果存在,您将使用,如果没有,则不使用,使用代理类可能是一种选择(如果库接口不是太大)。

我必须为 AWT/Swing 的 Mac OS X 特定扩展(在 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

}

如果您只需要来自外部库的几个类,那么所有这些都是有意义的,因为您必须在运行时通过反射来完成所有事情。对于较大的库,您可能需要一些方法来自动生成代理。但是,如果你真的那么依赖一个大型的外部库,你应该在编译时才需要它。

Peter Lawrey 的评论:(抱歉编辑,很难将代码放入评论中)

以下示例是按方法通用的,因此您无需了解所有涉及的方法。您也可以按类进行通用化,这样您只需要一个 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);
}

【讨论】:

  • 通过Manifold项目在Java中确实有这样的事情。它在条件编译方面提供了一个功能齐全的预处理器,即#define#if#else等。
【解决方案3】:

在 Java 中,可以使用多种方法来实现相同的结果:

Java 方法是将变化的行为放入通过接口抽象出来的一组单独的类中,然后在运行时插入所需的类。另见:

【讨论】:

  • +1 用于依赖注入。想象一下通过 XML 文件(作为示例)连接您的对象,然后针对您的各种情况使用不同的 XML 文件。
【解决方案4】:

嗯,Java 语法与 C 非常接近,您可以简单地使用 C 预处理器,它通常作为单独的可执行文件提供。

但无论如何,Java 并不是真的在编译时做事。我之前处理类似情况的方式是反思。在您的情况下,由于您对可能不存在的库的调用分散在整个代码中,我将创建一个包装器类,用对包装器类的调用替换对库的所有调用,然后在包装器类中使用反射如果存在,则调用该库。

【讨论】:

    【解决方案5】:

    使用constant

    本周我们创建了一些常量 具有使用的所有好处 C 预处理器的功能 定义编译时常量和 条件编译代码。

    Java 已经摆脱了整个 文本预处理器的概念(如果 你把 Java 看作是 C/C++)。然而,我们可以得到最好的 至少部分 C 的好处 Java中预处理器的特点: 常量和条件编译。

    【讨论】:

    • 我认为这不是他想要的。他不想要符号常量,而是相当于条件编译。不知道java,虽然我不知道......
    • 条件编译呢?你是如何在java中做到这一点的?
    • 我链接的文章讨论了这一点。
    • 作为链接文章的快速说明,现在有import static,整个“为常量创建接口并实现它”已经过时且荒谬。
    • 条件编译仍然只有在一切都编译的情况下才有效。如果,当 then 分支为真时,else 分支不会编译,因为这些方法不存在,或者其他什么,那么它将不会编译。 #if 很好地解决了这个问题。
    【解决方案6】:

    我不相信真的有这样的事情。大多数真正的 Java 用户会告诉您这是一件好事,应该不惜一切代价避免依赖条件编译。

    我不太同意他们的观点……

    您可以使用可以从编译行定义的常量,这会产生一些效果,但不是全部。 (例如,你不能有不编译的东西,但你仍然想要,在#if 0 ...(不,cmets 并不总是解决这个问题,因为嵌套 cmets 可能很棘手...... ))。

    我认为大多数人会告诉你使用某种形式的继承来做到这一点,但这也可能非常难看,有很多重复的代码......

    也就是说,你总是可以设置你的 IDE,让你的 java 在发送到 javac 之前通过预处理器......

    【讨论】:

      【解决方案7】:

      “最小化我的可执行文件大小”

      “可执行大小”是什么意思?

      如果您指的是在运行时加载的代码量,那么您可以通过类加载器有条件地加载类。因此,无论如何您都分发您的替代代码,但只有在它所代表的库丢失时才会实际加载它。您可以使用适配器(或类似的)来封装 API,以确保几乎所有代码都完全相同,并根据您的情况加载两个包装类中的一个。 Java 安全 SPI 可能会为您提供一些如何构建和实现它的想法。

      如果您指的是 .jar 文件的大小,那么您可以执行上述操作,但请告诉您的开发人员如何从 jar 中删除不必要的类,以防他们知道不需要这些类.

      【讨论】:

      • 该库将在Android应用程序中使用,因此我不想包含任何不必要的内容。
      • 这不符合您原始问题的参数。如果您在运行时进行链接,您仍然必须编译调用库的代码,因此#ifdef 无论如何都无济于事。如果您不是在运行时链接而是为不同的目标进行编译,那么 if(CONSTANT) 就足够了。哪一个是实际问题?
      • 不,这个常数是不够的。例如,if(false) {LibraryIDontHave.action();} 不会编译。
      【解决方案8】:

      我还有另一种最好的说法。

      你需要的是一个 final 变量。

      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 工作。任何一个块都将出现在可执行代码中。其他的会在编译时自行淘汰

      【讨论】:

        【解决方案9】:

        如果您正在寻找完全集成的 Java 预处理器,Manifold 可以为您提供支持。它直接插入编译器——没有构建步骤,没有中间代码生成,即,它快速且无忧。

        【讨论】:

          【解决方案10】:

          使用属性来做这种事情。

          使用 Class.forName 之类的东西来标识类。

          当您可以简单地将属性直接转换为类时,不要使用 if 语句。

          【讨论】:

            【解决方案11】:

            根据您正在做的事情(信息不足),您可以执行以下操作:

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

            然后用 -Dfoo.name=RealFoo|FakeFoo 运行 java

            忽略了makeFoo方法中的异常处理,你可以用其他方式来做……但是思路是一样的。

            这样您就可以编译 Foo 子类的两个版本,并让开发人员在运行时选择他们希望使用的版本。

            【讨论】:

              【解决方案12】:

              我看到您在这里指定了两个相互排斥的问题(或者,更有可能的是,您选择了一个,而我只是不明白您做出了哪个选择)。

              您必须做出选择:您是要发布两个版本的源代码(一个如果库存在,一个如果不存在),或者您是发布一个版本并期望它与库一起工作,如果图书馆存在。

              如果您想要一个版本来检测库的存在并在可用的情况下使用它,那么您必须在您的分布式代码中拥有所有代码来访问它——您无法将其删除。由于您将问题等同于使用#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;
              }
              

              现在,当你想使用你的库时(在 C 中你应该有一个 #ifdef 的情况)你有这个:

              if(LIBRARY_EXISTS)
                  library.doFunc()
              

              库是两种情况下都存在的接口。由于它始终受到 LIBRARY_EXISTS 的保护,因此它会编译出来(甚至不应该加载到您的类加载器中——但这取决于实现)。

              如果您的库是由第 3 方提供的预打包库,您可能必须将 Library 设为一个包装类,以便将其调用转发到您的库。由于如果 LIBRARY_EXISTS 为 false,则永远不会实例化您的库包装器,因此它甚至不应该在运行时加载(哎呀,如果 JVM 足够聪明,它甚至不应该被编译,因为它总是受到最终常量的保护。)但请记住在这两种情况下,包装器都必须在编译时可用。

              【讨论】:

                【解决方案13】:

                如果有帮助,请查看 j2me Polish 或 Using preprocessor directives in BlackBerry JDE plugin for eclipse?

                这是手机应用,但不能重复使用?

                【讨论】:

                  猜你喜欢
                  • 2011-09-11
                  • 2011-12-06
                  • 2016-07-24
                  • 2013-05-09
                  • 1970-01-01
                  • 2014-09-29
                  • 2011-01-21
                  • 2013-09-10
                  • 2021-01-05
                  相关资源
                  最近更新 更多