精华内容
下载资源
问答
  • JAVA中的传递都是值传递吗?有没有引用传递呢?在回答这两个问题前,让我们首先来看一段代码:Java代码public class ParamTest {// 初始值为0protected int num = 0;// 为方法参数重新赋值public void change(int i)...

    JAVA中的传递都是值传递吗?有没有引用传递呢?

    在回答这两个问题前,让我们首先来看一段代码:

    Java代码

    public class ParamTest {

    // 初始值为0

    protected int num = 0;

    // 为方法参数重新赋值

    public void change(int i) {

    i = 5;

    }

    // 为方法参数重新赋值

    public void change(ParamTest t) {

    ParamTest tmp = new ParamTest();

    tmp.num = 9;

    t = tmp;

    }

    // 改变方法参数的值

    public void add(int i) {

    i += 10;

    }

    // 改变方法参数属性的值

    public void add(ParamTest pt) {

    pt.num += 20;

    }

    public static void main(String[] args) {

    ParamTest t = new ParamTest();

    System.out.println("参数--基本类型");

    System.out.println("原有的值:" + t.num);

    // 为基本类型参数重新赋值

    t.change(t.num);

    System.out.println("赋值之后:" + t.num);

    // 为引用型参数重新赋值

    t.change(t);

    System.out.println("运算之后:" + t.num);

    System.out.println();

    t = new ParamTest();

    System.out.println("参数--引用类型");

    System.out.println("原有的值:" + t.num);

    // 改变基本类型参数的值

    t.add(t.num);

    System.out.println("赋引用后:" + t.num);

    // 改变引用类型参数所指向对象的属性值

    t.add(t);

    System.out.println("改属性后:" + t.num);

    }

    }

    public class ParamTest {

    // 初始值为0

    protected int num = 0;

    // 为方法参数重新赋值

    public void change(int i) {

    i = 5;

    }

    // 为方法参数重新赋值

    public void change(ParamTest T) {

    ParamTest tmp = new ParamTest();

    tmp.num = 9;

    T = tmp;

    }

    // 改变方法参数的值

    public void add(int i) {

    i += 10;

    }

    // 改变方法参数属性的值

    public void add(ParamTest pt) {

    pt.num += 20;

    }

    public static void main(String[] args) {

    ParamTest t = new ParamTest();

    System.out.println("参数--基本类型");

    System.out.println("原有的值:" + t.num);

    // 为基本类型参数重新赋值

    t.change(t.num);

    System.out.println("赋值之后:" + t.num);

    // 为引用型参数重新赋值

    t.change(t);

    System.out.println("运算之后:" + t.num);

    System.out.println();

    t = new ParamTest();

    System.out.println("参数--引用类型");

    System.ou

    展开全文
  • 有人说java只有值传递,也有人说java既有值传递也有引用传递,那么java中到底有没有引用传递呢,下面我来分析一下。一、首先来明确一下"值传递"和"引用传递的"区别值传递:是对所传递参数进行一次副本拷贝,对参数的...

    说明:本文的适用对象为java初学者、如果有读者发现文章中有叙述不妥之处,请指正。

    今天在论坛上有人提了一个关于java中调用函数时有没有引用传递的问题,可谓是吵的不可开交。有人说java只有值传递,也有人说java既有值传递也有引用传递,那么java中到底有没有引用传递呢,下面我来分析一下。

    一、首先来明确一下"值传递"和"引用传递的"区别

    值传递:是对所传递参数进行一次副本拷贝,对参数的修改只是对副本的修改,函数调用结束,副本丢弃,原来的变量不变(即实参不变)

    引用传递:参数被传递到函数时,不复制副本,而是直接将参数自身传入到函数,函数内对参数的任何改变都将反映到原来的变量上。

    二、java中引用的含义

    C++和java中都有引用的概念,但在这两种语言中却有完全不同的含义。C++中我们可以用形如"int &b=a”的形式来定义变量a的一个引用b,b只是a的一个别名,b和a在内存中占同一个存储单元,利用引用机制我们可以在调用函数时实现值的双向传递——即引用传递,看下面代码:

    示例一

    #include 

    using namespace std;

    int main()

    {

    void swap(int &,int &);

    int i=3,j=5;

    swap(i,j);

    cout<

    return 0;

    }

    void swap(int &a,int &b)

    {

    int temp;

    temp=a;

    a=b;

    b=temp;

    }

    执行上面的程序输出的是i=5 j=3,a和b传递给swap()函数的时候,是传递的他们本身的地址,不是他们的拷贝,所以在函数中对他们的改变可以直接影响到实参a和b,这就是引用传递。

    java中的引用更像C++中的指针,当我们定义一个对象时(比如Person p=new Person()),定义的对象实例会放到java堆中,而变量p(即引用)会放到java栈中,p指向堆中的Person对象实例。

    三、对引用传递的认识误区

    为什么有很多人认为java有引用传递呢?一种情况是有人认为调用函数时其参数有可能是引用(如上面的p),所以java有引用传递,这部分人对引用传递根本没有正确的认识;而另一种情况看似有道理,但是仔细分析也是不正确的的,他们往往会用如下的代码来证明他们的观点:

    实例二:

    classDemo{inta;public Demo(inta){this.a=a;

    }

    }public classTestQuote{public static voidmain(String args[]){

    Demo d1=new Demo(1);

    Demo d2=new Demo(2);

    System.out.println(d1.a);

    System.out.println(d2.a);

    function(d1,d2);

    System.out.println(d1.a);

    System.out.println(d2.a);

    }private static voidfunction(Demo d1,Demo d2){inta;

    a=d1.a;

    d1.a=d2.a;

    d2.a=a;

    }

    }

    他们的观点如下:执行上面的代码,调用function()函数以前输出的结果是1、2,调用function()函数之后输出的结果会是2、1,可见在函数内对d1和d2的改变反映到了原来的变量上,要不是不会输出2、1的。

    这种解释是很迷惑人的,看上去好像很正确,下面的代码会很好的反驳上面的观点:

    示例三:

    classDemo{inta;public Demo(inta){this.a=a;

    }

    }public classTestQuote{public static voidmain(String args[]){

    Demo d1=new Demo(1);

    Demo d2=new Demo(2);

    System.out.println(d1.a);

    System.out.println(d2.a);

    function(d1,d2);

    System.out.println(d1.a);

    System.out.println(d2.a);

    }private static voidfunction(Demo d1,Demo d2){

    Demo temp;

    temp=d1;

    d1=d2;

    d2=temp;

    }

    }

    执行上面的代码,调用function()前后程序输出的都是1、2,此程序试图通过调用function()交换d1和d2,但是没有成功,为什么呢?因为d1和d2是值传递,function()中的d1和d2是main()函数中d1和d2的副本,调用完function()不会对main()中的变量产生影响。再看示例二中,function()函数内改变的并不是d1和d2本身的值,而是d1和d2指向的对象的值,调用完function()后d1和d2仍然指向函数调用前的堆地址,即函数参数是栈中的d1和d2,而不是堆中d1和d2指向的对象,即使你在函数中改变了堆中的对象,但没有改变函数参数的值。所以示例二并不是什么引用传递;可见java中只有值传递。

    但是网上有很多针对"java值传递和引用传递进行的区别"的文章,如果读者看到的话一定要清楚,其中所说的引用传递是不正确的,他们所说的引用传递就是示例二中的那种情况。不幸的是,网上有很多文章都把示例二中的那样的传递看做引用传递,如果读者看到知道是什么意思就行了。

    展开全文
  • 错误理解一:值传递引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。错误理解二:Java引用传递。错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那...

    在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。

    错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。

    错误理解二:Java是引用传递。

    错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。

    实参与形参

    我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法:

    public static void main(String[] args)

    这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。

    形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。

    实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。

    简单举个例子:

    public static void main(String[] args) {

    ParamTest pt = new ParamTest();

    pt.sout("Hollis");//实际参数为 Hollis

    }

    public void sout(String name) { //形式参数为 name

    System.out.println(name);

    }

    实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。

    值传递与引用传递

    上面提到了,当我们调用一个有参函数的时候,会把实际参数传递给形式参数。但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。我们来看下程序语言中是如何定义和区分值传递和引用传递的。

    值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

    引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

    有了上面的概念,然后大家就可以写代码实践了,来看看Java中到底是值传递还是引用传递 ,于是,最简单的一段代码出来了:

    public static void main(String[] args) {

    ParamTest pt = new ParamTest();

    int i = 10;

    pt.pass(i );

    System.out.println("print in main , i is " + i);

    }

    public void pass(int j) {

    j = 20;

    System.out.println("print in pass , j is " + j);

    }

    上面的代码中,我们在pass方法中修改了参数j的值,然后分别在pass方法和main方法中打印参数的值。输出结果如下:

    print in pass , j is 20

    print in main , i is 10

    可见,pass方法内部对name的值的修改并没有改变实际参数i的值。那么,按照上面的定义,有人得到结论:Java的方法传递是值传递。

    但是,很快就有人提出质疑了(哈哈,所以,不要轻易下结论咯。)。然后,他们会搬出以下代码:

    public static void main(String[] args) {

    ParamTest pt = new ParamTest();

    User hollis = new User();

    hollis.setName("Hollis");

    hollis.setGender("Male");

    pt.pass(hollis);

    System.out.println("print in main , user is " + hollis);

    }

    public void pass(User user) {

    user.setName("hollischuang");

    System.out.println("print in pass , user is " + user);

    }

    同样是一个pass方法,同样是在pass方法内修改参数的值。输出结果如下:

    print in pass , user is User{name='hollischuang', gender='Male'}

    print in main , user is User{name='hollischuang', gender='Male'}

    经过pass方法执行后,实参的值竟然被改变了,那按照上面的引用传递的定义,实际参数的值被改变了,这不就是引用传递了么。于是,根据上面的两段代码,有人得出一个新的结论:Java的方法中,在传递普通类型的时候是值传递,在传递对象类型的时候是引用传递。

    但是,这种表述仍然是错误的。不信你看下面这个参数类型为对象的参数传递:

    public static void main(String[] args) {

    ParamTest pt = new ParamTest();

    String name = "Hollis";

    pt.pass(name);

    System.out.println("print in main , name is " + name);

    }

    public void pass(String name) {

    name = "hollischuang";

    System.out.println("print in pass , name is " + name);

    }

    上面的代码输出结果为

    print in pass , name is hollischuang

    print in main , name is Hollis

    这又作何解释呢?同样传递了一个对象,但是原始参数的值并没有被修改,难道传递对象又变成值传递了?

    Java中的值传递

    上面,我们举了三个例子,表现的结果却不一样,这也是导致很多初学者,甚至很多高级程序员对于Java的传递类型有困惑的原因。

    其实,我想告诉大家的是,上面的概念没有错,只是代码的例子有问题。来,我再来给大家画一下概念中的重点,然后再举几个真正恰当的例子。

    值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

    引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

    那么,我来给大家总结一下,值传递和引用传递之前的区别的重点是什么。

    我们上面看过的几个pass的例子中,都只关注了实际参数内容是否有改变。如传递的是User对象,我们试着改变他的name属性的值,然后检查是否有改变。其实,在实验方法上就错了,当然得到的结论也就有问题了。

    为什么说实验方法错了呢?这里我们来举一个形象的例子。再来深入理解一下值传递和引用传递,然后你就知道为啥错了。

    你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。

    你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。

    但是,不管上面哪种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?而我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。你改变的不是那把钥匙,而是钥匙打开的房子。

    还拿上面的一个例子来举例,我们真正的改变参数,看看会发生什么?

    public static void main(String[] args) {

    ParamTest pt = new ParamTest();

    User hollis = new User();

    hollis.setName("Hollis");

    hollis.setGender("Male");

    pt.pass(hollis);

    System.out.println("print in main , user is " + hollis);

    }

    public void pass(User user) {

    user = new User();

    user.setName("hollischuang");

    user.setGender("Male");

    System.out.println("print in pass , user is " + user);

    }

    上面的代码中,我们在pass方法中,改变了user对象,输出结果如下:

    print in pass , user is User{name='hollischuang', gender='Male'}

    print in main , user is User{name='Hollis', gender='Male'}

    我们来画一张图,看一下整个过程中发生了什么,然后我再告诉你,为啥Java中只有值传递。

    0600671e6ce7

    image.png

    稍微解释下这张图,当我们在main中创建一个User对象的时候,在堆中开辟一块内存,其中保存了name和gender等数据。然后hollis持有该内存的地址0x123456(图1)。

    当尝试调用pass方法,并且hollis作为实际参数传递给形式参数user的时候,会把这个地址0x123456交给user,这时,user也指向了这个地址(图2)。

    然后在pass方法内对参数进行修改的时候,即user = new User();,会重新开辟一块0X456789的内存,赋值给user。后面对user的任何修改都不会改变内存0X123456的内容(图3)。

    上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在执行user = new User();的时候,实际参数的引用也应该改为指向0X456789,但是实际上并没有。

    通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。

    我们再来回顾下之前的那个“砸电视”的例子,看那个例子中的传递过程发生了什么。

    0600671e6ce7

    image.png

    同样的,在参数传递的过程中,实际参数的地址0X1213456被拷贝给了形参,只是,在这个方法中,并没有对形参本身进行修改,而是修改的形参持有的地址中存储的内容。

    所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像钥匙和房子的关系。

    那么,既然这样,为啥上面同样是传递对象,传递的String对象和User对象的表现结果不一样呢?我们在pass方法中使用name = "hollischuang";试着去更改name的值,阴差阳错的直接改变了name的引用的地址。因为这段代码,会new一个String,再把引用交给name,即等价于:

    name = new String("hollischuang");

    而原来的那个"Hollis"字符串还是由实参持有着的,所以,并没有修改到实际参数的值。

    0600671e6ce7

    image.png

    所以说,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。

    总结

    无论是值传递还是引用传递,其实都是一种求值策略(Evaluation strategy)。在求值策略中,还有一种叫做按共享传递(call by sharing)。其实Java中的参数传递严格意义上说应该是按共享传递。

    按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。

    简单点说,Java中的传递,是值传递,而这个值,实际上是对象的引用。

    而按共享传递其实只是按值传递的一个特例罢了。所以我们可以说Java的传递是按共享传递,或者说Java中的传递是值传递。

    展开全文
  • Java值传递引用传递

    2021-02-28 14:42:48
    方法被调用时需要传递进来的参数,如:func(int a)的a,它只有在func被调用期间a才意义,也就是会被分配内存空间,在方法func执行完成后,a就会被销毁释放空间,也就是不存在了实参:方法被调用时是传入的实际...

    形参:方法被调用时需要传递进来的参数,如:func(int a)中的a,它只有在func被调用期间a才有意义,也就是会被分配内存空间,在方法func执行完成后,a就会被销毁释放空间,也就是不存在了

    实参:方法被调用时是传入的实际值,它在方法被调用前就已经被初始化并且在方法被调用时传入。

    1 public static void func(inta){2 a=20;3 System.out.println(a);4 }5 public static voidmain(String[] args) {6 int a=10;//实参

    7 func(a);8 }

    例子中

    int a=10;中的a在被调用之前就已经创建并初始化,在调用func方法时,他被当做参数传入,所以这个a是实参。

    而func(int a)中的a只有在func被调用时它的生命周期才开始,而在func调用结束之后,它也随之被JVM释放掉,,所以这个a是形参。

    2、Java的数据类型

    所谓数据类型,是编程语言中对内存的一种抽象表达方式,我们知道程序是由代码文件和静态资源组成,在程序被运行前,这些代码存在在硬盘里,程序开始运行,这些代码会被转成计算机能识别的内容放到内存中被执行。

    因此,数据类型实质上是用来定义编程语言中相同类型的数据的存储形式,也就是决定了如何将代表这些值的位存储到计算机的内存中。所以,数据在内存中的存储,是根据数据类型来划定存储形式和存储位置的。

    Java数据类型有基本类型和引用类型两大类:

    基本类型:编程语言中内置的最小粒度的数据类型。它包括四大类八种类型:

    4种整数类型:byte、short、int、long

    2种浮点数类型:float、double

    1种字符类型:char

    1种布尔类型:boolean

    引用类型:引用也叫句柄,引用类型,是编程语言中定义的在句柄中存放着实际内容所在地址的地址值的一种数据形式。它主要包括:

    接口

    数组

    有了数据类型,JVM对程序数据的管理就规范化了,不同的数据类型,它的存储形式和位置是不一样的,要想知道JVM是怎么存储各种类型的数据,就得先了解JVM的内存划分以及每部分的职能。

    3、JVM内存划分及职能

    Java语言本身是不能操作内存的,它的一切都是交给JVM来管理和控制的,因此Java内存区域的划分也就是JVM的区域划分,在说JVM的内存划分之前,我们先来看一下Java程序的执行过程,如下图:

    a9a60bd07e49ce03ff247054a4e38f3c.png

    由图可以看出:Java代码被编译器编译成字节码之后,JVM开辟一片内存空间(也叫运行时数据区),通过类加载器加到到运行时数据区来存储程序执行期间需要用到的数据和相关信息,在这个数据区中,它由以下几部分组成:

    虚拟机栈

    程序计数器

    方法区

    本地方法栈

    接着来了解一下每部分的原理以及具体用来存储程序执行过程中的哪些数据。

    1.虚拟机栈

    虚拟机栈是Java方法执行的内存模型,栈中存放着栈帧,每个栈帧分别对应一个被调用的方法,方法被调用的过程对应栈帧在虚拟机中入栈到出栈的过程。

    栈是线程私有的,也就是线程之间的栈是隔离的;当程序中某个线程开始执行一个方法时就会相应的创建一个栈帧并且入栈(位于栈顶),在方法结束后,栈帧出栈。下图表示了一个Java栈的模型以及栈帧的组成:

    13263e467c55b9a4e49a1fa45d25bfdd.png

    栈帧:是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。

    每个栈帧中包括:

    局部变量表:用来存储方法中的局部变量(非静态变量、函数形参)。当变量为基本数据类型时,直接存储值,当变量为引用类型时,存储的是指向具体对象的引用。

    操作数栈:Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中所指的栈就是指操作数栈。

    指向运行时常量池的引用:存储程序执行时可能用到常量的引用。

    方法返回地址:存储方法执行完成后的返回地址

    2.堆:用来存储对象本身和数组的,在JVM中只有一个堆,因此,堆是被所有线程共享的。

    3.方法区

    方法区是一块所有线程共享的内存逻辑区域,在JVM中只有一个方法区,用来存储一些线程可共享的内容,它是线程安全的,多个线程同时访问方法区中同一个内容时,只能有一个线程装载该数据,其它线程只能等待。

    方法区可存储的内容有:类的全路径名、类的直接超类的权全限定名、类的访问修饰符、类的类型(类或接口)、类的直接接口全限定名的有序列表、常量池(字段,方法信息,静态变量,类型引用(class))等。

    4.本地方法栈

    本地方法栈的功能和虚拟机栈是基本一致的,并且也是线程私有的,它们的区别在于虚拟机栈是为执行Java方法服务的,而本地方法栈是为执行本地方法服务的。

    5.程序技术器

    线程私有的。

    记录着当前线程所执行的字节码的行号指示器,在程序运行过程中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。

    4、数据如何在内存中存储

    从上面程序运行图我们可以看到,JVM在程序运行时的内存分配有三个地方:堆、栈、静态方法区、常量区

    相应地,每个存储区域都有自己的内存分配策略:堆式、栈式、静态

    我们已经知道:Java中的数据类型有基本数据类型和引用数据类型,那么这些数据的存储都使用哪一种策略呢?

    这里要分以下的情况进行探究:

    1. 基本数据类型的存储:

    A. 基本数据类型的局部变量:方法内定义的变量

    B. 基本数据类型的成员变量(aka.实例变量):类范围内、方法之外定义的变量

    C. 基本数据类型的静态变量(aka.类变量):类范围内、方法之外定义的变量

    2. 引用数据类型的存储

    1.基本数据类型的存储

    分别研究一下:

    A.基本数据类型的局部变量(方法内定义的变量)

    定义基本数据类型的局部变量以及数据都是直接存储在内存中的栈上,也就是前面说到的“虚拟机栈”,数据本身的值就是存储在栈空间里面。

    320644d9c12e0837a3b79ec013db175f.png

    如上图,在方法内定义的变量直接存储在栈中,如:

    1 int age = 50;2 int weight = 50;3 int grade = 6;

    当我们写“int age=50;”,其实是分为两步的:

    1 int age;//定义变量

    2 age=50;//赋值

    首先JVM创建一个名为age的变量,存于局部变量表中,然后去栈中查找是否存在有字面量值为50的内容,如果有就直接把age指向这个地址,如果没有,JVM会在栈中开辟一块空间来存储“50”这个内容,并且把age指向这个地址。因此我们可以知道:我们声明并初始化基本数据类型的局部变量时,变量名以及字面量值都是存储在栈中,而且是真实的内容。

    我们再来看“int weight=50;”,按照刚才的思路:字面量为50的内容在栈中已经存在,因此weight是直接指向这个地址的。由此可见:栈中的数据在当前线程下是共享的。那么如果再执行下面的代码呢?

    1 weight = 40;

    当代码中重新给weight变量进行赋值时,JVM会去栈中寻找字面量为40的内容,发现没有,就会开辟一块内存空间存储40这个内容,并且把weight指向这个地址。由此可知:

    基本数据类型的数据本身是不会改变的,当局部变量重新赋值时,并不是在内存中改变字面量内容,而是重新在栈中寻找已存在的相同的数据,若栈中不存在,则重新开辟内存存新数据,并把要重新赋值的局部变量的引用指向新数据所在地址。

    B. 基本数据类型的成员变量

    成员变量:顾名思义,就是在类体中定义的变量。看下图:

    5b8427bfcbd9527db142fc7960fa4c74.png

    我们看per的地址指向的是堆内存中的一块区域,我们来还原一下代码:

    public classPerson{private intage;privateString name;private intgrade;//篇幅较长,省略setter getter方法

    static voidrun(){

    System.out.println("run....");

    };

    }//调用

    Person per=new Person();

    同样是局部变量的age、name、grade却被存储到了堆中为per对象开辟的一块空间中。因此可知:基本数据类型的成员变量名和值都存储于堆中,其生命周期和对象的是一致的

    C. 基本数据类型的静态变量

    前面提到方法区用来存储一些共享数据,因此基本数据类型的静态变量名以及值存储于方法区的运行时常量池中,静态变量随类加载而加载,随类消失而消失。

    2. 引用数据类型的存储

    上面提到:堆是用来存储对象本身和数组,而引用(句柄)存放的是实际内容的地址值,因此通过上面的程序运行图,也可以看出,当我们定义一个对象时

    Person per = new Person();

    实际上,它也是有两个过程:

    1 Person per;//定义变量

    2 per=new Person();//赋值

    在执行Person per;时,JVM先在虚拟机栈中的变量表中开辟一块内存存放per变量,在执行per=new Person()时,JVM会创建一个Person类的实例对象并在堆中开辟一块内存存储这个实例,同时把实例的地址值赋值给per变量。因此可见:

    对于引用数据类型的对象/数组,变量名存在栈中,变量值存储的是对象的地址,并不是对象的实际内容,实际内容存储在堆中。

    6.值传递和引用传递

    值传递:在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。

    来看一下例子:

    public static void valueCrossTest(int age,floatweight){

    System.out.println("传入的age:"+age);

    System.out.println("传入的weight:"+weight);

    age=33;

    weight=89.5f;

    System.out.println("方法内重新赋值后的age:"+age); System.out.println("方法内重新赋值后的weight:"+weight);

    }//测试

    public static voidmain(String[] args) {int a=25;float w=77.5f;

    valueCrossTest(a,w);

    System.out.println("方法执行后的age:"+a);

    System.out.println("方法执行后的weight:"+w);

    }

    输出结果:

    1 传入的age:25

    2 传入的weight:77.5

    3

    4 方法内重新赋值后的age:33

    5 方法内重新赋值后的weight:89.5

    6

    7 方法执行后的age:25

    8 方法执行后的weight:77.5

    从上面的打印结果可以看到:

    a和w作为实参传入valueCrossTest之后,无论在方法内做了什么操作,最终a和w都没变化。

    这是什么原因呢?!!

    下面我们根据上面学到的知识点,进行详细的分析:

    首先程序运行时,调用mian()方法,此时JVM为main()方法往虚拟机栈中压入一个栈帧,即为当前栈帧,用来存放main()中的局部变量表(包括参数)、操作栈、方法出口等信息,如a和w都是main()方法中的局部变量,因此可以断定,a和w是躺在main方法所在的栈帧中

    如图:

    d45ec9c951fdfd87576cdd90cf8c5709.png

    而当执行到valueCrossTest()方法时,JVM也为其往虚拟机栈中压入一个栈,即为当前栈帧,用来存放valueCrossTest()中的局部变量等信息,因此age和weight是躺在valueCrossTest方法所在的栈帧中,而他们的值是从a和w的值copy了一份副本而得,如图:

    579f135e6cd04e89854eabb7a74c2f1a.png

    因而可以a和age、w和weight对应的内容是不一致的,所以当在方法内重新赋值时,实际流程如图:

    63bffa14a36db9c5952b7c840c9e51b3.png

    也就是说,age和weight的改动,只是改变了当前栈帧(valueCrossTest方法所在栈帧)里的内容,当方法执行结束之后,这些局部变量都会被销毁,mian方法所在栈帧重新回到栈顶,成为当前栈帧,再次输出a和w时,依然是初始化时的内容。因此:值传递传递的是真实内容的一个副本,对副本的操作不影响原内容,也就是形参怎么变化,不会影响实参对应的内容。

    引用传递:

    ”引用”也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向同一块内存地址,对形参的操作会影响的真实内容。

    举个例子:

    先定义一个对象:

    1 public classPerson {2         privateString name;3         private intage;4         publicString getName() {5             returnname;6 }7         public voidsetName(String name) {8             this.name =name;9 }10         public intgetAge() {11             returnage;12 }13         public void setAge(intage) {14             this.age =age;15 }16 }

    写个函数测试一下:

    public static void PersonCrossTest(Person person){

    System.out.println("传入的person的name:"+person.getName());

    person.setName("我是张小龙");

    System.out.println("方法内重新赋值后的name:"+person.getName());

    }//测试

    public static voidmain(String[] args) {

    Person p=newPerson();

    p.setName("我是马化腾");

    p.setAge(45);

    PersonCrossTest(p);

    System.out.println("方法执行后的name:"+p.getName());

    }

    输出结果:

    1 传入的person的name:我是马化腾2 方法内重新赋值后的name:我是张小龙3 方法执行后的name:我是张小龙

    可以看出,person经过personCrossTest()方法的执行之后,内容发生了改变,这印证了上面所说的“引用传递”,对形参的操作,改变了实际对象的内容。

    那么,到这里就结题了吗?

    不是的,没那么简单,

    能看得到想要的效果

    是因为刚好选对了例子而已!!!

    下面我们对上面的例子稍作修改,加上一行代码:

    public static voidPersonCrossTest(Person person){

    System.out.println("传入的person的name:"+person.getName());

    person=new Person();//加多此行代码

    person.setName("我是张小龙");

    System.out.println("方法内重新赋值后的name:"+person.getName());

    }

    输出结果:

    1 传入的person的name:我是马化腾2 方法内重新赋值后的name:我是张小龙3 方法执行后的name:我是马化腾

    为什么这次的输出和上次的不一样了呢?

    看出什么问题了吗?

    按照上面讲到JVM内存模型可以知道,对象和数组是存储在Java堆区的,而且堆区是共享的,因此程序执行到main()方法中的下列代码时

    Person p=newPerson();

    p.setName("我是马化腾");

    p.setAge(45);

    PersonCrossTest(p);

    JVM会在堆内开辟一块内存,用来存储p对象的所有内容,同时在main()方法所在线程的栈区中创建一个引用p存储堆区中p对象的真实地址,如图:

    2860bc7b49fda9b8dc1a3ebe0c29280a.png

    当执行到PersonCrossTest()方法时,因为方法内有这么一行代码:

    JVM需要在堆内另外开辟一块内存来存储new Person(),假如地址为“xo3333”,那此时形参person指向了这个地址,假如真的是引用传递,那么由上面讲到:引用传递中形参实参指向同一个对象,形参的操作会改变实参对象的改变。

    可以推出:实参也应该指向了新创建的person对象的地址,所以在执行PersonCrossTest()结束之后,最终输出的应该是后面创建的对象内容。

    然而实际上,最终的输出结果却跟我们推测的不一样,最终输出的仍然是一开始创建的对象的内容。

    由此可见:引用传递,在Java中并不存在。

    但是有人会疑问:为什么第一个例子中,在方法内修改了形参的内容,会导致原始对象的内容发生改变呢?

    这是因为:无论是基本类型和是引用类型,在实参传入形参时,都是值传递,也就是说传递的都是一个副本,而不是内容本身。

    e90ead2307cc6229d074e1727b92c6d1.png

    由图可以看出,方法内的形参person和实参p并无实质关联,它只是由p处copy了一份指向对象的地址,此时:p和person都是指向同一个对象。

    因此在第一个例子中,对形参p的操作,会影响到实参对应的对象内容。而在第二个例子中,当执行到new Person()之后,JVM在堆内开辟一块空间存储新对象,并且把person改成指向新对象的地址,此时:

    p依旧是指向旧对象的地址,person指向新对象的地址。

    所以此时对person的操作,实际上是对新对象的操作,与实参p中对应的对象毫无关系。

    结语

    因此可见:在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递,或者说是副本传递。

    只是在传递过程中:

    如果是对基本数据类型的数据进行操作,由于原始内容和副本都是存储实际值,并且是在不同的栈区,因此形参的操作,不影响原始内容。

    如果是对引用类型的数据进行操作,分两种情况,一种是形参和实参保持指向同一个对象地址,则形参的操作,会影响实参指向的对象的内容。一种是形参被改动指向新的对象地址(如重新赋值引用),则形参的操作,不会影响实参指向的对象的内容。

    展开全文
  • 今天,在研究值传递引用传递的时候,突然发现了这个问题,以前都是理解值传递就是复制一个值过去,引用传递就是传地址,现在看来当时的理解真的是太片面了。简单理解一句话:java只有值传递。我们先来了解一下什么...
  • 答案:Java中只有值传递。针对值类型,传递的是实参的值,对于引用类型,传递的是引用值所存储的地址。基本概念1.形参与实参形式参数:在定义函数名和函数体时使用的参数,目的是用来接收调用该函数是传入的参数。...
  • 1 public classTempTest {2 private void test1(inta){3 //做点事情4 }5 public static voidmain(String[] args) {6 TempTest t =...//这里传递的参数a就是按值传递9 }10 }按值传递重要特点:传递的是值的拷贝,也就...
  • 首先要说明,什么是值传递引用传递...值传递没有什么好说的,(当然在总结的时候我会结合java的堆栈模型详细分析值传递引用传递。)如果引用传递没听懂,我们来看一个直观的程序就可以了。参数传递.png上图说明在...
  • Java中引用传递

    2021-02-27 11:26:54
    我觉得引用传递 真的很好理解,不知道为什么大家觉得这么难,你...一般发生在函数调用的时候,最明显的特征就是 函数参数3)如果引用传递 实用过程 ,函数没有返回值,这个叫真正的引用传递没有改变对象的真实...
  • 一直来觉得对值传递和地址传递了解...按照以前的理解,java中基本数据类型是值传递,对象是地址(引用)传递。给大家看个例子:public class ObjectTrans {public static void main(String[] args) {String name = "1...
  • 而引用类型的变量保存引用值,"引用值"指向内存空间的地址,代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的空间里。Java的内存空间主要包括5部分:栈区,堆区,静态变量或常量存放...
  • Java中的参数传递机制一直以来大家...这里的内存模型设计Java中的参数传递机制一直以来大家都争论不休,究竟是"值传递"还是"引用传递",争论的双方各执一词,互不相让。不但"菜鸟"们一头雾水,一些"老鸟"们也只知道...
  • 而引用类型的变量保存引用值,"引用值"指向内存空间的地址,代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。基本类型包括:byte,short,int,long,char,float,...
  • public classPeople {publicString getName(Human h){System.out.println("变更之前测试》》》》》name="+h...//值传递,这里的h的地址与主方法ha指向同一个地址h.name="lowi2";h.age=33;System.out.println("变更...
  • 首先,不要纠结于 Pass By Value 和 Pass By Reference 的字面上的意义,否则很容易陷入所谓的“一切传引用其实本质上是传值”这种并不能解决问题无意义论战。更何况,要想知道Java到底是传值还是传引用,起码你要...
  • 1:java中值传递引用传递,本质上是没有区别的,只是看的角度不同而已。 基本类型传递变量,都可以直接理解为值传递值传递引用传递:比如一个对象作为一个方法的参数传递,那么在这个传递的过程,传递...
  • 对于Array(它只不过是一个对象),数组引用是通过值传递的。(就像通过值传递对象引用一样)。当您将数组传递给其他方法时,实际上复制了对该数组的引用。通过该引用对数组内容的任何更改都将影响原始数组。但是,将...
  • java中值传递引用传递(java中只有值传递没有引用传递)
  • 今天,我在一本面试书上看到了关于java的一个参数...Java中只有按值传递没有引用传递!回家后我就迫不及待地查询了这个问题,觉得自己对java这么基础的问题都搞错实在太丢人!综合网上的描述,我大概了解了是...
  • 人总结过:对象是按引用传递的Java应用程序且仅的一种参数传递机制,即按值传递值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本按引用传递意味着当将一个参数传递给一个函数时,...
  • 从我实习面试开始就在面试遇到过这个问题:Java值传递还是引用传递?当时的我只会背背面试题,但是网上的答案有些还是错的,导致我决心写这一篇文章。虽然网上已经很多文章珠玉在前,但是我还是想写一篇我...
  • Java中没有指针,所以也没有引用传递了,仅仅有值传递不过可以通过对象的方式来实现引用传递 类似java没有多继承 但可以用多次implements 接口实现多继承的功能值传递:方法调用时,实际参数把它的值传递给对应的...
  • Java中是否有引用传递

    2021-02-26 17:28:49
    前言总所周知,C++中有值传递、指针传递、引用传递三种参数传递类型。那么我们就来探究Java的传递类型。我们先从c++的值传递引用传递入手分析:值传递:调用时,将实参的值传递给对应的形参,即为值传递。由于形参...
  • 1.什么叫做值传递引用传递 值传递是指在调用函数时将实际参数复制一份传递到函数,这样在函数如果对参数进行修改,将不会影响到实际参数。 引用传递是指在调用函数时将实际参数的地址传递到函数,那么在...
  • java值传递引用传递

    2021-04-12 15:07:56
    java值传递引用传递1、对象是按引用传递的---带"="号的赋值操作2、Java 应用程序且仅的一种参数传递机制,即按值传递---由"函数调用"引起的3、按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始...
  • http://464772913.iteye.com/blog/1483163在谈java值传递引用传递之前必须搞清楚"形式参数"和"实际参数"两个概念在一个方法,比如method1(Object o),O就是形参,形参就是形式参数,当你调用这个方法时,要传入一个...
  • 有人说java只有值传递,也有人说java既有值传递也有引用传递,那么java中到底有没有引用传递呢,下面我来分析一下。45 一、首先来明确一下"值传递"和"引用传递的"区别67 值传递:是对所传递参数进行一次副本拷贝...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 712,667
精华内容 285,066
关键字:

java中有没有引用传递

java 订阅