在java学习中,理解对象以及对象的引用是万里长征的第一步。在Java中,有一组名词经常一起出现,它们就是“对象和对象引用”,很多朋友在初学Java的时候可能经常会混淆这2个概念,觉得它们是一回事。而且,现在大量的java学习书籍都将对象以及对象的引用混为一谈,然而实际上它们有着本质的区别。为了帮助更多的java学习者更好的理解,我将自己的学习理解记录下来。今天我们就来一起了解一下对象和对象引用之间的区别和联系。如有理解不全或者错误的地方,欢迎大家批评指正。
在Java中有一句比较流行的话,叫做“万物皆对象”,这是Java语言设计之初的理念之一。要理解什么是对象,需要跟类一起结合起来理解。下面这段话引自《Java编程思想》中的一段原话:
“按照通俗的说法,每个对象都是某个类(class)的一个实例(instance),这里,‘类’就是‘类型’的同义词。”
从这一句话就可以理解到对象的本质,简而言之,它就是类的实例,比如所有的动物统称为“动物”,这里的“动物”就是一个类(物种的一种类型),而具体到每个动物,比如狗这个动物,它就是对象,就是“动物”的实例。
我们先看一段话:
“每种编程语言都有自己的数据处理方式。有些时候,程序员必须注意将要处理的数据是什么类型。你是直接操纵元素,还是用某种基于特殊语法的间接表示(例如C/C++里的指针)来操作对象。所有这些在 Java 里都得到了简化,一切都被视为对象。因此,我们可采用一种统一的语法。“ 尽管将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“引用”(reference)。”
这段话来自于《Java编程思想》,很显然,从这段话可以看出对象和对象引用不是一回事,是两个完全不同的概念。举个例子,我们通常会用下面这一行代码来创建一个对象:
Animal animal = new Animal("狗");
有人会说,这里的person是一个对象,是Person类的一个实例。
也有人会说,这里的person并不是真正的对象,而是指向所创建的对象的引用。
到底哪种说法是对的?我们先不急着纠结哪种说法是对的,再看两行代码:
Animal animal;
animal = new Animal("狗");
这两行代码实现的功能和上面的一行代码是完全一样的。大家都知道,在Java中new是用来在堆上创建对象用的,如果person是一个对象的话,那么第二行为何还要通过new来创建对象呢?由此可见,person并不是所创建的对象,是什么?上面的一段话说的很清楚,“操纵的标识符实际上是指向一个对象的引用”,也就是说person是一个引用,是指向一个Person类对象的引用。真正创建对象的语句是右边的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指向的对象内容并未发生改变。
在这里要永远记住一点:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
创建字符串的方式很多,归纳起来有三类:
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方法的调用结果,可能很多人会有这种感觉:这不明明是按引用传递吗?对于这种问题,我想说这每个人的理解不同,你说只按值传递也可以,说有引用传递也可以,传值,对引用类型来说,可以是传引用值,所以说纠结传值还是传引用是没有必要的,每个人有每个人的理解!!!!
暂时就讲这么多了,感兴趣的朋友可以查阅相关文档和资料。