深入理解Java引用类型

深入理解Java引用类型

  在Java中类型可分为两大类:值类型与引用类型。值类型就是基本数据类型(如int ,double 等),而引用类型,是指除了基本的变量类型之外的所有类型(如通过 class 定义的类型)。所有的类型在内存中都会分配一定的存储空间(形参在使用的时候也会分配存储空间,方法调用完成之后,这块存储空间自动消失), 基本的变量类型只有一块存储空间(分配在stack中), 而引用类型有两块存储空间(一块在stack中,一块在heap中),在函数调用时Java是传值还是传引用,这个估计很多人至今都很糊涂,下面用图形与代码来解释:

  在上图中引用类型在传参时不是在heap中再分配一块内存来存变量c 所指向的A(),而是让a 指向同一个A 的实例,这就与C++ 中的指针一样,先声明指针变量a,b,c,d 在传参的时候让a 指向c所指向的内存,让 d 指向 b 所指向的内存。很明显Java中的引用与C++中的指针在原理上是相类似的,但记住Java没有指针,只有引用。下面再通过一些具体的代码来讨论引用:

1. 简单类型是按值传递的

Java 方法的参数是简单类型的时候,是按值传递的 (pass by value)。这一点我们可以通过一个简单的例子来说明:

package test;

public class Test {

//交换两个变量的值

public static void Swap(int a,int b){

int c=a;

a=b;

b=c;

System.out.println("a: "+a);

System.out.println("b: "+b);

}

public static void main(String[] args){

int c=10;

int d=20;

Swap(c,d);

System.out.println("After Swap:");

System.out.println("c: "+d);

System.out.println("d: "+c);

}

}

运行结果:

a: 20

b: 10

After Swap:

c: 20

d: 10

  不难看出,虽然在 Swap (a,b) 方法中改变了传进来的参数的值,但对这个参数源变量本身并没有影响,即对 main(String[]) 方法里的 a,b 变量没有影响。那说明,参数类型是简单类型的时候,是按值传递的。以参数形式传递简单类型的变量时,实际上是将参数的值作了一个拷贝传进方法函数的,那么在方法函数里再怎么改变其值,其结果都是只改变了拷贝的值,而不是源值。

2. 什么是引用

  Java 是传值还是传引用,问题主要出在对象的传递上,因为 Java 中简单类型没有引用。既然争论中提到了引用这个东西,为了搞清楚这个问题,我们必须要知道引用是什么。

  简单的说,引用其实就像是一个对象的名字或者别名 (alias),一个对象在内存中会请求一块空间来保存数据,根据对象的大小,它可能需要占用的空间大小也不等。访问对象的时候,我们不会直接是访问对象在内存中的数据,而是通过引用去访问。引用也是一种数据类型,我们可以把它想象为类似 C++ 语言中指针的东西,它指示了对象在内存中的地址——只不过我们不能够观察到这个地址究竟是什么。

如果我们定义了不止一个引用指向同一个对象,那么这些引用是不相同的,因为引用也是一种数据类型,需要一定的内存空间(stack,栈空间)来保存。但是它们的值是相同的,都指示同一个对象在内存(heap,堆空间)的中位置。比如:

String a="This is a Text!";

String b=a;

  通过上面的代码和图形示例不难看出,a 和 b 是不同的两个引用,我们使用了两个定义语句来定义它们。但它们的值是一样的,都指向同一个对象 "This is a Text!"。但要注意String 对象的值本身是不可更改的 (像 b = "World"; b = a; 这种情况不是改变了 "World" 这一对象的值,而是改变了它的引用 b 的值使之指向了另一个 String 对象 a)

  如图,开始b 的值为绿线所指向的“Word Two”,然后 b=a; 使 b 指向了红线所指向的”Word“.

这里我描述了两个要点:

(1) 引用是一种数据类型(保存在stack中),保存了对象在内存(heap,堆空间)中的地址,这种类型即不是我们平时所说的简单数据类型也不是类实例(对象);

