Java 中向后兼容的包装类

发布于 2024-08-31 21:03:32 字数 954 浏览 1 评论 0原文

这里有一篇关于维护 Java 向后兼容性的有趣的文章。在包装类部分,我实际上无法理解包装类完成的任务。在 MyApp 的以下代码中,WrapNewClass.checkAvailable() 可以替换为 Class.forName("NewClass")

static {
    try {
        WrapNewClass.checkAvailable();
        mNewClassAvailable = true;
    } catch (Throwable ex) {
        mNewClassAvailable = false;
    }
}

考虑 NewClass 何时不可用。在我们使用包装器的代码中(见下文),我们所做的就是用存在的类替换一个不存在的类,但该类无法编译,因为它使用了一个不存在的类。

public void diddle() {
    if (mNewClassAvailable) {
        WrapNewClass.setGlobalDiv(4);
        WrapNewClass wnc = new WrapNewClass(40);
        System.out.println("newer API is available - " + wnc.doStuff(10));
    }else {
        System.out.println("newer API not available");
    }
}

谁能解释为什么这会有所不同?我认为这与 Java 编译代码的方式有关 - 我对此不太了解。

There is an interesting article here on maintaing backwards compatibility for Java. In the wrapper class section, I can't actually understand what the wrapper class accomplishes. In the following code from MyApp, WrapNewClass.checkAvailable() could be replaced by Class.forName("NewClass").

static {
    try {
        WrapNewClass.checkAvailable();
        mNewClassAvailable = true;
    } catch (Throwable ex) {
        mNewClassAvailable = false;
    }
}

Consider when NewClass is unavailable. In the code where we use the wrapper (see below), all we have done is replace a class that doesn't exist, with one that exists, but which can't be compiled as it uses a class that doesn't exist.

public void diddle() {
    if (mNewClassAvailable) {
        WrapNewClass.setGlobalDiv(4);
        WrapNewClass wnc = new WrapNewClass(40);
        System.out.println("newer API is available - " + wnc.doStuff(10));
    }else {
        System.out.println("newer API not available");
    }
}

Can anyone explain why this makes a difference? I assume it has something to do with how Java compiles code - which I don't know much about.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

雪落纷纷 2024-09-07 21:03:33

我在 spring 和 richfaces 中看到过这种行为。例如,Spring 执行以下

  • 操作,对 JSF
  • 声明一个 private static 内部类,它在其中引用 JSF 类
  • try/catchs Class.forName(..)Class.forName(..) 一个 JSF 类
  • 如果没有抛出异常,则引用内部类(并且通过 faces 上下文获取 spring 上下文)
  • 如果抛出异常,则从另一个源(servlet 上下文)获取 spring 上下文

注意内部类在引用它们之前不会加载它们,因此存在不满足的依赖关系是可以的。

(spring 类是 org.springframework.web.context.request.RequestContextHolder )

I've seen this behaviour in spring and richfaces. Spring, for example, does the following

  • has a compile-time dependency on JSF
  • declares a private static inner class where it references the JSF classes
  • try/catches Class.forName(..) a JSF class
  • if no exception is thrown, the inner class is referenced (and the spring context is obtained through the faces context)
  • if exception is thrown, the spring context is obtained from another source (the servlet context)

Note that inner classes are not loaded until they are referenced, so it is OK to have a dependency that is not met in it.

(The spring class is org.springframework.web.context.request.RequestContextHolder)

鲸落 2024-09-07 21:03:32

这样做的要点是拥有针对某些在运行时可能不可用的类编译的代码。 WrapNewClass必须存在于javac的类路径中,否则这个东西无法编译。但是,它可能不存在于运行时的类路径中。

如果 mNewClassAvailable 为 false,您引用的代码将避免引用 WrapNewClass。因此,它只会打印“新 API 不可用”消息。

然而,我不能说我印象深刻。一般来说,我见过这种用 java.lang.reflect 安排的事情,而不是尝试捕获异常。顺便说一句,即使编译后,类也不会出现在视野中。

