一 简介
(1)Java中的Holder是什么?
我这里说的Holder即这个类:javax.xml.ws.Holder
这个类属于JAX-WS 2.0规范中的一个类。它的作用是为不可变的对象引用提供一个可变的包装,这样就可以使Java能够与支持INOUT、OUT参数的编程语言编写的web service进行通信。示例代码如下:
Java
/**
* 分页查询航线
*
* @param page
* 分页属性(如:总数、每页数目、当前页、排序等)
* @return 航线集合
*/
@WebMethod
public RPCResponse selectAirlineByPage(@WebParam(name = "pageInfoHolder", mode = WebParam.Mode.INOUT) Holder pageInfoHolder);
1
2
3
4
5
6
7
8
9/**
* 分页查询航线
*
* @param page
* 分页属性(如:总数、每页数目、当前页、排序等)
* @return 航线集合
*/
@WebMethod
publicRPCResponseselectAirlineByPage(@WebParam(name="pageInfoHolder",mode=WebParam.Mode.INOUT)HolderpageInfoHolder);
了解web service的同学应该可以看出,这是一个简单的分页查询的web service接口,其中页面属性“pageInfoHolder”就使用了Holder来包装。同时,这里还标注了其类型是INOUT,这就表示可以在方法中将 pageInfoHolder 修改之后,不需要显式使用return返回,web service就可以自动将这个pageInfoHolder参数在方法结束时返回
上面这个接口的实现代码如下:
@Override
public RPCResponse selectAirlineByPage(
Holder pageInfoHolder) {
RPCResponse response = new RPCResponse();
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20@Override
publicRPCResponseselectAirlineByPage(
HolderpageInfoHolder){
RPCResponseresponse=newRPCResponse();
if(pageInfoHolder.value!=null){
longcount=airlineManager.selectCountAirline();
PageInfopageInfo=newPageInfo(count,
pageInfoHolder.value.getPerSize(),
pageInfoHolder.value.getCurrentPage(),
pageInfoHolder.value.getSortName(),
pageInfoHolder.value.getSortOrder());
response.setResponseHolder(airlineManager
.selectAirlineByPage(pageInfo));
pageInfoHolder.value=pageInfo;
}
returnresponse;
}
从上面的代码可以看出,pageInfoHolder并没有使用return返回,但是我们调用这个web service接口之后的pageInfoHolder实例却可以对应发生改变(PS:即上面代码中新增的PageInfo属性——count)
注:如果希望开发的接口能够同时兼容RESTFUL风格和SOAP风格,那么最好不要使用上面这种INOUT类型的传参方式,而是将所有希望返回的内容再次使用对象封装之后再return返回
(2)一个简化实例
如果说上面的测试代码涉及到了web service,因此不熟悉相关内容的话可能不太好理解。那么不改变原理,针对上面的例子进行简化,大致相当于下面这个例子:
i)javax.xml.ws.Holder类的源码:
Java
/*
* 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 T
.
*
* @since JAX-WS 2.0
*/
public final class Holder implements Serializable {
private static final long serialVersionUID = 2623699057546497185L;
/**
* The value contained in the holder.
*/
public T value;
/**
* Creates a new holder with a null
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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
packagejavax.xml.ws;
importjava.io.Serializable;
/**
* Holds a value of type T
.
*
* @since JAX-WS 2.0
*/
publicfinalclassHolderimplementsSerializable{
privatestaticfinallongserialVersionUID=2623699057546497185L;
/**
* The value contained in the holder.
*/
publicTvalue;
/**
* Creates a new holder with a null
value.
*/
publicHolder(){
}
/**
* Create a new holder with the specified value.
*
* @param value The value to be stored in the holder.
*/
publicHolder(Tvalue){
this.value=value;
}
}
这里首先给出Holder类的源代码。可以发现代码很简单,仅仅只是一个对泛型的value对象的包装类
ii)测试实例:
Java
package cn.zifangsky.holder;
import javax.xml.ws.Holder;
public class Demo3 {
public static void testHolder(Holder 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 holder = new Holder();
//holder.value = user;
Holder holder = new Holder(user); //使用Holder对User进行包装
Demo3.testHolder(holder);
System.out.println("新增的主页是: " + holder.value.getHomepage());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32packagecn.zifangsky.holder;
importjavax.xml.ws.Holder;
publicclassDemo3{
publicstaticvoidtestHolder(HolderuHolder){
Useruser=newUser();
user.setId(uHolder.value.getId());
user.setName(uHolder.value.getName());
user.setPassword(uHolder.value.getPassword());
user.setHomepage("https://www.zifangsky.cn");//新增
uHolder.value=user;
}
publicstaticvoidmain(String[]args){
Useruser=newUser();
user.setId(1);
user.setName("zifangsky");
user.setPassword("123456");
// Holder holder = new Holder();
// holder.value = user;
Holderholder=newHolder(user);//使用Holder对User进行包装
Demo3.testHolder(holder);
System.out.println("新增的主页是: "+holder.value.getHomepage());
}
}
最后的输出如下:
新增的主页是: https://www.zifangsky.cn
1新增的主页是:https://www.zifangsky.cn
可以发现,在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对象)参数也存放在栈内存中,但是引用指向的具体的值却存放在堆内存中。下面先看一个简单的例子:
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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29packagecn.zifangsky.holder;
publicclassDemo{
publicstaticvoidmain(String[]args){
inta=1;
change(a);
System.out.println("a之后的值: "+a);
System.out.println("-----------------我是分割线--------------------");
Integerb=1;
change2(b);
System.out.println("b之后的值: "+b);
}
publicstaticvoidchange(intaa){
System.out.println("aa初始值: "+aa);
aa=10;
System.out.println("aa之后的值: "+aa);
}
publicstaticvoidchange2(Integerbb){
System.out.println("bb初始值: "+bb);
bb=10;
System.out.println("bb之后的值: "+bb);
}
}
输出如下:
aa初始值: 1
aa之后的值: 10
a之后的值: 1
-----------------我是分割线--------------------
bb初始值: 1
bb之后的值: 10
b之后的值: 1
1
2
3
4
5
6
7aa初始值: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类进行封装,其具体的代码基本上就是这样的:
Java
package cn.zifangsky.holder;
public class IntegerHolder {
public Integer value;
public IntegerHolder() {
}
public IntegerHolder(Integer value) {
this.value = value;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13packagecn.zifangsky.holder;
publicclassIntegerHolder{
publicIntegervalue;
publicIntegerHolder(){
}
publicIntegerHolder(Integervalue){
this.value=value;
}
}
然后,我们再写一个简单的测试实例:
Java
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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18packagecn.zifangsky.holder;
publicclassDemo2{
publicstaticvoidchangeInteger(IntegerHolderii){
ii.value=newInteger(10);
}
publicstaticvoidmain(String[]args){
IntegerHolderi=newIntegerHolder(1);
System.out.println("初始值: "+i.value);
changeInteger(i);
System.out.println("改变之后的值: "+i.value);
}
}
注:如果上面的两段代码使用javax.xml.ws.Holder这个类的话,可以等效于下面这段代码:
Java
package cn.zifangsky.holder;
import javax.xml.ws.Holder;
public class Demo2 {
public static void changeInteger(Holder ii) {
ii.value = new Integer(10);
}
public static void main(String[] args) {
Holder i = new Holder<>(1);
System.out.println("初始值: " + i.value);
changeInteger(i);
System.out.println("改变之后的值: " + i.value);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21packagecn.zifangsky.holder;
importjavax.xml.ws.Holder;
publicclassDemo2{
publicstaticvoidchangeInteger(Holderii){
ii.value=newInteger(10);
}
publicstaticvoidmain(String[]args){
Holderi=newHolder<>(1);
System.out.println("初始值: "+i.value);
changeInteger(i);
System.out.println("改变之后的值: "+i.value);
}
}
最后输出如下:
初始值: 1
改变之后的值: 10
1
2初始值:1
改变之后的值:10
发现没有,没有使用return返回值但是却让值发生了改变。如果还是用内存单元图来表示的话,那么大致上是这样的:
从上面的图可以看出,虽然在changeInteger方法中,变量 ii 的地址跟变量 i 的地址不一样,但是它们存储的内容(PS:ii.value这个Integer对象的地址)却从始至终都没有发生改变。相反,在changeInteger方法中改变的是ii.value这个Integer对象真正的值,ii.value的内存地址并没有发生改变,仍然和 i.value的内存地址一样。因此在这个方法结束之后 i.value对应的值也相应发生了改变。这就是Holder技术的实现原理
最后,如果弄明白了上面测试代码的原理的话,可以思考下下面这段代码最后的输出是什么?
Java
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 holder = new Holder(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 bb) {
aa = 2;
bb.value = new Integer(22);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39packagecn.zifangsky.holder;
importjavax.xml.ws.Holder;
publicclassDemo1{
publicstaticvoidmain(String[]args){
inta=1;
Integerb=newInteger(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);
Holderholder=newHolder(b);
add3(a,holder);
System.out.println("方法三 --> "+"a: "+a+" b: "+holder.value);
}
publicstaticvoidadd(intaa,Integerbb){
aa=2;
bb=22;
}
publicstaticvoidadd2(intaa,Integerbb){
aa=newInteger(2);
bb=newInteger(22);
}
publicstaticvoidadd3(intaa,Holderbb){
aa=2;
bb.value=newInteger(22);
}
}