前言科学的持久荣誉在于它对人类思想的工作,以克服人类在自身和自然界面之前的不安全感。—爱因斯坦的“不安全”编程是导致编程成本高昂的罪魁祸首之一。有两个安全问题:初始化和清理。除了遵循C++构造函数的概念,Java还使用了垃圾收集器(GarbageCollector,GC)来自动回收不再使用的对象占用的资源。构造函数保证初始化在Java中,类的设计者通过构造函数保证每个对象的初始化。如果一个类有构造函数,Java会在用户使用该对象之前自动调用该对象的构造方法来确保初始化。无参构造函数是不接收参数的构造函数,用于创建一个“默认对象”。如果你创建一个类,而类中没有构造函数,编译器会自动为你创建一个无参构造函数.Java构造函数命名方法:构造函数名与类名相同。classUser{//explicit无参数构造User(){//这是一个构造函数System.out.print("User");}publicstaticvoidmain(String[]args){newUser();}}publicclassUser{publicstaticvoidmain(String[]args){//隐式无参构造newUser();}}输出:User构造方法也可以传入参数来定义如何创建对象。classUser{User(Stringname,intage){//这是一个参数化构造函数System.out.print("name:"+name+",age:"+age);}publicstaticvoidmain(String[]args){newUser("codeFarmInsights",30);}}Output:name:CodeFarmInsights,age:30方法重载将人类语言的细微差别映射到编程语言会产生问题。通常,同一个词可以表达许多不同的意思——它们是“超载”的。方法是行为的名称。您可以按名称引用所有对象、属性和方法。一个命名良好的系统易于理解和修改。这就像写散文——目的是为了与读者交流。在Java中,还有一个因素也在推动方法重载的使用:构造函数。因为构造方法名必须和类名相同,所以一个类中只会有一个构造方法名。那么如何以不同的方式创建对象呢?如果两个方法的名称相同,则每个重载方法都必须具有唯一的参数列表。原始类型可以自动从较小的类型转换为较大的类型。如果传入的参数类型大于方法期望接收的参数类型,必须先做向下转型,否则编译器会报错。成员初始化Java试图确保所有变量在使用前都已正确初始化。类的每个原始数据成员都保证有一个初始值。publicclassUser{Stringname="CodeInsights";intage;User(){//这是一个参数化构造函数System.out.print("name:"+name+",age:"+age);}publicstaticvoidmain(String[]args){newUser();}}Output:name:CoderInsights,age:0如何给变量赋初值?一个很直接的方法就是在类成员变量定义的地方赋值。publicclassUser{Stringname="CodeInsights";intage=30;User(){//这是一个参数化构造函数System.out.print("name:"+name+",age:"+age);}publicstaticvoidmain(String[]args){newUser();}}Output:name:CoderInsights,age:30构造函数初始化可以用构造函数初始化,这种方式给你更大的灵活性,因为你可以调用方法运行时初始化。然而,这并不能阻止自动初始化的发生,它发生在调用构造函数之前。所以,如果你使用下面的代码:;System.out.println(user.age);}}输出:30age会先被初始化为0,然后变为30。这适用于所有原始类型和引用,包括其初始值在定义时明确指定的变量。因此,编译器不会强制您在构造函数中的某处或在使用它们之前初始化元素——初始化已经得到保证。初始化顺序变量在类中定义的顺序决定了它们被初始化的顺序。即使变量定义散布在方法定义之间,它们仍然在调用任何方法(包括构造函数)之前被初始化。垃圾收集器程序员了解初始化的重要性,但往往忽略清理的重要性。毕竟,谁会清理int?但是,当您使用完一个对象后,将其单独放置并不总是安全的。Java中有一个垃圾收集器,可以回收未使用对象占用的内存。但是现在考虑一个特例:你创建的对象不是new分配的,垃圾回收器只知道如何释放new创建的对象的内存,所以不知道如何回收new没有分配的内存.为了处理这种情况,Java允许在类中定义一个名为finalize()的方法。它的工作原理“假设”是这样的:当垃圾回收器准备回收对象的内存时,会先调用它的finalize()方法,对象占用的内存要到下一轮才真正回收垃圾收集发生。也就是说,使用垃圾回收的唯一原因是回收程序不再使用的内存。所以对于任何与垃圾收集相关的行为(尤其是finalize()方法),它们也必须与内存及其收集相关。垃圾收集器是如何工作的停止复制(stop-and-copy)顾名思义,这个需要先暂停程序的运行(不属于后台回收模式),然后从当前堆中复制所有存活的对象到另一个堆,没有复制它只需要被垃圾收集。另外,当对象被复制到新的堆中时,它们彼此紧密排列,然后可以像上面描述的那样简单直接地分配新空间。当一个对象从一个地方复制到另一个地方时,对它的所有引用都必须是固定的。位于堆栈或静态存储中的引用可以直接固定,但在遍历期间可能会找到对这些对象的其他引用(想想将旧地址映射到新地址的表)。标记-清除(mark-and-sweep)“标记-清除”的思想还是从栈和静态存储区出发,遍历所有的引用,找到所有存活的对象。但是,每当找到存活的对象时,都会在该对象上设置一个标记,并且不会对其进行回收。只有在标记过程完成后,清洁动作才会开始。在清理期间,未标记的对象被释放并且不进行复制。“标记清除”后剩余的堆空间是不连续的。如果垃圾收集器想要获得连续的空间,就需要重新排列剩余的对象。Sun的早期版本的Java虚拟机一直在使用这种技术。Java虚拟机中有许多额外的技术来提高速度。特别是涉及到加载器的运行,这项技术被称为“即时”(Just-In-Time,JIT)编译器。该技术可以将程序全部或部分翻译成本地机器码,因此不需要JVM进行翻译,因此运行速度更快。当一个类需要被加载时(通常是该类的第一个对象被创建),编译器首先找到它的.class文件,然后将类的字节码加载到内存中。你可以让即时编译器编译所有的代码,但是这种做法有两个缺点:一是这个加载动作贯穿了整个程序生命周期,加起来需要更多的时间;另一个是它会增加可执行代码的长度(字节码比即时编译器扩展的本地机器码小得多),从而导致分页,这肯定会减慢程序的速度。另一种做法称为惰性求值,这意味着即时编译器只在必要时编译代码。因此,从未执行过的代码可能根本就没有经过JIT编译。新版JDK中的JavaHotSpot技术采用了类似的方式。代码在每次执行时都会进行优化,因此执行的次数越多,速度就越快。总结由于需要确保创建所有对象,构造函数实际上比这里讨论的要复杂。特别是在通过组合或继承创建新类时,这种保证仍然有效,并且需要一些额外的语法来支持它。想要了解更多,敬请关注后续文章!最后一篇为初学者提供学习指南,对从业者有参考价值。我坚信编码员也有能力产生洞察力。扫描下方二维码关注、学习、交流!
