当前位置: 首页 > 后端技术 > Java

面试官:Java中newanobject的过程是怎样的?完全不知所措,.如何创建

时间:2023-04-01 16:17:55 Java

对象,这个太熟悉了,来new吧(其实有很多种方式,反射、反序列化、克隆等等,这里介绍最简单的new):Dogdog=newDog();我们总是习惯于固定语句的执行,但对它背后的执行过程却缺乏了解。理解这个过程其实对后面晦涩的反射和代理有很大的帮助,所以请一定要学好这个内容。在看这篇文章之前,先说一句啰嗦的话:如果你死记硬背下面提到的过程,那是毫无价值的。即使你现在记住了,一周、一个月你能记住多少,因为对象创建过程的知识,平时的工作基本不会涉及到,太底层了,熟悉的知识点也没有经常使用但很容易忘记,那么我的建议是什么?过程大概脑子里有一个数字,里面涉及到关键知识,记住就好。JVM内存我简单说一下java虚拟机的内存模型和存储内容的区别。分为两部分:栈内存存储基本类型的数据和对象的引用变量,可以直接访问数据,比堆快。堆内存存储创建的对象和数组,将由java虚拟机的自动垃圾回收(GC)管理。当一个对象被创建并放入堆中时,会在栈上创建一个指向该对象在堆内存中地址的引用变量。下面说的对象就是存在于这段记忆中,我们会根据对象生成的过程,对过程中涉及到的各种概念一一进行解释。首先,有这么一个类,后面的初始化都是按这个解释的:/***@authorWeige*@since2021-04-1811:01:41**执行顺序:(优先级从高到低。)静态代码块>构造代码块>构造方法>普通方法。*其中静态代码块只执行一次。每次创建对象时都会执行构造代码块。*/publicclassDog{//狗的默认最大年龄是16岁privatestaticintdog_max_age=16;//狗的名字privateStringdog_name;{System.out.println("狗的构造代码块");}static{System.out.println("狗的静态代码块");}//没有参数的构造函数不是故意设置的//有参数的构造函数publicDog(Stringdog_name){this.dog_name=dog_name;}publicvoidgetDogInfo(){System.out.println("名字是:"+dog_name+"年龄:"+dog_max_age);}//狗叫声publicstaticvoidbarking(){System.out.println("Wowwoof~~~");}}JVM生成.class文件。编译时会初始化一个java文件,生成.class字节码文件。字节码文件由JVM专门读取。我们平时写的代码行最后会被编译成机器可以理解的语句,这个文件稍后会被类加载器加载到内存中。类加载器加载.class文件《深入理解Java的虚拟机》,大概有这么一句话:当虚拟机遇到新的指令时,会检查是否能在静态常量池中定位到一个类的符号引用(对于这个类path+name),检查这个符号引用代表的类是否已经被加载、解析和初始化。如果不是第一次使用,则必须先执行相应的类加载过程,这个过程由类加载器完成。类加载的字面意思可以理解为加载一个类文件。更准确的说,它会将类文件变成二进制流加载到内存中,也就是将类的描述信息加载到Metaspace中。至于类加载器是如何找到并把一个class文件转换成IO流加载到内存中的。稍后我会写一篇关于类加载器的文章。在这里,你只需要了解创建对象有这么一个步骤即可。不过这里有一个很重要的概念不得不说一下:类对象知识拓展:类对象是重点,这是一个很重要的概念,理解它对后面理解反射和代理类加载器有很大的帮助ClassLoader加载类在创建文件时,类中的一些数值常量、方法、类信息等会被加载到内存中,称为类的元数据。最终目的是生成一个Class对象来描述类,这个对象会保存在.class文件中。在class文件中,有些新手看到这里可能会疑惑,难道class也有对象吗?当然,Class是真正的类(用来描述类的类,有点啰嗦),有构造函数(private表示可以生成对象,不能手动生成,Class对象是自动生成的)由JVM创建),类加载器会为每个java文件创建一个Class对象来描述类。我画个图://下面的操作只能jvm做,我们手动做不了Classcls1=newClass(Dog.class.getClassLoader());Classcls2=newClass(Cat.class.getClassLoader();Classcls3=newClass(People.class.getClassLoader());这个Class对象除了描述对应的类外还有什么作用呢?也可以生成对象,这是java的反射概念(通过Class实例获取类信息)。上面说了Class类是用来描述People.Class这样的类的,所以它必须包含所有可以描述这个类的属性,比如类名、方法、接口等,我们先来看看Class类源码:有个方法newInstance(),就是创建一个对象,我把源码贴出来简单分析一下:@CallerSensitivepublicTnewInstance()throwsInstantiationException,IllegalAccessException{if(System.getSecurityManager()!=null){checkMemberAccess(Member.PUBLIC,Reflection.getCallerClass(),false);}if(cachedConstructor==null){if(this==Class.class){thrownewIllegalAccessException("无法在java.lang.Class类上调用newInstance()");}尝试{Class[]empty={};//声明无参构建对象finalConstructorc=getConstructor0(empty,Member.DECLARED);//禁用构造函数的可访问性检查//因为无论如何我们都必须在这里进行安全检查//(构造函数的堆栈深度是错误的//安全检查才能工作)java.security.AccessController.doPrivileged(newjava.security.PrivilegedAction(){publicVoidrun(){c.setAccessible(true);返回null;}});cachedConstructor=c;}catch(NoSuchMethodExceptione){//如果类中没有无参构造函数,抛出InstantiationException错误throw(InstantiationException)newInstantiationException(getName()).initCause(e);}}构造函数tmpConstructor=cachedConstructor;//安全检查(与java.lang.reflect.Constructor相同)intmodifiers=tmpConstructor.getModifiers();如果(!Reflection.quickCheckMemberAccess(this,modifiers)){Classcaller=Reflection.getCallerClass();if(newInstanceCallerCache!=caller){Reflection.ensureMemberAccess(caller,this,null,modifiers);newInstanceCallerCache=调用者;}}//Runconstructortry{//最后调用无参构造器对象newInstance方法returntmpConstructor.newInstance((Object[])null);}catch(InvocationTargetExceptione){Unsafe.getUnsafe().throwException(e.getTargetException());//未达到returnnull;首先找出newInstance的两个方法的区别:Class.newInstance()只能调用不带参数的Constructor,即默认构造函数。我们在Class源码中也看到,其实最后调用的是无参构造函数对象Constructor的newInstance方法。例如:Dog.class中没有无参构造函数,所以会直接抛出一个InstantiationException://Dog类中只有一个dog_name参数化构造方法Classc=Class.forName("com.service.ClassAnalysis.Dog");Dogdog=(Dog)c.newInstance();//直接抛出InstantiationException。Constructor.newInstance()可以根据传入的参数调用任意构造函数,也可以体现私有构造函数(理解就好)//Dog类中只有一个dog_name参数化构造函数Constructorcs=Dog.class.getConstructor(String.class);Dogdog=(Dog)cs.newInstance("小黑");//执行没有问题,但是newInstance和我们要说的new方法有区别这一次,两个create对象的创建方式不同,创建条件也不同:使用newInstance时,必须保证类已经加载,连接已经建立,即已经被加载类记录器,不需要类对象的newInstance方法只能无参构造。上面说了new不需要前者使用类加载机制,是一个方法,后者是创建一个新类,一个关键字。这不能说newInstance不方便。相反,它被用在了反射、工厂设计模式中,对我后续的代理起到了重要的作用proxy和reflection我也会记下来,因为真的很费解,还有一点要注意,无论哪种方式创建对象,对应的Class对象都是一样的Dogdog1=newDog("Wangcai");Dogdog2=newDog("小黑");Classc=Class.forName("com.service.classload.Dog");//为了测试,增加了一个无参构造Dogdog3=(Dog)c.newInstance();System.out.println(dog1.getClass()==dog2.getClass());System.out.println(dog1.getClass()==dog3.getClass());这个阶段连接和初始化都是静态的先在变量内存中分配存储空间,设置初始值(还没有初始化)例如:publicstaticinti=666;//加载到内存中就会执行由类加载器赋初值publicstaticIntegerii=newInteger(666);//同样赋初值但注意i的初值实际上是0,不是666,初始其他基本数据类型如boolean的值为false等。如果是引用类型的成员变量ii,则初始值为null。Dogdog=newDog("Wangcai");//这里打断点执行,先执行静态成员变量初始化,默认值为0:但也有例外,如果加上final修饰符,初始值是设定值。然后给分配了存储空间的静态变量赋值,比如给上面的dog_max_age赋值16,执行一段静态代码块,类似于下面的代码:static{System.out.println("Dog的静态代码块");}至此,该类的加载过程算是完成了。创建实例加载类后,根据类信息确定所需对象的大小。具体创建步骤如下:首先给对象分配内存(包括本类和父类的所有实例变量,不包括上面的静态变量),并设置默认值,如果有引用对象,就申请堆栈内存中的空间指向实际对象。执行初始化代码实例化,先初始化父类再初始化子类,并赋值给定(尊老爱幼是java的传统美德)。对象实例化后,如果有引用对象,第一步的栈对象需要指向堆。内存中的实际对象,以便创建真正可用的对象。说了这么多,估计很多人都没有概念。在无知的状态下,其实很简单。我们只需要记住,创建新对象需要两个步骤:初始化和实例化,然后给你画一张图:很容易理解。②③④是Initialization⑤Instantiation(妈的,我这该死的责任感!)这篇文章不指望你看懂java虚拟机底层加载和创建对象的过程(其实有些步骤我懒得讲了,因为它们都是非常理论化的。不是很有趣),我想让你知道对象的诞生过程中涉及哪些重要的概念。了解这些概念比简单了解对象创建的过程更有意义:类加载器,网上可以查到资料,很多,只要了解Class类和Class对象的概念,请重点掌握,否则会反射和动态代理很难理解,spring的源码也会很难理解栈内存,堆内存以及对应的基本类型,而且引用的类型也很重要,尝试理解源码:blog.csdn.net/qq_16887951/article/details/115872678近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.不要用爆破爆满画面,试试装饰者模式,这才是优雅的方式!!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!