在编译时有条件地删除 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
与所有来源,但如果你需要特定的方法,例如debug
和 release
构建来做/或不做特定的事情然后在 src
中创建 debug/java
和 release/java
并将 YourClass
放在那里。请注意,通过这样做,您必须在 debug/java
和 release/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 组件模型。如果您使用 JList
或 JComboBox
在 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
我正在尝试实现类似于 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
与所有来源,但如果你需要特定的方法,例如debug
和 release
构建来做/或不做特定的事情然后在 src
中创建 debug/java
和 release/java
并将 YourClass
放在那里。请注意,通过这样做,您必须在 debug/java
和 release/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 组件模型。如果您使用 JList
或 JComboBox
在 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