我正在编写一个库,如果包含特定库,则需要一些代码。由于此代码分散在项目各处,因此如果用户不必自己注释/取消注释所有内容,那就太好了。
在 C 中,这很容易,只需在标头中添加 #define
,然后用 #ifdefs
包围代码块。当然,Java 没有 C 预处理器......
澄清一下 - 几个外部库将与我的一起分发。我不想将它们全部包含在内以最小化可执行文件的大小。如果开发人员确实包含一个库,我需要能够使用它,如果没有,那么它可以被忽略。
在 Java 中执行此操作的最佳方法是什么?
I'm writing a library that needs to have some code if a particular library is included. Since this code is scattered all around the project, it would be nice if users didn't have to comment/uncomment everything themselves.
In C, this would be easy enough with a #define
in a header, and then code blocks surrounded with #ifdefs
. Of course, Java doesn't have the C preprocessor...
To clarify - several external libraries will be distributed with mine. I do not want to have to include them all to minimize my executable size. If a developer does include a library, I need to be able to use it, and if not, then it can just be ignored.
What is the best way to do this in Java?
发布评论
评论(12)
嗯,Java 语法与 C 非常接近,您可以简单地使用 C 预处理器,它通常作为单独的可执行文件提供。
但无论如何,Java 并不是真正在编译时做事。我以前处理类似情况的方法就是反思。在您的情况下,由于对可能不存在的库的调用分散在整个代码中,因此我将创建一个包装器类,将所有对库的调用替换为对包装器类的调用,然后在包装器类中使用反射调用库(如果存在)。
Well, Java syntax is close enough to C that you could simply use the C preprocessor, which is usually shipped as a separate executable.
But Java isn't really about doing things at compile time anyway. The way I've handled similar situations before is with reflection. In your case, since your calls to the possibly-non-present library are scattered throughout the code, I would make a wrapper class, replace all the calls to the library with calls to the wrapper class, and then use reflection inside the wrapper class to invoke on the library if it is present.
使用 常量:
Use a constant:
我不相信真的有这样的事情。大多数真正的 Java 用户会告诉您这是一件好事,并且应该不惜一切代价避免依赖条件编译。
我不太同意他们的观点...
您可以使用可以从编译行定义的常量,这会产生一些效果,但并不是全部。 (例如,你不能有无法编译的东西,但你仍然想要在 #if 0 内...(不,注释并不总能解决这个问题,因为嵌套注释可能很棘手... ))。
我认为大多数人会告诉你使用某种形式的继承来做到这一点,但这也可能非常难看,有很多重复的代码......
也就是说,你总是可以设置你的 IDE 来抛出你的 java在将其发送到 javac 之前通过预处理器...
I don't believe that there really is such a thing. Most true Java users will tell you that this is a Good Thing, and that relying on conditional compilation should be avoided at almost all costs.
I'm don't really agree with them...
You CAN use constants that can be defined from the compile line, and that will have some of the effect, but not really all. (For example, you can't have things that don't compile, but you still want, inside #if 0... (and no, comments don't always solve that problem, because nesting comments can be tricky...)).
I think that most people will tell you to use some form of inheritance to do this, but that can be very ugly as well, with lots of repeated code...
That said, you CAN always just set up your IDE to throw your java through the pre-processor before sending it to javac...
“最小化我的可执行文件大小”
“可执行文件大小”是什么意思?
如果您指的是运行时加载的代码量,那么您可以通过类加载器有条件地加载类。因此,无论如何,您都会分发替代代码,但只有在缺少它所代表的库时才会实际加载它。您可以使用适配器(或类似的)来封装 API,以确保几乎所有代码都完全相同,并且根据您的情况加载两个包装类之一。 Java 安全 SPI 可能会给您一些如何构建和实现的想法。
如果您指的是 .jar 文件的大小,那么您可以执行上述操作,但告诉您的开发人员如何从 jar 中删除不必要的类,以防他们知道不需要这些类。
"to minimize my executable size"
What do you mean by "executable size"?
If you mean the amount of code loaded at runtime, then you can conditionally load classes through the classloader. So you distribute your alternative code no matter what, but it's only actually loaded if the library that it stands in for is missing. You can use an Adapter (or similar) to encapsulate the API, to make sure that almost all of your code is exactly the same either way, and one of two wrapper classes is loaded according to your case. The Java security SPI might give you some ideas how this can be structured and implemented.
If you mean the size of your .jar file, then you can do the above, but tell your developers how to strip the unnecessary classes out of the jar, in the case where they know they aren't going to be needed.
我还有一种最好的说法。
你需要的是一个最终变量。
然后在代码中说:
这将像#ifdef一样工作。任何一个块都将出现在可执行代码中。其他的将在编译时本身被消除
I have one more best way to say.
What you need is a final variable.
Then inside the code say as
This will work as #ifdef. Any one of the blocks will be present in the executable code. Other will be eliminated in the compile time itself
使用属性来做这种事情。
使用 Class.forName 之类的东西来标识类。
当您可以轻松地将属性直接转换为类时,请勿使用 if 语句。
Use properties to do this kind of thing.
Use things like Class.forName to identify the class.
Do not use if-statements when you can trivially translate a property directly to a class.
根据您正在做的事情(信息不够),您可以执行以下操作:
然后提供一个类来抽象实例化:
然后使用 -Dfoo.name=RealFoo|FakeFoo 运行 java
忽略 makeFoo 方法中的异常处理并你可以用其他方法来做……但想法是一样的。
这样,您就可以编译 Foo 子类的两个版本,并让开发人员在运行时选择他们希望使用的版本。
Depending on what you are doing (not quite enough information) you could do something like this:
and then provide a class to abstract the instantiation:
Then run java with -Dfoo.name=RealFoo|FakeFoo
Ignored the exception handling in the makeFoo method and you can do it other ways... but the idea is the same.
That way you compile both versions of the Foo subclasses and let the developer choose at runtime which they wish to use.
我看到您在这里指定了两个相互排斥的问题(或者,更有可能的是,您选择了一个,而我只是不明白您做出了哪一个选择)。
您必须做出选择:您是否发布源代码的两个版本(如果库存在则发布一个版本,如果库不存在则发布一个版本),或者您是否发布单个版本并希望它能够与库一起使用(如果库存在) 。
如果您想要一个版本来检测库的存在并使用它(如果可用),那么您必须在分布式代码中拥有访问它的所有代码——您无法删除它。由于您将问题等同于使用 #define,所以我认为这不是您的目标——您想要发布 2 个版本(#define 可以工作的唯一方法)
因此,使用 2 个版本您可以定义一个库接口。这可以是一个包装您的库并将所有调用转发给您的库的对象,也可以是一个接口——无论哪种情况,该对象都必须在两种模式的编译时存在。
现在,当您想要使用您的库(在 C 中拥有 #ifdef 的情况)时,您会看到:
库是在这两种情况下都存在的接口。由于它始终受到 LIBRARY_EXISTS 的保护,因此它将编译出来(甚至不应该加载到类加载器中——但这取决于实现)。
如果您的库是由第 3 方提供的预打包库,您可能必须将 Library 设为一个包装类,将其调用转发到您的库。由于如果 LIBRARY_EXISTS 为 false,您的库包装器永远不会被实例化,因此它甚至不应该在运行时加载(哎呀,如果 JVM 足够智能,它甚至不应该被编译,因为它总是受到最终常量的保护。)但请记住在这两种情况下,包装器必须在编译时可用。
I see you specifying two mutually exclusive problems here (or, more likely, you have chosen one and I'm just not understanding which choice you've made).
You have to make a choice: Are you shipping two versions of your source code (one if the library exists, and one if it does not), or are you shipping a single version and expecting it to work with the library if the library exists.
If you want a single version to detect the library's existence and use it if available, then you MUST have all the code to access it in your distributed code--you cannot trim it out. Since you are equating your problem with using a #define, I assumed this was not your goal--you want to ship 2 versions (The only way #define can work)
So, with 2 versions you can define a libraryInterface. This can either be an object that wraps your library and forwards all the calls to the library for you or an interface--in either case this object MUST exist at compile time for both modes.
Now, when you want to USE your library (cases where you would have had a #ifdef in C) you have this:
Library is an interface that exists in both cases. Since it's always protected by LIBRARY_EXISTS, it will compile out (should never even load into your class loader--but that's implementation dependent).
If your library is a pre-packaged library provided by a 3rd party, you may have to make Library a wrapper class that forwards it's calls to your library. Since your library wrapper is never instantiated if LIBRARY_EXISTS is false, it shouldn't even be loaded at runtime (Heck, it shouldn't even be compiled in if the JVM is smart enough since it's always protected by a final constant.) but remember that the wrapper MUST be available at compile time in both cases.
如果有帮助,请查看 j2me Polish 或 在中使用预处理器指令用于 eclipse 的 BlackBerry JDE 插件?
这是针对移动应用程序的,但可以重复使用,不是吗?
If it helps have a look at j2me polish or Using preprocessor directives in BlackBerry JDE plugin for eclipse?
this is for mobiles app but this can be reused no ?
在 Java 中没有办法做你想做的事。您可以预处理 Java 源文件,但这超出了 Java 的范围。
你能不能抽象出差异然后改变实现吗?
根据您的澄清,听起来您也许能够创建一个工厂方法,该方法将返回来自外部库之一的对象或“存根”类,其函数将执行您在“不可用”中所做的操作” 条件代码。
There's no way to do what you want from within Java. You could preprocess the Java source files, but that's outside the scope of Java.
Can you not abstract the differences and then vary the implementation?
Based on your clarification, it sounds like you might be able to create a factory method that will return either an object from one of the external libraries or a "stub" class whose functions will do what you would have done in the "not-available" conditional code.
正如其他人所说,Java 中没有 #define/#ifdef 这样的东西。但是,关于您拥有可选外部库的问题,如果存在,您将使用这些库,如果没有,则不使用,使用代理类可能是一种选择(如果库接口不是太大)。
我必须为 AWT/Swing 的 Mac OS X 特定扩展(在 com.apple.eawt.* 中找到)执行此操作。当然,如果应用程序在 Mac OS 上运行,这些类仅位于类路径上。为了能够使用它们,但仍然允许在其他平台上使用相同的应用程序,我编写了简单的代理类,它只提供与原始 EAWT 类相同的方法。在内部,代理使用一些反射来确定真正的类是否位于类路径上并且会传递所有方法调用。通过使用 java.lang.reflect.Proxy 类,您甚至可以创建并传递外部库中定义的类型的对象,而无需在编译时使用它。
例如,com.apple.eawt.ApplicationListener 的代理看起来像这样:
如果您只需要外部库中的几个类,那么所有这些才有意义,因为您必须在运行时通过反射来完成所有操作。对于较大的库,您可能需要某种方法来自动生成代理。但是,如果您确实如此依赖于大型外部库,则应该在编译时需要它。
Peter Lawrey 的评论:(抱歉编辑,很难将代码放入评论中)
以下示例按方法是通用的,因此您不需要了解所涉及的所有方法。您还可以按类使其通用,这样您只需要编写一个 InvocableHandler 类即可涵盖所有情况。
As other have said, there is no such thing as #define/#ifdef in Java. But regarding your problem of having optional external libraries, which you would use, if present, and not use if not, using proxy classes might be an option (if the library interfaces aren't too big).
I had to do this once for the Mac OS X specific extensions for AWT/Swing (found in com.apple.eawt.*). The classes are, of course, only on the class-path if the application is running on Mac OS. To be able to use them but still allow the same app to be used on other platforms, I wrote simple proxy classes, which just offered the same methods as the original EAWT classes. Internally, the proxies used some reflection to determine if the real classes were on the class-path and would pass through all method calls. By using the java.lang.reflect.Proxy class, you can even create and pass around objects of a type defined in the external library, without having it available at compile time.
For example, the proxy for com.apple.eawt.ApplicationListener looked like this:
All this only makes sense, if you need just a few classes from an external library, because you have to do everything via reflection at runtime. For larger libraries, you probably would need some way to automate the generation of the proxies. But then, if you really are that dependent on a large external library, you should just require it at compile time.
Comment by Peter Lawrey: (Sorry to edit, its very hard to put code into a comment)
The follow example is generic by method so you don't need to know all the methods involved. You can also make this generic by class so you only need one InvocationHandler class coded to cover all cases.
在 Java 中,可以使用多种方法来实现相同的结果:
依赖注入
注释
反射
Java 方法是将不同的行为放入通过接口抽象的一组单独的类中,然后在运行时插入所需的类。另请参阅:
工厂模式
构建器模式
策略模式
In Java one could use a variety of approaches to achieve the same result:
Dependency Injection
Annotations
Reflection
The Java way is to put behaviour that varies into a set of separate classes abstracted through an interface, then plug the required class at run time. See also:
Factory pattern
Builder pattern
Strategy pattern