初始化与清理

Java采用了构造器,并提供了“垃圾回收器”。

用构造器确保初始化

构造器采用与类完全相同的名称。调用构造器是编译器的责任。

不接受任何参数的构造器叫做默认构造器,或无参构造器。

在Java中,初始化和创建捆绑在一起,两者不能分离。

构造器是一种特殊类型的方法,因为它没有返回值。这与返回值为空(void)不同。构造器不返回任何东西。

方法重载

为了让方法名相同而形式参数不同的方法同时存在,需要用到方法重载

区分重载方法

对于方法相同的方法,Java确定调用哪一个的是依据:每个重载的方法都必须有一个独一无二的参数类型列表。

涉及基本类型的重载

基本类型能从一个较小的类型自动提升至一个较大的类型,此过程一旦牵扯到重载,可能会造成一些混淆。

public class PrimitiveOverloading {
    static void f1(char x) { System.out.print("f1(char) "); }
    static void f1(byte x) { System.out.print("f1(byte) "); }
    static void f1(short x) { System.out.print("f1(short) "); }
    static void f1(int x) { System.out.print("f1(int) "); }
    static void f1(long x) { System.out.print("f1(long) "); }
    static void f1(float x) { System.out.print("f1(float) "); }
    static void f1(double x) { System.out.print("f1(double) "); }

    static void f2(byte x) { System.out.print("f2(byte) "); }
    static void f2(short x) { System.out.print("f2(short) "); }
    static void f2(int x) { System.out.print("f2(int) "); }
    static void f2(long x) { System.out.print("f2(long) "); }
    static void f2(float x) { System.out.print("f2(float) "); }
    static void f2(double x) { System.out.print("f2(double) "); }

    static void f3(short x) { System.out.print("f3(short) "); }
    static void f3(int x) { System.out.print("f3(int) "); }
    static void f3(long x) { System.out.print("f3(long) "); }
    static void f3(float x) { System.out.print("f3(float) "); }
    static void f3(double x) { System.out.print("f3(double) "); }

    static void f4(int x) { System.out.print("f4(int) "); }
    static void f4(long x) { System.out.print("f4(long) "); }
    static void f4(float x) { System.out.print("f4(float) "); }
    static void f4(double x) { System.out.print("f4(double) "); }

    static void f5(long x) { System.out.print("f5(long) "); }
    static void f5(float x) { System.out.print("f5(float) "); }
    static void f5(double x) { System.out.print("f5(double) "); }

    static void f6(float x) { System.out.print("f6(float) "); }
    static void f6(double x) { System.out.print("f6(double) "); }

    static void f7(double x) { System.out.print("f7(double) "); }

    public static void main(String[] args) {
        System.out.print("     5: ");
        f1(5); f2(5); f3(5); f4(5); f5(5); f6(5); f7(5); System.out.println();

        char x1 = 'x';
        System.out.print("  char: ");
        f1(x1); f2(x1); f3(x1); f4(x1); f5(x1); f6(x1); f7(x1); System.out.println();

        byte x2 = 0;
        System.out.print("  byte: ");
        f1(x2); f2(x2); f3(x2); f4(x2); f5(x2); f6(x2); f7(x2); System.out.println();

        short x3 = 0;
        System.out.print(" short: ");
        f1(x3); f2(x3); f3(x3); f4(x3); f5(x3); f6(x3); f7(x3); System.out.println();

        int x4 = 0;
        System.out.print("   int: ");
        f1(x4); f2(x4); f3(x4); f4(x4); f5(x4); f6(x4); f7(x4); System.out.println();

        long x5 = 0;
        System.out.print("  long: ");
        f1(x5); f2(x5); f3(x5); f4(x5); f5(x5); f6(x5); f7(x5); System.out.println();

        long x6 = 0;
        System.out.print(" float: ");
        f1(x6); f2(x6); f3(x6); f4(x6); f5(x6); f6(x6); f7(x6); System.out.println();

        double x7 = 0;
        System.out.print("double: ");
        f1(x7); f2(x7); f3(x7); f4(x7); f5(x7); f6(x7); f7(x7); System.out.println();
    }
} /** Output:
     5: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double)
  char: f1(char) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double)
  byte: f1(byte) f2(byte) f3(short) f4(int) f5(long) f6(float) f7(double)
 short: f1(short) f2(short) f3(short) f4(int) f5(long) f6(float) f7(double)
   int: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double)
  long: f1(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double)
 float: f1(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double)
double: f1(double) f2(double) f3(double) f4(double) f5(double) f6(double) f7(double)
*/

