前言
这学期学校开了Java, 所以服务器组的小伙伴们都开心地写起了Java. 昨天晚上萌萌哒的校园问了我一个问题, 在C语言中参数的传递中, 我们可以使用指针进行传递, 从而更改参数的值, 可是在Java中, 并没有取地址操作, 也没有C++里面的引用操作, 那么应该怎样更改参数的值呢?
所以在这里来谈一谈我对Java中的参数传递的一些粗浅的认识.
1. Java中的数据类型?
在说到Java参数传递问题之前, 我们先来了解一下Java里面的数据类型.
在Java中, 有两种数据类型, 一种是基本数据类型, 一种是类类型. 类类型也称为引用数据类型.
基本数据类型就是我们常用的八大基本数据类型: short, int, long, float, double, char, byte, boolean. 在Java中是没有unsignd int这种数据类型的, 因为Java是运行于JVM之上, Java的设计就是为了脱离对计算机底层的依赖.
除了基本数据类型, 其余可以在Java中可以声明变量的类型都是类类型. 比如我们常用的数组类型, 在Java中也属于类类型, 还有String类型等都属于类类型. 除了这些, 我们自定义的类也属于类类型.
类类型又称为引用类型. 所以类类型声明的变量也称为引用变量. 为了更好的说明引用类型, 我们先来看一个例子:
public class Book {
public String name;
public String artist;
public static void main(String[] args){
Book book = new Book();
book.name = "Java学习笔记";
book.artist = "林信良";
Book book1 = book;
System.out.println(book.name);
System.out.println(book1.name);
}
}
这个Book类里面只有两个数据成员, 我们首先创建book对象并给它赋值, book将有一段属于自己的内存空间来存储自己的成员. 然后将book实例赋值给另外一个引用变量book1时会发生什么呢? Java会将book对象的数据在内存里重新复制一份吗?
当然不会! Java没有这么笨, Java仅仅会让book1这个引用变量指向book这个对象所拥有的这段空间. 也就是说, book和book1共同指向一段内存空间.
我们定义的这个book1这个变量实际上是一个引用, 它被放在栈内存中, 指向实际的Book对象; 而真正的Book对象(即存有”Java学习笔记”和”林信良”的内存空间)是被存放在堆内存中的.
当Java中的一个对象被成功创建之后, 这个对象将被保存于堆空间里, Java不允许直接访问堆内存中的对象, 只能通过该对象的引用操作该对象.
引用变量与C语言中的指针很像, 它们都只存储一个地址值, 通过这个地址值指向真正的存储内容的内存空间. 实际上, Java中的引用就是C语言中的指针, 只是Java将这个指针进行了封装, 避免我们直指内存进行操作. 这也是Java语言比较安全的一个原因.
2. Java中的参数传递机制
Java中的参数传递机制只有一种! 就是值传递!!!
在Java中, 并没有C++的引用传递这种操作! 它所有的参数传递都遵循值传递的机制.
比如来看一下经典的swap例子.
public class SwapTest {
public static void swap(int a, int b){
int temp;
temp = a;
a = b;
b = temp;
System.out.println("swap里的: a = " + a + " b = " + b);
}
public static void main(String[] args){
int a = 3;
int b = 4;
swap(3, 4);
System.out.println("main方法里交换后的: a = " + a + " b = " + b);
}
}
来看一下输出结果:
swap里的: a = 4 b = 3
main方法里交换后的: a = 3 b = 4
从这里可以看出swap并没有真正的实现a和b的交换.
这是因为, Java的参数传递机制是值传递. a和b都是int类型的值, 在main()方法里调用swap()方法, 是将a和b的值复制一份, 传给swap(), 复制之后的这份值存于swap()方法的栈区, 与main()方法的栈区是相互独立的. 所以在swap()方法里做的操作并不会影响main()方法里a和b的值的改变.
这就像是我们在C语言里函数调用过程中的形参与实参问题.
那么, 真的没有办法在方法里改变变量的值吗? 当然是有的.
我们可以在方法里改变一个类的数据成员变量, 就像下面这样:
public class DataWrapper {
public int a;
public int b;
public static void swap(DataWrapper A){
int temp;
temp = A.a;
A.a = A.b;
A.b = temp;
System.out.println("swap里的: a = " + A.a + " b = " + A.b);
}
public static void main(String[] args){
DataWrapper A = new DataWrapper();
A.a = 3;
A.b = 4;
swap(A);
System.out.println("main方法里交换后的: a = " + A.a + " b = " + A.b);
}
}
运行结果如下:
swap里的: a = 4 b = 3
main方法里交换后的: a = 4 b = 3
从上面的代码可以看出, a和b被封装在了DataWrapper类中了, A作为DataWrapper类的对象, 是一个引用类型的变量, 它在main()方法的栈区的存在形式也就是我们前面提到的是以一个地址存在的, 而这个地址指向真正的存放A对象的数据的堆内存空间. 那也就是说, 此时对main()方法而言, A的值就是一串地址. 接着main()方法调用swap()方法, 将这个地址复制一份传给swap(), 随后在swap()里面进行的操作, 都是对这个地址指向(也可以理解为指针)的内存空间, 即A对象在堆上真正存储数据的内存空间进行操作的.
仍然是值传递, 只不过引用变量的值就是地址而已.
这就是Java中的参数传递机制.
但是, 校园还提到了, 为什么使用Integer类来进行操作还是不能实现真正的交换呢? Integer对象虽然是打包之后的int值, 但是它也是对象啊, 为什么还是不能成功呢?
这是因为Java的自动拆箱自动装箱机制, 当我们使用Integer类的时候, Java进行了自动装箱, 就是对一个int值进行打包, 使它具有对象的性质, 而当我们真正对这个Integer对象进行操作的时候, Java又会进行自动拆箱, 也就是说, 当我们真正操作的不过是一个int值. 这样就可以解释即使使用Integer类也不能完成swap()操作.
下面我们来验证一下:
public class IntegerSwapTest {
public static void swap(Integer a, Integer b){
int temp;
temp = a;
a = b;
b = temp;
System.out.println("swap里的: a = " + a + " b = " + b);
}
public static void main(String[] args){
Integer a = 3;
Integer b = 4;
swap(a, b);
System.out.println("main方法里交换后的: a = " + a + " b = " + b);
}
}
运行结果如下:
swap里的: a = 4 b = 3
main方法里交换后的: a = 3 b = 4