问题
I would like to use the java.text.Normalizer class from Java 1.6 to do Unicode normalization, but my code has to be able to run on Java 1.5.
I don't mind if the code running on 1.5 doesn't do normalization, but I don't want it to give NoClassDefFoundError
s or ClassNotFoundException
s when it runs.
What's the best way to achieve this?
回答1:
The usual way of doing this is via reflection, i.e. don't refer directly to the class in question, but invoke it programmatically. This allows you to catch exceptions gracefully if the code in question doesn't exist, and either ignore it, or try something else. Reflection throws ClassNotFoundException
, which is a nice normal exception, rather than NoClassDefFoundError
, which is a bit scarier.
In the case of java.text.Normalizer
, this should be pretty easy, since it's just a couple of static methods, and easy to invoke via reflection.
回答2:
public interface NfcNormalizer
{
public String normalize(String str);
}
public class IdentityNfcNormalizer implements NfcNormalizer
{
public String normalize(String str)
{
return str;
}
}
public class JDK16NfcNormalizer implements NfcNormalizer
{
public String normalize(String str)
{
return Normalizer.normalize(str, Normalizer.Form.NFC);
}
}
In your client code:
NfcNormalizer normalizer;
try
{
normalizer = Class.forName("JDK16NfcNormalizer").newInstance();
}
catch(Exception e)
{
normalizer = new IdentityNfcNormalizer();
}
回答3:
I don't mind if the code running on 1.5 doesn't do normalization, but I don't want it to give NoClassDefFoundErrors or ClassNotFoundExceptions when it runs.
If you want to avoid reflection, you can actually catch those Errors.
This way, you can compile against the shiny new classes with a Java6 compiler, and it will still work (as in "not do anything, but also not crash") on Java5.
You can also combine the two approaches, and check if the class exists using reflection, and if it does continue to call it in a non-reflective way. This is what Andrew's solution is doing.
If you also need to compile on Java5, then you need to go reflection all the way.
回答4:
I have had this same need, since we have code that needs to run on all versions of Java from Java 1.2, but some code needs to take advantage of newer API's if they are available.
After various permutations using reflection to obtain Method objects and invoking them dynamically, I have settled on a wrapper style approach as best, in general (although under some circumstances, just storing the reflected Method as a static and invoking it is better - it depends).
Following is an example "System Utility" class which exposes certain newer API's for Java 5 when running an earlier version - the same principles hold for Java 6 in earlier JVMs. This example uses a Singleton, but could easily instantiate multiple objects if the underlying API needed that.
There are two classes:
- SysUtil
- SysUtil_J5
The latter is the one used if the run-time JVM is Java 5 or later. Otherwise fallback methods which are compatible in contract are used from the default implementation in SysUtil which utilizes only Java 4 or earlier APIs. Each class is compiled with the specific version's compiler, so that there is no accidental usage of a Java 5+ API in the Java 4 class:
SysUtil (compiled with the Java 4 compiler)
import java.io.*;
import java.util.*;
/**
* Masks direct use of select system methods to allow transparent use of facilities only
* available in Java 5+ JVM.
*
* Threading Design : [ ] Single Threaded [x] Threadsafe [ ] Immutable [ ] Isolated
*/
public class SysUtil
extends Object
{
/** Package protected to allow subclass SysUtil_J5 to invoke it. */
SysUtil() {
super();
}
// *****************************************************************************
// INSTANCE METHODS - SUBCLASS OVERRIDE REQUIRED
// *****************************************************************************
/** Package protected to allow subclass SysUtil_J5 to override it. */
int availableProcessors() {
return 1;
}
/** Package protected to allow subclass SysUtil_J5 to override it. */
long milliTime() {
return System.currentTimeMillis();
}
/** Package protected to allow subclass SysUtil_J5 to override it. */
long nanoTime() {
return (System.currentTimeMillis()*1000000L);
}
// *****************************************************************************
// STATIC PROPERTIES
// *****************************************************************************
static private final SysUtil INSTANCE;
static {
SysUtil instance=null;
try { instance=(SysUtil)Class.forName("SysUtil_J5").newInstance(); } // can't use new SysUtil_J5() - compiler reports "class file has wrong version 49.0, should be 47.0"
catch(Throwable thr) { instance=new SysUtil(); }
INSTANCE=instance;
}
// *****************************************************************************
// STATIC METHODS
// *****************************************************************************
/**
* Returns the number of processors available to the Java virtual machine.
* <p>
* This value may change during a particular invocation of the virtual machine. Applications that are sensitive to the
* number of available processors should therefore occasionally poll this property and adjust their resource usage
* appropriately.
*/
static public int getAvailableProcessors() {
return INSTANCE.availableProcessors();
}
/**
* Returns the current time in milliseconds.
* <p>
* Note that while the unit of time of the return value is a millisecond, the granularity of the value depends on the
* underlying operating system and may be larger. For example, many operating systems measure time in units of tens of
* milliseconds.
* <p>
* See the description of the class Date for a discussion of slight discrepancies that may arise between "computer time"
* and coordinated universal time (UTC).
* <p>
* @return The difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC.
*/
static public long getMilliTime() {
return INSTANCE.milliTime();
}
/**
* Returns the current value of the most precise available system timer, in nanoseconds.
* <p>
* This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock
* time. The value returned represents nanoseconds since some fixed but arbitrary time (perhaps in the future, so values
* may be negative). This method provides nanosecond precision, but not necessarily nanosecond accuracy. No guarantees
* are made about how frequently values change. Differences in successive calls that span greater than approximately 292
* years (263 nanoseconds) will not accurately compute elapsed time due to numerical overflow.
* <p>
* For example, to measure how long some code takes to execute:
* <p><pre>
* long startTime = SysUtil.getNanoTime();
* // ... the code being measured ...
* long estimatedTime = SysUtil.getNanoTime() - startTime;
* </pre>
* <p>
* @return The current value of the system timer, in nanoseconds.
*/
static public long getNanoTime() {
return INSTANCE.nanoTime();
}
} // END PUBLIC CLASS
SysUtil_J5 (compiled with the Java 5 compiler)
import java.util.*;
class SysUtil_J5
extends SysUtil
{
private final Runtime runtime;
SysUtil_J5() {
super();
runtime=Runtime.getRuntime();
}
// *****************************************************************************
// INSTANCE METHODS
// *****************************************************************************
int availableProcessors() {
return runtime.availableProcessors();
}
long milliTime() {
return System.currentTimeMillis();
}
long nanoTime() {
return System.nanoTime();
}
} // END PUBLIC CLASS
回答5:
Check/use/modify class info.olteanu.utils.TextNormalizer in Phramer project (http://sourceforge.net/projects/phramer/ , www.phramer.org ) - the code is BSD licensed.
That code can be compiled in Java 5 and runs both in Java 5 or Java 6 (or future Java versions). Also, it can be compiled in Java 6 and be run in Java 5 (if it was compiled with the proper "-target", for bytecode compatibility) or Java 6 or any other future version.
IMHO this fully solves your problem - you are free to compile on any Java 5+ platform, and you are able to get the functionality desired (normalization) on any Java 5+ platform (*)
(*) The SUN Java 5 solution for normalization will most likely not be present on all Java 5 implementations, so in the worst case scenario you will end up getting a ClassNotFoundException when you call getNormalizationStringFilter() method.
回答6:
String str = "éèà";
try {
Class c = Class.forName("java.text.Normalizer");
Class f = Class.forName("java.text.Normalizer$Form");
Field ff = f.getField("NFD");
Method m = c.getDeclaredMethod("normalize", new Class[]{java.lang.CharSequence.class,f});
temp = (String) m.invoke(null, new Object[]{str,ff.get(null)});
} catch (Throwable e) {
System.err.println("Unsupported Normalisation method (jvm <1.6)");
}
System.out.println(temp+" should produce [eea]");
回答7:
This is old question, but still actual. I found out some possibilities that are not mentioned in answers.
Usually it is recommended to use reflection as is shown in some other answers here. But if you don't want to put mess in your code, you can use icu4j library. It contains com.ibm.icu.text.Normalizer
class with normalize()
method that perform the same job as java.text.Normalizer/sun.text.Normalizer. Icu library has (should have) own implementation of Normalizer so you can share your project with library and that should be java-independent.
Disadvantage is that the icu library is quite big.
If you using Normalizer class just for removing accents/diacritics from Strings, there's also another way. You can use Apache commons lang library (ver. 3) that contains StringUtils
with method stripAccents()
:
String noAccentsString = org.apache.commons.lang3.StringUtils.stripAccents(s);
Lang3 library probably use reflection to invoke appropriate Normalizer according to java version. So advantage is that you don't have reflection mess in your code.
来源:https://stackoverflow.com/questions/1277270/how-do-i-refer-to-java-1-6-apis-while-degrading-gracefully-against-java-1-5