(2) 不同的引用可能指向同一个对象,换句话说,一个对象可以有多个引用,即该类类型的变量。

 

3. 对象是如何传递的呢

  随着学习的深入,你也许会对对象的传递方式产生疑问,即对象究竟是“按值传递”还是“按引用传递”?

(1)认为是“按值传递”的:

package test;

public class Test {

public static void Sample(int a){

a+=20;

System.out.println("a: "+a);

}

public static void main(String[] args){

int b=10;

Sample(b);

System.out.println("b: "+b);

}

}

运行结果:

a: 30

b: 10

在这段代码里,修改变量 a 的值,不改变变量 b 的值,所以它是“值传递”。

(2)认为是“按引用传递”的:

package test;

public class Test {

public static void Sample(StringBuffer a){

a.append(" Changed ");

System.out.println("a: "+a);

}

public static void main(String[] args){

StringBuffer b=new StringBuffer("This is a test!");

Sample(b);

System.out.println("b: "+b);

}

}

运行结果:

a: This is a test! Changed

b: This is a test! Changed

  在Sample(StringBuffer)这个函数中,修改了引用 a 的值,同时 b 的值也变化了,所以它是“按引用传递”的!

  那么对象(记住在Java中一切皆对象,无论是int a;还是String a;,这两个变量a都是对象)在传递的时候究竟是按什么方式传递的呢?其答案就只能是:即是按值传递也是按引用传递,但通常基本数据类型(如int,double等)我们认为其是“值传递”,而自定义数据类型(class)我们认为其是“引用传递”。

 

4. 正确看待传值还是传引用的问题

  要正确的看待这个问题必须要搞清楚为什么会有这样一个问题。

  实际上,问题来源于 C,而不是 Java。

  C 语言中有一种数据类型叫做指针,于是将一个数据作为参数传递给某个函数的时候,就有两种方式:传值,或是传指针。 在值传递时,修改函数中的变量值不会改变原有变量的值,但是通过指针却会改变。

void Swap(int a,int b){ int c=a;a=b;b=c;}

void Swap(int *a,int *b){ int c=*a;*a=*b;*b=c; }

int c=10;

int d=20;

Swap(c,d);    //不改变 c , d 的值

Swap(&c,&d);  //改变 c , d 的值

  许多的 C 程序员开始转向学习 Java,他们发现,使用类似 SwapValue(T,T)(当T 为值类型时) 的方法仍然不能改变通过参数传递进来的简单数据类型的值,但是如果T时一个引用类型时,则可能将其成员随意更改。于是他们觉得这很像是 C 语言中传值/传指针的问题。但是 Java 中没有指针,那么这个问题就演变成了传值/传引用的问题。可惜将这个问题放在 Java 中进行讨论并不恰当。

  讨论这样一个问题的最终目的只是为了搞清楚何种情况才能在方法函数中方便的更改参数的值并使之长期有效。

5. 如何实现类似 swap 的方法

  传值还是传引用的问题,到此已经算是解决了,但是我们仍然不能解决这样一个问题:如果我有两个 int型的变量 a 和 b,我想写一个方法来交换它们的值,应该怎么办?有很多方法,这里介绍一种简单的方法:

package test;

public class Test {

public static void Swap(int[] a){

int c=a[0];

a[0]=a[1];

a[1]=c;

}

public static void main(String[] args){

int[] a=new int[2];

a[0]=10;

a[1]=20;

Swap(a);

System.out.println(a[0]);

System.out.println(a[1]);

}

}

  通过数组可以方便的实现值类型的数据源的交换,不过还有一种方法是将所有变量封装到一个类里面去,通过引用类型来实现。

时间: 2024-12-26 17:17:39

深入理解Java引用类型的相关文章

Java引用类型详解

