阅读 352

Java易错点1

这是我参与更文挑战的第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行

image-20210608144647495.png

a和b都是基本类型,所以只在栈内存申请内存空间,而String和Integer(Integer是int的包装类,Integer实际是对象的引用)是在栈内存只是定义一个特殊的变量,而这个变量指定了堆内存的首地址。

  • 15-17行、34-41行

java2.png

change执行后,只是把a和b的值传递给a1和b1,对a和b并无任何影响。

java3.png

此时执行change方法,交换的只是a1和b1的值,当change执行完毕,a1和b1就结束了他的生命周期,固栈内存中空间会被释放掉。

java4.png

  • 18-27

    当创建People对象的时候,就会在堆内存申请空间,同时在栈中定义一个特殊的变量,该变量指向堆内存的首地址,当给对象赋值时,就会指向值的地址,所以此时c和people.name指向的都是同一个堆内存地址。

java5.png

创建people1的过程同上

java6.png

  • 剩余代码

    进行交换时,因为是值传递,所以传递的是堆内存的地址。所以同a1,b1,c1一样,他们的交换,与people、people1无关。

java7.png

数组

示例代码

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对程序而言是相等的,所以无法达到我们预想的效果(打印十次)。

文章分类
后端
文章标签