按值传递,引用传递 浅析java String ,对象与对象引用的区别
目录
一、前言
在java学习中,理解对象以及对象的引用是万里长征的第一步。在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事。而且,现在大量的java学习书籍都将对象以及对象的引用混为一谈,然而实际上它们有着本质的区别。为了帮助更多的java学习者更好的理解,我将自己的学习理解记录下来。今天我们就来一起了解一下对象和对象引用之间的区别和联系。如有理解不全或者错误的地方,欢迎大家批评指正。
二、何谓对象?
在Java中有一句比较流行的话,叫做“万物皆对象”,这是Java语言设计之初的理念之一。要理解什么是对象,需要跟类一起结合起来理解。下面这段话引自《Java编程思想》中的一段原话:
“按照通俗的说法,每个对象都是某个类(class)的一个实例(instance),这里,‘类’就是‘类型’的同义词。”
从这一句话就可以理解到对象的本质,简而言之,它就是类的实例,比如所有的动物统称为“动物”,这里的“动物”就是一个类(物种的一种类型),而具体到每个动物,比如狗这个动物,它就是对象,就是“动物”的实例。
三、何谓对象引用?
我们先看一段话:
“每种编程语言都有自己的数据处理方式。有些时候,程序员必须注意将要处理的数据是什么类型。你是直接操纵元素,还是用某种基于特殊语法的间接表示(例如C/C++里的指针)来操作对象。所有这些在 Java 里都得到了简化,一切都被视为对象。因此,我们可采用一种统一的语法。“ 尽管将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“引用”(reference)。”
这段话来自于《Java编程思想》,很显然,从这段话可以看出对象和对象引用不是一回事,是两个完全不同的概念。举个例子,我们通常会用下面这一行代码来创建一个对象:
Animal animal = new Animal("狗");
有人会说,这里的animal是一个对象,是Animal类的一个实例。
也有人会说,这里的animal并不是真正的对象,而是指向所创建的对象的引用。
到底哪种说法是对的?我们先不急着纠结哪种说法是对的,再看两行代码:
Animal animal;
animal = new Animal("狗");
这两行代码实现的功能和上面的一行代码是完全一样的。大家都知道,在Java中new是用来在堆上创建对象用的,如果animal是一个对象的话,那么第二行为何还要通过new来创建对象呢?由此可见,animal并不是所创建的对象,是什么?上面的一段话说的很清楚,“操纵的标识符实际上是指向一个对象的引用”,也就是说animal是一个引用,是指向一个Animal类对象的引用。真正创建对象的语句是右边的new Animal("狗");
我们仔细研究一下第二句,找找刚创建的对象叫什么名字?有人说,它叫“Animal”。不对,“Animal”是类(对象的创建模板)的名字。
一个Animal类可以据此创建出无数个对象,这些对象不可能全叫“Animal”。
对象连名都没有,没法直接访问它。我们只能通过对象引用来间接访问对象。
为了形象地说明对象、引用及它们之间的关系,可以做一个或许不很妥当的比喻。对象好比是一只很大的气球,大到我们抓不住它。引用变量是一根绳, 可以用来系汽球。
再看一个例子:
Animal animal;
animal = new Animal("狗");
animal = new Animal("猫");
这里让animal先指向了“狗”这个对象,然后又指向了“猫”这个对象。也就是说,Animal animal,这句话只是声明了一个Animal类的引用,它可以指向任何Animal类的实例。
也就是说,一个引用可以指向多个对象,但是最终一个引用只能指向一个对象!!!而一个对象可不可以被多个引用所指呢?答案当然是可以的。
比如:
Demo demo1,demo2,demo3;//创建多个对象引用
demo1=new Demo();
demo2=demo1;
demo3=demo2;//创建对象,并被多个对象引用指向
四、创建对象
Vehicle veh1 = new Vehicle();
通常把这条语句的动作称之为创建一个对象,其实,它包含了四个动作。
1)右边的“new Vehicle”,是以Vehicle类为模板,在堆空间里创建一个Vehicle类对象(也简称为Vehicle对象)。
2)末尾new Vehicle()意味着,在对象创建后,为Vehicle类的成员变量分配内存空间,立即调用Vehicle类的构造方法,对刚生成的对象(也就是对那些成员变量)进行初始化。构造方法是肯定有的。如果你没写,Java会给你补上一个默认的构造函数。分配内存后,将计算出的引用值赋给引用变量 Veh1
3)左边的“Vehicle veh 1”创建了一个Vehicle类引用变量。所谓Vehicle类引用,就是以后可以用来指向Vehicle对象的对象引用(简称对象也是可以的,但是实际上是引用变量,指向Vehicle对象。。。)。
4)“=”操作符使对象引用指向刚创建的那个Vehicle对象。
五、参数传值
public static void main(String[] args) {
String x = new String("ab");
change(x);
System.out.println(x);
}
public static void change(String s) {
s = "cd";
}
打印结果:ab
在change方法中,参数也是对象,当字符串“ab”被创建,java分配存储字符串对象所需的内存空间。然后,将对象分配给变量x,该变量是被实际分配的引用对象。此引用是该对象被存储在内存的地址。变量x包含一个字符串对象的引用。 x不是引用本身!它是用于存储一个引用(内存地址)的变量。
当把x传给参数s时,s得到的是x的引用值的拷贝也就是副本,在方法体内改变了s指向的对象(也就是s指向了别的对象,牵着气球的绳子换气球了),给s重新赋值后,方法change()创建另一个对象“cd”,它有一个不同的引用。它是被改变的x副本指向“cd”的变量,而不是x引用本身。s与x已经毫无关联,它和x指向了不同的对象,指向ab的s已经被覆盖掉了,没有了!(图片下方的x为文中所说的s)所以不管对s做什么操作,都不会影响x指向的对象,故调用change方法前后x指向的对象内容并未发生改变。
如果再简单化理解 ,就理解为将"ab"这个字符串常量赋值给变量x,方法调用将变量x的值传给形参变量s,将cd赋值给s,变量的本质是什么,就是代替复杂的地址,所以这两个变量其实不是同一个变量,即使它俩同名,我是指形参与实参这里,它们是不同的内存地址,所以形参的改变对实参是无影响的,即使你把void change改为 String change 加return 也没用,因为你没用变量去接收这个值,简单化理解就是把cd返回给了原方法,用原来变量接收了这个值,底层一些就是将"cd"的引用又传给了x,就这样整一圈,又回来了。
public static void main(String[] args) {
String x = new String("ab");
x=change(x);
System.out.println(x);
}
public static String change(String s) {
s = "cd";
System.out.println(s);
return s;
}
}
自行理解吧,我也是自己想出来的,
对基本数据类型来说,你可以用原来的变量去接收这个值。
比如:
public class Person1 {
public static void main(String[] args) {
int p=10;
p=Value(p);
System.out.println(p);
}
private static int Value(int p) {
p=p*3;
return p;
}
}
如果我有哪方面是错误理解,请各位大佬指教,谢谢!!
六、Java Sting
在这里要永远记住一点:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
创建字符串的方式很多,归纳起来有三类:
- 使用new关键字创建字符串,比如String s1 = new String("abc");这种写编译器会提示你是多余的,除非显式original
- 直接指定。比如String s2 = "abc";
- 使用串联生成新的字符串。比如String s3 = "ab" + "c"; 《String对象的创建》 String对象的创建也有很多门道,关键是要明白其原理。
- 每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的对象引用(即地址)。如果字符串不存在常量池中,就会在字符串常量池中实例化该字符串(也就是创建该字符串对象)并返回这个对象的地址。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(重要)。
- new创建字符串时首先查看池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址;如果池中没有,则在堆中创建一份,然后返回堆中的地址(注意,此时不需要从堆中复制到池中,否则,将使得堆中的字符串永远是池中的子集,导致浪费池的空间)!
JAVA为了提高效率,对String类型进行了特别的处理---为string类型提供了串池 定义一个string类型的变量有两种方式:
String name= "tom ";(String name="t"+"o"+"m"的效果和此处是相同的)
String name =new String( "tom ")
如果你使用了第一种方式,那么当你在声明一个内容也是 "tom "的string时,它将使用串池里原来的那个内存,而不会重新分配内存,也就是说,string saname= "tom ",将会指向同一块内存。而如果用第二种方式,不管串池里有没有"tom",它都会在堆中重新分配一块内存,定义一个新的对象。
另外关于string类型是不可改变的问题: string类型是不可改变的,也就是说,当你想改变一个string对象的时候,比如name= "madding " 那么虚拟机不会改变原来的对象,而是生成一个新的string对象,然后让name去指向它,如果原来的那个 "tom "没有任何对象去引用它,虚拟机的垃圾回收机制将接收它。
按值传递为什么形参改变对实参无影响呢?? 那可能是因为实参传值给了形参,并且两个变量指向了不同的内存空间,所以两者互不干扰!
对于change方法的调用结果,可能很多人会有这种感觉:这不明明是按引用传递吗?对于这种问题,我想说这每个人的理解不同,你说只按值传递也可以,说有引用传递也可以,传值,对引用类型来说,可以是传引用值,所以说纠结传值还是传引用是没有必要的,每个人有每个人的理解!!!!
var code = "cc9a58e4-3711-40ee-ba33-65dbc86cbca4"
暂时就讲这么多了,感兴趣的朋友可以查阅相关文档和资料。