2. Classes and Objects
2.1. A Simple Class
Java比C++的简单在于,对象全部是动态创建的,Java的变量只有两种意义,要不就是基本类型变量,要不就是对象引用(类似于C++的指针)。
定义一个类的时候,可以在Class关键字前面加上修饰符。这里作者推荐了一个修饰符的顺序表:
- 1.annotations(这个是java里面新加入的特性)。
- 2.public:如果不加这个修饰符,定义的类就只能在包内部使用
- 3.abstract:加入这个修饰符,表明这个类是个抽象类,含有抽象方法,不能用创建这个类的对象。
- 4.final:加入这个修饰符,这个类就不能被继承了。
- 5.strictfp:这个类的所有浮点运算须要精确执行。
按照作者的推荐,定义类的时候,应该按照这个顺序给类加上修饰符,当然abstract和final这两个修饰符是不能共存的。同时一个文件中应当有且仅有一个和这个文件同名的public类。
2.2. Fields
域(Field)指的是类的数据成员,包括基本类型和对象引用。每个域定义的前面都可以加上修饰符,和C++不太相同的是,访问控制(比如public,private)这种修饰符必须加到每个域的前面。和class的修饰符顺序列表相似,对于域,也有对应的一个列表。
-
1.annotations
-
2.access control modify,包括public,private,protected。
-
3.static
-
4.final
-
5.transient
-
6.volatile
-
final和volatile不能共存。
域的定义后面可以跟初始化语句,
-
double zero = 0.0; // constant
-
double sum = 4.5 + 3.7; // constant expression
-
double zeroCopy = zero; // field
-
double rootTwo = Math.sqrt(2); // method invocation
-
double someVal = sum + 2*Math.sqrt(rootTwo); // mixed
-
final域表明这个域经过初始化以后,就不能再被赋值,因此只能被初始化一次。对于static final域来说,必须在这个类初始化的时候被初始化,而对于非static final域,则须要在对象被初始化的时候被初始化。
2.3. Access Control
access control有四种类型:
- 1.private:域只能在这个类内部被访问。
- 2.public:只要能访问这个类,就能访问这个域
- 3.protected:可以在这个类内部,同一个包(packet)的内部,或者派生类的内部访问这个域。
- 4.如果没有使用修饰符的话,那么定义的类或者域只能在包内部访问。
而private和protected仅仅能用在member的定义中,不能用于class或者interface的定义。
在一个类中,要访问protected域,必须使用这个类或者其派生类对象的引用,不能使用这个类超类对象的引用。
比如这个例子:
- //in one packet
- pubic class A
- {
- protected int a;
- }
- //in another packet
- class B extends A
- {
- void test(A objectA)
- {
- //error:cannot access "a" with a superclass object reference
- System.out.println(objectA.a);
- }
- }
2.5. Construction and Initialization
Java的构造函数和C++很类似,不过Java没有C++那种成员初始化列表,因为即使是final成员,也可以推迟到构造函数内部进行初始化。对于超类的部分,也可以在构造函数中,调用超类的构造函数初始化。
Java同c++一样,如果不提供初始化函数,那么编译器就会生成一个默认的不带参数的构造函数,C++生成的默认构造函数是public的,而Java生成的构造函数的访问控制同这个类的访问控制相同,也就是说这个类是public类,那么构造函数就是public的,如果类是packet的,那么构造函数也是packet的。
Java不会有默认的拷贝构造函数(copy constructor),因为拷贝构造函数对于Java来说意义不大,不过用户也可以提供拷贝构造函数。Java所有类(除去基本类型)从Object类中继承了clone方法。
Java在构造函数中,可以通过this关键字调用另外一个构造函数,这被称为显式构造函数调用,如果在构造函数中,出现了显式构造函数调用的语句,那么这个语句必须出现在构造函数的第一行,而且提供给显式构造函数的参数,不能引用这个类里面的非静态成员。理由很简单,在调用完这个语句之前,对象应该还未准备好。
C++中的域都是在构造函数中初始化的,而Java中域的初始化的方式就多了,可以在域的定义后提供初始化语句,可以提供初始化块(initialization block)。
初始化块分为static和非static两种,前者不能引用非static域或者函数。初始化块不仅仅可以初始化域,它能够做其他任意的事情。在一个类的对象被构造的时候,首先按照初始化语句和初始化块出现的顺序依次执行,之后再调用构造函数,当然如果这个类是个派生类,那么先构造基类的对象。
而当这个类首次被使用的时候,就会按照出现的先后顺序,依次执行这个类的初始化语句和初始化块,来初始化这个类。当然如果这个类是派生类,那么首先初始化这个类的基类。
-
2.6. Methods
-
方法定义的修饰符顺序:
-
annotations
-
access modifiers
-
abstract
-
static
-
final
-
synchronized
-
native
-
strictfp
于C++的不同,方法默认就是virtual的,除非用了final修饰符。abstract方法是不能有函数体的,如果一个类中有了abstract方法,那么这个类就是abstract的。abstract方法不能有native,strictfp, synchronized、final、static修饰符。
在Java中定义可变数量参数的方法很简单,比如
public void test(Object... objects);
就可以通过任意数量的Object对象调用这个函数了,在函数体中,objects可以当作Object对象引用的数组来使用。不过也可以直接通过Object对象引用的数组来调用这个函数。
书上给出了一个有意思的例子,用null来调用这个函数,那么编译器就不知道到底这个null是指的是一个为null的数组,还是一个为null的Object对象,所以这种情况下,应该通过显式的类型转换来告诉编译器。