本文分析下java对象当做参数传递时在方法内部是否可改变的问题
目录
- 1、值传递 VS 引用传递
- 2、基本类型作为参数传递
- 3、引用对象作为参数传递
java中对象作为参数传递时只有值传递,没有引用传递,即在方法内部传入参数对象的引用永远不会改变,改变的只有可能是值
1、值传递 VS 引用传递
首先,我们必须要搞清楚,到底什么是值传递,什么是引用传递,否则,讨论 Java 到底是值传递还是引用传递就显得毫无意义。
当一个参数按照值的方式在两个方法之间传递时,调用者和被调用者其实是用的两个不同的变量——被调用者中的变量(原始值)是调用者中变量的一份拷贝,对它们当中的任何一个变量修改都不会影响到另外一个变量。
而当一个参数按照引用传递的方式在两个方法之间传递时,调用者和被调用者其实用的是同一个变量,当该变量被修改时,双方都是可见的。
Java 程序员之所以容易搞混值传递和引用传递,主要是因为 Java 有两种数据类型,一种是基本类型,比如说 int,另外一种是引用类型,比如说 String。
基本类型的变量存储的都是实际的值,而引用类型的变量存储的是对象的引用——指向了对象在内存中的地址。值和引用存储在 stack(栈)中,而对象存储在 heap(堆)中。
2、基本类型作为参数传递
传递的参数仅仅只是基本类型对象的一个值副本,因为基本类型没有引用可言,所以在方法内改变基本类型的值不会对原有对象做任何改变
public class Test3 {
public void add(int value) {
// 调用进入后在栈中创建了变量value值为a1的值(变量和值都存储在栈),
// 所以无论怎么修改value都不会对传进来的变量造成影响
value = 2;
}
public static void main(String[] args) {
Test3 test3 = new Test3();
int a1 = 0;
test3.add(a1);
System.out.println("a1:" + a1);//不改变原有对象值 a1:0
}
}
3、引用对象作为参数传递
可以改变对象的值,不能改变对象的引用
public class Test22 {
void add(List<String> list1) {
// 进入后相当于在栈中创建了一个变量list1并且指向了
// 调用方传进来的list在堆中的内存地址
// 即此时list1和调用的客户端的list变量指向堆内存中的同一个对象
// 那么修改堆中的对象会同时影响list和list1变量
// 重新赋值了list和list1不会改变堆中的对象
list1.add("list2");
list1 = new ArrayList<>();
}
void change(List<String> list) {
// 修改了list的引用
list = new ArrayList<>();
}
public static void main(String[] args) {
Test22 test22 = new Test22();
List<String> list = new ArrayList<>();
list.add("list1");
test22.add(list);
System.out.println(list); // [list1, list2]
test22.change(list);
System.out.println(list); // [list1, list2] 引用不会改变
// list传递了多个方法后,最后引用还是不可变的
}
}
下面再看一个面试题,如下如何在最后输出a1=2
public static void main(String[] args) {
Test3 test3 = new Test3();
Integer a1 = 1;
test3.add(a1);
System.out.println("a1:" + a1);
}
由上规则可得,Integer a1在方法内部的值可以改变,引用不可改变,那么我们只能修改a1的值。
public void add(Integer value) {
// 这里可以理解为在方法内部栈上创建了一个变量value指向了堆内存中a1的内存地址
// 所以此时value==a1,因为都指向了堆中的同一个对象
value = 2;// 等同于Integer.valueOf(2),此时变量value指向堆中的另外一个内存地址,所以value!=a1
}
如下代码看似修改了a1的值,但是实际是改变了a1的引用(因为Integer是不可变类)实际代码等同于value = new Integer(2);那么应该怎么修改Integer的值呢,我们看下Integer的源码
public final class Integer extends Number implements Comparable<Integer> {
public static final int MIN_VALUE = -2147483648;
public static final int MAX_VALUE = 2147483647;
public static final Class<Integer> TYPE = Class.getPrimitiveClass("int");
static final char[] digits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
static final char[] DigitTens = new char[]{'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', '9', '9', '9', '9', '9', '9', '9', '9', '9', '9'};
static final char[] DigitOnes = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
static final int[] sizeTable = new int[]{9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, 2147483647};
// 不可变的value
private final int value;
public static final int SIZE = 32;
public static final int BYTES = 4;
private static final long serialVersionUID = 1360826667806852920L;
.......
}
由上可以看出Integer的value被设计成了final,那么我们要怎么改变呢?
我们可以使用无所不能的反射
public void add(Integer value) {
try {
Class czz = value.getClass();
Field field = czz.getDeclaredField("value");
// 暴力访问,及时是私有变量也可访问
field.setAccessible(true);
field.set(value,2);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
最后总结一句话:java只有值传递,没有引用传递