目录
- 数据的内存模型
- Java 对象的赋值
- Java 对象的传递
- JavaScript 的数组对象
1. 数据的内存模型
Java 中的数据存放在“栈”和“堆”中,新建一个变量时,(非基本数据类型)值会存放在堆中,变量名会存放在栈中,变量名在栈中的内容是值在堆中的地址。变量赋值或者传递的时候,是复制栈中的内容,也就是复制对应变量在堆中的地址。
2. Java 对象的赋值
对于 Java 的数据模型,基本数据类型数据均是传值引用,其他的对象(包括数组)都是传址引用。
2.1. 小结
- 对于基本数据类型的数据,赋值操作是传值操作;
- 对于 String 类型数据,赋值是传址操作,而不是直观上的传值操作;
- 对于数组及其他自定义的数据类型,赋值操作是传址操作。
2.2. 基本数据类型
基本数据类型的赋值是传值操作。
int i = 1;
int j = i;
j = 3;
System.out.println("i = " + i);
System.out.println("j = " + j);
System.out.println(i == j ? true : false);
输出
i = 1
j = 3
false
2.3. String 类型
从下面的例子可以证明:
String 类型数据的是传址引用,而不是传值引用。否则,下面程序段中的 str3 == str4 ? true : false 语句应该被判断为 true。
String str1 = "测试"; // 系统为 "测试" 字符串创建内存,并将 str1 指向该内存空间
String str2 = str1; // 将 str1 装载的内存内容(指向的地址)复制给 str2
System.out.println(str1 == str2 ? true : false); // 判断两者的内存内容(指向的地址)是否相同;结果:true
String str3 = new String("测试"); // 开辟新的内存区域存放 "测试" 字符串
String str4 = new String("测试");
System.out.println(str3 == str4 ? true : false); // 判断两者的指向的地址是否相同;结果:false
System.out.println(str3.equals(str4)); // 判断两者的字符串内容是否相同,结果:true
str2 = "发生修改"; // 新建 "发生修改" 的内存,并将 str2 指向该内存空间
System.out.println(str1 == str2 ? true : false); // 因为指向的内存空间已经是不相同的,所以结果:false
System.out.println("str1 = " + str1);
System.out.println("str2 = " + str2);
输出
true
false
true
false
str1 = 测试
str2 = 发生修改
2.4. 数组
直接对数组赋值,其实是传址操作。
int[] arr1 = { 0, 0, 0 };
int[] arr2 = arr1;
arr2[1] = 1;
System.out.println(arr1[1]);
System.out.println(arr2[1]);
System.out.println(arr1 == arr2 ? true : false);
输出:
1
1
true
2.5. 自定义类型
自定义类型的赋值操作也是传址引用。
Car car1 = new Car();
car1.setiCarSeriesNum(0);
car1.setStrCarName("测试");
Car car2 = car1;
car2.setiCarSeriesNum(2);
car2.setStrCarName("修改后的 Car");
System.out.println(car1.toString());
System.out.println(car2.toString());
System.out.println(car1 == car2 ? true : false);
输出
Car [iCarSeriesNum=2, strCarName=修改后的 Car]
Car [iCarSeriesNum=2, strCarName=修改后的 Car]
true
3. Java 对象的传递
与对象的赋值一样,对象的传递规则也是按照数据类型划分。
1. 对于基本数据类型,对象的传递是传值;
2. 对于 String 和其他类型的数据,是传址。
下面使用 2 个案例对对象传递的传址进行说明:
案例1
public static void main(String[] args) {
DataType dt = new DataType();
StringBuffer sb = new StringBuffer("这是");
// 将sb的内存地址传递给byRef方法的stringBuffer参数
// “dt.byRef(sb);” 语句相当于:
// StringBuffer stringBuffer = sb;
// stringBuffer.append("传址操作");
dt.byRef(sb);
System.out.println(sb);
}
public void byRef(StringBuffer stringBuffer){
stringBuffer.append("传址操作");
}
输出:这是传址操作
上面的例子可以成功的说明非基本数据类型对象的传递是传址引用的。那么,String 类型的数据是否还是传址引用呢?
案例2
public static void main(String[] args) {
DataType dt = new DataType();
String str = "测试";
dt.byRef(str);
System.out.println(str);
}
public void byRef(String string) {
string = "传址操作";
}
输出:测试
从测试结果来看,似乎 String 类型的数据并不是传址引用,否则怎么不修改掉 str 的值呢。其实,上面的案例可以视为以下代码:
String str = "测试";
String string = str;
string = "传址操作";
String 类型数据的传递其实也是传址引用,只不过系统新建了属于 “传址操作” 的内存空间,string 参数指向了它。并没有改变 str 的指向。这个与上面提到的 String 数据的赋值是同样的概念。
4. JavaScript 的对象
其实,JavaScript 的对象模型与 Java 也有相似之处,贴下我最近使用 JS 的经历。
附:
最近,我在用 NodeJS 做一个小项目,第一次使用了 JS。当我将 JSON 格式的对象传入数组的时候,出现了意想不到的错误。接着发现对数组的赋值操作并不是传值,而是传址。
下面使用一个 demo 对这种现象进行总结。这个 demo 的目的是遍历树状图的子节点,并将子节点数据传入 arrNodeData 。
在第一段程序中,遍历子节点的数据,将数据存放到 objNodeData 中,并将其 push 进 arrNodeData 中。继而发现,遍历完之后,数组所有的对象均是最后一个元素的内容。第二段程序中,修改了一句话,将 arrNodeData.push(objNodeData); 修改为 arrNodeData.push(JSON.parse(JSON.stringify(objNodeData)));,程序能够按照预期正常输出。
分析如下:第一个程序中,传进 arrNodeData 数组的均是 objNodeData 的引用,所以数组的输出是最后一次修改 objNodeData 的数据。第二个程序中,JSON.stringify(objNodeData) 对 JSON 对象进行了字符串转化,在内存中重建成了新的对象,然后再将其转化为 JSON 对象,传进数组的是新的对象引用。所以就可以正常显示了。
代码段1:
var arrNodeData = [];
var objNodeData = {'sn': 0, 'id': 0, 'name': '', 'isFirst': false, 'isLast': false};
if (typeof (treeNode.children) !== 'undefined') {
for (var i = 0; i < treeNode.children.length; i++) {
var childNode = treeNode.children[i];
objNodeData['sn'] = Number(childNode.sn);
objNodeData['id'] = Number(getTreeNodeID(childNode.id));
objNodeData['name'] = childNode.name;
arrNodeData.push(objNodeData);
console.log(JSON.stringify(arrNodeData));
}
}
return arrNodeData;
输出:
[{"sn":1,"id":1,"name":"C","isFirst":false,"isLast":false}]
[{"sn":2,"id":9,"name":"E","isFirst":false,"isLast":false},{"sn":2,"id":9,"name":"E","isFirst":false,"isLast":false}]
[{"sn":3,"id":8,"name":"S","isFirst":false,"isLast":false},{"sn":3,"id":8,"name":"S","isFirst":false,"isLast":false},{"sn":3,"id":8,"name":"S","isFirst":false,"isLast":false}]
[{"sn":4,"id":10,"name":"GLE","isFirst":false,"isLast":false},{"sn":4,"id":10,"name":"GLE","isFirst":false,"isLast":false},{"sn":4,"id":10,"name":"GLE","isFirst":false,"isLast":false},{"sn":4,"id":10,"name":"GLE","isFirst":false,"isLast":false}]
代码段2:
var arrNodeData = [];
var objNodeData = {'sn': 0, 'id': 0, 'name': '', 'isFirst': false, 'isLast': false};
if (typeof (treeNode.children) !== 'undefined') {
for (var i = 0; i < treeNode.children.length; i++) {
var childNode = treeNode.children[i];
objNodeData['sn'] = Number(childNode.sn);
objNodeData['id'] = Number(getTreeNodeID(childNode.id));
objNodeData['name'] = childNode.name;
// arrNodeData.push(objNodeData);
arrNodeData.push(JSON.parse(JSON.stringify(objNodeData)));
console.log(JSON.stringify(arrNodeData));
}
}
return arrNodeData;
输出:
[{"sn":1,"id":1,"name":"C","isFirst":false,"isLast":false}]
[{"sn":1,"id":1,"name":"C","isFirst":false,"isLast":false},{"sn":2,"id":9,"name":"E","isFirst":false,"isLast":false}]
[{"sn":1,"id":1,"name":"C","isFirst":false,"isLast":false},{"sn":2,"id":9,"name":"E","isFirst":false,"isLast":false},{"sn":3,"id":8,"name":"S","isFirst":false,"isLast":false}]
[{"sn":1,"id":1,"name":"C","isFirst":false,"isLast":false},{"sn":2,"id":9,"name":"E","isFirst":false,"isLast":false},{"sn":3,"id":8,"name":"S","isFirst":false,"isLast":false},{"sn":4,"id":10,"name":"GLE","isFirst":false,"isLast":false}]