如果包含特定库,我正在编写一个需要一些代码的库。由于此代码分散在整个项目中,如果用户不必自己注释/取消注释所有内容,那就太好了。
在 C 语言中,这很容易#define
在标头中使用 a ,然后使用 . 包围的代码块#ifdefs
。当然,Java 没有 C 预处理器……
澄清一下 - 几个外部库将与我一起分发。我不想将它们全部包含在内以最小化我的可执行文件大小。如果开发人员确实包含了一个库,我需要能够使用它,如果没有,那么它可以被忽略。
在 Java 中执行此操作的最佳方法是什么?
如果包含特定库,我正在编写一个需要一些代码的库。由于此代码分散在整个项目中,如果用户不必自己注释/取消注释所有内容,那就太好了。
在 C 语言中,这很容易#define
在标头中使用 a ,然后使用 . 包围的代码块#ifdefs
。当然,Java 没有 C 预处理器……
澄清一下 - 几个外部库将与我一起分发。我不想将它们全部包含在内以最小化我的可执行文件大小。如果开发人员确实包含了一个库,我需要能够使用它,如果没有,那么它可以被忽略。
在 Java 中执行此操作的最佳方法是什么?
没有办法在 Java 中做你想做的事。您可以预处理 Java 源文件,但这超出了 Java 的范围。
你能不抽象差异然后改变实现吗?
根据您的说明,听起来您可能能够创建一个工厂方法,该方法将返回来自外部库之一的对象或“存根”类,其功能将执行您在“不可用”中所做的事情" 条件代码。
正如其他人所说,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);
}
好吧,Java 语法与 C 非常接近,您可以简单地使用 C 预处理器,它通常作为单独的可执行文件提供。
但无论如何,Java 并不是真的要在编译时做事。我之前处理类似情况的方式是反思。在您的情况下,由于您对可能不存在的库的调用分散在整个代码中,我将创建一个包装类,将所有对库的调用替换为对包装类的调用,然后在包装类中使用反射如果存在,则在库上调用。
使用常量:
本周我们创建了一些常量,这些常量具有使用 C 预处理器定义编译时常量和条件编译代码的所有好处。
Java 已经摆脱了文本预处理器的整个概念(如果您将 Java 视为 C/C++ 的“后代”)。然而,我们至少可以从 Java 中的 C 预处理器的一些特性中获得最大的好处:常量和条件编译。
我不相信真的有这样的事情。大多数真正的 Java 用户会告诉您这是一件好事,应该不惜一切代价避免依赖条件编译。
我真的不同意他们...
您可以使用可以从编译行定义的常量,这会产生一些效果,但不是全部。(例如,你不能有不编译的东西,但你仍然想要,在#if 0 ...(不,注释并不总是解决这个问题,因为嵌套注释可能很棘手...... ))。
我认为大多数人会告诉你使用某种形式的继承来做到这一点,但这也可能非常难看,有很多重复的代码......
也就是说,您总是可以设置您的 IDE,以便在将其发送到 javac 之前将您的 java 通过预处理器...
“最小化我的可执行文件大小”
“可执行大小”是什么意思?
如果您的意思是在运行时加载的代码量,那么您可以通过类加载器有条件地加载类。因此,无论如何您都分发您的替代代码,但只有在它所代表的库丢失时才会实际加载。您可以使用适配器(或类似的)来封装 API,以确保几乎所有代码都完全相同,并根据您的情况加载两个包装类中的一个。Java 安全 SPI 可能会给您一些关于如何构建和实现它的想法。
如果您指的是 .jar 文件的大小,那么您可以执行上述操作,但告诉您的开发人员如何从 jar 中删除不必要的类,以防他们知道不需要它们。
我还有另一种最好的说法。
您需要的是最终变量。
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 工作。任何一个块都将出现在可执行代码中。其他会在编译时自行淘汰
如果您正在寻找一个完全集成的 Java 预处理器,Manifold可以为您提供支持。它直接插入编译器——没有构建步骤,没有中间代码生成,即,它快速且无忧。
使用属性来做这种事情。
使用 Class.forName 之类的东西来识别类。
当您可以简单地将属性直接转换为类时,不要使用 if 语句。
根据您在做什么(信息不足),您可以执行以下操作:
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 子类的两个版本,并让开发人员在运行时选择他们希望使用的版本。
我看到您在这里指定了两个相互排斥的问题(或者,更有可能的是,您选择了一个,而我只是不明白您做出了哪个选择)。
您必须做出选择:您是发布两个版本的源代码(一个如果库存在,一个如果不存在),或者您是否发布一个版本并期望它在库存在的情况下与库一起使用.
如果您想要一个版本来检测库的存在并在可用时使用它,那么您必须在您的分布式代码中拥有所有代码来访问它——您无法将其删除。由于您将问题等同于使用#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 足够聪明,它甚至不应该被编译,因为它总是受到最终常量的保护。)但请记住在这两种情况下,包装器必须在编译时可用。
如果它有助于查看 j2me Polish 或Using preprocessor directives in BlackBerry JDE plugin for eclipse?
这是用于手机应用程序的,但可以重复使用吗?