JVM  的垃圾回收器对于不同类型的引用有不同的处理方式.java中对于一个对象来说,只要有引用的存在,它就会一直存在于内存中.如果这样的对象越来越多,超出了JVM中的内存总数,JVM就会抛出OutOfMemory错误.虽然垃圾回收的具体运行是由JVM来控制的,但是开发人员仍然可以在一定程度上与垃圾回收器进行交互,其目的在于更好的帮助垃圾回收器管理好应用的内存.这种交互方式就是使用JDK1.2 引入的  java.lang.ref包. 强引用(strong reference) 在一般的 Jav

深入理解java虚拟机笔记(一)-java内存区域与内存溢出

1. 前言 这是深入理解java虚拟机一书的笔记,来自第二章.因为这本书讲的比较深奥,这是第二次看,需要记录一下笔记. 2. 运行时数据区域 java虚拟机所管理的内存分为以下几个区域. ps:图片来自网络 2.1 程序计数器 程序计数器是一块较小的内存空间,他可以看做是当前线程所执行字节码的行号指示器.字节码解释器工作时就是通过改变这个计数器的值来选去下一条要执行的字节码指令,分之.循环.跳转.异常处理.线程恢复等基础功能都需要依赖这个计数器来完成. 这块内存是线程私有的内存. 如果线程在执行

《深入理解java虚拟机》:类的初始化

深入理解java虚拟机>:类的初始化 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.其中验证.准备.解析3个部分统称为连接.类加载的过程包括了加载.验证.准备.解析.初始化五个阶段. 加载.验证.准备.初始化和卸载这5个阶段的顺序时确定的,类的加载过程必须按照这种顺序按部就班的开始,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定).另外注意这里的

《深入理解Java虚拟机 JVM高级特性...》核心笔记

深入理解Java虚拟机 JVM高级特性与最佳实践(第二版) 核心笔记 JAVA 环境: JAVA虚拟机高级特性: 一:java内存区域与内存异常 一):运行数据区     1:程序计数器(Program Counter Register),也称"PC寄存器" A:用来指示需要执行哪条指令的.(在汇编语言中,CPU在得到指令之后,程序计数器便自动加1或者根据                    转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令.) B:由于在JVM中,多线程

深刻理解Java中形参与实参,引用与对象的关系

声明:本博客为原创博客,未经允许,不得转载!原文链接为http://blog.csdn.net/bettarwang/article/details/30989755 我们都知道,在Java中,除了基本数据类型之外,其他的都是引用类型,当它们作为函数参数时,传递的也是引用,通过引用可以改变对象的值,很多人便因此而忽略形参与实参,引用与对象的关系问题.废话不多说,先看下面一个例子: import java.util.*; public class Student { private String

深入理解java内存模型

深入理解Java内存模型(一)——基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信. 同步是指程序用于控制不同线程之

java 引用类型及作用

java 引用类型 0. 引言 Java 中一共有 4 种类型的引用 : StrongReference. SoftReference. WeakReference 以及 PhantomReference , 这 4 种类型的引用与 GC 有着密切的关系. 1. 强引用 (StrongReference) 普通的引用做法,如: String str = "hello"; 只要引用存在就不会被回收,除非手动置为null,或者超出范围,gc才会回收 2. 软引用 (SoftReferenc

[转]理解java的三大特性之多态

java提高篇(四)-----理解java的三大特性之多态 面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承是为了重用父类代码.两个类若存在IS-A的关系就可以使用继承.,同时继承也为实现多态做了铺垫.那么什么是多态呢?多态的实现机制又是什么?请看我一一为你揭开: 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在

(3) 深入理解Java Class文件格式(二)

好文转载:http://blog.csdn.net/zhangjg_blog/article/details/21487287 在上一篇文章 深入理解Java Class文件格式(一) 中, 介绍了class文件在整个Java体系结构中的位置和作用, 并对class文件的整体格式做了说明, 介绍了其中的魔数和版本号的相关内容, 并对常量池做了概述. 在本文章, 继续介绍class文件中的其他内容. class文件中的特殊字符串 首先说明一下, 所谓的特殊字符串出现在class文件中的常量池中,