简体   繁体   English

如何在针对Java 1.5优雅降级的同时引用Java 1.6 API?

[英]How do I refer to Java 1.6 APIs while degrading gracefully against Java 1.5?

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. 我想使用Java 1.6中的java.text.Normalizer类来进行Unicode规范化,但我的代码必须能够在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. 我不介意在1.5上运行的代码是否不进行规范化,但我不希望它在运行时给出NoClassDefFoundErrorClassNotFoundException

What's the best way to achieve this? 实现这一目标的最佳方法是什么?

The usual way of doing this is via reflection, ie 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. 反射抛出ClassNotFoundException ,这是一个很好的正常异常,而不是NoClassDefFoundError ,这有点可怕。

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. java.text.Normalizer的情况下,这应该很简单,因为它只是几个静态方法,并且很容易通过反射调用。

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();
}

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. 我不介意在1.5上运行的代码是否不进行规范化,但我不希望它在运行时给出NoClassDefFoundErrors或ClassNotFoundExceptions。

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. 这样,您可以使用Java6编译器针对闪亮的新类进行编译,并且它仍然可以在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. 这就是Andrew的解决方案正在做的事情。

If you also need to compile on Java5, then you need to go reflection all the way. 如果你还需要在Java5上编译 ,那么你需要一直反思。

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. 我有同样的需求,因为我们的代码需要在Java 1.2的所有Java版本上运行,但有些代码需要利用更新的API(如果可用的话)。

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. 下面是一个示例“系统实用程序”类,它在运行早期版本时公开了Java 5的某些较新的API - 在早期JVM中,Java 6的原理相同。 This example uses a Singleton, but could easily instantiate multiple objects if the underlying API needed that. 此示例使用Singleton,但如果底层API需要,则可以轻松实例化多个对象。

There are two classes: 有两个类:

  • SysUtil SysUtil
  • SysUtil_J5 SysUtil_J5

The latter is the one used if the run-time JVM is Java 5 or later. 如果运行时JVM是Java 5或更高版本,则使用后者。 Otherwise fallback methods which are compatible in contract are used from the default implementation in SysUtil which utilizes only Java 4 or earlier APIs. 否则,在SysUtil中的默认实现中使用兼容兼容的回退方法,该实现仅使用Java 4或更早的API。 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: 每个类都使用特定版本的编译器进行编译,因此Java 4类中不会意外使用Java 5+ API:

SysUtil (compiled with the Java 4 compiler) SysUtil(使用Java 4编译器编译)

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) SysUtil_J5(使用Java 5编译器编译)

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

Check/use/modify class info.olteanu.utils.TextNormalizer in Phramer project ( http://sourceforge.net/projects/phramer/ , www.phramer.org ) - the code is BSD licensed. 在Phramer项目中检查/使用/修改类info.olteanu.utils.TextNormalizerhttp://sourceforge.net/projects/phramer/,www.phramer.org ) - 代码是BSD许可的。

That code can be compiled in Java 5 and runs both in Java 5 or Java 6 (or future Java versions). 该代码可以在Java 5中编译,并在Java 5或Java 6(或未来的Java版本)中运行。 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. 此外,它可以在Java 6中编译并在Java 5中运行(如果它使用适当的“-target”编译,用于字节码兼容性)或Java 6或任何其他未来版本。

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 (*) 恕我直言这完全解决了您的问题 - 您可以在任何Java 5+平台上自由编译,并且您可以在任何Java 5+平台上获得所需的功能(规范化)(*)

(*) 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. (*)用于规范化的SUN Java 5解决方案很可能不会出现在所有Java 5实现上,因此在最坏的情况下,当调用getNormalizationStringFilter()方法时,最终会得到ClassNotFoundException。

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 . 但是如果你不想在代码中加入混乱,可以使用icu4j库 It contains com.ibm.icu.text.Normalizer class with normalize() method that perform the same job as java.text.Normalizer/sun.text.Normalizer. 它包含带有normalize()方法的com.ibm.icu.text.Normalizer类,该方法执行与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. Icu库有(应该有)自己的Normalizer实现,因此您可以与库共享您的项目,这应该是独立于Java的。
Disadvantage is that the icu library is quite big. 缺点是icu库非常大。

If you using Normalizer class just for removing accents/diacritics from Strings, there's also another way. 如果您使用Normalizer类只是为了从Strings中删除重音符号/变音符号,那么还有另一种方法。 You can use Apache commons lang library (ver. 3) that contains StringUtils with method stripAccents() : 您可以使用包含StringUtils Apache commons lang库(版本3)和方法stripAccents()

String noAccentsString = org.apache.commons.lang3.StringUtils.stripAccents(s);

Lang3 library probably use reflection to invoke appropriate Normalizer according to java version. Lang3库可能使用反射来根据java版本调用适当的Normalizer。 So advantage is that you don't have reflection mess in your code. 所以优点是您的代码中没有反射混乱。

    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]");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM