理解new关键字
”new“在 Java 中意思是”新的“,可以说是 Java 开发者最常用的关键字。在 Java 中 new 的操作往往意味着在内存中开辟新的空间,这个内存空间分配在内存的堆区。
堆是用来存放由 new 创建的对象和数组,即动态申请的内存都存放在堆区。栈是用来存放在方法中定义的一些基本类型的变量和对象的引用变量。
Java 中一般使用 new 来创建对象,它可以动态地为一个对象分配地址。它的通用格式如下:
classname obj = new classname( );
其中,obj 是创建的对象,classname 是类的名字,类名后边的( )指明了类的构造方法。构造方法定义了当创建一个对象时要进行的操作。
说的简单点 new运算符创建对象的过程就是在堆区中动态地为对象分配内存
为什么说是动态的 因为它是按顺序执行的:
1.类加载:当使用new关键字创建对象时,Java虚拟机会首先检查该类是否已经被加载。如果没有,虚拟机会通过类加载器将类的字节码文件加载到内存中,并创建一个对应的Class对象
2.内存分配:当类完成装载步骤之后,就已经完全确定出创建对象实例时所需的内存空间大小,接下来JVM将会对其进行内存分配,以存储所生成的对象实例。
3.初始化零值:将分配的内存空间初始化为默认的零值。基本数据类型的实例变量会被初始化为对应的零值(例如,int类型为0,boolean类型为false),而引用类型的实例变量会被初始化为null。
4.利用构造函数初始化:实例化之后,进行初始化(初始化对象头和实例数据)。构造函数会根据定义的参数列表执行一系列操作,例如初始化实例变量、执行其他方法等
5.返回对象引用:构造函数执行完毕后,将对象的引用(内存地址)返回给使用new关键字创建对象的代码,使得后续的代码可以通过该引用访问对象的实例变量和方法
下面我们通过 String 这个类举例说明。
public class Test {
public static void main(String[] args) {
String a = “C语言中文网”;
String b = new String(“C语言中文网”);
String c = “C语言中文网”;
String d = new String(“C语言中文网”);
System.out.println(a == b);
System.out.println(a == c);
System.out.println(d == b);
System.out.println(a);
a = “Java”;
System.out.println(a);
}
}
输出结果为:
false
true
false
C语言中文网
Java
不同方式定义字符串时堆和栈的变化:
String a; 只是在栈中创建了一个 String 类的对象引用变量 a。
String a = “C语言中文网”;在栈中创建一个 String 类的对象引用变量 a,然后查找栈中有没有存放“C语言中文网”,如果有则直接指向“C语言中文网",如果没有,则将”C语言中文网“存放进栈,再指向。
String a = new String(“C语言中文网”);不仅在栈中创建一个 String 类的对象引用变量 a,同时也在堆中开辟一块空间存放新建的 String 对象“C语言中文网”,变量 a 指向堆中的新建的 String 对象”C语言中文网“。
==用来比较两个对象在堆区存放的地址是否相同。根据上面的输出结果,我们可以看出:
使用 new 运算符创建的 String 对象进行 == 操作时,两个地址是不同的。这就说明,每次对象进行 new 操作后,系统都为我们开辟堆区空间,虽然值是一样,但是地址却是不一样的。
当我们没有使用 new 运算符的时候,系统会默认将这个变量保存在内存的栈区。如果变量的值存放在栈中,使用 == 比较时,比较的是具体的值。如果变量的值存放在堆中,使用== 比较时,比较的是值所在的地址。因此在变量 a 与变量 c 进行==操作的时候,返回 true,因为变量 a 和变量 c 比较的是具体的值,即“C语言中文网”。
在改变变量 a 的值后(如 a = “Java”),再次输出时,我们发现输出的结果是”Java“。事实上原来的那个“C语言中文网”在内存中并没有清除掉,而是在栈区的地址发生了改变,这次指向的是”Java“所在的地址。
注意:如果需要比较两个使用 new 创建的对象具体的值,则需要通过“equal()”方法去实现,这样才是比较引用类型变量具体值的正确方式。
这时,你可能想知道为什么对整数或字符这样的简单变量不使用 new 运算符。答案是 Java 的简单类型不是作为对象实现的。出于效率的考虑,它们是作为“常规”变量实现的。
对象有许多属性和方法,这使得 Java 对对象的处理不同于简单类型。Java 在处理对象和处理简单类型时开销不同,Java 能更高效地实现简单类型。当然,如果你希望完全使用对象类型,那么 Java 也提供了简单类型的对象版本,也就是包装类。
包装类
在Java中,基础类型的包装类都位于java.lang包中。这个包是Java语言的核心包,包含了许多基本的类和接口,如Object、String、System等。由于java.lang包是默认导入的,因此在使用这些包装类时,不需要显式导入该包。
以下是Java中基础类型及其对应的包装类:
基础类型包装类byteByteshortShortintIntegerlongLongfloatFloatdoubleDoublecharCharacterbooleanBoolean这些包装类提供了一种将基本数据类型转换为对象的方式,使得基本数据类型可以在需要对象的场景中使用,例如在集合框架中或者作为方法的参数传递时。包装类还提供了一些实用的方法,如类型转换、字符串解析等。
大家一定要明白,new 运算符是在运行期间为对象分配内存的,这使得内存的分配更加灵活和高效,你的程序在运行期间可以根据实际情况来合理地分配内存。但是,内存是有限的,因此 new 有可能由于内存不足而无法给一个对象分配内存。如果出现这种情况,就会发生运行时异常。
对于本教程中的示例程序,你不必担心内存不足的情况,但是在实际的编程中你必须考虑这种可能性。
————————————————
原文链接:https://blog.csdn.net/weixin_40005542/article/details/114179560
那么在理解了new关键字之后 我们再来详细深入理解一下:
使用new关键字创建对象的过程
1. 分配内存空间
目的
当使用new关键字创建一个对象时,第一步就是为这个即将诞生的对象在堆(Heap)内存中分配足够的空间。堆是Java中用于存储对象实例的区域。不同类型的对象所需的空间大小取决于对象的成员变量(包括实例变量)的类型和数量等因素。例如,一个包含多个int类型成员变量和一些引用类型成员变量的类所占用的空间,会比只有几个int类型成员变量的类占用的空间要大。内存管理机制
在Java中,内存管理由Java虚拟机(JVM)自动处理。JVM会根据对象的需求,在堆内存中找到一块合适的连续空间。这个空间大小足够容纳对象的所有成员变量以及一些与对象相关的内部管理信息(如对象头信息,其中可能包含对象的哈希码、分代年龄等)。例如,对于一个Person类的对象,如果Person类中有name(String类型)、age(int类型)和address(String类型)三个成员变量,JVM会计算出存储这些变量所需的大致空间,并在堆中分配。2. 执行构造方法初始化对象
构造方法的调用
一旦内存空间分配成功,就会立即调用对象所属类的构造方法。构造方法是一种特殊的方法,它的名称与类名相同,没有返回值(甚至不需要声明为void)。如果在类中没有显式定义构造方法,Java会提供一个默认的无参构造方法(对于没有继承关系的类而言)。初始化操作
在构造方法中,可以进行对象的初始化操作。这包括对对象的成员变量赋初始值。例如,对于Person类的构造方法,可能会像这样初始化成员变量:public class Person {
private String name;
private int age;
private String address;
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
}
这里通过构造方法的参数,将传入的值赋给对象相应的成员变量,从而完成对象在创建时的初始化工作。如果成员变量是基本数据类型,会直接赋给它们对应的初始值;如果是引用类型,可能会将其初始化为null(在没有在构造方法中显式赋值的情况下),或者根据构造方法中的逻辑进行初始化(如创建新的对象实例并赋值)。3. 把这个对象指向这个空间
引用的建立
在Java中,对象是通过引用进行操作的。当对象在堆内存中的空间分配好并且经过构造方法初始化后,会创建一个引用变量(可以是局部变量、成员变量等)来指向这个对象。这个引用变量存储在栈(Stack)内存中(对于局部变量而言)或者作为对象的成员变量存储在堆内存中(对于对象的成员变量而言)。例如:Person p = new Person("John", 30, "New York");
在这个语句中,new Person("John", 30, "New York") 完成了前面两步操作,即在堆内存中创建了一个Person对象并初始化,然后p这个引用变量就指向了堆内存中刚刚创建的Person对象所在的空间。通过这个引用变量p,就可以在程序中操作这个Person对象,如访问对象的成员变量(p.age)或者调用对象的方法(p.sayHello())。