关键字new
new是Java中的关键字,它将类的实例化为一个对象。类相当于是一张图纸,而new这个关键字的工作就是按照这个图纸建造出一栋房子来。
class Person{
String name;
public Person(String name){
this.name=name;
}
public void whoIam(){
System.out.println("我是人类:"+this.name);
}
}
public class Test {
public static void main(String[] args) {
Person zs=new Person("张三");
Person ls=new Person("李四");
zs.whoIam();
ls.whoIam();
}
}
运行结果:
我是人类:张三
我是人类:李四
如上面的结果所示,new制造出的对象均属于Person,而每个对象又是独立的个体。面向对象的编程语言有个很重要的特性,叫做继承。假如说有一个类继承了Person那么它实例化后和Person有什么关系呢?
class Person {
String name;
public Person(String name) {
this.name = name;
}
public void whoIam() {
System.out.println("我是人类:" + this.name);
}
}
class Student extends Person {
public Student(String name) {
super(name);
}
public void goToSchool(){
System.out.println(this.name+"正在上学");
}
@Override
public void whoIam() {
System.out.println("我是学生:" + this.name);
}
}
public class Test {
public static void main(String[] args) {
Person zs = new Person("张三");
Student ls = new Student("李四");
Person ww = new Student("王五");
zs.whoIam();
ls.whoIam();
ls.goToSchool();
ww.whoIam();
}
}
运行结果:
我是人类:张三
我是学生:李四
李四正在上学
我是学生:王五
从上面的代码可以看到,Student继承了Person,当我们使用Person ww = new Student("王五");
这种语句进行实例化时,实际上是按照new后面指定的类进行实例化的,也就是说new按照Student类为蓝图构建了一个对象。但是,这还没有结束当创建好对象之后,系统将会对这个对象进行向上转型,将其转换为Person对象,其实并不能这么说,可以说Person类是实例ww的表现类,ww只能使用Person类的方法表,这也是为什么ww对象没有goToSchool()这一方法的原因。当然,转型这一操作已经和new无关了,我们主要讨论的是new的工作。
对象的实例化意味着空间的使用,我们必须在内存中划分出一块地方用于存储对象的数据。JVM将会在java堆中保存对象的实例数据,在方法区中保存对象类型信息(class信息,如类有哪些成员属性、方法等,这对于反射机制的实现十分重要,使用object.getClass()可以获取到类型信息)。不过,在分配内存之前JVM首先得知道如何分配,就像之前说的,new会根据后面指定的类作为蓝图创建对象,首先得分析完这个蓝图,我们才能开始建造。所以,在创建一个对象时,我们首先得加载类,对类进行解析和初始化。由于多线程的存在,在内存分配的过程中必须要考虑到资源被抢占或者读取到脏数据的问题。一般来说有两种解决方案,一种是CAS加失败重试,一种是预分配。CAS(Compare And Swap)比较和交换,它有三个操作数,内存值V、预期值E、新值N,只有当V==N
为真时(这意味着内存值没有被其他线程所改变)才更新数据。预分配则是为每个线程预先在Java堆中分配一定的空间用来实例化对象,这个空间称为本地线程缓冲区(TLAB)。下面用伪代码来表示其流程
if(check(a_class)){//检查是否被加载过
length=a_class_objSize();//获取对象的长度
result=null;
if(useTLAB){//是否使用本地线程缓冲区
resutl=tlab.creatInstance();//在本地缓冲区创建实例
}
if(result==null){//它为空意味着没有使用本地线程缓冲区,或者缓冲区已满,创建失败
retry:
javaHeapTop=getHeapTop()//获取堆顶地址
newTop=javaHeapTop+length;//新值N,用于CAS操作
if(doCAS(javaHeapTop,getHeapTop(),newTop)){//cas操作成功
result=newTop;
}else
go retry;//回到重试结点进行重试
}
if(result!=null){
doInital(result);//对对象进行初始化
javaStack.add(result);//将引用入栈,
doNextCommand();//继续进行接下来的指令。
}
}else {
load(a_class);//启用类加载程序
}
注意,以上代码仅仅只是伪代码,用以演示流程。
空间分配完毕、对象的初始化也完成了之后,对象的引用可以通过操作符=
赋值给=之前的obj来保存,那么就可以通过操作obj来操作对象了。一如之前的Person ww = new Student("王五");ww.whoIam();
对象的引用用于查找对象的实例数据及类型信息。不同的JVM对于对象的访问也有不同的处理方式。有的JVM使用直接指针的方式寻找,也就是说,这个引用直接指向实例数据的地址。另一种方式则是在中间加了个句柄。引用指向句柄,而句柄指向实例数据及方法区的类型数据。这两种方式各有千秋,第一种方式对于对象的访问速度会更快,第二种方式则对于对象的移动操作以及对象转型操作更加方便,只需要改变句柄中的相应的地址即可。
至此,new关键字的工作过程就介绍完了,若有漏洞或错误欢迎在评论区中指出。