我想使用Java 1.6中的java.text.Normalizer类来进行Unicode规范化,但我的代码必须能够在Java 1.5上运行。
我不介意1.5上运行的代码是否不进行规范化,但我不希望它在运行时提供NoClassDefFoundError
或ClassNotFoundException
。
实现这一目标的最佳方式是什么?
答案 0 :(得分:13)
通常的做法是通过反射,即不直接引用相关类,而是以编程方式调用它。如果相关代码不存在,您可以优雅地捕获异常,并忽略它或尝试其他方法。反射抛出ClassNotFoundException
,这是一个很好的正常异常,而不是NoClassDefFoundError
,这有点可怕。
在java.text.Normalizer
的情况下,这应该很简单,因为它只是几个静态方法,并且很容易通过反射调用。
答案 1 :(得分:9)
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);
}
}
在您的客户端代码中:
NfcNormalizer normalizer;
try
{
normalizer = Class.forName("JDK16NfcNormalizer").newInstance();
}
catch(Exception e)
{
normalizer = new IdentityNfcNormalizer();
}
答案 2 :(得分:3)
我不介意在1.5上运行的代码是否不进行规范化,但我不希望它在运行时给出NoClassDefFoundErrors或ClassNotFoundExceptions。
如果你想避免反思,你实际上可以捕捉那些错误。
这样,您可以使用Java6编译器针对闪亮的新类进行编译,它仍然可以在Java5上工作(如“不做任何事情,但也不会崩溃”)。
您还可以组合这两种方法,并使用反射检查类是否存在,以及是否继续以非反射方式调用它。这就是安德鲁的解决方案正在做的事情。
如果你还需要在Java5上编译,那么你需要一直反思。
答案 3 :(得分:1)
我有同样的需求,因为我们的代码需要在Java 1.2的所有Java版本上运行,但有些代码需要利用更新的API(如果可用的话)。
在使用反射获取方法对象并动态调用它们的各种排列之后,我总体上已经确定了最好的包装样式方法(尽管在某些情况下,只是将反射的方法存储为静态并调用它更好 - 它取决于。)
以下是一个示例“系统实用程序”类,它在运行早期版本时公开了Java 5的某些较新的API - 在早期JVM中,Java 6的原则相同。此示例使用Singleton,但如果底层API需要,则可以轻松实例化多个对象。
有两个类:
如果运行时JVM是Java 5或更高版本,则使用后者。否则,在SysUtil中的默认实现中使用兼容兼容的回退方法,该实现仅使用Java 4或更早的API。每个类都使用特定版本的编译器进行编译,因此Java 4类中不会意外使用Java 5+ API:
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
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
答案 4 :(得分:1)
在Phramer项目中检查/使用/修改课程info.olteanu.utils.TextNormalizer(http://sourceforge.net/projects/phramer/,www.phramer.org) - 该代码已获得BSD许可。
该代码可以在Java 5中编译,并在Java 5或Java 6(或未来的Java版本)中运行。此外,它可以在Java 6中编译并在Java 5中运行(如果使用适当的“-target”编译,用于字节码兼容性)或Java 6或任何其他未来版本。
恕我直言这完全解决了您的问题 - 您可以在任何Java 5+平台上自由编译,并且您可以在任何Java 5+平台上获得所需的功能(规范化)(*)
(*)用于规范化的SUN Java 5解决方案很可能不会出现在所有Java 5实现上,因此在最坏的情况下,当调用getNormalizationStringFilter()方法时,最终会得到ClassNotFoundException。
答案 5 :(得分:0)
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]");
答案 6 :(得分:0)
这是一个古老的问题,但仍然是实际问题。我发现了答案中没有提到的一些可能性。
通常建议使用反射,如此处的其他一些答案所示。但是,如果您不想在代码中加入混乱,可以使用icu4j library。它包含com.ibm.icu.text.Normalizer
类和normalize()
方法,它执行与java.text.Normalizer / sun.text.Normalizer相同的工作。 Icu库具有(应该)自己的Normalizer实现,因此您可以与库共享您的项目,这应该是独立于Java的。
缺点是icu库非常大。
如果你使用Normalizer类只是为了从Strings中删除重音符号/变音符号,那么还有另一种方法。您可以使用包含StringUtils
方法stripAccents()
的{{3}}:
String noAccentsString = org.apache.commons.lang3.StringUtils.stripAccents(s);
Lang3库可能会使用反射来根据java版本调用相应的Normalizer。所以优点是您的代码中没有反射混乱。