温馨提示:本系列博文(含示例代码)已经同步到 GitHub,地址为「java-skills」,欢迎感兴趣的童鞋
Star
、Fork
,纠错。
与其他语言相比,Java 的一大特点就是其自动的初始化与清理功能。对于基本数据类型的全局变量,Java 自动将其初始化为对应的默认值,具体可以参考「对象漫谈」中的内容;对于对象,我们可以通过构造方法对其进行初始化;对于清理操作,Java 提供了垃圾回收机制,其可以帮我们自动清理不再使用的对象,释放资源。
构造方法是一种特殊的方法,它是一个与类同名且没有返回值类型的方法。对象的创建就是通过构造方法来完成,其功能主要是完成对象的初始化。当类实例化一个对象时会自动调用构造方法。构造方法和其他方法一样也可以重载。在 Java 中,任何变量在被使用前都必须先设置初值,构造方法就是专门为类的成员变量赋初值的方法。构造方法的特殊性主要反映在如下几个方面:
void
也没有;new
操作完成的;static
、final
、synchronized
、abstract
和native
修饰;我们也可以通过构造代码块(包括静态和非静态两种)给对象进行初始化,对象一建立构造代码块就执行,而且优先于构造函数执行。构造代码块和构造函数的区别在于构造代码块是给所有不同对象的共性进行统一初始化,构造函数则是给对应的对象进行初始化。
此外,还有一点需要特别注意:如果类中定义了构造方法且都不是无参的,那么编译器也不会自动创建无参的构造方法,而是根据参数个数和类型,按顺序进行匹配,直到找到对应的构造方法;当我们调用了无参的构造方法实例化对象时,编译器就会报错啦,因为现在全是有参的构造方法,没有无参的构造方法。
在 GitHub 的项目「java-skills」中,提供了测试代码,感谢兴趣的同学可以自行下载体验。
对于 Java 语言,它会尽量保证:所有变量在使用前都能够得到恰当的初始化。我们也无法阻止自动初始化的进行,它将在构造器被调用之前发生。在类的内部,变量定义的先后顺序决定了初始化的顺序,即使变量定义散布于方法定义之间,它们仍然会在任何方法(包括构造器)被调用之前得到初始化。
无论创建多少个对象,静态数据都只占用一份存储区域。由于static
关键字不用应用于局部变量,因此它只能作用于域。静态初始化只有在必要时刻才会进行,静态对象也不会再次被初始化。
初始化的顺序是先静态对象(如果它们尚未因前面的对象创建过程而被初始化),然后是非静态对象。构造方法可以看成静态方法。静态初始化只在Class
对象首次加载的时候进行一次。
显式的静态初始化和非静态的实例初始化类似,都在构造器之前执行初始化动作,两者的区别在于:静态初始化有static
关键字修饰且只能被初始化一次;而非静态的实例初始化则可以被初始化多次。
垃圾回收,一直都是 Java 语言中一个值得称赞的特性。But,它并没有我们想象中的那么好用。垃圾回收只与内存有关,也就是说,使用垃圾回收的唯一原因就是为了回收程序不再使用的内存。如果 JVM 并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。这意味着,垃圾回收不能如我们预期那样的工作,甚至它可能都不会工作,以至于某些已经废弃的对象根本就不会被回收。
有些人可能会说,“我们可以调用finaliza()
方法来保证垃圾回收器的工作”,这其实是误解了finaliza()
方法的含义。finaliza()
方法的工作原理是:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finaliza()
方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。因此,利用finaliza()
方法,我们可以在垃圾回收器执行时做一些重要的清理工作。
在「对象漫谈」中,我们曾说过,方法名和参数列表构成了「方法签名」,它能够唯一的标识出一个具体的方法。方法重载,是指方法名相同,而参数个数不同、参数类型不同或者参数个数和参数类型都不同。实际上,参数顺序不同也可以区分两个方法,但是建议不要这么做,因此这会让代码难以维护。例如,
package com.hit.chapter5;
/**
* author:Charies Gavin
* date:2017/12/22,21:04
* https:github.com/guobinhit
* description:测试方法重载
*/
public class Overload {
public static void main(String[] args) {
introduce(18, "Charies");
introduce("Charies", 18);
}
public static void introduce(int age, String name) {
System.out.println("My name is " + name + ", I'm " + age + " old!");
}
public static void introduce(String name, int age) {
System.out.println("My name is " + name + ", I'm " + age + " old!");
}
}
如上述代码所示,我们通过声明不同的参数顺序,区分了重载方法。But,我们不能通过方法的返回值来区分重载方法。
此外,如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,实际参数类型就会被提升,如声明int
类型、传入short
类型,则short
类型会自动被提升为int
类型。如果传入的实际参数类型较大,就会通过类型转换来执行窄化转换,如声明int
类型、传入double
类型,则double
类型会自动窄化为int
类型。
对于同一个类型的多个对象,编译器是如何让它们能够正确调用各自的方法呢?例如,
package com.hit.chapter5;
/**
* author:Charies Gavin
* date:2017/12/25,9:07
* https:github.com/guobinhit
* description:测试 this 关键字
*/
public class MottoHIT {
public static void main(String[] args) {
Hiter hiter_1 = new Hiter();
Hiter hiter_2 = new Hiter();
hiter_1.sayMotto("Zora");
hiter_2.sayMotto("Charies");
}
}
class Hiter {
public void sayMotto(String name) {
System.out.println(name + ": 规格严格,功夫到家");
}
}
实际上,在编译的时候,编译器会“偷偷”的将“所操作对象的引用”当做第一个参数传递给调用的方法,例如上述的调用会变为:
Hiter.sayMotto(hiter_1, "Zora");
Hiter.sayMotto(hiter_2, "Charies");
上面的格式为编译器内部的表示形式,我们并不能这样书写代码,也编译不过。为此,Java 提供了一个关键字this
,this
只能在方法的内部调用,表示“调用方法的那个对象”的引用。
此外,如果在构造器中为this
添加了参数列表,那么将产生对符合此参数列表的构造器的明确调用。尽管可以用this
调用构造器,但仅能调用一个不能调用多个,并且对构造器的调用必须为构造器内的第一行代码,否则编译器就会报错。如下图所示,
在了解this
关键字之后,我们会发现static
方法其实就是没有this
关键字的方法。我们也应该听说过,“在static
方法的内部不能调用非静态方法,反之可以”。实际上,这并不是绝对的,如果我们传递一个对象的引用到静态方法里,然后通过这个引用,我们就能够调用非静态方法和非静态的数据成员。
———— ☆☆☆ —— 返回 -> 那些年,关于 Java 的那些事儿 <- 目录 —— ☆☆☆ ————