一.值传递?
所谓值传递就是一个参数被传进方法中修改,却不影响原始值,为什么原始值不受影响呢?因为jvm是将原始值复制了一份传递出去的,所以叫值传递(这句话很重要,这决定了对象数据类型到底是值传递正确,还是引用传递正确)。
二.引用传递?
引用传递就是一个参数被传进方法中修改,会影响到原始的值,传递的是原始值得引用。
这便是值传递与引用传递最直观的展现,这里要特别注意的是,引用传递是你传递到方法里的值如果改变,你原始数据是必须要变得,这才能叫做引用传递(这句话也非常重要,手动敲黑板,请记住)。
三.那么java中到底是值传递?还是引用传递?
3.1有人认为基本数据类型是值传递,对象类型是引用传递
基本数据类型,应该没神马争议,是值传递。
public static void main(String args[]){ // testList(); // testNull(); test1(); } //int public static void test1(){ int a = 1; changeInt(a); System.out.println(a); } public static void changeInt(int a){ a = 2; }
这个输出应该没有神马争议,输出是1,所以基本数据类型是值传递大家一致认可。
F:\java\bin\java.exe... 1 Process finished with exit code 0
那引用数据类型呢?举个栗子
public static void main(String args[]){ // testList(); // testNull(); // test1(); test2(); } //list public static void test2(){ List list = new ArrayList(); list.add("a"); list.add("b"); list.add("c"); changeList(list); list.forEach(str ->{ System.out.println(str); }); } public static void changeList(List list){ list.add("d"); }
这个输出大部分人应该也没有争议,list的值应该是a,b,c,d
F:\java\bin\java.exe... a b c d Process finished with exit code 0
到这里大部分就认为对象类型是引用传递,因为原始值改变了传递的自然是引用了,如果你也这么认为,那我要告诉你,兄弟关于这个问题(对象类型是引用还是值传递)只验证到这,其实只是验证了一半,这一半验证成功是因为jvm将引用数据类型的地址引用放在了栈里,看过一些解释,都是验证到这里就没有下文了,如果我要不是碰到问题,下面的我也没想起来(插一句,验证对象类型不要使用final修饰的类,比如String,当你改变他时,其实是在创建一个新的对象,看起来和基本数据类型类似)。
3.2 java其实只有值传递
public static void main(String args[]){ // testList(); // testNull(); // test1(); test2(); } //list public static void test2(){ List list = new ArrayList(); list.add("a"); list.add("b"); list.add("c"); changeList(list); list.forEach(str ->{ System.out.println(str); }); } public static void changeList(List list){ list.add("d"); list = null; }
这块代码和3.1中的第二段代码只是在changList方法中加了一句:list = null;,加了这一句,大家不妨想一想会输出什么?空指针异常?还是a,b,c,d?其实是a,b,c,d,这里不展示结果,给不信得人去试试的机会。如果是引用传递,那么我将引用传递进来,输出结果也应该是空指针异常,因为我将引用置空了,不是吗。到这里有些人可能会有这样的疑问:在changList方法中为list增加一个元素,原始的list会增加一个元素,但是在changList方法中将list置空,原始的list却不变?有点懵?突然感觉还涉及到玄学了呢,其实这是因为java是值传递所以才有这种现象。在1和2中画的重点还记得吗,如果不记得可以往上看下,值传递是复制了一个值传递出去并不会影响到原始值,而引用传递才会影响到原始值。
下面说下为什么是值传递:
(**终于说到重点,前面都是铺垫 **)
java中方法传参其实无论是基本类型还是对象类型,都是先将他们在栈空间的地址复制一份然后传入进入方法中(引用传递是不会复制值进行传递的),方法体其实操作的都是这个复制的栈空间地址。所以才有这种现象:操作一个对象类型你改变他的值,原始值也会变。因为复制了的栈空间地址和原始的栈空间地址指向同一个堆内存。也有了这种现象:在方法中将传入的对象参数置空,但是原始的对象类型却并不影响,因为你置空的是是和原始引用相同的复制体,说白了。你改变得只是克隆体,所以真正的引用你改变不了。说到这里应该是明白为什么说java只有值传递了,对象类型中,你改变的其实也是一个复制体,和基本数据类型一样,你操作的都是一个原始值复制出来的值(引用传递并不会复制值传递出去,而是传递一个引用),并不是这个对象的真正的引用,所以对象类型并不是引用传递,而是值传递。
3.4说个关于项目中碰到的值传递与引用传递的问题(好像是我们架构师的代码)
3.4.1问题描述
将一个list放入到返回对象中,且方法return之前调用了list的clear方法。代码如下
public ResultDTO querySystemDicListByDicCode(String dicCode) { ResultDTO resultDTO; SupplierSystemDic supplierSystemDic; //调用数据中心查询服务 QueryPacker queryPacker = QueryPacker.create(TableConstant.TABLE_SYSTEM_DIC); queryPacker.where(Criteria.field("dic_code").eq(dicCode)); ResponseModel<PageResult> responseModel = queryPersistenceCoreDataService.queryData(CommonConstant.APPKEY, queryPacker, CommonConstant.SOURCE); if (responseModel.isSuccess()) { resultDTO = new ResultDTO(ExceptionEnum.COMMON_SUCCESS_30.getResultCode()); List<Map<String, Object>> mapList = responseModel.getData().getResultList(); if (mapList != null && mapList.size() > 0) { resultDTO.setData(mapList); //清空数据 mapList.clear(); responseModel.getData().getResultList().clear(); supplierSystemDic = null; } else { if (resultDTO != null) { resultDTO.setData(null); } } } else { log.error("方法querySystemDicByDicCod,字典代码=" + dicCode + "查询字典表数据异常,异常原因:" + responseModel.getMessage()); resultDTO = new ResultDTO(ExceptionEnum.RPC_30.getResultCode()); } //清空数据 responseModel = null; queryPacker.clearColumns(); queryPacker = null; return resultDTO; }
如果3.4之前内容都理解了,那么这里如果不用maplist.clear();而用maplist=null;他俩的区别肯定也清楚了,如果用的是maplist=null;则置空的是maplist的一个引用,而不是真正的maplist,而resultDTO里面存储的maplist依然是存在的。故使用maplist=null;不会有任何问题。
但是使用maplist.clear();则不一样,这个方法在ArrayList中的实现是这样的
public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
ArrayList底层是一个数据,elementData便是那个数组,这里操作不再是集合的引用,而是集合里的单个元素,并把size置为0;实际上这个集合在堆中便不存在了,所以resultDTO中设置的(复制的栈空间引用与真实的引用指向同一个堆内存)maplist也不会存在了,所以这个返回值resutDTO中无论什么时候date这个属性都不会有值。
结尾语:如果有不同意见,欢迎评论共同探讨。