The point of this is to have code which is compiled against some class which may not be available at runtime. WrapNewClass has to be present in the classpath of javac, or this thing can't be compiled. However, it can be absent from the classpath at runtime.

The code you quote avoids references to WrapNewClass if mNewClassAvailable is false. Thus, it will just print the 'new API not available' message.

However, I can't say that I'm impressed. In general, I've seen this sort of thing arranged with java.lang.reflect instead of trying to catch the exception. That, in passing, allows the class to be nowhere in sight even when compiled.

只是我以为 2024-09-07 21:03:32

长期以来,我一直需要在 JSE 中支持自 1.1 版以来的所有 JVM,并使用这些包装技术来兼容地支持可选 API——也就是说,API 可以使应用程序更好地工作,但对于应用程序来说并不是必需的。

我使用的两种技术似乎在您引用的文章中进行了描述(很差?)。我不会对此进行进一步评论,而是提供我如何做到这一点的真实示例。

最简单 - 静态包装方法

需要:调用 API(如果可用),否则不执行任何操作。这可以针对任何 JVM 版本进行编译。

首先,设置一个具有反射方法的静态 Method,如下所示:

static private final java.lang.reflect.Method SET_ACCELERATION_PRIORITY;
static {
    java.lang.reflect.Method mth=null;
    try { mth=java.awt.Image.class.getMethod("setAccelerationPriority",new Class[]{Float.TYPE}); } catch(Throwable thr) { mth=null; }
    SET_ACCELERATION_PRIORITY=mth;
    }

并包装反射方法,而不是使用直接调用:

static public void setImageAcceleration(Image img, int accpty) {
    if(accpty>0 && SET_ACCELERATION_PRIORITY!=null) {
        try { SET_ACCELERATION_PRIORITY.invoke(img,new Object[]{new Float(accpty)}); } 
        catch(Throwable thr) { throw new RuntimeException(thr); } // exception will never happen, but don't swallow - that's bad practice
        }
    }

更难 - 静态包装类

需要:调用 API(如果可用) ,或以其他方式调用较旧的 API 以获得等效但降级的功能。这必须针对较新的 JVM 版本进行编译。

首先设置一个静态包装类;这可能是一个静态单例包装器,或者您可能需要包装每个实例创建。下面的示例使用静态单例:

package xxx;

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

/** 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 milliTick() {
    return System.currentTimeMillis();
    }

/** Package protected to allow subclass SysUtil_J5 to override it. */
long nanoTick() {
    return (System.currentTimeMillis()*1000000L);
    }

// *****************************************************************************
// STATIC PROPERTIES
// *****************************************************************************

