java 深入理解引用类型
关于深入理解java的引用类型:
在java中,引用类型可以分为两大类:值类型,引用类型。
其中值类型就是基本数据类型,如int,double类型,而引用类型就是除了基本数据类型之外的所有类型(如class类型),所有的类型在内存中都会分匹配
一定的空间,包括形参,而形参在方法调用完成后被分配的那块内存就会被取消,基本的变量类型的储存空间被分配到栈中,而引用类型有两块储存空间,
一块在栈中,一块在堆中,那么在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: "+c); System.out.println("d: "+d); } }
运行结果:
a: 20
b: 10
After Swap:
c: 10
d: 20
传值后数值交换失败,主方法打印数组中0,1元素数值后并没有改变
原因:
不难看出,虽然在 Swap (a,b) 方法中改变了传进来的参数的值,但对这个参数源变量本身并没有影响,即对 main(String[]) 方法里的 a,b 变量没有影响。那说明,参数类型是简单类型的时候,是按值传递的。以参数形式传递简单类型的变量时,实际上是将参数的值作了一个拷贝传进方法函数的,那么在方法函数里再怎么改变其值,其结果都是只改变了拷贝的值,而不是源值。
public class Main{
public static void main(String[] args) {
int a = 10;
int b = 20;
// int tmp = 0;
// tmp = a;
// a = b;
// b = tmp;
// System.out.println("a:"+a+" b:"+b);
swap(a,b);
System.out.println("a:"+a+" b:"+b); }
public static void swap(int a,int b){
int tmp = 0;
tmp = a;
a = b;
b = tmp;
// System.out.println("a:"+a+" b:"+b); }
}
换成String类型也一样,交换只在主函数中或者方法中生效
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)认为是“按引用传递”的:
public class Main{
public static void main(String[] args) {
String a = new String("hello"); //结果是hello world hello
// StringBuffer a = new StringBuffer("hello");//结果是hello world hello world
swap(a);
System.out.println(a); }
public static void swap(String a){
a+=" world";
// a.append(" world");
System.out.println(a); }
}
那么对象(记住在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,我想写一个方法来交换它们的值,应该怎么办?有很多方法,这里介绍一种简单的方法(也可以在主函数中直接写交换):
通过数组交换数值
class Test { public static void swap(int[] d) {
int c = d[0];
d[0] = d[1];
d[1] = c;
System.out.println(d[0]);
System.out.println(d[1]); }
public static void main(String[] args) {
int[] a = new int[2];
a[0] = 10;
a[1] = 20;
swap(a);
System.out.println("---------------");
System.out.println(a[0]);
System.out.println(a[1]);
//下面的代码时编译不过的,形参中单东西在形参所在函数执行完毕后,便被java垃圾内存回收站回收了
//此代码所做的事情,是通过上面形参函数交换实参里面数组内的两个值。
// System.out.println(d[0]);
//System.out.println(d[1]); }
}
public class Test { public static void main(String[] args) { int [] arr={1,2,3,4,5}; System.out.println("交换值之前:"+Arrays.toString(arr)); change(arr, 1,3); System.out.println("交换值之后:"+Arrays.toString(arr)); } public static void change(int []arr,int i,int j){ int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } }
交换后结果:1 2 3 4 5
1 4 3 2 5
通过:对象的值的交换:
class Te{
int x=10; public Te(int x) {
this.x = x;
} }
public class Test { public static void main(String[] args) {
Te test=new Te(1);
System.out.println("交换值之前:"+test.x);
change(test, 4);
System.out.println("交换值之后:"+test.x);
}
public static void change(Te test,int x){
test.x=x;
}
}
执行结果:
public class Main{
public static void main(String[] args) {
String a = new String("hello");
// StringBuffer a = new StringBuffer("hello");
swap(a);
System.out.println(a); }
public static void swap(String a){
a+=" world";
// a.append(" world");
System.out.println(a); }
}
java 深入理解引用类型的更多相关文章
- Effective Java通俗理解(持续更新)
这篇博客是Java经典书籍<Effective Java(第二版)>的读书笔记,此书共有78条关于编写高质量Java代码的建议,我会试着逐一对其进行更为通俗易懂地讲解,故此篇博客的更新大约 ...
- Effective Java通俗理解(下)
Effective Java通俗理解(上) 第31条:用实例域代替序数 枚举类型有一个ordinal方法,它范围该常量的序数从0开始,不建议使用这个方法,因为这不能很好地对枚举进行维护,正确应该是利用 ...
- Effective Java通俗理解(上)
这篇博客是Java经典书籍<Effective Java(第二版)>的读书笔记,此书共有78条关于编写高质量Java代码的建议,我会试着逐一对其进行更为通俗易懂地讲解,故此篇博客的更新大约 ...
- Java中的引用类型和使用场景
作者:Grey 原文地址:Java中的引用类型和使用场景 Java中的引用类型有哪几种? Java中的引用类型分成强引用, 软引用, 弱引用, 虚引用. 强引用 没有引用指向这个对象,垃圾回收会回收 ...
- [java] 深入理解内部类: inner-classes
[java] 深入理解内部类: inner-classes // */ // ]]> [java] 深入理解内部类: inner-classes Table of Contents 1 简介 ...
- Java中的引用类型(强引用、弱引用)和垃圾回收
Java中的引用类型和垃圾回收 强引用Strong References 强引用是最常见的引用: 比如: StringBuffer buffer = new StringBuffer(); 创建了一个 ...
- Java初始化理解与总结 转载
Java的初始化可以分为两个部分: (a)类的初始化 (b)对象的创建 一.类的初始化 1.1 概念介绍: 一个类(class)要被使用必须经过装载,连接,初始化这样的过程. 在装载阶段,类装载器会把 ...
- 从Java视角理解CPU上下文切换(Context Switch)
从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态 在高性能编程时,经常接触到多线程. 起初我们的理解是, 多个线程并行地执行总比单个线程要快, 就像多个人一起干活总比一个人干要快 ...
- 从Java视角理解CPU缓存(CPU Cache)
从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态众所周知, CPU是计算机的大脑, 它负责执行程序的指令; 内存负责存数据, 包括程序自身数据. 同样大家都知道, 内存比CPU慢很多 ...
随机推荐
- vue-cli中自定义路径别名 assets和static文件夹的区别
转自:vue-cli中自定义路径别名 assets和static文件夹的区别 静态资源处理: assets和static文件夹的区别 相信有很多人知道vue-cli有两个放置静态资源的地方,分别是sr ...
- Python初学者第十一天 文件处理_batch
11day 文件的操作分为读.写.修改 1.读: f = open(file='D:\新建文本文档.txt',mode='r',encoding='gbk') data = f.read() prin ...
- nginx限制IP恶意调用短信接口处理方法
真实案例: 查看nginx日志,发现别有用心的人恶意调用API接口刷短信: /Jun/::: +] "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:27.0) ...
- Django 查询集的过滤内置条件
条件选取querySet的时候,filter表示=,exclude表示!=.querySet.distinct() 去重复__exact 精确等于 like 'aaa' __iexact 精确等于 忽 ...
- Django 模型中DateField字段
DateField¶ class DateField([auto_now=False, auto_now_add=False, **options])¶ 这是一个使用Python的datetime.d ...
- pycharm 设置字体大写和显示代码行号
打开pycharm,我们看到左边是没有行号显示的. 在工具栏中点击扳手的标志,打开. 找到 Ide-setting ——>Editor ——>Apperance ——> ...
- PHP------文件------文件整体操作
文件整体操作 [1]创建文件 touch("路径"); touch("./test.docx");//当前路径创建文件,创建的文档 显示的结果: touch ...
- Telnet配置
一.环境 路由 IP:192.168.56.2 本地云 IP:192.168.56.1 二.认证模式 AAA模式 认证 授权 计费的安全技术 当配置用户界面的认证方式为AAA时, 用户登录设备时需要首 ...
- JSON数据转换之net.sf.json包的使用
转载 解析json之net.sf.json https://blog.csdn.net/itlwc/article/details/38442667 一.介绍 使用之前需要导入的jar包: json- ...
- java中printf的用法
目前printf支持以下格式: %c 单个字符 %d 十进制整数 %f 十进制浮点数 ...