成员初始化
所有变量在使用前都能得到恰当的初始化。
对于方法的局部变量,Java以编译时错误的形式来贯彻这种保证。
类的数据成员是基础类型,情况就会变得有些不同。因为类的每一个基础类型成员保证都有一个初始值。
在类里定义一个对象引用时,如果不将其初始化,此引用就会获得一个特殊值null。
甚至可以通过某个方法来提供初值,方法也可以带有参数,但是参数必须是被初始化了的。
public class Banana {
int i = f();
int j = g(i);
int f(){
return 10;
}
int g(int n){
return n * 10;
}
}
上述程序的正确性取决于初始化的顺序,而与编译方式无关。所以,编译器恰当地对向前引用发出了警告。
构造器初始化
可用构造器初始化,但无法阻止自动初始化的进行,它将在构造器被调用之前发生(基础类型和所有对象都适用)。因此,编译器不会强制你一定要在构造器的某个地方或在使用它们之前对元素进行初始化——因为初始化早就得到了保证。
初始化顺序
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量散布于方法定义之间,它们依旧会在任何方法(包括构造器)被调用之前得到初始化。
public class Window {
Window(int market){
System.out.println("Window(" + market + ")");
}
}
public class House {
Window w1 = new Window(1);
House(){
System.out.println("House()");
w3 = new Window(33);
}
Window w2 = new Window(2);
void f(){
System.out.println("f()");
}
Window w3 = new Window(3);
}
public class Test {
public static void main(String args[]){
House house = new House();
house.f();
}
}
结果:
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
可见w3被初始化两次:一次在调用构造器前,一次在调用期(第一次引用的对象将被丢弃,并做为垃圾回收)。
静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能应用于局部变量,因此它只能作用于域。如果一个域是静态的基本类型域,且没有初始化,那么它就会获得基本类型的标准初值;如果他是一个对象引用,那么他的默认初始化值是null。
如果想在定义处进行初始化,采取的方法与非静态数据没什么不同。
public class Bowl {
Bowl(int marker){
System.out.println("Bowl(" + marker + ")");
}
void f1(int marker){
System.out.println("f1("+ marker +")");
}
}
public class Table {
static Bowl bowl1 = new Bowl(1);
Table(){
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker){
System.out.println("f2("+ marker +")");
}
static Bowl bowl2 = new Bowl(2);
}
public class Cupboard {
Bowl bowl3 = new Bowl(3);
static Bowl bowl4 = new Bowl(4);
Cupboard(){
System.out.println("Cupboard()");
bowl4.f1(2);
}
void f3(int marker){
System.out.println("f3("+ marker +")");
}
static Bowl bowl5 = new Bowl(5);
}
public class Test {
public static void main(String args[]){
System.out.println("Creating new Cupboard() in main");
new Cupboard();
System.out.println("Creating new Cupboard() in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
输出:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
由此可见,静态初始化只有在必要的时候才会进行。如果不创建Table对象,也不引用b1和b2,那么静态的b1,b2永远都不会被创建。只有第一个Table对象被创建或者第一次被访问静态数据,他们才会被初始化。此后,静态对象不会再次被初始化。
初始化顺序是先静态对象(如果他们尚未因前面的对象创建过程而被初始化),而后是非静态对象。
总结(Dog类):
1、即使没有显式的使用static关键字,构造器实际上也是静态方法。因此当首次创建类型为Dog的对象时,或者Dog类的静态方法/静态域首次访问时,Java解释器必须查找类路径,一定为Dog.class文件。
2、然后载入Dog.class(创建一个Class对象),有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
3、当用new Dog()创建对象时候,首先将在堆上为Gog对象分配足够的存储空间。
4、这块存储空间会被清零,这就自动地将Dog对象中的所有基础类型数据都设置成默认,引用为null。
5、执行所有出现于字段定义处的初始化动作。
6、执行构造器。
显式的静态初始化
Java允许将多个静态初始化动作组织成一个特殊的静态子句(静态块)。
public class Cup {
Cup(int marker){
System.out.println("Cup("+ marker +")");
}
void f(int marker){
System.out.println("f("+ marker +")");
}
}
public class Cups {
static Cup cup1;
static Cup cup2;
static {
cup1 = new Cup(1);
cup1 = new Cup(2);
}
Cups(){
System.out.println("Cups()");
}
}
public class Test {
public static void main(String args[]){
Cups.cup1.f(99); //1
}
//static Cups cups1 = new Cups(); //2
//static Cups cups2 = new Cups(); //2
}
输出:
Cup(1)
Cup(2)
f(99)
static {}看起来像一个方法,但它实际上只是一段跟在static后面的代码。与其他静态初始化动作一样,这段代码仅执行一次:当首次生成这个类的一个对象时,或者首次访问属于那个类的静态数据成员时(即使从未生成过那个类的对象)。
无论是运行1还是2,Cups的静态初始化动作都会得到执行。如果同时注释掉,那么静态初始化动作就不会执行。另外无论是激活cups1还是cups2都无关紧要,静态初始化动作只进行一次。
非静态实例初始化
Java中也有被成为实例初始化的类似语法,用来初始化每一个对象的非静态变量。
public class Cup {
Cup(int marker){
System.out.println("Cup("+ marker +")");
}
void f(int marker){
System.out.println("f("+ marker +")");
}
}
public class Cups {
Cup cup1;
Cup cup2;
{
cup1 = new Cup(1);
cup1 = new Cup(2);
System.out.println("cup1 & cup2 initialized");
}
Cups(){
System.out.println("Cups()");
}
Cups(int i){
System.out.println("Cups(int)");
}
}
public class Test {
public static void main(String args[]){
new Cups();
new Cups(1);
}
}
输出:
Cup(1)
Cup(2)
cup1 & cup2 initialized
Cups()
Cup(1)
Cup(2)
cup1 & cup2 initialized
Cups(int)
{
cup1 = new Cup(1);
cup1 = new Cup(2);
}看起来它与静态初始化子句一模一样,只是少了一个static关键字。这种语法对于支持匿名内部类的初始化是必须的,但是它也使得你可以保证无论调用了哪个显式构造器,某些操作都会发生。从输出中可以看到实例初始化子句是在两个构造器之前执行的。
数组初始化
定义数组:int[] a1;或int a1[],两种格式的含义是一样的,后一种格式符合c和c++程序员的习惯。前一种格式更合理,它表明“一个int型数组“。
编译器不允许指定数组的大小。现在拥有的只是对数组的一个引用(已分配足够的空间),而且没有给数组对象分配任何空间。为了给数组创建相应的存储空间,必须写初始化表达式。初始化动作可以出现在代码任何地方。但int[] a1 = {1,2,3,4,5};这种,存储空间的分配(等价于new)将有编译器负责。
public class Test {
public static void main(String args[]){
int[] a1 = {1,2,3,4,5};//静态初始化数组
int[] a2;
a2 = a1;
for(int i = 0;i< a2.length;i++){
a2[i] = a2[i] + 1;
}
for(int i = 0;i< a1.length;i++){
System.out.println("a["+ i +"]:" + a1[i]);
}
//动态初始化数据
String books[] = new String[2];
books[0] = "Thinking in Java";
books[1] = "Effective Java";
}
}
输出:
a[0]:2
a[1]:3
a[2]:4
a[3]:5
a[4]:6
代码只给出了a1的初始值,但a2没有,a2只是复制了a1的引用。引用型数据
所有数组(无论它的元素是对象还是基本类型)都有一个固定成员(length),可以通过它获知数组包含多少个元素,但不能对其修改。最大下标length-1,c和c++允许超出边界,并允许访问所有内存,Java一旦访问下标过界,则会出现运行时异常。
int[] a = new int[5];
不能确定数组元素个数可以直接使用new在数组里创建元素。数组元素中的基本类型数值会自动初始化成空值。
Array.toString()方法属于java.util标准类库,打印一维数组。
初始化对象数组:
1、Integer[] a = new Integer[2];
a[0] = 100;
2、Integer[] a = new Integer[]{1,2,3};
可变参数列表
public class VarArgs {
static void printArray(Object[] args){
for(Object obj : args){
System.out.print(obj + " ");
}
System.out.println();
}
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(),new A()});
}
}
输出:
47 3.14 11.11
one two three
thinking.A@7f31245a thinking.A@6d6f6e28 thinking.A@135fbaa4
获得与C的可变参数列表一样的效果。创建以Object数组为参数的方法。
标准Java库中的类能输出有意义的内容,但这里建立的类的对象只输出了类名和@符号以及十六进制数字。于是,默认行为(没有定义toString()方法的话)就是打印类的名字和对象地址。
Java SE5特性:
public class VarArgs {
static void printArray(Object... args){
for(Object obj : args){
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String[] args) {
printArray(new Integer(47),new Float(3.14),new Double(11.11));
printArray("one","two","three");
printArray(new Object[]{new A(),new A(),new A()});
printArray();
}
}
有了可变参数就再也不用显式的编写数组语法了,当指定参数时,编译器实际上会为你把元素转换成数组。而编译器发现已经是一个数组,就不会做任何转换。
重载:
public class VarArgs {
static void printArray(Character... args){
System.out.println("first");
for(Object obj : args){
System.out.print(obj + " ");
}
System.out.println();
}
static void printArray(Integer... args){
System.out.println("second");
for(Object obj : args){
System.out.print(obj + " ");
}
System.out.println();
}
static void printArray(Long... args){
System.out.println("third");
for(Object obj : args){
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String[] args) {
printArray('a','b','c');
printArray(1);
printArray(0L);
//printArray();
}
}
输出:
first
a b c
second
1
third
0
编译器会使用自动包装机制来匹配重载的方法,然后调用最明确的方法。在使用无参调用时,无法知道调用那个方法。可以在某个方法中增加一个非可变参数来解决问题。static void printArray(float i,Character… args)
枚举类型
enum
public enum Spiciness {
NOT,MILD,MEDIUM,HOT,FLAMING
}
public class Test {
public static void main(String[] args){
Spiciness howHot = Spiciness.MEDIUM;
System.out.println(howHot);
}
}
/* Output
MEDIUM
*/
命名惯例都用大写字母。
编译器会自动添加一些有用的特性,创建toString()方法,一边显示某个enum实例的名字。还会创建ordinal()方法,来表示某个特定enum常量的声明顺序,以及static values()方法,用来按照enum常量的声明顺序,产生由这些常量值构成的数组。
public class Test {
public static void main(String[] args){
for (Spiciness s : Spiciness.values()){
System.out.println(s + ", ordinal " + s.ordinal());
}
}
}/*Output
NOT, ordinal 0
MILD, ordinal 1
MEDIUM, ordinal 2
HOT, ordinal 3
FLAMING, ordinal 4
*/
由于switch是要在有限的可能值集合中进行选择,因此它与enum正是绝佳的组合。
public class Burrito {
Spiciness degree;
public Burrito(Spiciness degree){
this.degree = degree;
}
public void describe(){
System.out.print("This burrito is ");
switch (degree){
case NOT:
System.out.println("not spicy at all.");
break;
case MILD:
case MEDIUM:
System.out.println("a little hot.");
break;
case HOT:
case FLAMING:
default:
System.out.println("maybe too hot.");
}
}
}
public class Test {
public static void main(String[] args){
Burrito
plain = new Burrito(Spiciness.NOT),
greenChile = new Burrito(Spiciness.MEDIUM),
jalapeno = new Burrito(Spiciness.HOT);
plain.describe();
greenChile.describe();
jalapeno.describe();
}
}/*Output
This burrito is not spicy at all.
This burrito is a little hot.
This burrito is maybe too hot.
*/