static private final SysUtil            INSTANCE;
static {
    SysUtil                             instance=null;

    try                  { instance=(SysUtil)Class.forName("xxx.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 value of the most precise available system timer, in milliseconds.
 * <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 milliseconds since some fixed but arbitrary time (perhaps in the future, so
 * values may be negative). This method provides millisecond precision, but not necessarily millisecond accuracy. No
 * guarantees are made about how frequently values change. Differences in successive calls that span greater than
 * approximately 292,000 years 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.getNanoTick();
 *    // ... the code being measured ...
 *    long estimatedTime = SysUtil.getNanoTick() - startTime;
 * </pre>
 * <p>
 * @return          The current value of the system timer, in milliseconds.
 */
static public long getMilliTick() {
    return INSTANCE.milliTick();
    }

/**
 * 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 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.getNanoTick();
 *    // ... the code being measured ...
 *    long estimatedTime = SysUtil.getNanoTick() - startTime;
 * </pre>
 * <p>
 * @return          The current value of the system timer, in nanoseconds.
 */
static public long getNanoTick() {
    return INSTANCE.nanoTick();
    }

} // END PUBLIC CLASS

并创建一个子类以在可用时提供更新的功能:

package xxx;

import java.util.*;

class SysUtil_J5
extends SysUtil
{

private final Runtime                   runtime;

SysUtil_J5() {
    super();

    runtime=Runtime.getRuntime();
    }

int availableProcessors() {
    return runtime.availableProcessors();
    }

long milliTick() {
    return (System.nanoTime()/1000000);
    }

long nanoTick() {
    return System.nanoTime();
    }
} // END PUBLIC CLASS

I have long had the need to support every JVM since 1.1 in JSE and have used these kind of wrapping techniques to compatibly support optional APIs - that is, APIs which make the application work better, but are not essential to it.

The two techniques I use seem to be (poorly?) described in the article you referenced. Rather than comment further on that, I will instead provide real examples of how I have done this.

Easiest - Static Wrapper Method

Need: To invoke an API if it is available, or otherwise do nothing. This can be compiled against any JVM version.

First, set up a static Method which has the reflected method, like so:

static private final java.lang.reflect.Method SET_ACCELERATION_PRIORITY;
static {
    java.lang.reflect.Method mth=null;
    try { mth=java.awt.Image.class.getMethod("setAccelerationPriority",new Class[]{Float.TYPE}); } catch(Throwable thr) { mth=null; }
    SET_ACCELERATION_PRIORITY=mth;
    }

and wrap the reflected method instead of using a direct call:

static public void setImageAcceleration(Image img, int accpty) {
    if(accpty>0 && SET_ACCELERATION_PRIORITY!=null) {
        try { SET_ACCELERATION_PRIORITY.invoke(img,new Object[]{new Float(accpty)}); } 
        catch(Throwable thr) { throw new RuntimeException(thr); } // exception will never happen, but don't swallow - that's bad practice
        }
    }

Harder - Static Wrapper Class

Need: To invoke an API if it is available, or otherwise invoke an older API for equivalent, but degraded, functionality. This must be compiled against the newer JVM version.

First set up a static wrapper class; this may be a static singleton wrapper, or you might need to wrap every instance creation. The example which follows uses a static singleton:

package xxx;

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

/** 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 milliTick() {
    return System.currentTimeMillis();
    }

/** Package protected to allow subclass SysUtil_J5 to override it. */
long nanoTick() {
    return (System.currentTimeMillis()*1000000L);
    }

// *****************************************************************************
// STATIC PROPERTIES
// *****************************************************************************

static private final SysUtil            INSTANCE;
static {
    SysUtil                             instance=null;

    try                  { instance=(SysUtil)Class.forName("xxx.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 value of the most precise available system timer, in milliseconds.
 * <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 milliseconds since some fixed but arbitrary time (perhaps in the future, so
 * values may be negative). This method provides millisecond precision, but not necessarily millisecond accuracy. No
 * guarantees are made about how frequently values change. Differences in successive calls that span greater than
 * approximately 292,000 years 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.getNanoTick();
 *    // ... the code being measured ...
 *    long estimatedTime = SysUtil.getNanoTick() - startTime;
 * </pre>
 * <p>
 * @return          The current value of the system timer, in milliseconds.
 */
static public long getMilliTick() {
    return INSTANCE.milliTick();
    }

/**
 * 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 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.getNanoTick();
 *    // ... the code being measured ...
 *    long estimatedTime = SysUtil.getNanoTick() - startTime;
 * </pre>
 * <p>
 * @return          The current value of the system timer, in nanoseconds.
 */
static public long getNanoTick() {
    return INSTANCE.nanoTick();
    }

} // END PUBLIC CLASS

and create a subclass to provide the newer functionality when available:

package xxx;

import java.util.*;

class SysUtil_J5
extends SysUtil
{

private final Runtime                   runtime;

SysUtil_J5() {
    super();

    runtime=Runtime.getRuntime();
    }

int availableProcessors() {
    return runtime.availableProcessors();
    }

long milliTick() {
    return (System.nanoTime()/1000000);
    }

long nanoTick() {
    return System.nanoTime();
    }
} // END PUBLIC CLASS
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文