java里边的new深入剖析 - Go语言中文社区

java里边的new深入剖析


new剖析

jvm运行时的数据区(runtime data area)
在这里插入图片描述
”new“在 Java 中意思是”新的“,可以说是 Java 开发者最常用的关键字。在 Java 中 new 的操作往往意味着在内存中开辟新的空间,这个内存空间分配在内存的堆区。

堆是用来存放由 new 创建的对象和数组,即动态申请的内存都存放在堆区栈是用来存放在方法中定义的一些基本类型的变量和对象的引用变量。
Java 中一般使用 new 来创建对象,它可以动态地为一个对象分配地址。它的通用格式如下:

classname obj = new classname( );

其中,obj 是创建的对象,classname 是类的名字,类名后边的( )指明了类的构造方法。构造方法定义了当创建一个对象时要进行的操作。

下面我们通过 String 这个类举例说明。

package classandobject;

public class New_Mis {

        public static void main(String[] args) {
            String a = "qwe";//隐式创建对象,由 Java 虚拟机隐含地创建。
            String b = new String("qwe");//在堆中会创建一个对象,返回地址
            String c = "qwe";//先在栈里边寻找是否已经有这个内容了,没有就创建,有就直接将c指向这个地址。
            String d = new String("qwe");//在堆中会创建一个对象,返回地址
            System.out.println(a == b);//false
            System.out.println(a == c);//true,已经有qwe,c指向的地址与a同
            System.out.println(d == b);//false,两个对象的引用的地址肯定不同
            System.out.println(a);//qwe
            a = "Java";//重新赋值
            System.out.println(a);
        }
}

在这里插入图片描述
不同方式定义字符串时堆和栈的变化:

String a; 只是在栈中创建了一个 String 类的对象引用变量 a。

String a = “qwe”;在栈中创建一个 String 类的对象引用变量 a,然后查找栈中有没有存放“qwe”,如果有则直接指向“qwe",如果没有,则将”qwe“存放进栈,再指向。

String a = new String(“qwe”);不仅在栈中创建一个 String 类的对象引用变量 a,同时也在堆中开辟一块空间存放新建的 String 对象“qwe”,变量 a 指向堆中的新建的 String 对象”qwe“。

==用来比较两个对象在堆区存放的地址是否相同。

根据上面的输出结果,我们可以看出:
使用 new 运算符创建的 String 对象进行==操作时,两个地址是不同的。这就说明,每次对象进行 new 操作后,系统都为我们开辟堆区空间,虽然值是一样,但是地址却是不一样的。

当我们没有使用 new 运算符的时候(这里一定要注意自动装箱的过程),系统会默认将这个变量保存在内存的栈区如果变量的值存放在栈中,使用==比较时,比较的是具体的值。如果变量的值存放在堆中,使用==比较时,比较的是值所在的地址。因此在变量 a 与变量 c 进行==操作的时候,返回 true,因为变量 a 和变量 c 比较的是具体的值,即“qwe”。
在改变变量 a 的值后(如 a = “Java”),再次输出时,我们发现输出的结果是”Java“。事实上原来的那个“qwe”在内存中并没有清除掉,而是在栈区的地址发生了改变,这次指向的是”Java“所在的地址。
注意:如果需要比较两个使用 new 创建的对象具体的值,则需要通过“equal()”方法去实现,这样才是比较引用类型变量具体值的正确方式

这时,你可能想知道为什么对整数或字符这样的简单变量不使用 new 运算符。答案是 Java 的简单类型不是作为对象实现的。出于效率的考虑,它们是作为“常规”变量实现的。

对象有许多属性和方法,这使得 Java 对对象的处理不同于简单类型。Java 在处理对象和处理简单类型时开销不同,Java 能更高效地实现简单类型。当然,如果你希望完全使用对象类型,那么 Java 也提供了简单类型的对象版本,也就是包装类。

大家一定要明白,new 运算符是在运行期间为对象分配内存的,这使得内存的分配更加灵活和高效,你的程序在运行期间可以根据实际情况来合理地分配内存。但是,内存是有限的,因此 new 有可能由于内存不足而无法给一个对象分配内存。如果出现这种情况,就会发生运行时异常。

常规变量及装箱拆箱的例子

1)int与Integer

package classandobject;

public class Integer_Int {
    public static void main(String[]args){
        // 1)基本类型和包装类型
        int a = 100;//常规量,默认栈中
        Integer b = 100;//自动装箱,但是100在-128到127之间,使用栈中缓存
        System.out.println(a == b);//b拆箱,这里就是比较值的大小了。因为大家都是在栈里边

        // 2)两个包装类型
        Integer c = 100;//自动装箱,但是100在-128到127之间,没有使用new,默认放在栈里边
        Integer d = 100;//自动装箱,但是100在-128到127之间,没有使用new,默认放在栈里边
        System.out.println(c == d);//true,比较值大小

        // 3)常规量
        c = 200;//c = c.valueOf(200),200不在-128到127之间,所以会使用new创建对象
        d = 200;//也就是说这里c,d都使用了new,
        System.out.println(c == d);//自然这里比较的是地址,false
        System.out.println(c.equals(d));//自然这里比较的是对象的值,true

    }
}

在这里插入图片描述

解析
之前我们已经知道了,自动装箱是通过 Integer.valueOf() 完成的,那我们就来看看这个方法的源码吧。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)//low=-128,high = 127
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);//发现没有,装箱过程可能会用到new
}
//我们来看一下条件是什么:
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        int i = parseInt(integerCacheHighPropValue);//
        i = Math.max(i, 127);
        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);//基本都是h = i了。。。
        high = h;//high = i;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }
}

valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。在 Java 8 中,Integer 缓存池的大小默认为 -128~127。编译器会在自动装箱过程调用 valueOf() 方法,因此

多个值相同且值在缓存池范围内的 Integer 实例使用自动装箱来创建,那么就会引用相同的对象。

2)double与Double

下面我们看double与Double,发现它则不会使用缓存池里边的数,都是直接new

 public static Double valueOf(String s) throws NumberFormatException {
        return new Double(parseDouble(s));
    }
    public static Double valueOf(double d) {
        return new Double(d);
    }

我们把上面的例子改为double及Double

package classandobject;

public class Integer_Int {
    public static void main(String[]args){
        // 1)基本类型和包装类型
        double a = 10.0;//常规量,默认栈中
        Double b = 10.0;//自动装箱,使用了new
        System.out.println(a == b);//基本类型与包装类比较,b自动拆箱,这里就是比较值,true

        // 2)两个包装类型
        Double c = 10.0;//自动装箱,使用new,
        Double d = 10.0;//自动装箱,使用new
        System.out.println(c == d);//比较地址,false

        // 3)常规量
        c = 20.0;
        d = 20.0;//也就是说这里c,d都使用了new,
        System.out.println(c == d);//自然这里比较的是地址,false
        System.out.println(c.equals(d));//自然这里比较的是对象的值,true
    }
}

在这里插入图片描述

参考博客1
参考博客2

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_44861675/article/details/105428749
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-04-18 15:27:45
  • 阅读 ( 1927 )
  • 分类:Go深入理解

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