Java 基础系列 18:Holder 技术的实现原理分析

4,232 阅读7分钟

一 简介

(1)Java中的Holder是什么?

我这里说的Holder即这个类:

javax.xml.ws.Holder

这个类属于JAX-WS 2.0规范中的一个类。它的作用是为不可变的对象引用提供一个可变的包装,这样就可以使Java能够与支持INOUT、OUT参数的编程语言编写的web service进行通信。示例代码如下:

	/**
	 * 分页查询航线
	 * 
	 * @param page
	 *             分页属性(如:总数、每页数目、当前页、排序等)
	 * @return 航线集合
	 */
	@WebMethod
	public RPCResponse<Airline> selectAirlineByPage(@WebParam(name = "pageInfoHolder", mode = WebParam.Mode.INOUT) Holder<PageInfo> pageInfoHolder);

了解web service的同学应该可以看出,这是一个简单的分页查询的web service接口,其中页面属性“pageInfoHolder”就使用了Holder来包装。同时,这里还标注了其类型是INOUT,这就表示可以在方法中将 pageInfoHolder 修改之后,不需要显式使用return返回,web service就可以自动将这个pageInfoHolder参数在方法结束时返回

上面这个接口的实现代码如下:

	@Override
	public RPCResponse<Airline> selectAirlineByPage(
			Holder<PageInfo> pageInfoHolder) {
		RPCResponse<Airline> response = new RPCResponse<Airline>();
		if (pageInfoHolder.value != null) {
			long count = airlineManager.selectCountAirline();

			PageInfo pageInfo = new PageInfo(count,
					pageInfoHolder.value.getPerSize(),
					pageInfoHolder.value.getCurrentPage(),
					pageInfoHolder.value.getSortName(),
					pageInfoHolder.value.getSortOrder());

			response.setResponseHolder(airlineManager
					.selectAirlineByPage(pageInfo));
			pageInfoHolder.value = pageInfo;
		}

		return response;
	}

从上面的代码可以看出,pageInfoHolder并没有使用return返回,但是我们调用这个web service接口之后的pageInfoHolder实例却可以对应发生改变(PS:即上面代码中新增的PageInfo属性——count)

注:如果希望开发的接口能够同时兼容RESTFUL风格和SOAP风格,那么最好不要使用上面这种INOUT类型的传参方式,而是将所有希望返回的内容再次使用对象封装之后再return返回

(2)一个简化实例

如果说上面的测试代码涉及到了web service,因此不熟悉相关内容的话可能不太好理解。那么不改变原理,针对上面的例子进行简化,大致相当于下面这个例子:

i)javax.xml.ws.Holder类的源码:

/*
 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package javax.xml.ws;

import java.io.Serializable;

/**
 * Holds a value of type <code>T</code>.
 *
 * @since JAX-WS 2.0
 */
public final class Holder<T> implements Serializable {

    private static final long serialVersionUID = 2623699057546497185L;

    /**
     * The value contained in the holder.
     */
    public T value;

    /**
     * Creates a new holder with a <code>null</code> value.
     */
    public Holder() {
    }

    /**
     * Create a new holder with the specified value.
     *
     * @param value The value to be stored in the holder.
     */
    public Holder(T value) {
        this.value = value;
    }
}

这里首先给出Holder类的源代码。可以发现代码很简单,仅仅只是一个对泛型的value对象的包装类

注:关于泛型的一些基本用法可以参考我的这篇文章:www.zifangsky.cn/280.html

ii)测试实例:

package cn.zifangsky.holder;

import javax.xml.ws.Holder;

public class Demo3 {

	public static void testHolder(Holder<User> uHolder){
		User user = new User();
		user.setId(uHolder.value.getId());
		user.setName(uHolder.value.getName());
		user.setPassword(uHolder.value.getPassword());
		user.setHomepage("https://www.zifangsky.cn");  //新增
		
		uHolder.value = user;
	}

	public static void main(String[] args) {
		User user = new User();
		user.setId(1);
		user.setName("zifangsky");
		user.setPassword("123456");
		
//		Holder<User> holder = new Holder<User>();
//		holder.value = user;
		Holder<User> holder = new Holder<User>(user);  //使用Holder对User进行包装
		
		Demo3.testHolder(holder);
		System.out.println("新增的主页是: " + holder.value.getHomepage());
	}

}

可以发现,在testHolder方法中是没有返回值的,但是我们在testHolder方法中对uHolder的value属性——User对象改变之后,这种改变反应到了方法调用之前的holder.value

这就是Holder这个类的使用地方,即:不通过返回值在一个方法中改变一个对象

二 Holder技术的实现原理分析

(1)Java中的参数传递:

在正式介绍Holder技术的实现原理之前,容我简单介绍下Java中的参数传递

在Java中,虽然我们通常会说值传递引用传递。但是,在底层工作原理上是不存在传引用的概念的,Java的参数传递只有传值

Java中的基本类型(PS:int、long、boolean等)存放在栈内存中,Java对象引用(PS:Integer、Long、Boolean等其他Java对象)参数也存放在栈内存中,但是引用指向的具体的值却存放在堆内存中。下面先看一个简单的例子:

