这是我参与更文挑战的第8天,活动详情查看: 更文挑战
Java易错点1
如有理解错误的话,恳请大家指正!!!
内存
Java自动管理栈和堆,程序员不能直接地设置栈或堆。
- 栈内存:
- 一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象的引用变量(对象句柄)。
- 定义变量时,就在栈中分配内存空间,当超过变量的作用域后,会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
- 存取速度比堆要快,仅次于寄存器
- 数据可以共享
- 存在栈中的数据大小与生存期必须是确定的,缺乏灵活性
- 堆内存
- 存放由new创建的对象和数组。
- 由Java虚拟机的自动垃圾回收器来管理。
- 创建一个数组或对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或对象在堆内存中的首地址,这个变量就成了数组或对象的引用变量。 使用栈中的引用变量来访问堆中的数组或对象。在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。 栈中的变量指向堆内存中的变量,这就是 Java 中的指针!
- 运行时数据区,类的(对象)从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立
- 动态地分配内存大小,生存期也不必事先告诉编译器
- 由于要在运行时动态分配内存,存取速度较慢。
示例代码
people对象
package com.wangscaler;
public class People {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "People{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
复制代码
main函数
package com.wangscaler;
public class Main {
public static void main(String[] args) {
// write your code here
int a = 10;
int b = 20;
String c = "wang";
Integer d = 30;
System.out.println("c的值为" + c + ",内存地址为" + System.identityHashCode(c));
System.out.println("d的值为" + d + ",内存地址为" + System.identityHashCode(d));
System.out.println("a的值为" + a);
System.out.println("b的值为" + b);
chage(a, b);
System.out.println("a的值为" + a);
System.out.println("b的值为" + b);
People people = new People();
people.setAge(30);
people.setName("wang");
System.out.println("people的值为" + people + ",内存地址为" + System.identityHashCode(people));
System.out.println("people.name的值为" + people.getName() + ",内存地址为" + System.identityHashCode(people.getName()));
System.out.println("people.age的值为" + people.getAge() + ",内存地址为" + System.identityHashCode(people.getAge()));
People people1 = new People();
people1.setName("scaler");
people1.setAge(40);
System.out.println("people1的值为" + people1 + ",内存地址为" + System.identityHashCode(people1));
chagePeople(people, people1);
System.out.println("交换后people的值" + people + ",内存地址为" + System.identityHashCode(people));
System.out.println("交换后people1的值" + people1 + ",内存地址为" + System.identityHashCode(people1));
}
public static void chage(int a1, int b1) {
int c1 = a1;
a1 = b1;
b1 = c1;
System.out.println("c1的值为" + c1);
System.out.println("a1的值为" + a1);
System.out.println("b1的值为" + b1);
}
public static void chagePeople(People people2, People people3) {
People people4 = people2;
people2 = people3;
people3 = people4;
System.out.println("people4的值为" + people4 + ",内存地址为" + System.identityHashCode(people4));
System.out.println("people3的值为" + people3 + ",内存地址为" + System.identityHashCode(people3));
System.out.println("people2的值为" + people2 + ",内存地址为" + System.identityHashCode(people2));
}
}
复制代码
执行结果
c的值为wang,内存地址为356573597
d的值为30,内存地址为1735600054
a的值为10
b的值为20
c1的值为10
a1的值为20
b1的值为10
a的值为10
b的值为20
people的值为People{age=30, name='wang'},内存地址为21685669
people.name的值为wang,内存地址为356573597
people.age的值为30,内存地址为1735600054
people1的值为People{age=40, name='scaler'},内存地址为2133927002
people4的值为People{age=30, name='wang'},内存地址为21685669
people3的值为People{age=30, name='wang'},内存地址为21685669
people2的值为People{age=40, name='scaler'},内存地址为2133927002
交换后people的值People{age=30, name='wang'},内存地址为21685669
交换后people1的值People{age=40, name='scaler'},内存地址为2133927002
Process finished with exit code 0
复制代码
原理
-
前14行
a和b都是基本类型,所以只在栈内存申请内存空间,而String和Integer(Integer是int的包装类,Integer实际是对象的引用)是在栈内存只是定义一个特殊的变量,而这个变量指定了堆内存的首地址。
-
15-17行、34-41行
change执行后,只是把a和b的值传递给a1和b1,对a和b并无任何影响。
此时执行change方法,交换的只是a1和b1的值,当change执行完毕,a1和b1就结束了他的生命周期,固栈内存中空间会被释放掉。
-
18-27
当创建People对象的时候,就会在堆内存申请空间,同时在栈中定义一个特殊的变量,该变量指向堆内存的首地址,当给对象赋值时,就会指向值的地址,所以此时c和people.name指向的都是同一个堆内存地址。
创建people1的过程同上
-
剩余代码
进行交换时,因为是值传递,所以传递的是堆内存的地址。所以同a1,b1,c1一样,他们的交换,与people、people1无关。
数组
示例代码
package com.wangscaler;
public class TestArray {
public static void main(String[] args) {
int[] data = new int[3];
int[] temp = new int[3];
System.out.println("data的值为" + data + ",内存地址为" + System.identityHashCode(data));
System.out.println("data的值为" + temp + ",内存地址为" + System.identityHashCode(temp));
data[0] = 99;
data[1] = 20;
data[1] = 30;
data = temp;
temp[0] = 100;
System.out.println("data[0]的值为" + data[0] + ",内存地址为" + System.identityHashCode(data));
System.out.println("temp[0]的值为" + temp[0] + ",内存地址为" + System.identityHashCode(temp));
System.out.println("data的值为" + data + ",内存地址为" + System.identityHashCode(data));
System.out.println("temp的值为" + temp + ",内存地址为" + System.identityHashCode(temp));
}
}
复制代码
执行结果
data的值为[I@1540e19d,内存地址为356573597
data的值为[I@677327b6,内存地址为1735600054
data[0]的值为100,内存地址为1735600054
temp[0]的值为100,内存地址为1735600054
data的值为[I@677327b6,内存地址为1735600054
temp的值为[I@677327b6,内存地址为1735600054
复制代码
data和temp指向了同一个堆内存地址,所以输出的推地址是一样的,都是[I@677327b6。此时如果修改任意一个数组的数据,另一个跟着变化。
浮点数不可作为循环变量
因为浮点数精度问题,在程序运行的时候会损失精度,如果程序中使用浮点数作为循环变量,往往不能达到预期的效果
示例代码
public class TestFloat {
public static void main(String[] args) {
float data = 2000000010f;
for (float i = 2000000000f; i <= data; i++) {
System.out.println(i);
}
}
}
复制代码
运行结果
2.0E9
2.0E9
2.0E9
2.0E9
2.0E9
....
//无限循环
//将i <= data改为i <data无任何输出
复制代码
原理
1、这里的2000000000f转换成二进制表示为1110111001101011001010000000000, 在计算机存储中,需要使用二进制的科学计数法表示,计算机不认识十进制数。
2、即1110111001101011001010000000000=1.110111001101011001010000000000*2^30
3、指数为30+127(IEEE754约定的单精度偏移量)=157,转换成二进制为10011101
4、尾数部分截取小数点后的23位(IEEE754约定的单精度尾数长度)
4、所以2000000000f就变成0(符号位1位,0代表正,1代表为负 )10011101(指数位8位)11011100110101100101000(尾数部分23位)即在内存中存储的二进制为01001110111011100110101100101000
5、同理20000000010f—->1110111001101011001010000001010—>1.110111001101011001010000001010*2^30—>指数为相同
6、20000000010f在内存的表示为0100111011101110011010110010100
7、对比发现2000000000f和20000000010f在内存中的二进制表示方式是一样的。所以在这里2000000000f和2000000010f对程序而言是相等的,所以无法达到我们预想的效果(打印十次)。