一 简介
(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变量也没有发生改变
注:
- 我画图时为了简便并没有严格区分栈内存和堆内存,大家明白这个意思就行
- 上图中的内存地址是我随便虚构的,一段代码执行时的实际内存地址并不一定是这样
那么,我在最上面提到的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返回值但是却让值发生了改变。如果还是用内存单元图来表示的话,那么大致上是这样的:
从上面的图可以看出,虽然在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);
}
}