package cn.zifangsky.holder;

public class Demo {

	public static void main(String[] args) {
		int a = 1;
		change(a);
		System.out.println("a之后的值: " + a);
		System.out.println("-----------------我是分割线--------------------");
		
		Integer b = 1;
		change2(b);
		System.out.println("b之后的值: " + b);
	}
	
	public static void change(int aa){
		System.out.println("aa初始值: " + aa);
		aa = 10;
		System.out.println("aa之后的值: " + aa);
	}
	
	public static void change2(Integer bb){
		System.out.println("bb初始值: " + bb);
		bb = 10;
		System.out.println("bb之后的值: " + bb);
	}

}

输出如下:

aa初始值: 1
aa之后的值: 10
a之后的值: 1
-----------------我是分割线--------------------
bb初始值: 1
bb之后的值: 10
b之后的值: 1

很明显,这两个方法都没能改变原始的a、b变量。上面这段代码执行时的内存单元变化大致是这样的:

传参

从这个图可以看出,执行change方法时,aa变量先是在另一个内存单元中复制了一份a变量的东西,接着在方法中一直改变的都是aa变量,因此很明显a变量从始至终都没有发生改变;执行change2方法时,同样bb变量也是在另一个内存单元中复制了一份b变量的东西,接着方法中改变了bb变量具体指向的值(即:堆内存中的 1 改变成了 10),很明显这个过程跟b变量也没有关系,因此在change2方法执行完毕之后b变量也没有发生改变

注:

  1. 我画图时为了简便并没有严格区分栈内存和堆内存,大家明白这个意思就行
  2. 上图中的内存地址是我随便虚构的,一段代码执行时的实际内存地址并不一定是这样

那么,我在最上面提到的Holder技术是怎么实现在一个方法中改变对象的值的呢?

(2)Holder技术的实现分析:

上面我已经说过了,javax.xml.ws.Holder这个类实际上就是一个简单的泛型——对一个Java类的简单封装。那么,如果我们手动对Integer类进行封装,其具体的代码基本上就是这样的:

package cn.zifangsky.holder;

public class IntegerHolder {
	public Integer value;

	public IntegerHolder() {

	}

	public IntegerHolder(Integer value) {
		this.value = value;
	}
}

然后,我们再写一个简单的测试实例:

package cn.zifangsky.holder;

public class Demo2 {

	public static void changeInteger(IntegerHolder ii){
		ii.value = new Integer(10);
	}

	public static void main(String[] args) {
		IntegerHolder i = new IntegerHolder(1);
		
		System.out.println("初始值: " + i.value);
		
		changeInteger(i);
		System.out.println("改变之后的值: " + i.value);
	}

}

注:如果上面的两段代码使用javax.xml.ws.Holder这个类的话,可以等效于下面这段代码:

package cn.zifangsky.holder;

import javax.xml.ws.Holder;

public class Demo2 {

	public static void changeInteger(Holder<Integer> ii) {
		ii.value = new Integer(10);
	}

	public static void main(String[] args) {
		Holder<Integer> i = new Holder<>(1);

		System.out.println("初始值: " + i.value);

		changeInteger(i);
		System.out.println("改变之后的值: " + i.value);
	}

}

发现没有,没有使用return返回值但是却让值发生了改变。如果还是用内存单元图来表示的话,那么大致上是这样的:

holder%e5%8e%9f%e7%90%86

从上面的图可以看出,虽然在changeInteger方法中,变量 ii 的地址跟变量 i 的地址不一样,但是它们存储的内容(PS:ii.value这个Integer对象的地址)却从始至终都没有发生改变。相反,在changeInteger方法中改变的是ii.value这个Integer对象真正的值,ii.value的内存地址并没有发生改变,仍然和 i.value的内存地址一样。因此在这个方法结束之后 i.value对应的值也相应发生了改变。这就是Holder技术的实现原理

最后,如果弄明白了上面测试代码的原理的话,可以思考下下面这段代码最后的输出是什么?

package cn.zifangsky.holder;

import javax.xml.ws.Holder;

public class Demo1 {

	public static void main(String[] args) {
		int a = 1;
		Integer b = new Integer(11);
		System.out.println("初始状态--> " + "a: " + a + "    b: " + b);

		add(a, b);
		System.out.println("方法一    --> " + "a: " + a + "    b: " + b);

		add2(a, b);
		System.out.println("方法二    --> " + "a: " + a + "    b: " + b);

		Holder<Integer> holder = new Holder<Integer>(b);
		add3(a, holder);
		System.out.println("方法三    --> " + "a: " + a + "    b: " + holder.value);
	}

	public static void add(int aa, Integer bb) {
		aa = 2;
		bb = 22;
	}

	public static void add2(int aa, Integer bb) {
		aa = new Integer(2);
		bb = new Integer(22);
	}

	public static void add3(int aa, Holder<Integer> bb) {
		aa = 2;
		bb.value = new Integer(22);
	}

}