随着计算机革命的发展,不安全的编码方式逐渐成为编程代价高昂的主因之一。
前言
初始化和清理是涉及程序安全的了两个问题,如果处理不好的话,很容易在运行的时候出现奔溃,造成很不好的用户体验。首先初始化,在开发的过程中,每当创建基本类型或者是其他对象,都会下意识的初始化,虽然全局变量会默认有个初始值,局部变量编译时期就会报错,但是我们还是要养成创建同时初始化的好习惯。至于清理,不像 C/C++
内存需要自己分配和释放,Java
内置的虚拟机,已经帮助我们完成这件事情了,但是在开发的过程中,还是要注意一些资源的释放,免得造成内存泄漏等异常。
用构造器确保初始化
如果每个对象都要构造一个Initialize()
的方法,显然是不实际的,总不能让每次创建对象的时候都去调用这个方法吧。因此Java
引入了构造函数这个概念。
Student(int age) {
System.out.println("student init age:" + age);
}
有点类似方法的形式,名称和类名保持一致,首字母大写,可选择携带参数,无返回值。
new Student(12);
这个时候将会分配存储空间,并调用相应的构造器,这就确保了在你能操作对象之前,它已经被恰当的初始化了。
方法重载
拥有名称重复的方法,但是参数的个数或者类型要不一致,构造函数亦然。那Java
如何知道是哪一个方法呢?其实规则很简单:每个重载方法都必须有一个独一无二的参数类型列表。
甚至参数顺序的不同也足以区分两个方法,不过一般情况下别这么做,因为会使代码难以维护。
那为什么不以返回值区分重载方法呢?
void f();
int f();
int a = f()
这种确实可以区分,但是f()
这种方式编译器就不知道你需要调用的是哪个方法了。
this 关键字
this
关键字只能在方法中使用,表示对调用方法的那个对象的引用。this
的用法和其他对象引用并无不同,但要注意的是,如果在方法内部调用同一个类和同一个方法,就不必使用this
,直接调用即可。
构造函数中是可以调用自身的构造器的,常见的就是创建一个自定义view
,这个时候默认创建 4 个构造函数,最终都是调用最长的那个构造函数。
class People {
People(){
this(12, "default");
}
People(int age){
this(age, "default");
}
People(int age, String name){
//....
}
}
static
就更好理解了,static
方法就是没有this
的方法,在static
方法的内部不能调用非静态方法,反过来是可以的。
清理:终结处理和垃圾回收
Java
里的对象并非总是被垃圾回收,主要有三个特点:
- 对象可能不被垃圾回收。
- 垃圾回收并不等于析构。
- 垃圾回收只与内存有关。
finalize
Java
允许在类中定义一个名为finalize
的方法,它的工作原理假定是这样的:一旦下一次垃圾回收器准备好释放对象占用的内存空间,将首先调用一个名为finalize
的方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存,所以要是你打算用finalize()
,就能在垃圾回收时刻做一些重要的清理工作。但是它是无法预料,常常是危险的,总之是多余的。
垃圾回收器如何工作
在以前,在堆上分配对象的代价十分昂贵,然而垃圾回收期对于提高对象的创建速度,却有明显的效果,这意味着Java
从堆分配空间的速度,可以和其他语言从堆栈中分配空间的速度相媲美。
「引用计数」是一种简单但是速度很慢的垃圾回收技术。每个对象都要有一个引用计数器。当有引用连接至对象时,引用计数就加1,当引用离开对象或被置为null
。虽然管理引用计数的开销不大,但这项开销在整个程序生命周期中将持续发生,垃圾回收期会在含有全部对象的列表上遍历,当发现对象的引用计数为 0 时,就释放器占用的控件,这种方法有个缺陷,当对象之间存在循环引用,虽然引用计数不为零,但是其实这个对象应当被回收,因此引用计数似乎未被应用于任何一种Java
虚拟机实现中。
现在有一种区别于引用计数技术,它们依据的思想是:对任何“活”的对象,一定能最终追溯其存活在堆栈或静态存储区中的引用。这就解决了交互自引用的对象组的问题。
在这种技术下,Java
虚拟机将采用一种「自适应」的垃圾回收技术。至于如何处理找到存活的对象,取决于不同的Java
虚拟机实现。
「停止-复制」,需要暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的全部都是垃圾。当对象被复制到新的堆中时,它们是一个挨着一个,所以新堆保持紧凑排列,然后可以分配新空间了。对于这种方式,效率会降低,这有两个原因。首先两个堆,然后得在这两个分离的堆之间来回倒腾,从而维护比实际需要多一倍的空间。某些Java
虚拟机对此问题的处理方式是:按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。第二个问题在于复制,程序进入稳定状态之后,可能会产生少量垃圾,甚至没有垃圾,尽管如此,复制式回收器会将所有内存自一处复制到另一处,这很浪费。
为了避免这种浪费,一些Java
虚拟机会进行检查,要是没有新垃圾产生,就会转换到另一种工作模式。
「标记-清扫」,对一般用途而言,速度相当慢,但是当你知道只会产生少量垃圾甚至不会产生垃圾时,它的速度就会非常快。它所依据的思路同样是从堆栈和静态存储区出发,便利所有的引用,进而找出所有存活的对象。每当找到一个存活对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理动作才开始。不会发生复制动作,所以剩下的对空间是不连续的,垃圾回收器要是希望得到连续的空间的话,就得重新整理剩下的对象。
Java
虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器效率降低的话,就切换到「标记-清扫」方式,同样,Java
虚拟机会跟踪效果,要是堆空间出现很对碎片,就会切换回到「停止-复制」的方式,这就是「自适应」技术。
数组的初始化
数组是相同类型的、同一个标识符名称被封装到一个对象序列活基本类型数据序列。
可变参数
public void f(Object... args) {
for (Object arg : args) {
System.out.println(arg);
}
}
Student student = new Student(2);
student.f("x",2, "dfa", 0.2f);
// output
x
2
dfa
0.2
枚举
enum Color {
RED, BLUE, BLACK
}
Color red = Color.RED;
switch (red) {
case RED:
System.out.println("red");
break;
case BLUE:
System.out.println("blue");
break;
default:
break;
}
总结
在Java
中,垃圾回收器会自动为对象释放内存,所以在很多场合下,类似的清理方法在Java
中就不太需要了(不过当要用的时候,你就只能自己动手了)。在不需要类似析构函数的行为的时候,Java
的垃圾回收器可以极大地简化编程工作,而且在处理内存的时候也更安全,有些垃圾回收器甚至能清理其他资源,比如图形和文件句柄。