Java 版 (精华区)
发信人: joyfree (thinking in java中.....), 信区: Java
标 题: Java语言规格说明
发信站: 哈工大紫丁香 (2002年08月19日22:59:27 星期一), 站内信件
1.程序结构
Java语言的源程序代码由一个或多个编译单元(compilation unit)组
成,每个编译单元只能包含下列内容(空格和注释除外):
* 一个程序包语句(package statement )
* 引入语句(import statements)
* 类的声明(class declarations)
* 界面声明(interface declarations)
每个Java的编译单元可包含多个类或界面,但是每个编译单元却至
多有一个类或者界面是公共的。
Java 的源程序代码被编译之后,便产生了Java字节代码(bytecode)。
Java的字节代码由一些不依赖于机器的指令组成,这些指令能被Java的
运行系统(runtime system)有效地解释。Java的运行系统工作起来如同一
台虚拟机。
在当前的Java实现中,每个编译单元就是一个以.java为后缀的文件。
每个编译单元有若干个类,编译后,每个类生成一个.class文件。.class
文件是Java虚机器码。
2. 词法问题
在编译的过程中,Java源程序代码中的字符被划分为一系列的标记
(token)。Java编译器可以识别五种标记: 标识符、关键字、字面量、运
算符以及特殊分隔符。注释以及诸如空格、制表符、换行符等字符 ,都
不属于标识之列,但他们却常被用来分隔标记。
Java程序的编写采用泛代码Unicode字符集,若采用其它的字符集,
则需在编译前转换成Unicode。
2.1 注释
Java语言提供了3种形式的注释:
//text 从//到本行结束的所有字符均作为注释而被编译器忽略。
/* text */ 从/*到*/ 间的所有字符会被编译器忽略。
/** text */
当这类注释出现在任何声明之前时将会作特殊处理,它们不能再用
在代码的任何地方。这类注释意味着被括起来的正文部分,应该作为声
明项目的描述,而被包含在自动产生的文档中。
2.2 标识符
标识符的首字符必须是一个字母,下划线("_")或美元符号("$")。 后
面的字符也可是数字0-9。 Java使用泛代码字符集,为了便于识别好一
合法标识符,下面列出它的“字母”:
* 大写字母“A”~“Z”
* 小写字母“a”~“z”
* 泛代码(Unicode)中所有字符编码在十六进制数00C0之前的字
符。标识符中,首字母后的字符可以是任意的。当然,Unicode区段中
那些被保留作特殊字符的除外。
由此,“garton”及“MjΦlner”都是合法标识符,但是,包括
诸如“π”的字符串却不是合法的。
为了取得更多的有关泛代码标准的信息,请参阅“The Unicode
Standard”,“World Wide Character Encoding version 1.0,volumes 1 &
2”,Unicode公司的FTP地址是unicode.org。
2.3 关键字
下面的标识符被保留用作关键字,他们不能作任何其它的用途。
abstract default goto* null synchronized
boolean do if package this
break double implements private threadsafe
byte else import protected throw
byvalve * extends instanceof public transient
case false int return true
catch final interface short try
char finally long static void
class float native super while
const * for new switch
continue
其中,加*标记后是被保留但当前却未使用的。
2.4 字面量
字面量(literal)是某些类型值的基本表述,这些类型包括整型,浮点
型,布尔量,字符及字符串。
2.4.1 整型字面量
整数可有三种表示形式: 十进制,八进制和十六进制。一个十进制
整型字面量由一系列的数字组成,但它的第一个数字不能是0(有时十进
制数字也可象下面讲的那样加后缀)。整数也可表达成八进制或十六进制
形式。以0开头的整型字面量,意味着它是一个十六进制的。十六进制
整数可以包括数字0-9以及字母a-f及A-F。八进制整数中则只能是出现
数字0-7。在超过32位所能表示的范围之前,整型字面量的类型即为int,
否则为long型。一个整型字面量可通过加后缀L或l而强迫成long型。
下面的均为合法的整型字面量。
2 2L 0777
2.4.2 浮点字面量
一个浮点字面量可包括以下部分: 一个十进制整数,一个小数点“.”,
小数部分(另外一个十进制整数),指数部分,一个类型后缀。指数部分
是一个e或E后跟一个整数。浮点字面量至少包含有一个数字,外加或
者一个小数点或者一个e(或E),下面举一些浮点字面量的例子:
3.1415 3.1E12 .1e12 2E12
就象在后面描述的那样,Java语言有两种浮点类型: float 及
double,用户可按以下写法区分:
2.0d或2.0D double 型
2.0f或2.0F或2.0 float型
2.4.3 布尔字面量
布尔(boolean)字面量有两个值: true及false。
2.4.4 字符字面量
字符字面量是一个由单引号括起的字符(或者是由一组字符来表述
一个字符)。字符属于char类型,并且均从泛代码字符集中得来。而下面
列出的转义序列则用来描述一些非图形字符,它们以反斜杠“\”开始以
作转义用。
续行符头 <newline> \
换行 NL(LF) \n
垂直制表符 HT \t
退格 BS \b
回车 CR \r
走纸换页 FF \f
反斜杠 \ \\
单引号 ' \'
双引号 " \"
八进制数 0ddd \ddd
十六进制数 0xdd \xdd
泛代码字符 0xdddd \udddd
2.4.5 串字面量
串字面量是双引号引起的零个或多个字符的序列。每个串字面量被
看作是一个串对象,而并非是一个字符的数组,例如“abc”创建了一
个新的串类的实例。下面的都是合法的串字面量:
" " \\空串
"\" " \\只包含一个双引号的串
"This is a string"
"This is a \
two-line string"
2.5 运算符及特殊分隔符
下面这些字符在Java源程序中作运算符或分隔符用:
+ — ! % ^ & * | ~ / > <
( ) { } [ ] ; ? : ,· =
另外,下面这些复合字符被用作运算符:
++ -- == <= >= != << >>
>>> += -= *= /= &= /=
^= %= <<= >>= >>>= ‖ &&
后面还要在运算符一节中作详细介绍。
3. 类型
任何一个变量或表达式都有一个类型,类型决定变量可能的取值范
围,决定对这些值允许的操作,以及这些操作的意义是什么。Java语言
中提供了内置定义类型,程序员也可以利用类及界面(interface)机制构造
新类型。
Java语言有两种类型: 简单类型和复合类型。简单类型指那些不能
再分割的原子类型。如:整型、浮点型、布尔型、字符型均为简单类型。
复合类型建立在简单类型的基础上。Java语言有三种复合类型:数组、
类及界面。在本节中,我们主要讨论简单类型及数组。
3.1 数值类型
3.1.1 整数类型
整数与C及C++中相似,但有两点区别: 其一,所有的整数类型
均是独立于机器的;其二,对某些传统的定义作出改变,以反映自C问
世以来所带来的变化,四种整数类型分别具有8位、16位、32位及64
位的宽度,并且均是有符号的(signed)。如下所示:
宽度
类型名
8
byte
16
short
32
int
64
long
一个变量的类型不会直接影响它的存储的分配,类型仅仅决定变量
的算术性质以及合法的取值范围。如果把一个超出合法范围的值赋给一
变量,那么这个值将是对合法值域取模后的值。
3.1.2 浮点类型
关键字float表示单精度(32位),而double则表示双精度(64位),两
个float型数运算的结果仍是float型,若有其中之一为double型,则结
果为double型。浮点运算及数据格式按IEEE754中的定义,细节问题请
参阅 “附录A: 浮点”中有关浮点实现的细节。
3.1.3 字符类型
Java全部使用泛代码字符集,因此char类型数据被定义成一个16
位的无符号整数。
3.2 布尔类型
当一个变量的取值或为ture或为false,或者是当一个方法的返回值为ture或false时,
它?nbsp;
嵌际遣级嘈偷摹A硗猓叵翟怂愕慕峁嗍遣?nbsp;
尔型的。
布尔值不是数值型,因此不能用强制类型转换把它们转化成数值。
3.3 数组
数组在Java语言中属第一类对象。由它们代替了指针运算,所有的
对象(包括数组)都可通过标识来引用。即使被当作数运算,标识的值也
不应被破坏。通过new运算符可创建一个数组。
char s[]=new char[30];
数组第一元素的下标为0,在声明中指定维数是不允许的。每次都
必须显式地用new分配数组:
int i [] =new int [3];
Java语言不支持多维数组,但是,程序员却可以创建数组的数组。
int i [ ] [ ]=new int [3][4];
至少有一维要明确给定,而其它维则可在以后再确定。例如:
int i[] []=new int [3] [ ]
是一个合法的声明。
除了在变量名及方法名后跟方括号这种C风格的声明之外,Java语
言允许方括号跟在数组类型之后,下面两行是等价的:
int iarray[ ];
int [ ] iarray;
同样地,方法声明也一样:
byte f(int n)[ ];
byte [ ] f(int n);
运行时检查下标保证它们是合法的:
int a[ ]=new int [10];
a[5]=1;
a[1]=a[0]+a[2];
a[-1]=4; // 运行时引发一个ArrayIndexOutOfBoundsException(数组下
标越界)异常
a[10]=2; //运行时引发一个ArrayIndexOutOfBoundsException(数组下
标越界)异常
数组的大小必须使用整数表达式:
int n;
float arr[ ]=new float[n+1]
数组的长度可通过.length 查找:
int a[] []=new int [10][3];
println (a.length) //打印出10
println (a[0].length) //打印出3。
3.3.1 数组细节
我们所定义的数组都是Object类的一个子类的实例,在类的层次结
构中有一个被称为Array的子类,它有一个实例变量“length”。对每
一个基本类型,都有一个相应的Array的子类。同理,每一个类也都有
一个相应的Array子类存在。例如: new Thread[n] 创建一个Thread[ ]
的实例。如果类A是类B的超类,那么,A[]是B[]的超类,见下图:
Object
Array A
int[] float[] A[] B
B[]
因此,可以把数组赋给一个Object变量。
Object o;
int a[]=new int [10];
o=a;
并且可通过强制类型转换把object变量赋给一数组变量。
a=(int [])o;
Array类本身不能显式地产生子类。
4. 类
类(class)是传统面向对象编程模型的象征。它们支持数据抽象以及实
现对数据的约束,在Java中,每一个新的类创建一个新类型。
要想得到一个新的类,程序员必须首先找到一个已有的类,新类即
在这个已有类的基础上构造,我们称之为派生(derived)。派生出的类亦称
为原来类的子类,而这个类我们称为超类(super class)。
类的派生具有传递性: 如果B是A的子类,C是B的子类,则C
是A的子类。
一个类的直接超类以及指示这个类如何实现的界面(interface),在类
的声明中,由关键字extends及implements标出。如下示(黑体表示关键字)::
[doc_ comment] [modifer] class ClassName
extends Superclassname
implements interface {,interface } ] {
class body
}
举例:
/** 2 dimension point */
public class Points {
float x,y;
......
}
/** printable point */
class PinttablePoint extends Points implements Printable {
......
public void Print ( ) {
}
}
所有的类均从一个根类 Object中派生出来。除Object之外的任何类
都有一个直接超类。如果一个类在声明时未指明其直接超类,那么缺省
即为Object。如下述:
class Point {
float x,y
}
与下面写法等价
class Point extends Object {
float x, y;
}
Java语言仅支持单继承,通过一个被称作“界面”的机制,来支持
某些在其它语言中用多继承实现的机制(详见“界面”一节)。Java之所
以没有采用C++的多继承机制,是为了避免多继承带来的诸多不便,例
如:可能产生的二义性,编译器更加复杂,程序难以优化等问题。
4.1 类类型之间的强制转换
Java语言支持在两个类型之间的强制转换,因为每个类即是一个新
的类型。Java支持类类型之间的强制转换,如果B是A的子类,那么B
的一个实例亦可作为A的实例来使用,虽然不需要显式的转换,但显式
转换亦是合法的,这被称作拓宽(widening)。如果A的一个实例,想当作
B的实例使用,程序员就应写一个类型转换叫作削窄(narrowing)的强
制。从一个类到其子类的强制转
换在运行时要作例行的检查以确保这个对象就是其子类的一个实例
(或其子类之一)。兄弟类之间的强制类型转换是一个编译错误,类的强制转换的语法如
下?nbsp;
?nbsp;
(classname) ref
其中,(classname)是要转换的目的类,而ref是被转换的对象。
强制转换仅仅影响到对象的引用,而不会影响对象本身。然而,对实例
变量的访问却受到对象
引用的类型的影响。一个对象从一个类型到另一类型的强制转换后,
可以使同一变量名对不同的实例变量访问。
class ClassA{
String name = "ClassA"
}
class ClassB extends ClassA { //ClassB是ClassA的子类
String name="ClassB";
}
class AccessTest {
void test( ) {
ClassB b=new ClassB( );
println (b.name); //打印: ClassB
ClassA a
a=(ClassA)b;
println (a.name); //打印: ClassA
}
}
4.2 方法
方法(method)是可施于对象或类上的操作,它们既可在类中,也可在
界面中声明。但是他们却只能在类中实现(Java中所有用户定义的操作均
用方法来实现)。
类中的方法声明按以下方式:
[Doc_ comment] [Access Specifiers] ReturnType
methodName(parameterList)
{
method body(本地的native及抽象的方法没有体部分)
}
除构造函数可以无返回类型外,其余的方法都有一个返回类型。如
果一个不是构造函数的方法不返回任何值,那么它必须有一个void的返
回类型。
参数表由逗号分隔的成对的类型及参数名组成,如果方法无参数,
则参数表为空。方法内部定义的变量(局部变量)不能隐藏同一方法的其
它变量或参数。例如: 如果一个方法带以名为i的参数实现,且方法内
又定义一个名为i的局部变量,则会产生编译错误,例如:
class Rectangle {
void vertex (int i,int j) {
for (int i=0; i<=100; i++) { //出错
…
}
}
}
方法体内循环语句中声明的i是一个编译错误。
Java语言允许多态方法命名,即用一个名字声明方法,这个名字已
在这个类或其超类中使用过,从而实现方法的覆盖(overriding)及重载
(overloadding)。所谓覆盖是对继承来的方法提供另一种不同的实现。而
重载是指声明一个方法,它与另外一个方法有相同的名字,但参数表不同。
注: 返回类型不能用来区别方法,即在一个类的范围内,具有相同
的名字,相同的参数表(包括个数、位置及类型)的方法,必须返回相同
的类型。若这样的两个方法有不同的返回类型,将会产生一个编译错误。
4.2.1 实例变量
实例变量(instance variables)是指那些在类内声明,但在方法的作用
域之外尚未被static标记的变( 参照 “静态方法,变量及初始化”段)。
而在一个方法的作用域之内声明的变量是局部变量。实例变量可以有修
饰符(见修饰符)。
实例变量可以是任何的类型,并且可以有初始值。如果一个实例变
量无初始值,它将被初始化成0。布尔型变量被初始化成flase,对象被
初始化成null。下面是一个实例变量j具有初始化值的例子:
class A{
int j =23;
……
}
4.2.2 this 和super变量
在一个非静态方法的作用域内,this这个名字代表了当前对象。例
如: 一个对象可能需要把自己作为参数传给另一个对象的方法:
class MyClass {
void Method (OtherClass obj) {
…
obj.Method (this)
…
}
}
不论何时,一个方法引用它自己的实例变量及方法时,在每个引用
的前面都隐含着“this”。
如:
class Foo {
int a,b,c;
......
void myPrint ( ) {
print (a+ "\n"); // a=="this.a"
}
......
}
super变量类似于this变量。this变量实际上是对当前对象的引用,
它的类型就是包含当前正在处理的方法的类。而super变量则是对其超类
类型对象的引用。
4.2.3 设置局部变量
方法都要经过严格的检查,以确保所有的局部变量(在方法内声明的
变量)在被引用之前已设初,被初始化之前就使用局部变量是一个编译错
误。
4.3 覆盖方法
要想覆盖(overiding)一个方法,必须在声明这个方法的类的子类中声
明一个与它具有相同名字,相同返回类型,以及相同参数表的方法。当
子类的实例调用这个方法时,被调用的是新定义的方法而不是最初的那
个。被覆盖了的方法可通过super变量来调用,如下:
setThermostat(…) //指覆盖的方法
super. setThermostat(…) //指被覆盖的方法
4.4 重载的认定
重载(overload)的方法与已有的某个方法同名,但是变元的个数及/
或类型不同,重载的认定是要决定调用的是哪一个方法,在认定重载的
方法时,不考虑返回类型。方法可以在同一类的内部重载,类内部方法
声明的顺序并不重要。
可同时改变变元个数和类型来实现方法的重载。 编译器认定相匹配
的方法时以最低的类型转换开销为准。只有具有相同名字及相同变元个
数的方法才会优先考虑。所有变元都必须转换,是匹配方法时的最大开
销。有两种变元类型是必需考虑的:对象类型和基本类型。
对象类型间转换的开销的大小指类树上实在参数的类与原型参数的
类之间连线的个数。只考虑拓宽型转换(对象类型转换的详细信息,请参
阅“类之间的强制类型转换”)。对于类型完全匹配的变元,无须进行转
换,它们的开销为零。
基本类型的转换开销按下表计算,开销为零是严格匹配。
to
byte short char int long float double
byte 0 1 2 3 4 6 7
short 10 0 10 1 2 4 5
from char 11 10 0 1 2 4 5
int 12 11 11 0 1 5 4
long 12 11 11 10 0 6 5
float 15 14 13 12 11 0 1
double 16 15 14 13 12 10 0
注:开销 >=10 易引起数据丢失。
一经认定某一匹配方法是哪种转换开销,编译器则选用转换开销最
小的匹配。如果有多于一个方法,其最小开销相同,则匹配有二义性,
要出编译时的错误。
例如:
class A{
int method (Object o, Thread t);
int method(Thread t,Object o);
void g(Object o,Thread t){
method(o,t); //调用第一个方法
method(t,o); //调用第二个方法
method(t,t); //有二义性,编译出错
}
}
4.5 构造函数
构造函数(constructor)是提供初始化的专用方法。它和类的名字相同,
但没有任何返回类型。构造函数在对象创建时被自动地调用,它不能被
对象显式调用。如果你想在包(package)之外调用构造函数,就将构造函
数设为“public”(参见“访问指明符“一节)。
构造函数也可以用不同个数和类型的参数重载,就象其它的方法被
重载一样。
class Foo {
int x;
float y;
Foo() {
x=0;
y=0.0;
}
Foo (int a ) {
x=a;
y=0.0;
}
Foo (float a ) {
x=0;
y=a;
}
Foo (int a,float b ) {
x=a;
y=b;
}
static void myFoo( ) {
Foo obj1=new Foo( ); //调用Foo( );
Foo obj2=new Foo(4 ); //调用Foo( int a );
Foo obj3=new Foo(4.0 ); //调用Foo( float a );
Foo obj4=new Foo(4,4.0 ); //调用Foo(int a , float b );
}
}
超类的实例变量由调用超类的或当前类的构造函数初始化。如果在
代码中没有指定由谁初始化,则调用的是超类中的无参数的构造函数。
如果一个构造函数调用了这个类或其直接超类的另一个构造函数,这个
调用必须是构造函数体的第一条语句,在调用构造函数之前实例变量不
引用。
调用直接超类的构造函数如下:
class MyClass extends OtherClass {
MyClass (someParamenters ) {
/* 调用父类构造函数 */
super(otherParameters);
} …
}…
调用当前类的构造函数如下示:
class MyClass extends OtherClass {
MyClass (someParameters) {
…
}
MyClass(otherParameters) {
/*调用当前类的构造函数,该函数有指定的参数表*/
this (someParameters);
…
}
…
}
下面的Foo和FooSub类的方法中使用构造函数的例子:
class Foo extends Bar {
int a;
Foo(int a) {
//隐含调用Bar( )
this.a=a;
}
Foo( ) {
this (42); //调用Foo(42),代替Bar( )
}
}
class FooSub extends Foo {
int b;
FooSub (int b) {
super(13); //调用Foo(13); 去掉此行将调用Foo( )
this.b=b;
}
}
4.6 用new运算符创建对象
类是用来定义对象的状态和行为的模板,对象是类的实例。类的所
有实例都分配在可作无用单元回收的堆中。声明一个对象引用并不会为
该对象分配存储,程序员必须显式地为对象分配存储,但不必显式删除
存储,无用单元回收器会自动回收无用的内存。
分配对象存储用new运算符。除了分配存储之外,new还初始化实
例变量,调用实例的构造函数。构造函数是初始化对象的一种方法(见“构
造函数”),下面的例子是分配并初始化ClassA的一个实例:
a = new ClassA( );
以下构造函数是带有参数的例子:
b = new ClassA(3,2);
分配的第三种形式允许以串表达式形式提供类名字,字符串在运行
时刻求值,new返回一个Object类型的对象,再转换成所希望的类型。
b = new ("class"+"A");
这种情况下,调用构造函数时无需参数。
4.6.1 无用单元收集
无用单元收集器(Garbage Collector)使存储管理的许多方面变得简
单、安全。程序不需要显式释放内存,它是自动处理的。无用单元收集
器不会释放正在被引用的内存,只释放那些不再被引用的空间。这就防
止了悬挂指针和存储漏洞(leak)。它也使设计人员从系统的存储管理中解
脱出来。
4.6.2终止
Java的构造函数完成一个对象初始化工作,Java的终止
(finalization)方法则完成和析构函数类似的功能,但与C++不同之处,是
它可显式调用。虽然Java的无用单元的回收功能可以自动地释放对象所
占有的资源,但是对象占有的某些资源,例如: 文件描述符、套接字
(socket),无用单元回收是无法处理的。所以程序员必须用终止函数来处
理。诸如:关闭打开的文件,终止网络连接等程序善后工作。
下面的例子是取自Java FileOutpntStream类中的终止函数。这个终止
函数是一个实例的方法,没有参数,也无返回值,名字必须取finalize()。
这里我们应注意它和C++析构函数的区别:
/**
* closes the stream when garbage is collected
* checks the file descriptor first to make sure it
* is not already closed.
*/
protected void finalize( ) throws IOException {
if (fd!=null) close();
}
下面是关于终止函数的几点注意事项:
* 如果一个对象含有终止函数,那么这个方法,在系统收集该对象所占
内存之前被调用。
* Java解释器可能在没有完成无用单元收集的情况下退出,那么某些终
止函数不可能被调用。在这种情况下,未释放的资源通常由操作系统来
处理。
* Java的无用单元的收集发生的时刻是不确定的,因此,Java无法保证
在何时调用终止函数、终止函数的调用次序、以及由哪一个线程执行终
止函数。
* 在终止函数被调用时,对象会不立刻被回收。因为终止函数要使用对
象的this指针做某些处理,意味着对象又被引用了。只有在finalize()执
行之后,无用单元收集程序才有可能处理该对象。
* 上面例子中,终止函数可以引发一个异常。但是如果在终止函数中发
生了其它异常,将被系统忽。
4.6.3 null引用
关键字null是一个预定义的常量,表示“无实例”。null可以用在
实例能够出现的任何地方,可以被转换成任何类类型。
4.7 静态方法,变量和初始化
变量和方法都可以声明为静态(static)的,这样它们只能用于类本
身,而不是类的实例。此外,类定义中的一块代码也可以声明为静态的。
这样的程序块叫做静态初始化段。
静态变量可以有初值,就象实例变量一样,(参见的“初始化顺序”)。
静态变量只在类中出现一次,而不论这个类有多少个实例。静态变量和
方法都是通过类名字来访问的。为方便起见,也可以用类实例来访问。
class Ahem {
int i; //实例变量
static int j; //静态变量
static int arr[ ] = new int[12];
static { //静态初始成员,初始化数组
for (int i =0; i 〈arr. length; i++) {
arr[i] =i;
}
}
void seti(int i) { //实例方法
this.i =i;
}
static void setj (int j) { //静态方法
Ahem.j=j;
}
static void clearThroat( ) {
Ahem a = new Ahem ();
Ahem.j=2; //有效: 通过类访问静态变量
a.j =3; //有效: 通过实例访问静态变量
Ahem.setj(2); //有效: 通过类访问静态方法
a.setj(3); //有效: 通过实例访问静态方法
a.i=4; //有效: 通过实例访问实例变量
Ahem.i=5; //错误: 通过类访问实例变量
a.seti(a); //有效: 通过实例访问实例方法
Ahem.seti(5); //错误: 通过类访问实例方法
}
}
7.1 声明的顺序
类和它的方法以及实例变量声明的次序并不重要,但是初始化时循
环依赖是存在的。有关初始化时循环依赖的信息参见“初始化顺序”一
节。方法可以自由向前引用其它的方法和实例变量。以下例子是合法的:
class A {
void a () {
f.set(42);
}
B f;
}
class B {
void set (long n) {
this .n=n;
}
long n;
}
4.7.2 初始化顺序
加载一个类时,它的静态初始化代码被执行。静态初始化代码段与
静态变量初始化同时进行 。它们按词法顺序执行。例如,一个类C声
明如下:
class C {
static int a=1;
static {
a++;
b=7;
}
static int b=2;
}
当C被加载时,按以下顺序执行:
* a置为1。
* 静态初始化段执行,a置为2,b置为7。
* b置为2。
如果静态初始化代码引用了其它的未加载的类,这个类将被加载,
它的静态初始化代码将先执行。在静态初始化期间引用未加载的类,该
类先加载并初始化。在初始化序列中,若引用到在它之前的一个未初始
化的类时,就会发生循环。这将导致一个NoClassDefFoundException错
误。
例如,当类A加载时,它的静态初始化代码被执行。但A的静态初
始化代码引用了另一个未加载的类B。这样B将被加载,它的静态初始
化代码要在A之前执行。A的静态初始化,随后执行。但如果B中的静
态初始化代码引用A,就导致了循环引用。
若实例或静态变量初始化时,存在前向依赖,则出编译时刻错误。
例如:
int i = j+2;
int j = 4;
将产生编译时刻错误。
实例变量初始化时可以有对静态变量的前向依赖。例如以下代码段:
int i = j+2; //实例变量
static int j = 4; //静态变量
i前向依赖于j,i将被初始化为6,j为4。因为j是静态变量,它
的初始化先于实例变量。
静态的方法不能引用实例变量; 只能引用静态变量和方法。
4.8 访问指定符
访问指定符(access specifier)就是程序员控制对方法和变量访问的修
饰符。用于控制访问的关键字是public,private,和protected。public
方法可以被任何人访问。private方法只能在类定义时在类的内部访问。
因为private方法在类之外是不可见的,它们是final(定止)的,并且不
能被覆盖(参见第2.4.10.3节“定止类,方法,变量”)。此外不能将一个
非私有方发覆盖成私有方法,protected访问指定符使方法和变量只许其
子类访问,其它类都不行。
public可用于类,方法和变量。标以public 的类、方法和变量,可
为任何地方的任何其他类、方法访问。覆盖不能改变public访问权限。
没有被指定public或private的类,方法和变量,只能在声明它们的
包(package)中访问(参见第2.6节 “包”)。
4.9 变量作用域规则
在一个包内,当一个类被定义成另一个类的子类时,超类中所作的
声明对子类均可见。当方法定义中引用变量时,将用到下面的作用域规
则:
1. 首先搜索当前块,然后是所有外套的块,直到包容该块的当前方
法,这被认为是局部作用域。在局部作用域后,还继续在类的作用域中
搜索。
2. 搜索当前类中的变量。
3. 如果没有找到变量,则搜索所有的超类中的变量,首先从直接超
类开始,直到Object类。如果没有找到变量,搜索引入的类和包名。如
果还没有找到,就成为编译时刻错误。
在一个类中多个变量出现相同的名字,也是编译错误。
4.10 修饰符
Java有以下关键字用作修饰符(Modifier)。
4.10.1 线程安全变量
标记了Threadsafe(线程安全)的实例或静态变量是指当一个线程
使用它时不能被其它线程更改,即变量不能被异步改变。将变量标记为
Threadsafe的目的是允许编译器进行优化,来屏蔽异步更改的发生。使用
Threadsafe的优化措施是将寄存器中的实例变量放入高速缓存。
4.10.2瞬态变量
transient(瞬态)标志被解释器用于持久性对象。当一个类的实例以
持久对象写出时,若变量标记为transient,则作特殊处理。
4.10.3 定止类,方法和变量
final(定止)关键字是一个修饰符,标记一个类不能有子类,一个
方法不能被覆盖,或者一个变量为一个常值。由定止类派生子类,覆盖
定止方法,更改定止变量的值,都将产生编译错误。final变量的作用与
字面量相同。
使用final使编译器能够作很多优化。其中的一个优化是方法体的内
联扩展(inline expansion),这个方法体必须是小的final函数。小到什么程
度依赖于编译器的实现。
final声明的例子如下:
class Foo {
final int value =3; //定止变量
final int foo (int a,int b) { //定止方法
}
}
4.10.4 本地方法
标有native(本地)的方法是由平台相关的语言(如C语言)实现的,
不是Java的本身方法,没有方法体,声明以分号结束。构造函数不能为
native。虽然本地方法是由平台相关的语言实现的,但它们的使用与不是
native的方法一样,例如:可以覆盖。本地方法声明的例子如下:
native long timeofDay();
4.10.5 抽象方法
抽象方法(abstract method)为超类或界面提供了一种手段,它实际上
是必须由子类实现的一组协议。标记为abstract的方法必须在其子类中定
义。抽象方法没有方法体, 而声明由分号结束。
使用abstract关键字的规则如下:
* 构造函数,不能为abstract。
* 静态方法不能为abstract。
* private 方法不能为abstract。
* abstract方法必须在声明它的类的子类中定义。
* 不能覆盖超类中的abstract方法。
* 包含abstract方法的类和继承abstract方法的类被认为是抽象类。
* 初始化抽象类或直接调用abstract方法,将导致编译时刻错误。
4.10.6 方法和块的同步
synchronized(同步)关键字是一个修饰符,标记一个方法或代码块
需要取得锁(lock)。为了保证当一代码访问某一资源时,另一要求访问该
资源的代码将不能执行,锁是必要的。每个对象都有一个锁与之联系, 每
个类也同样有一个锁。同步方法是可再入的。
每当调用一个被同步的方法时,它将等待直到它得到当前实例(如
果是静态方法则为类)的锁,然后执行代码,释放锁。
同步块的行为与同步方法相似。区别在于它所用的锁是块内由
synchronized关键字所在语句指定的对象或类的锁,而不是当前类的锁。
同步块的声明如下:
/* 本方法前文代码*/
synchronized (<object or className>) {// 同步模块
/* 同步访问代码 */
}
/*本方法其余代码*/
声明同步方法的例子如下:
class Point {
float x,y;
synchronized void scale (float f ) {
x*=f;
y*=f;
}
}
同步模块的例子如下:
class Rectangle {
Point topLeft;
…
void print ( ) {
synchronied (topLeft) {
println (“topLeft.x=”+ topLeft.x);
println (“topLeft.y=”+ topLeft.y);
}
…
}
}
5. 界面
界面(interface)定义了一组方法,而没有实现其体。界面只提供方法
协议的封装,而不限制实现必须在什么继承树上。当一个类实现界面时,
一般必须实现界面描述的所有方法的体。(如果实现界面的类是抽象类,
它可以把实现界面方法的任务交给它的子类。)
界面解决了因多继承运行时过大开销这类问题。 但由于界面采用动
态方法束定,使用它们的时候在性能上会受到一点影响。
界面允许几个类同时享有一个程序设计界面,而彼此不完全知道对
方具体的实现。下面的例子是一个界面的声明(即用了interface 关键字),
并由一个类实现。
public interface Storing {
void freezeDry (Stream s);
void reconstitute (Stream s);
}
--
如果C#有用的话,还要JAVA干什么? | Browser就是未来的桌面
曾经是C++的奴隶, | JAVA VM就是未来的OS
直到有一天JAVA的到来,彻底的改变了我的生活 | APPLET就是未来的程序
JAVA:我选择,我喜欢。 |硬盘速度能超过Ethernet么?
(想学好JAVA,建议参看我的说明档) |你还在等待什么?
※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 61.190.23.138]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:419.691毫秒