在编译时有条件地删除 Java 方法

Conditionally Remove Java Methods at Compile-Time

我正在尝试实现类似于 C# 预处理器的功能。我知道 Java 没有相同的预处理器功能,并且知道有一些方法可以使用诸如 Factory 之类的设计模式来实现类似的结果。但是,我仍然有兴趣找到这个问题的解决方案。

目前,我所做的是创建一个包含几个静态最终布尔属性的class,例如以下示例:

public class Preprocessor
{
  public static final boolean FULLACCESS = false;
}

然后我按以下方式使用它:

public ClassName getClassName()
{
    if(Preprocessor.FULLACCESS)
    {
        return this;
    }
    else
    {
        return this.DeepCopy();
    }
}

到目前为止一切顺利,这解决了我的问题(上面的示例很简单,但我确实在其他有帮助的情况下使用了它)。我的问题是,是否有一种方法可以围绕整个方法放置条件,以便在给定正确的 "Preprocessor" 变量的情况下方法本身不可用?例如,我希望能够使特定的构造函数仅对给定 "Full Access" 的包可用,如下所示:

public ClassName()
{
    // do things
}

if(FULLACCESS)
{
public ClassName(ClassName thing)
{
    // copy contents from thing to the object being created
}
}

同样,我知道 Java 作为一种语言的局限性(或设计决策),并且我知道在大多数情况下这是不必要的。事实上,我考虑过简单地创建这些 "extra" 方法并将它们的整个代码放在一个条件中,同时如果条件不活跃则抛出异常,但这是一个非常粗略的解决方案,不当我向他们提供这些库时,他们似乎对我的程序员很有帮助。

非常感谢您的帮助。

编辑:

为了补充这个问题,我尝试这样做的原因是,通过使用异常作为解决方案,IDE 会将方法显示为 "available",而实际上它们不是。但是,同样,这可能只是我对 Java.

一无所知的情况

我想这样做的原因主要是我可能有不止一个 public 接口可用,比如说,一个限制性的方法中控制更严格,一个更宽松的直接改变属性是允许的。但是,我也确实希望能够主动从 .class 中删除部分代码,例如,在某些变体不可用的产品线开发方法中。

编辑 2.:

此外,重要的是要注意我也会有条件地生成文档。因此,软件包的每个编译版本都有自己的文档,仅包含实际可用的文档。

嗯,你可以做到的。不过请注意...

我只记得有一次我认为这种方法是最好的方法,结果证明我错了。更改 class 的 public 界面的情况对我来说尤其像是一个危险信号。当访问级别不足以调用方法时抛出异常可能对代码更友好。

但无论如何,当我想我想要一个预处理器时,我所做的就是写一个。我创建了一个自定义注释以放置在有条件可用的方法上,抓取了一个 Java 解析器并编写了一个小程序,该程序使用该解析器查找和删除具有注释的方法。然后(有条件地)将其添加到构建过程中。

因为结果证明对我没用,所以我丢弃了我的;而且我从未见过其他人这样做并发表过;据我所知,你必须自己动手。

使用 Gradle 您可以管理您的资源,我认为不再需要预处理器宏。现在在 src 目录中,你有 main/java 与所有来源,但如果你需要特定的方法,例如debugrelease 构建来做/或不做特定的事情然后在 src 中创建 debug/javarelease/java 并将 YourClass 放在那里。请注意,通过这样做,您必须在 debug/javarelease/java 中有 YourClass,但在 main/java.

中没有

此答案部分基于您对问题留下的评论和马克的回答。

我建议您使用 Java 接口执行此操作,该接口仅公开您想要的 API。当您需要限制较少的 API 合同时,扩展接口或创建现有接口的单独实现以获得您需要的东西。

public interface A
{
    void f();
}

A以上是你的一般API。现在你想要一些特殊的额外方法来测试 A 或调试它或操纵它或其他任何...

public interface B extends A
{
    void specialAccess();
}

此外,Java 现在支持接口的默认方法实现,这可能对您有用,具体取决于您如何实现 API。它们采用以下形式...

public interface A
{
    List getList();

    // this is still only an interface, but you have a default impl. here
    default void add(Object o)
    {
        getList().add(o);
    }
}

您可以在 Oracle's page about it here 上阅读有关默认方法的更多信息。

在您的 API 中,您的一般分发可以包括 A 并完全省略 B,并省略任何提供特殊访问权限的实现;那么您可以包含 B 和您提到的 API 的特殊访问版本的特殊实现。这将允许普通的旧 Java 对象,除了额外的接口和可能的额外实现之外,与代码没有什么不同。自定义部分只会在您的图书馆包装中。如果你想给某人一个 "non-special" 低访问版本,给他们一个不包含 B 并且不包含任何可能的 BImplementation 的 jar,可能通过单独的构建脚本。

我的 Java 工作使用 Netbeans,我喜欢让它使用它自动生成的默认构建脚本。因此,如果我这样做并且我在 Netbeans 中这样做,我可能会创建两个项目,一个用于基础 API,一个用于特殊访问 API,我会制作一个特殊访问的项目依赖于基础项目。那会让我有两个罐子而不是一个罐子,但我会接受的;如果两个 jar 足够困扰我,我会完成上面提到的为特殊访问版本制作构建脚本的额外步骤。


一些例子直接来自Java

Swing 有这种模式的例子。请注意,GUI 组件有一个 void paint(Graphics g)Graphics 为您提供了一组特定的功能。通常,g 实际上是一个 Graphics2D,所以如果您愿意,可以这样对待它。

void paint(Graphics g)
{
    Graphics2d g2d = Graphics2d.class.cast(g);
}

另一个例子是 Swing 组件模型。如果您使用 JListJComboBox 在 GUI 中显示对象列表,如果您想随时间更改该列表,则可能不会使用它附带的默认模型。相反,您创建一个具有附加功能的新模型并注入它。

JList list = new JList();
DefaultListModel model = new DefaultListModel();
list.setModel(model);

现在您的 JList 模型具有通常不明显的额外功能,包括轻松添加和删除项目的能力。

这样不仅增加了额外的功能,而且 ListModel 的原作者甚至不需要知道这个功能的存在。

在 Java 中达到此目的的唯一方法是使用预处理器,例如 PostgresJDBC 团队使用 java comment preprocessor for such manipulations, here is example from their Driver.java

  //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1"
  @Override
  public java.util.logging.Logger getParentLogger() {
    return PARENT_LOGGER;
  }
  //#endif