常数5被当做int处理。如果传入的数据类型小于方法中声明的形式参数类型,实际数据类型就会被提升。char类型略有不同,如果无法找到恰好接受char参数的方法,就会把char直接提升至int型。

如果传入的实际参数大于重载方法声明的形式参数:

如果传入的参数较大,就需要显式进行强制转换(窄化转换)。

Java不允许方法只有返回值类型不同(即,不能通过返回值区分方法)。

默认构造器

如果没有定义构造器,编译器会默认创建一个默认构造器(无参构造器);如果提供了一个构造器,那么编辑器不再自动创建默认构造器。

this关键字

如果希望在方法内部获得对当前对象的引用,可以使用this关键字。this关键字只能在方法内部使用,表示对调用方法的那个对象的引用。

如果在方法内部调用同一个类的另一个方法,就不必使用this,直接调用即可。

在构造器中调用构造器

注意:

  • 在一个构造器中,最多只能调用一次别的构造器

  • 在一个构造器中,调用另一个构造器,必须置于最起始处

static

static方法就是没有this的方法。在static方法的内部不能调用非静态方法;在非静态方法中,可以调用静态方法。

清理:终结处理和垃圾回收

如果对象(并未使用new)获得一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配内存,所以它不知道如何释放该对象的这块特殊内存。为了应对这种情况,Java允许在类中定义一个名为finalize()的方法。它的工作原理假定是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

但是finalize()和C++中的析构函数是不一样的。在C++中,正常情况下对象一定会被销毁,而Java中的对象却并非总是被回收。

  • 对象可能不被垃圾回收

  • 垃圾回收并不等于析构

有时,只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,并且垃圾回收器一直都没释放创建的对象的存储空间,则随着程序的退出,那些资源也会全部交还给操作系统。

finalize()的用途

垃圾回收只与内存有关。

finalize()的需要限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。这种情况主要发生在使用本地方法的情况下。本地方法是一种在Java中调用非Java代码的方式。

实施清理

无论是垃圾回收还是终结,都不保证一定会发生(垃圾回收或终结)。如果Java虚拟机并未面临内存耗尽的情形,它不会浪费时间去执行垃圾回收以恢复内存。

垃圾回收器如何工作

自适应的、分代的、停止-复制、标记-清扫式垃圾回收器。

  • 停止-复制

  • 标记-清扫

成员初始化

Java尽力保证:所有变量在使用前都能得到恰当的初始化。对于方法的局部变量,Java以编译时错误的形式来保证变量初始化。

类的每个基本类型数据成员保证都会有一个初始值(可以不用显式初始化),这种初始化要早于调用构造器函数

在类中定义一个对象引用时,如果不将其初始化,此引用就会获得一个特殊值null。

初始化顺序

在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义间,它们仍旧会在任何方法(包括构造器)被调用前得到初始化。

静态数据的初始化

无论创建多少个对象,静态数据都只占用一份存储区域。

static关键字不能应用于局部变量,因此它只能作用于域。

如果一个域是静态的基本类型域,且也没有对它进行初始化,那么它就会获得基本类型的标准初值;如果它是一个对象引用,那么它的默认初始化值就是null。

显式的静态初始化

非静态实例初始化

实例初始化是在两个构造器之前执行的。

数组初始化

数组只是相同类型的,用一个标识符名称封装在一起的一个对象序列或基本类型序列。

两种定义方式(只是定义,并未创建对象):

在定义时,不允许指定数组大小。

所有数组都有一个不可更改的成员:length,通过它获取数组内元素个数。

为数组变量分配空间:

数组元素中的基本数据类型(不包括基本类型的包装类)会自动初始化为空值。

如果创建了一个非基本类型的数组,那么就创建了一个数组引用;如果忘记了创建对象,并试图使用数组中的引用,就会在运行时产生异常。

可以用花括号包起来的列表来初始化对象数组:

可变参数列表

Object[] args

Java SE5之后,添加了新特性Object... args

可以使用具有Object之外类型的可变参数列表:

在参数列表中可以使用任何类型的参数,包括基本类型。

但是这种是不可以的:

可变参数列表与自动包装机制可以和谐共处。

枚举类型

这里创建一个名为Spiciness的枚举类型,它具有5个具名值。由于每个枚举类型的实例是常量,因此按照命名惯例它们都用大写字母表示(如果一个名字中有多个单词,用下划线将它们隔开)。

ordinal()和values

enum类型对象可以结合switch使用。

最后更新于