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 虚拟机隐含地创建。数据qwe存储在方法区的常量池
            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“存放进方法区的常量池返回地址引用,再指向。

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”在内存中并没有清除掉,而是a在栈区的地址发生了改变,这次指向的是”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,默认放在栈里边,是地址引用,100存储在方法区的常量池中
        Integer d = 100;//自动装箱,但是100在-128到127之间,没有使用new,默认放在栈里边,是地址引用,100存储在方法区的常量池中
        System.out.println(c == d);//true,地址引用相同
        System.out.println(c==a);//true 自动拆箱
        Integer c_new = new Integer(100);
        System.out.println(c_new==c);//false,因为地址引用不同
        // 3)常规量
        c = 200;//c = c.valueOf(200),200不在-128到127之间,所以会使用new创建对象
        d = 200;//也就是说这里c,d都使用了new,

        System.out.println(c == d);//使用new创建的对象地址引用不同,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

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨夜※繁华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值