java漫谈-Java只有值传递

本文首发WindCoder:java漫谈-Java只有值传递

《Head First Java》中关于 Java 参数传递的说明:

Java 中所传递的所有东西都是值,但此值是变量所携带的值。引用对象的变量所携带的是远程控制而不是对象本身,若你对方法传入参数,实际上传入的是远程控制的拷贝

《Java编程思想 第四版》中第二章第一节“用引用操作对象”中写到:

尽管一切都看作对象,但操作的标识符实际上是一个对象的引用(reference)。

文中用遥控器(引用)操作电视(对象)为例形象的说明了该引用名词的含义,同时在对定义的“引用”该名词的注释中提到:

有人认为:“很明显,它是一个指针。”但是这种说法是基于底层实现的某种假设。并且,Java中的引用,在语法上更接近C++的引用而不是指针

还是有很多人不同意用“引用”这个术语。我曾读到的一本书中这样说:“Java所支持的‘按址传递’是完全错误的”,因为Java对象标识符(按那位作者所说)实际上是“对象引用”。并且他接着说任何事物都是“按值传递”的。也许有人会赞成这种精准却让人费解的解释,但我认为我的这种方法可以简化概念上的理解并且不会伤害到任何事物。

对于基本类型(int等)没用争议,肯定是值传递。但String、基本类型的封装类(Integer等)、自定义类(如User等)传递的是一个地址,这就容易让人联想到C++中的指针传递和引用传递。以一个User类为例:

public class User {
    private String name;
    private int age;
    private Integer height;

    public User(String name, int age, Integer height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    // ......

    // 省略了无参构造函数、所有set/get函数。

    // ......

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height+'\'' +
                ", address=" + super.toString() +
                '}';
    }
}

示例1:

public class MainDemo {
    public static void main(String[] args) {
        User a = new User("a",10,10);
        User b = new User("b",11, 11);
        PrintUtill.println("交换前:");
        PrintUtill.println("a: "+a);
        PrintUtill.println("b: "+b);
        PrintUtill.printlnRule();
        swap(a,b);
        PrintUtill.println("交换最后:");
        PrintUtill.println("a: "+a);
        PrintUtill.println("b: "+b);
        PrintUtill.printlnRule();
        change(a);
        PrintUtill.println("修改最后:");
        PrintUtill.println("a: "+a);
    }

    public static void swap(Object sa, Object sb){
        Object sc = sa;
        sa = sb;
        sb = sc;
        PrintUtill.println("交换中:");
        PrintUtill.println("sa: " + sa);
        PrintUtill.println("sb: " + sb);
        PrintUtill.printlnRule();
    }


}

结果

交换前:
a: User{name='a', age=10, height=10', address=Others.base.common.User@2503dbd3}
b: User{name='b', age=11, height=11', address=Others.base.common.User@4b67cf4d}

--------windcoder.com----------

交换中:
sa: User{name='b', age=11, height=11', address=Others.base.common.User@4b67cf4d}
sb: User{name='a', age=10, height=10', address=Others.base.common.User@2503dbd3}

--------windcoder.com----------

交换最后:
a: User{name='a', age=10, height=10', address=Others.base.common.User@2503dbd3}
b: User{name='b', age=11, height=11', address=Others.base.common.User@4b67cf4d}

--------windcoder.com----------

不管是断点跟踪还是最终打印出的结果,swap方法中确实做了交换(sa和sb的地址做了交换),但并未对方法外有任何影响(a和b的地址指向依旧是原来的未变)。

每个方法的运行都会在Java虚拟机栈中创建一个栈帧,里面存放了局部变量表等内容,方法的运行就是一个栈帧进栈出栈的过程。

如方法swap:

public static void swap(Object sa, Object sb)

此时的sa,sb属于形参,就是形式参数,用于定义方法的时候使用的参数,用来接收调用者传递的参数。形参只有在方法被调用的时候,虚拟机才会分配内存单元,在方法调用结束之后便会释放所分配的内存单元。

相关知识:
JVM-Java内存区域
JVM基础小结

换种说法,当调用swap方法时,sa类似C++中的引用,成为地址2503dbd3的一个别名,亦既上面关于《思想》中的引用说的更接近引用而不是指针,不同之处是一旦执行sa = sb;,改变的仅是是sa的指向,对原地址2503dbd3不会造成任何影响。一旦方法执行完,sa被分配到内存单元便会被释放。该实例执行如图:

image.png

示例2:

在示例1中追加代码后如下:

public class MainDemo {
    public static void main(String[] args) {
        User a = new User("a",10,10);
        User b = new User("b",11, 11);
        // ......
        // 省略示例1中的代码
        // ......
        change(a);
        PrintUtill.println("修改最后:");
        PrintUtill.println("a: "+a);
    }
    public static void swap(Object sa, Object sb){
        
        // ...省略
        
    }
    public static void change(User sa){
        sa.setName("a2");
        sa.setAge(11);
        sa.setHeight(12);
        PrintUtill.println("修改中:");
        PrintUtill.println("sa: " + sa);
    }
}

结果

// ......
// 省略之前的

交换最后:
a: User{name='a', age=10, height=10', address=Others.base.common.User@2503dbd3}
b: User{name='b', age=11, height=11', address=Others.base.common.User@4b67cf4d}

--------windcoder.com----------

修改中:
sa: User{name='a2', age=11, height=12', address=Others.base.common.User@2503dbd3}
修改最后:
a: User{name='a2', age=11, height=12', address=Others.base.common.User@2503dbd3}

此时函数内发生了变化影响到函数外的数据,change方法外的a和方法的参数sa指向的均是地址2503dbd3,指向未发生变化,但函数中的操作导致所指向的地址2503dbd3中的内容发生了变化,

示例3

public class ListDemo {
    public static void main(String[] args) {
        List<String> list = null;
        // List<String>  list = new ArrayList<String>();
        add(list);
        list.add("3");
        list.add("4");
        PrintUtill.println("list.size:" + list.size());
    }

    public static void add(List<String> list){
        if (list==null){
            list = new ArrayList<String>();
        }
        list.add("1");
        list.add("2");
    }
}

该示例中list最开始为null,意味着没有地址,当作为实参传进add方法中,add.list没有地址,从而执行了list = new ArrayList<String>();,add.list被分配了新的地址,当执行完add方法,add.list就会被释放,但main.list仍旧为空,从而导致NullPointerException(空指针异常)。

扩展

C++中函数参数的几种类型:

image.png

参考地址

Java 中的参数传递和引用类型

C++ 函数

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,093评论 1 32
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,414评论 1 14
  • 一:java概述: 1,JDK:Java Development Kit,java的开发和运行环境,java的开发...
    慕容小伟阅读 1,778评论 0 10
  • 整理来自互联网 1,JDK:Java Development Kit,java的开发和运行环境,java的开发工具...
    Ncompass阅读 1,537评论 0 6
  • 01 上周日的晚上,当上三年级的儿子把数学考试卷拿给家长签字的时候,我情绪失控了。我看到考试卷上赫然写着一个红红的...
    蓝莓书阅读 390评论 0 1