记一次 java方法传值问题

项目场景:

项目中每次请求new一个上下文Context,用来存储一次请求所有使用到的对象(java皆对象),全局带着Context,需要值时,直接从里面取。


问题描述

在进行方法转递时,修改了Context中一个Order对象属性的值,然后拿着Context再去调用其他方法,结束后,再从Context中去获取Order,操作原来的值,得到的是改变后的Order对象。


原因分析:

方法调用有值传递和引用传递两种:

值传递:在方法里改变了传递对象,原来的对象不发生改变。

引用传递:在方法里改变了传递对象,原来的对象也跟着改变。

java中方法调用 -- 共享传递:属于值传递,它传递的是对象的引用,然后通过引用去操作堆中的对象。

这次就是在java方法中修改原来的对象,而方法调用后,需要得到原来的对象。


解决方案:

1.最简单方法:

  1. new 一个Order,将原来的Order copy过去,方法调用结束,将原来的Order塞进Context中。
  2. new 一个Order,将原来的Order copy过去,在Context中新建一个Order属性,将新Order塞给新建属性,方法中直接操作新属性。

但是同事脑洞大开,觉得Context公用的,新建属性不太合适,每次方法去保存旧Order太繁琐,就想提个公共方法。然后就想到了克隆和 java的序列化:

2.克隆:将对象复制一个副本。

java实现克隆:implement Cloneable,重写Object的 clone()方法。

public class CloneableTest implements Cloneable {
    @Override
    protected  Object clone() throws CloneNotSupportedException {
       return  super.clone();
    }
}

 但是克隆还涉及了深、浅 copy。

浅copy:只copy对象中的基本类型属性值。

深copy:对象中的对象属性值+基本类型属性值都重新copy一份,生成全新的一个对象。

针对Context深copy,需要Context的对象属性值Order也implement Cloneable,重写clone()方法。

可能同事对克隆不太熟悉,但他了解java的序列化和反序列化,也能实现该效果:

3.java序列化和反序列化

序列化/反序列化:在进行网络传输或者磁盘保存时,都是将报文转成二进制字节流进行传输或者磁盘存储。需要implement Serializable 接口,该对象的对象属性也需要implement Serializable。

将二进制字节流读到IO流中,反序列时再从IO流中读出。因为是在流中操作,所以反序列化只能一次。

有一次,这块代码报错了,同事还不在,我看了下逻辑:就是调用上面的方法,然后从Context中获取一个对象,然后获取该对象的一个属性,报了空指针。麻了:不知道Context都序列化了什么内容,也不知道反序列化了哪些东西,即使能打印出二进制流,也不知道到底序列化了什么。

但是序列化不止有java序列化/反序列化,还有其他的序列化/反序列化格式:序列化成JSON、yaml、xml等格式。

4.序列化/反序列化JSON格式

我们能想到的序列化/反序列化JSON格式的典型操作莫属Spring了,在使用Spring框架进行开发时,我们只需要写好Controller就能进行正常的网络传输,其实Spring是将要传输的对象转成了String类型的JSON格式。

使用fastjson来实现:

SerializerFeature属性值参考:https://blog.csdn.net/weixin_72984629/article/details/126852879

相比 java序列化/反序列化二进制流,JSON格式我们经常使用,打印出的内容也可看,且不需要对原来的对象做修改。

在使用fastjson时,有许多需要注意事项:

1.使用JSONObject.toJSON()方法时,属性不能有循环依赖:

最开始,做demo时,直接将Context转JSON,但栈溢出了(java.lang.StackOverflowError)。

toJSON方法里有递归调用,如果属性里还有属性,就会再次调用toJSON方法:

排查到logback的Logger对象(implement slf4j的Logger接口):

可以看到Logger里面的LoggerContext和Logger产生了循环依赖。

本身logback的Logger和LoggerContext不存在循环依赖的,出现这种情况的原因:

toJSON方法通过反射(反射依赖getter来获取字段和属性值)来序列化,Context中重写了Logger的getter方法,里面又通过LoggerFactory.getLogger()创建Logger,并赋值给Logger属性。

可以在Logger属性上添加@JSONField(serialize=false)注解,使Logger对象不参加序列化或者将重写的getLogger方法换个方法名

JSONObject.toJSONString()方法有循环检查,避免了无限循环的问题。

2.反序列化的对象必须有默认的空构造方法和setter方法,因为反序列化是通过反射来实现的(构造方法用于构建对象,setter方法用于赋值,为什么必须要有空构造方法,请参考fastJson反序列化和构造函数之间的一点小秘密_fastjson 无参构造函数-CSDN博客)。没有空构造方法,反序列化时属性有可能是空对象(对象属性值是null)。

3.反序列化对象不要使用接口和抽象类来定义成员变量,不然会生成空字符串。

4.不能有虚拟的getter/setter方法(虚拟getter/setter方法--getter/setter没有对应的具体属性字段),属性字段对应的getter/getter不要有复杂的操作,最好是只get/set对应的属性字段。可序列化时,可设置SerializerFeature.IgnoreNonFieldGetter这个属性--忽略所有没有对应成员变量(Field)的getter方法。

注意事项参考了Fastjson踩“坑”记录和“深度”学习_fastjson底层_阿里技术的博客-CSDN博客

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值