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

时间:2017-03-27 17:20:55

标签: java preprocessor processor conditional-compilation

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

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

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

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

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

到目前为止,这很好,这解决了我的问题(上面的例子是微不足道的,但我确实在其他有用的情况下使用它)。我的问题是,是否有办法将条件置于整个方法周围,以便在给定正确的"预处理器"之前,方法本身将不可用。变量?例如,我希望能够使特定的构造函数仅适用于给定"完全访问"的包,如下所示:

public ClassName()
{
    // do things
}

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

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

非常感谢您提前寻求帮助。

编辑:

为了补充这个问题,我试图这样做的原因是,通过使用异常作为解决方案,IDE将显示方法为"可用"当它们实际上不是。然而,再一次,它可能仅仅是我对Java的无知。

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

EDIT2:

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

4 个答案:

答案 0 :(得分:6)

嗯,你可以实现它。提醒一下,不过......

我只能想到有一次我认为这种方法是最好的方法,而事实证明我错了。更改班级公共界面的情况对我来说尤其像是一面红旗。当访问级别不足以调用该方法时抛出异常可能对代码更友好。

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

因为事实证明这对我没用,我放弃了我的;我从来没有见过其他人这样做并发表它;所以据我所知,你必须自己动手。

答案 1 :(得分:4)

这个答案部分基于您在问题上留下的评论以及Mark的回答。

我建议你使用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对象,除了额外的接口之外没有什么不同的代码,也许是它的额外实现。自定义部件将位于库的包装中。如果你想交给某人一个非特殊的"低访问版本,将它们放在一个不包含B的jar中,并且不包含任何可能的BImplementation,可能有一个单独的构建脚本。

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

直接来自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的原始作者甚至不需要知道此功能可能存在。

答案 2 :(得分:0)

使用Gradle,您可以管理您的源,我认为不再需要预处理器宏。目前在src目录中,您拥有main/java所有来源,但如果您需要特定方法,例如debugrelease构建要执行/不执行特定事项,然后在debug/java中创建release/javasrc并将YourClass放在那里。请注意,通过这样做,您必须YourClassdebug/javarelease/java,而main/java中不能{。}}。

答案 3 :(得分:0)

Java中达到这一目标的唯一方法是使用预处理器,例如PostgresJDBC团队使用java comment preprocessor进行此类操作,这里是他们Driver.java

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