[转载]java 值传递 引用传递

本文通过具体示例深入探讨了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);  
    }  

这段代码的运行结果如下:

参数--基本类型
原有的值:0
赋值之后:0
运算之后:0

参数--引用类型
原有的值:0
赋引用后:0
改属性后:20

从上面这个直观的结果中我们很容易得出如下结论:

对于基本类型,在方法体内对方法参数进行重新赋值,并不会改变原有变量的值。
对于引用类型,在方法体内对方法参数进行重新赋予引用,并不会改变原有变量所持有的引用。
方法体内对参数进行运算,不影响原有变量的值。
方法体内对参数所指向对象的属性进行运算,将改变原有变量所指向对象的属性值。

上面总结出来的不过是我们所看到的表面现象。那么,为什么会出现这样的现象呢?这就要说到值传递和引用传递的概念了。这个问题向来是颇有争议的。

大家都知道,在JAVA中变量有以下两种:

基本类型变量,包括char、byte、short、int、long、float、double、boolean。
引用类型变量,包括类、接口、数组(基本类型数组和对象数组)。

当基本类型的变量被当作参数传递给方法时,JAVA虚拟机所做的工作是把这个值拷贝了一份,然后把拷贝后的值传递到了方法的内部。因此在上面的例子中,我们回头来看看这个方法:
Java代码 
// 为方法参数重新赋值   
public   void  change( int  i) {  
    i = 5 ;  
}  
Java代码
// 为方法参数重新赋值  
public void change(int i) {  
    i = 5;  

 // 为方法参数重新赋值
 public void change(int i) {
  i = 5;
 }

在这个方法被调用时,变量i和ParamTest型对象t的属性num具有相同的值,却是两个不同变量。变量i是由JAVA虚拟机创建的作用域在 change(int i)方法内的局部变量,在这个方法执行完毕后,它的生命周期就结束了。在JAVA虚拟机中,它们是以类似如下的方式存储的:

 

很明显,在基本类型被作为参数传递给方式时,是值传递,在整个过程中根本没有牵扯到引用这个概念。这也是大家所公认的。对于布尔型变量当然也是如此,请看下面的例子:
Java代码 
public   class  BooleanTest {  
    // 布尔型值   
    boolean  bool =  true ;  
  
    // 为布尔型参数重新赋值   
    public   void  change( boolean  b) {  
        b = false ;  
    }  
  
    // 对布尔型参数进行运算   
    public   void  calculate( boolean  b) {  
        b = b && false ;  
        // 为了方便对比,将运算结果输出   
        System.out.println("b运算后的值:"  + b);  
    }  
  
    public   static   void  main(String[] args) {  
        BooleanTest t = new  BooleanTest();  
  
        System.out.println("参数--布尔型" );  
        System.out.println("原有的值:"  + t.bool);  
        // 为布尔型参数重新赋值   
        t.change(t.bool);  
        System.out.println("赋值之后:"  + t.bool);  
  
        // 改变布尔型参数的值   
        t.calculate(t.bool);  
        System.out.println("运算之后:"  + t.bool);  
    }  
}  
Java代码
public class BooleanTest {  
    // 布尔型值  
    boolean bool = true;  
 
    // 为布尔型参数重新赋值  
    public void change(boolean b) {  
        b = false;  
    }  
 
    // 对布尔型参数进行运算  
    public void calculate(boolean b) {  
        b = b && false;  
        // 为了方便对比,将运算结果输出  
        System.out.println("b运算后的值:" + b);  
    }  
 
    public static void main(String[] args) {  
        BooleanTest t = new BooleanTest();  
 
        System.out.println("参数--布尔型");  
        System.out.println("原有的值:" + t.bool);  
        // 为布尔型参数重新赋值  
        t.change(t.bool);  
        System.out.println("赋值之后:" + t.bool);  
 
        // 改变布尔型参数的值  
        t.calculate(t.bool);  
        System.out.println("运算之后:" + t.bool);  
    }  

输出结果如下:

参数--布尔型
原有的值:true
赋值之后:true
b运算后的值:false
运算之后:true

那么当引用型变量被当作参数传递给方法时JAVA虚拟机又是怎样处理的呢?同样,它会拷贝一份这个变量所持有的引用,然后把它传递给JAVA虚拟机为方法创建的局部变量,从而这两个变量指向了同一个对象。在篇首所举的示例中,ParamTest类型变量t和局部变量pt在JAVA虚拟机中是以如下的方式存储的:

 

有一种说法是当一个对象或引用类型变量被当作参数传递时,也是值传递,这个值就是对象的引用,因此JAVA中只有值传递,没有引用传递。还有一种说法是引用可以看作是对象的别名,当对象被当作参数传递给方法时,传递的是对象的引用,因此是引用传递。这两种观点各有支持者,但是前一种观点被绝大多数人所接受,其中有《Core Java》一书的作者,以及JAVA的创造者James Gosling,而《Thinking in Java》一书的作者Bruce Eckel则站在了中立的立场上。

我个人认为值传递中的值指的是基本类型的数值,即使对于布尔型,虽然它的表现形式为true和false,但是在栈中,它仍然是以数值形式保存的,即0表示false,其它数值表示true。而引用是我们用来操作对象的工具,它包含了对象在堆中保存地址的信息。即使在被作为参数传递给方法时,实际上传递的是它的拷贝,但那仍是引用。因此,用引用传递来区别与值传递,概念上更加清晰。

最后我们得出如下的结论:

基本类型和基本类型变量被当作参数传递给方法时,是值传递。在方法实体中,无法给原变量重新赋值,也无法改变它的值。
对象和引用型变量被当作参数传递给方法时,在方法实体中,无法给原变量重新赋值,但是可以改变它所指向对象的属性。至于到底它是值传递还是引用传递,这并不重要,重要的是我们要清楚当一个引用被作为参数传递给一个方法时,在这个方法体内会发生什么。

Java 的学习与开发过程中,总有一些概念容易让人混淆,从而在编程时出现各种问题。今天,我们就来深入剖析几对极易混淆的概念,通过代码示例与图示,帮助大家彻底理清它们之间的区别与联系。 目录 一、线程与进程 1.1 概念 1.2 区别 1.3 代码示例 二、值传递引用传递 2.1 概念 2.2 区别 三、接口与抽象类 3.1 概念 3.2 区别 3.3 代码示例 总结 一、线程与进程 1.1 概念 进程:是计算机中已运行的程序的实体,是系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间、系统资源等,进程之间相互隔离,安全性高。例如,当我们同时打开浏览器、音乐播放器和文档编辑器时,它们分别对应一个进程,彼此互不干扰。 线程:是进程中的一个执行单元,是程序执行的最小单位。一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间、文件句柄等。线程之间的切换和通信开销相对较小,能提高程序的执行效率。 1.2 区别 我们可以通过一张表格直观地看出线程与进程的区别: 对比项 进程 线程 资源分配 独立分配资源,有独立内存空间 共享进程资源 执行开销 创建、撤销、切换开销大 创建、撤销、切换开销小 并发性能 进程间并发,开销大 线程间并发,开销小 安全性 进程间相互隔离,安全性高 共享资源,需考虑同步问题 1.3 代码示例 下面是一个简单的 Java 多线程示例,通过创建线程类并启动线程,展示线程的使用: class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " 正在执行:" + i); } } } public class ThreadAndProcess { public static void main(String[] args) { MyThread thread1 = new MyThread(); MyThread thread2 = new MyThread(); thread1.start(); thread2.start(); } } AI写代码 java 运行 在上述代码中,我们创建了MyThread类继承自Thread类,重写run方法定义线程执行的逻辑。在main方法中创建两个线程对象并启动,此时两个线程会并发执行run方法中的代码。 二、值传递引用传递 2.1 概念 值传递:是指在方法调用时,实际参数将其值复制一份传递给形式参数,在方法内部对形式参数的修改不会影响到实际参数的值。简单来说,就是传递的是数据的副本。 引用传递:在方法调用时,实际参数将其引用(内存地址)传递给形式参数,形式参数和实际参数指向同一内存地址,在方法内部对形式参数的修改会直接影响到实际参数。 2.2 区别 在 Java 中,只有值传递。对于基本数据类型,传递的是值本身;对于引用数据类型,传递的是对象的引用(地址值)。虽然看起来像是引用传递,但本质上还是值传递,因为传递的是引用的副本。我们通过代码来验证: public class PassByValue { public static void changeInt(int num) { num = 100; } public static void changeArray(int[] arr) { arr[0] = 100; } public static void main(String[] args) { int num = 10; changeInt(num); System.out.println("修改后的num:" + num); int[] arr = {10}; changeArray(arr); System.out.println("修改后的arr[0]:" + arr[0]); } } AI写代码 java 运行 在上述代码中,changeInt方法接收一个基本数据类型int的参数,在方法内部修改参数值后,不会影响到main方法中num的值。而changeArray方法接收一个引用数据类型int[]的参数,在方法内部修改数组元素的值,会影响到main方法中arr数组对应元素的值。这是因为传递的是数组的引用副本,通过该引用副本依然可以访问和修改原数组。 三、接口与抽象类 3.1 概念 抽象类:使用abstract关键字修饰的类,抽象类中可以包含抽象方法(只有方法声明,没有方法体)和非抽象方法。抽象类不能被实例化,只能被继承,子类必须实现抽象类中的抽象方法(除非子类也是抽象类)。抽象类通常用于抽取相关类的共性,作为父类来定义一些通用的行为和属性。 接口:使用interface关键字定义,接口中的方法默认是public abstract的(可以省略不写),属性默认是public static final的常量。接口不能被实例化,类通过implements关键字实现接口,一个类可以实现多个接口。接口用于定义一种规范,实现接口的类必须实现接口中定义的所有方法。 3.2 区别 同样,我们通过表格来对比接口与抽象类: 对比项 抽象类 接口 定义方式 使用abstract class定义 使用interface定义 继承 / 实现 子类通过extends继承抽象类 类通过implements实现接口 方法 可以包含抽象方法和非抽象方法 只能包含抽象方法(JDK 8 后可添加默认方法和静态方法) 属性 可以包含各种类型的属性 只能包含public static final常量 多继承 一个类只能继承一个抽象类 一个类可以实现多个接口 3.3 代码示例 先看抽象类的示例: abstract class Animal { protected String name; public Animal(String name) { this.name = name; } public abstract void speak(); } class Dog extends Animal { public Dog(String name) { super(name); } @Override public void speak() { System.out.println(name + " 汪汪叫"); } } AI写代码 java 运行 在上述代码中,Animal是抽象类,包含一个抽象方法speak。Dog类继承自Animal类,并实现了speak方法。 再看接口的示例: interface Flyable { void fly(); } class Bird implements Flyable { @Override public void fly() { System.out.println("鸟儿在飞翔"); } } AI写代码 java 运行 总结 这里定义了Flyable接口,包含fly抽象方法,Bird类实现了Flyable接口并实现了fly方法。 通过以上对线程与进程、值传递引用传递、接口与抽象类的深入剖析和对比,我们可以更清晰地理解它们之间的区别与联系。 在学习 Java 编程过程中,理解这些基础概念至关重要。线程与进程是操作系统中的重要概念,而值传递引用传递则涉及到方法调用时参数传递的机制,对于理解 Java 中的数据传递有着重要意义。同时,接口与抽象类是面向对象编程中的重要组成部分,能够帮助我们更好地设计和组织代码。 通过深入研究这些概念,结合代码示例和区别对比,我们可以更好地掌握 Java 编程中的关键知识点,避免混淆和错误的应用,提高编程效率和质量。希望以上内容能够帮助您更好地理解和运用这些概念,在 Java 学习与开发中取得更好的成果。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.youkuaiyun.com/2401_88952086/article/details/148977946补充说明
最新发布
06-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值