【问题标题】:Conditionally Remove Java Methods at Compile-Time在编译时有条件地删除 Java 方法
【发布时间】:2017-08-20 12:48:10
【问题描述】:

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

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

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 作为一门语言的局限性(或设计决策),并且知道在大多数情况下这是不必要的。事实上,我考虑过简单地创建这些“额外”方法并将它们的整个代码放在条件中,如果条件不活跃则抛出异常,但这是一个非常粗略的解决方案,似乎没有帮助当我向我的程序员提供这些库时,他们可以使用这些库。

非常感谢您的任何帮助。

编辑:

为了补充这个问题,我尝试这样做的原因是通过使用异常作为解决方案,IDE 会将方法显示为“可用”,而实际上它们不是。但是,再次强调,这可能只是我对 Java 一无所知。

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

编辑2.:

此外,请务必注意,我还将有条件地生成文档。因此,包的每个编译版本都有自己的文档,仅包含实际可用的文档。

【问题讨论】:

  • 好问题 (+1),但我仍然不同意您关于抛出异常粗鲁且无益的结束语。在阅读完您的问题之前,我打算将其发布为答案。抛出一个安全异常正是你应该做的。事实上,Java 已经内置了安全包,它在内部使用这些包来处理这类事情。您可以尝试利用已经存在的安全相关功能,也可以创建自己的特定安全异常。
  • @Aaron 这是有争议的,但确实安全管理器可能是要走的路(如果它支持这个 - 我不知道)
  • 如果您的库的用户想要调用一个方法,并且如果该用户可能没有使用该方法的权限,则不应该涉及一些黑魔法。它应该只是 try { securityCheckedMethod(); } catch (AccessException ex) { /* you-don't-have-permission handler */ } 的形式
  • 或者,如果首选其他方法,也许@FledglingPidgeon 可以详细说明他希望 API 用户如何处理他们无权访问的方法,即使它的描述方式与问题中的if(FULLACCESS) 示例相同。这应该是问题的一部分。
  • 为了补充我刚才所说的,关于“黑魔法”,这个想法很简单,就是让 IDE 显示方法在实际上不可用时作为“可用”,似乎没有帮助。然而,再一次,这可能只是我对 Java 一无所知的情况,在这种情况下,可以通过安全异常来解决“许可接口问题”,即使它不允许我主动删除部分代码.class,例如,在某些变体不可用的产品线开发方法中。

标签: java preprocessor processor conditional-compilation


【解决方案1】:

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

我只能想到一次,我认为这种方法是最好的方法,结果我错了。更改类的公共接口的情况对我来说尤其像是一个危险信号。当访问级别不足以调用方法时抛出异常可能对代码更友好。

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

因为它对我没有用,所以我丢弃了我的;而且我从未见过其他人这样做并发布它;据我所知,您必须自己动手。

【讨论】:

  • 您认为尝试使用该方法的 API 用户会发生什么?如果他们没有权限,他们会收到关于缺少方法的错误。还是您的系统不像 OP,OP 需要安全权限系统?让我想到一个 API 似乎很奇怪,其中文档指出“如果您没有使用它的权限,这种方法甚至不存在。”
  • @Aaron - 在上下文中,我不确定您为什么认为这很重要,但如果它可以帮助您了解重点,注释名称是 @TestOnly
  • 如果可能的话,我想避免实际创建一个完整的预处理器,但如果有必要我愿意这样做。关于 Aaron 的问题,在我的情况下,文档本身将根据这些预处理器参数生成。因此,库的文档将特定于它的特定编译版本。
  • 我指的是方法的可访问性,而不是访问它们的注释名称。也许我应该用一个例子来详细说明:我是你的 API 的用户。我想使用 API 中的f()。我称之为-f();。对我来说,API 最终用户看起来不错,因为如果您的 API 有 f()。根据您设置框架的方式,我要么得到一个编译错误未知符号,要么得到一个运行时错误,因为f() 在运行时不存在。无论哪种方式,我作为 API 用户都会感到惊讶,因为这不是我对过去 99.9% 经验的期望。我在 SO 上发帖:API 包含 f 但我收到错误
  • @FledglingPidgeon 啊,我明白了。在阅读了有关该问题的 cmets 并阅读了此答案后,我明白了您的意思。我也将添加一个答案,因为您可能想多了。
【解决方案2】:

此答案部分基于您在问题上留下的 cmets 和 Mark 的答案。

我建议您使用仅公开所需 API 的 Java 接口来执行此操作。当您需要限制较少的 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 并且不包含任何可能的 BImplementation 的 jar,可能通过单独的构建脚本。

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


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

【讨论】:

  • 非常感谢,这解决了我遇到的问题。它比 C# 的解决方案稍微耗时一些,但结果基本相同,因此只需对项目架构稍作修改即可实现。由于缩放问题,稍后可能需要一个特殊的构建脚本,但目前这是完美的。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2018-04-05
      • 1970-01-01
      • 2011-05-08
      • 2013-07-19
      • 2013-12-21
      • 1970-01-01
      • 1970-01-01
      • 2022-09-26
      • 2021-10-19
      相关资源
      最近更新 更多