一.值传递?

所谓值传递就是一个参数被传进方法中修改,却不影响原始值,为什么原始值不受影响呢?因为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是值传递所以才有这种现象。在12中画的重点还记得吗,如果不记得可以往上看下,值传递是复制了一个值传递出去并不会影响到原始值,而引用传递才会影响到原始值。
下面说下为什么是值传递:
(**终于说到重点,前面都是铺垫 **)
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这个属性都不会有值。
结尾语:如果有不同意见,欢迎评论共同探讨。