• 一段递归代码引发的对于传参以及关于基本类型的一点了解


    首先附上为了模拟场景简化的递归代码:

    public class Recursion {
        public static void main(String[] args) {
            Recursion recursion = new Recursion();
    
            List<Long> list = new ArrayList<Long>();
            list.add(100L);
            Long  num = 100L;
    
            recursion.recursionFunction(5L, list);
            recursion.recursionFunction(5L, num);
            System.out.println("recursionFunction:list:" + list.get(0));
            System.out.println("recursionFunction:num:" + num);
        }
        //为了后面方便描述,以后称:listfunction
        private void recursionFunction(Long i, List<Long> list) {
            i--;
            if (i == 0) {
                list.set(0,i);
            } else {
                recursionFunction(i, list);
            }
        }
        //为了后面方便描述,以后称:longfunction
        private void recursionFunction(Long i, Long  num) {
            i--;
            if (i == 0) {
                num = i;
            } else {
                recursionFunction(i, num);
            }
        }
    }


    模拟出来的递归场景,有几个朋友在我跟他们说了打印结果并不是“recursionFunction:list:0”和“recursionFunction:list:0"的时候他们也表示很惊讶,为什么不是呢?
    其实我也正是因为打印结果不是这样而觉得不可思议。
    当我遇到这个现象的时候我的第一反应是:怎么会这样,明明逻辑都是一样的,然后是把参数num改外全局变量,打印结果和预期的一样。然后问题就来了,为什么参数list不用改为全局变量就可以呢?
    接着是第一个猜测:是不是java虚拟机在声明变量的时候分配内存空间的不同?据我了解:只要声明变量,java虚拟机都会开辟内存空间。我还是仔细的想了一下,自己否定了;
    然后是有第一个猜想引发的第二个猜想:会不会调用方发的时候形参和实参用的不是同一个对象,可问题就出在这里:如果用的不一样,那为什么list就可以,而num就不可以呢?于是我陷入了一个很迷茫的地步。
    第三个猜想:是不是因为Long封装类的原因?经过测试,发现并不是因为这样。
    第四个猜想:是不是因为基本类型的原因?经测试,直接想到的就是Object类,但结果也并不是我想的那样。
    于是开始带着这段自认为神奇的代码,给朋友看,我大概都能想到朋友在得知结果与预期结果不相同时的惊讶。
    有朋友让我用别的类型试试比如:StringBuffer和StringBuilder试试,我在没试之前就觉得不行的,结果我只是了一个真的不行,第二个就没有试了。
    和另一朋友开始了讨论:
      第一:Long型的比较他建议我用longValue()这个方法[补充一],但是因为和0比较,肯定是不存在问题的,不过这点还是很需要注意的,因为往往能用到Long型都是ID相关的,
    通常都是一些ID生成算生成的大多数都在16位左右。
      第二:Long类型的自动封装,这个和我的第三个猜想是一样的。那为什么全局变量就可以呢?
      第三:递归方法中num出栈就会销毁,也就是递归方法用完,数据就会销毁。那还是同样的问题,为什么list就可以呢?
    然后他查看了class文件反编译的代码,我突然想到,ArrayList的底层就是用可变数组实现的,然后我就试了一下数组,结果和list的效果一样,赋值成功。这个时候,我似乎有点顿悟。
    好像明白为什么list的值可以赋值成功,但是不明白num为什么不成功。
      第四:他告诉我,如果在listfunction中第一局执行list = new ArrayList<Long>();结果也会不止不成功,并告诉我好像破案了。[ps:这个过程真的像是在破案或者是玩寻宝游戏,我很喜欢]
    这个时候我们好像都明白了为什么打印结果会出乎意料的是“recursionFunction:list:0”和“recursionFunction:list:100",为了肯定答案,我们又去查了资料。
    我将理解整理如下:
      关于传参:java的传参有两种,分别是{值传递和引用传递},八种基本类型就是属于值传递,而他复合类型都是引用传递。
        值传递————就是把实际对应的对象值传递过去,比如代码中,虽然看上去传递的是num这个变量对象,但实际是传递了100L过去的,就直接是个数字传过去了,方法中的运算和num并没有什么关系。
        引用传递————就是,传递的实际对象的内存地址过去,比如代码中,真的是把list传递过去了,list实际上是一个引用是new ArrayList()这个对象的内存地址。[补充二]。
            那么String也是复合类型,为什么也不能赋值成功呢?【注】
      关于基本类型的赋值:
        java说一切都是对象。随机即使是基本类型在赋值的时候本质上其实是new了一个新的对象,改变引用地址。比如说:int i = 0;(Integer i = 0;) ==> int  i = new Integer(0);(Integer i = new Integer(0);)所以你可能就瞬间懂了,为什么递归中赋值语句 num = i;明明赋值成功了,为什么递归结束后,num还是100L,因为基本类型传递值 i = 0,其实就是 num = new Long(0),递归结束,new Long(0)销毁。
            那么String也是复合类型,为什么也不能赋值成功呢?【注】
        
        现在我们走一遍代码:listfunction方法,传进去了list指向的new ArrayList()对象的内存地址,然后再对这段地址的中的第一个单元进行赋值为0,然后递归结束,0对象销毁。而本身list这个引用就指向new ArrayList()这个对象,再拿出来这个对象第一个单元的值,现象是赋值成功。反例,我们再listfunction方法中加上list = new ArrayList();依然传进来的是list指向的new ArrayList()对象的内存地址,但第一句,改变了list的指向地址,而且每一次递归调用都是指向了一个新的new ArrayList()对象地址,那么在最后一次赋值成功,其实是给new ArrayList()这个最新的对象的第一个单元地址赋值,递归结束,这些在递归局部内的对象销毁,list又指向了原来的对象地址,而原来的对象是main方法中的new ArrayList(),所以赋值失败。
     

    [补充一:如果直接用“==”比较,比较范围只能是在Long型范围在Byte范围内,如果超出了Byte类型的范围(-128~127)需要使用equals()或者longValue()比较]
    [补充二:声明变量赋值 格式是:类型名称 变量名称 = 对象;,其实本质的理解并不是变量名称等于对象,而是变量名称等于对象所在的内存地址,所以我们也叫这个变量名称为引用。它并不是对象,只是引用了对象。所以在复合类型传参时,其实传递的是:变量名 = 内存地址,但在基本类型中传参就是值(num = 100L),而不是值对应的内存地址]
    【注】:String虽然是复合类型,但string对象和其他对象是不同的,string对象是不能被改变的,内容改变就会产生新对象。也就是每次赋值都是改变变量名所指向的内存地址。也就是每次String类型赋值时是new除了一个新的对象。比如 String str = “str”; ==>String str = new String("str");,那么就可以理解成其他的复合类型在赋值的时候其实是 变量名 = 内存地址(这部分是不变的),改变的是这段内存地址里面存放的数据。

  • 相关阅读:
    Spring_7_AOP之Advice应用
    JAVA豆知识
    SPRING事务_2
    JSP_5_JavaBean
    Spring事务_1
    java基本类型和包装类型
    SVN使用教程总结
    通过反射来创建对象?getConstructor()和getDeclaredConstructor()区别?
    Java泛型中extends和super的区别?
    数字签名、数字证书、对称加密算法、非对称加密算法、单向加密(散列算法)
  • 原文地址:https://www.cnblogs.com/ben-mario/p/8905522.html
Copyright © 2020-2023  润新知