• 浅谈Java-String到底是值传递还是引用传递?


    参数传递#

    Java 中的参数传递分为 “值传递”“引用传递”
    如果你学过 C/C++应该很好理解,就是所谓的 "值传递""指针传递"

    值传递#

    在 Java 中,"值传递" 就是传递真实值的一个副本,其实就是重新生成了一个值,新的值怎么改变并不影响原来的值,举个例子:

    public class Test {
    	
    	public static void main(String[] args) {
    		Test t1 = new Test();
    		int a = 1;
    		t1.paramTest(1);
    		System.out.println("main: " + a);
    		
    	}
    	
    	private void intParamTest(int a) {
    		a++;
    		System.out.println(a);
    	}
    
    }
    

    输出结果:
    2
    main: 1
    我们可以看到,虽然我们去调用了方法让 a 的值自增 1 ,但是 main 方法中的 a 还是 1
    实践出真知:我们可以得出一个结论,基本数据类型是值传递(传递的是值的一个克隆)

    引用传递#

    引用传递 顾名思义传递的是参数的引用,也就是参数的地址,在 Java 中,对象作为参数传递时,传递的就是对象的引用地址
    所以当我们对 对象进行操作的时候,实际上我们操作的是真实的对象,原对象也会随之改变,还是来实践出真知:

    public class Test {
    	
    	public static void main(String[] args) {
    		Test t1 = new Test();
    		Person p = new Person("小冯同学",18);
    		System.out.println("方法调用前:" + p.name);
    		t1.personTest(p);
    		System.out.println("方法调用后:" + p.name);
    		
    	}
    	
    	private void personTest(Person p) {
    		p.name = "张三同学";
    	}
    
    }
    class Person{
    	public String name;
    	public int age;
    	public Person(String name,int age) {
    		this.name = name;
    		this.age = age;
    	}
    }
    

    输出结果:
    方法调用前:小冯同学
    方法调用后:张三同学
    看到这里,我们可以得出一个结论:对象作为参数的时候,传递的是对象的引用地址。

    总结#

    根据以上两个结论,我们总结一下:

    • 当参数类型是基本数据类型时,传递是的一个拷贝值,拷贝值是生是死不影响原来的值
    • 当参数类型是一个对象时,传递的是对象的引用地址,操作的是同一个对象

    分享一个小故事,大概是两年前刚开始接触 SSM框架的时候,当时有一个需求就是:插入一个 user,然后返回插入成功和 user的基本信息(包括 userid),然后我们呢当时数据库的 user表的 id自增的,插入的 user是没有 id的,id是插入后 mysql自增主键生成的,当时借助了 mybatis的主键回添功能,大概长这样子:

    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into t_user (u_name,u_age) values (#{name},#{age});
    </insert>
    

    然后之前插入的那条 user记录就有 id了,当时年少气盛的我还不懂什么值传递引用传递,当时的表情是这样的

    属实是 Java 基础太拉了,哈哈
    然后现在发现其实原理就这么简单,插入后把 id 查出来,直接塞到你之前插入的那个 user 对象中 o 了

    String#

    相信大家在面试场上,面试官经常会问你一些关于 String 的问题
    然后你发现你之前背的结论不管用了(基本数据类型值传递,对象引用传递
    还是实践出真知:

    public class Test {
    	
    	public static void main(String[] args) {
    		Test t1 = new Test();
    		String s = "hello";
    		t1.append(s);
    		System.out.println("main: " + s);
    		
    	}
    	
    	private void append(String s) {
    		s += " world";
    		System.out.println("method: " + s);
    	}
    
    }
    

    输出结果:
    method: hello world
    main: hello
    很多同学应该也是这个表情

    “String 是对象啊,传递的不是地址吗,操作的不是真实的对象吗,为什么没有改变啊”
    同学们呼声很大啊
    你可以把 String 归类到基本数据类型中去,我这样说大家肯定不会买账,这还要从 String 的关键字 final 说起
    大家都知道被 final 修饰的都是常量,常量有什么特点?当然是不能改变啊
    所以我们对 String 进行改变的时候,编译器在底层是为我们重新生成了一个 String 对象,来看一道经典面试题:

    	public static void main(String[] args) {
    		String s1 = "a";
    		String s2 = s1;
    		s2 = "ab";
    		String s3 = new String("ab");
    		String s4 = s3 + "cd";
    		String s5 = "abcd";
    	}
    

    请问这段程序一共生成了多少个对象?
    有没有猜到 5 个的,正确答案就是 5 个。
    池化技术了解过吧?线程池,对象池,人才池...
    String 底层用到的就是 常量池
    第二行:s1 = "a" ,生成常量 a 放入池中,然后 s1 指定这个常量的地址
    第三行:s2 = s1 ,就是生成一个 s2 的变量指向 s1 的地址。
    第四行:s2 = "ab",是把 "a" 变成的 "ab" 么?当然不是,人家都是常量了,不能被改变,那怎么办?当然是重新生成一个常量 "ab",放入池中,然后 s2 指向 "ab" 的地址
    第五行: s3 = new String("ab"),使用了 new 关键字,所以会生成一个新的对象,但是 new String("ab") 中的 "ab" 在常量池中存在,所以也复用了常量池中的 "ab",但是 s2 和 s3 并不是同一个对象,因为你使用了 new 关键字,会重新生成一个对象
    第六行:池里没有
    "cd" ,创建 "cd" 扔到池子里,s3 是 "ab" ,用加号连接起来就是 "abcd",池里没有 "abcd",创建 "abcd" 扔到池里
    第七行:
    s5 = "abcd",因为池里有 "abcd",所以直接复用就是,不用重新创建
    所以一共生成了 5 个对象,分别是:
    "a","ab","ab","cd","abcd"**
    至于 Java 为什么要这样设计,大家感兴趣可以去探究探究
    据我所知,Java 里用得最多的数据类型就是 String,如果不停的创建 String 对象,对内存来说应该是一笔很大的开销,使用常量池可以对其进行复用,算是对 String 的优化
    可能不太准确,hhh,也欢迎大佬在下面评论
    文笔不是很专业,但还是希望你没看一篇博客都能吸收到新的知识,认真沉淀~

  • 相关阅读:
    【ansible第三次作业】
    关于 硬盘
    【源码分析】Spring的循环依赖(setter注入、构造器注入、多例、AOP)
    面试题20231008-导航页
    解决vue3 mitt路由跳转后 on事件获取不到值的奇葩问题解决
    Java基础之ArrayList集合(最简单最详细)
    c++ chilkat-9.5.0 库使用CkZipW创建压缩包
    Java开发学习(二十二)----Spring事务属性、事务传播行为
    【软件测试】路径覆盖
    零基础学Java(8)数组
  • 原文地址:https://www.cnblogs.com/Fzeng/p/16225317.html