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