23 December 2013

最近一直在学习Android,荒废了博客。与其说是学习其他的技术,倒不如说是突然就没有了动力,相信大家也时常会出现突然什么都不想做的情况。开始写“从零”系列,就知道自己给自己挖了一个大坑,这种系列有个特点,就是一种线性系统的学习,而不是觉得哪里有问题,哪里有趣,就写哪里。但这无不鞭笞着自己系统的学习,加固基础。也许这部分是几周之前看过的,但重新整理后仍然发现很多地方第一次阅读没有注意到,真真是“温故而知新”。

今天要说说初始化,Java尽力保证所有变量在使用前都能得到恰当的初始化。对于方法的局部变量,Java以编译错误的形式来贯彻这种保证。这确实是Java众多好处之一,我们不会出现像C/C++语言中的,引用未被初始化的指针这种情况,它无疑使我们的编程更加“安全”。对于基本类型,Java会自动为其初始化,而其他类的成员等,需要显式初始化,否则会编译错误。

尽管如此,但自动初始化的进行我们无法阻止,它将在构造器调用之前发生,比如:

public class Counter {
    int i;
    Counter() {i = 7;}
}

这里,i值首先被初始化0,再被构造器赋值为7,这里的自动初始化无法避免。

初始化顺序的问题,我也引用书中的例子,非常直观:

class Window {
    Window(int marker) {print("Window(" + marker +")");}
}

class House {
    Window w1 = new Window(1);
    House() {
        print("House()");
        w3 = new Window(33);
    }
    Window w2 = new Window(2);
    void f() {print("f()");}
    Window w3 = new Window(3);
}

public class OrderOfInitialization {
    public static void main(String[] args) {
        House h = new House();
        h.f();
    }
}

看看输出是什么:

Window(1)
Window(2)
Window(3)
House()
Window(33)
f()

看以看到,无论Window对象的对应在哪里(本例中散乱在四处),初始化都是自动进行,且在构造器之前进行。在House的构造器中,w3又被初始化了一次。

静态数据的初始化需要注意,static关键字不能应用于局部变量,因此它只能作用于域。静态数据的初始化的不同处在于,只有在对象被创建(或者第一次访问静态数据时),它们(类中的静态数据)才被初始化。此后,静态数据不会再被初始化。先初始化静态对象(如果它们尚未因前面的对象创建过程而被初始化。),另外,构造器实际上也是静态方法。

Java允许将多静态初始化动作组织成一个特殊的”静态子句“(也叫静态块):

public class Spoon {
    static int i;
    static {i = 47;}
}

类中第二行即是一个静态子句,它仅仅执行一次(当首次生成这个类的一个对象时,后者首次访问属于那个类的静态数据成员时。)。Java也支持非静态实例的初始化:

public class Mugs {
    Mug mug1;
    Mug mug2;
    {
        mug1 = new Mug(1);
        mug2 = new Mug(2);
    }
    Mugs() {}
}

接下来看看数组初始化我们需要注意的地方。要定义一个数组,有两种方法:

int[] a;
int a[];

第二种比较常见,类似C/C++中的数组定义。这两种定义方法完全一样,但在Java中,还是使用第一种方法比较容易理解。数组比较特殊的地方就是它的长度都是固定的,按照上面的定义,我们并没有指定数组长度。实际上,编译器不允许指定数组的大小,但我们可以这样初始化数组:

int[] a = {1, 2, 3, 4, 5};

在C/C++中也有这种方法初始化数组,这时,数组的大小就是5。

其实之所以不允许直接定义数组大小,因为我们实际上定义的只是数组的引用而已,引用在没有初始化时当然不知道其指向的数组大小。引用也可以相互进行赋值操作:

int[] b;
b = a;

这里只是复制了数组的引用,如同C/C++中的指针,一个变化了另一个也做相应的变化。我们也可以通过new来初始化数组,尤其我们不确定数组大小时:

Random rand = new Random(47);
int[] a;
a = new int[rand.nextInt(20)];

数组也可以是非基础类型,比如:

Integer[] a = new Integer[rand.nextInt(20)];

也可以如此初始化:

Integer[] b = {
    new Integer(1),
    new Integer(2),
    3,
};

最后一个逗号可选。

接下来说说可变参数列表。可变参数列表在Java中实现非常简单,先看一个比较有趣的用法:

public static void main(String[] args) {
	printArray(new Object[]{
        new Integer(47), new Float(3.14), new Double(11.11);
    });
    printArray(new Object[]{
         "one", "two", "three";
	});
	printArray(new Object[]{
		new A(), new A();
	});
}

准确来讲这并不能完全算作可变参数列表的例子,因为还是要手动添加数组中的变量,算在一种已知的情况下,但是这里我们看到,例子中的数组能够包含多种数据形式。由于所有的类都是直接或间接的继承Object类,在这里,这些类都提升到Object类。

再举一例:

public class OptionTrailingArg {
	static void f(int require, String...trailing) {
		System.out.print("require : " + require + " ");
		for (String s: trailing)
			System.out.print(s + " ");
		System.out.println();
	}
	public static void main(String[] args) {
		f(1, "one");
		f(2, "two");
		f(0);
		f(3, "four", "five")
	}
}

输出为:

require: 1 one
require: 2 two
require: 0
require: 3 four five

没错,Java中用“…”表示可变数组。那么可变的范围是多少,从0到无穷(可能吧?)。所以第三行输出仅仅是0,字符串没有输入。

尽管Java提供的可变数组如此方便,但滥用也会带来不良的效果。比如:

public class Overloading {
	static void f(float i, Character...args) {
		System.out.print("first");
	}
	static void f(Character...args) {
		System.out.print("second");
	}
	public static void main(String[] args) {
		f(1, 'a');
        f('a', 'b');
    }
}

这时编译会提示错误,第一次调用f可以找到是第一个f方法,但第二次的调用,Java便不知道哪一个是最佳的匹配。所以,应该只在重载方法的一个版本上使用可变参数,或者压根就不使用它。



blog comments powered by Disqus