『Thinking in Java 读书笔记』—— 5-初始化与清理

  |     |   本文总阅读量:

Thinking in java 读书笔记

随着计算机革命的发展,不安全的编码方式逐渐成为编程代价高昂的主因之一。

前言

初始化和清理是涉及程序安全的了两个问题,如果处理不好的话,很容易在运行的时候出现奔溃,造成很不好的用户体验。首先初始化,在开发的过程中,每当创建基本类型或者是其他对象,都会下意识的初始化,虽然全局变量会默认有个初始值,局部变量编译时期就会报错,但是我们还是要养成创建同时初始化的好习惯。至于清理,不像 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里的对象并非总是被垃圾回收,主要有三个特点:

  1. 对象可能不被垃圾回收。
  2. 垃圾回收并不等于析构。
  3. 垃圾回收只与内存有关。

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的垃圾回收器可以极大地简化编程工作,而且在处理内存的时候也更安全,有些垃圾回收器甚至能清理其他资源,比如图形和文件句柄。


赏我 e(=2.72) 元咖啡钱吧,您的支持将鼓励我继续创作!



文章目录
  1. 1. 前言
  2. 2. 用构造器确保初始化
  3. 3. 方法重载
  4. 4. this 关键字
  5. 5. 清理:终结处理和垃圾回收
  6. 6. 数组的初始化
  7. 7. 枚举
  8. 8. 总结
您是第 位小伙伴 | 本站总访问量 | 已经写了 85.9k 字啦

载入天数...载入时分秒...