1Chp8三个修饰符本章导读本章将向读者介绍Java中的三个非常重要的修饰符:static、final和abstract。学习修饰符,应该始终明确:该修饰符能够修饰什么程序组件,而修饰某个组件的时候,又表示了什么含义。1staticstatic修饰符也被称为静态修饰符。这个修饰符能够修饰三种程序组件:属性、方法、初始化代码块。static在修饰这三个不同的组件的时候,分别表示不同的含义。需要注意的是,static不能修饰局部变量和类。下面根据修饰的组件不同,我们分别阐述static修饰属性、方法以及初始化代码块所代表的含义。1.1静态属性static修饰属性,则该属性就成为静态属性。静态属性是全类公有的属性。例如,有如下代码:classMyValue{inta;staticintb;}publicclassTestStatic{publicstaticvoidmain(Stringargs[]){MyValuemv1=newMyValue();MyValuemv2=newMyValue();mv1.a=100;mv1.b=200;mv2.a=300;mv2.b=400;System.out.println(mv1.a);System.out.println(mv1.b);System.out.println(mv2.a);System.out.println(mv2.b);}}编译运行之后,输出结果为:1004003004002要注意,400这个数字出现了两次,200没有出现。原因在于:b属性是一个静态属性。MyValue类的所有对象,都公用一个b属性,每一个对象的b属性,都指向同一块内存。如下图所示:可以看到,mv1对象的a属性和mv2对象的a属性,彼此之间是相互独立的。而mv1和mv2两个对象的b属性,指向的是内存中的同一块区域。因此,当代码执行到mv1.b=200;时,把这块区域设置为200;执行到mv2.b=400时,把内存中的同一块区域设置为400。这时,无论通过mv1.b,还是mv2.b,读取到的都是同一块内存区域的值,因此输出语句输出的都是400。每个对象的静态属性都指向同一块内存区域,事实上,这个属性不属于任何一个特定对象,而属于“类”。因此,可以使用类名直接调用静态属性。例如,可以把上面的TestStatic程序改写成下面的样子:classMyValue{inta;staticintb;}publicclassTestStatic{publicstaticvoidmain(Stringargs[]){MyValuemv1=newMyValue();MyValuemv2=newMyValue();mv1.a=100;MyValue.b=200;mv2.a=300;MyValue.b=400;System.out.println(mv1.a);System.out.println(MyValue.b);System.out.println(mv2.a);System.out.println(MyValue.b);}}这段代码中,对b属性的使用都是用类名直接调用。在实际编程过程中,为了避免错误和误会,对静态属性的使用,应当尽量用“类名直接调用”的写法。用这种写法能够把静态属性和实例变量加以区分,从而提高程序的可读性。要注意的是,由于静态属性不属于任何一个对象,因此,在一个对象都没有创建的情况之下,照样可以使用静态属性。这个时候,就只能用类名直接访问静态属性。例如下面的代mv1mv2100300aabb4003码:classMyValue{inta;staticintb;}publicclassTestStatic{publicstaticvoidmain(Stringargs[]){//没有创建任何MyValue对象就直接使用属性MyValue.b=200;System.out.println(MyValue.b);}}从概念上怎么来理解静态属性呢?举一个生活中的例子,例如:在一个班级中,每个学生对象都有一个属性:“Java老师”。对于同一个班级的同学来说,Java老师这个属性总是一样的,即使上课过程中更换老师,班级中所有学生对象的“Java老师”属性都会进行同样的改变。因此,如果将学生看做是一个类,“Java老师”这个属性,就属于这个类的“公有”属性,也就是静态属性。最后,介绍一下几个名词。在之前的课程中,我们接触过的“属性”就是指的实例变量。现在,我们接触到了静态属性这个概念,再提“属性”这个概念,就分为静态属性和非静态属性两种。其中,静态属性也可以叫“类变量”,而非静态属性就是我们所说的“实例变量”。这几个名词的关系如下:1.2静态方法用static修饰的方法称之为静态方法。首先我们看一个代码的例子,这个例子中反映了静态方法与非静态方法分别能访问什么样的属性和方法。classTestStatic{inta=10;//非静态属性staticintb=20;//静态属性publicvoidma(){}//非静态方法publicstaticvoidmb(){}//静态方法publicvoidfa(){//fa是一个非静态方法System.out.println(a);//非静态方法能够访问非静态属性System.out.println(b);//非静态方法能够访问静态属性ma();//非静态方法中,能够调用非静态方法mb();//非静态方法中,能够调用静态方法}publicstaticvoidfb(){//fb是一个静态方法System.out.println(a);//编译错误静态方法中不能访问非静态属性System.out.println(b);//静态方法中可以访问静态属性4ma();//编译错误,静态方法中调用非静态方法mb();//静态方法中可以调用静态方法}}从上面这个例子中,我们可以总结出如下规律:在非静态方法中,无论方法或属性是否是静态的,都能够访问;而在静态方法中,只能访问静态属性和方法。静态方法和静态属性统称为静态成员。以上的规律可以记成:静态方法中只能访问静态成员。需要注明的是,在静态方法中不能使用this关键字。除此之外,静态方法与静态属性一样,也能够用类名直接调用。例如下面的代码:publicclassTestStaticMethod{publicstaticvoidmain(Stringargs[]){TestStatic.fb();}}静态方法是属于全类公有的方法。从概念上来理解,调用静态方法时,并不针对某个特定的对象,这个方法是全类共同的方法。例如,对于班级来说,有一个“办联欢会”的方法。想要办好一个班级联欢会,不能依靠某个特定同学(也就是某个对象)的努力,而应该是全班同学共同配合完成。因此,这个方法不属于某个特定对象,而是“全类公有”的方法。除了上面所说的特点之外,静态方法还有一个非常重要的特性,这个特性跟方法覆盖有关。看下面的代码:classSuper{publicvoidm1(){System.out.println(“m1inSuper”);}publicstaticvoidm2(){System.out.println(“m2inSuper”);}}classSub1extendsSuper{publicvoidm1(){//非静态方法覆盖非静态方法,编译通过System.out.println(“m1inSub”);}publicstaticvoidm2(){//静态方法覆盖静态方法,编译通过System.out.println(“m2inSub”);}}classSub2extendsSuper{publicstaticvoidm1(){}//静态方法覆盖非静态方法,编译出错!publicvoidm2(){}//非静态方法覆盖静态方法,编译出错!}根据上面的例子,可以发现:静态方法只能被静态方法覆盖,非静态方法只能被非静态5方法覆盖。除此之外,我们再写一个程序来测试一个Super类和Sub1类。publicclassTestStaticOverride{publicstaticvoidmain(Stringargs[]){Supersup=newSub1();sup.m1();sup.m2();Sub1sub=(Sub)sup;sub.m1();sub.m2();}}编译运行,结果如下:m1inSubm2inSuperm1inSubm2inSub注意到,对于m1这个非静态方法来说。无论引用类型是Super还是Sub1,调用m1方法,结果都是m1inSub。这是上一章我们所讲的多态特性:运行时会根据对象的实际类型调用子类覆盖以后的方法。然后,如果是m2这个静态方法,情况则大大不同。在引用类型为Super时,调用的是m2inSuper;当引用类型为Sub1时,调用的是m2inSub。这是静态方法很重要的一个特性:静态方法没有多态。当我们对一个引用调用静态方法的时候,等同于对这个引用的引用类型调用静态方法:所以代码super.m2();相当于Super.m2();sub.m2();相当于Sub.m2();总结一下静态方法的几个性质:1.静态方法可以用类名直接调用。2.静态方法中只能访问类的静态成员。3.静态方法只能被静态方法覆盖,并且没有多态。1.3静态初始化代码块static修饰符修饰初始化代码块,就称之为静态初始化代码块。首先,简单介绍一下初始化代码块。看如下的代码例子publicclassMyClass{{//此处为初始化代码块}inta;publicMyClass(){System.out.println(“MyClass()”);}}在类的里面,所有方法的外面定义的代码块称之为初始化代码块(如上面的例子所示)。6能够用static修饰初始化代码块,使之成为静态初始化代码块。例如:publicclassMyClass{static{//此处为静态初始化代码块System.out.println(“InMyClassStatic”);}inta;publicMyClass(){System.out.println(“MyClass()”);}}这样就定义好了静态初始化代码块。那么这个代码块什么时候执行呢?静态初始化代码块执行的时机非常特别,下面我们就来介绍这个问题。1.3.1类加载首先我们来简单考虑一下下面的代码。//TestStudent.java01:classStudent{02:static{03:System.out.println(“inStudentstatic”);04:}05:publicStudent(){06:System.out.println(“Student()”);07:}08:}09:publicclassTestStudent{10:publicstaticvoidmain(Stringargs[]){11:Studentstu1=newStudent();12:Studentstu2=newStudent();13:}14:}上面的TestStudent.java文件,编译生成两个.class文件:一个Student.class,一个TestStudent.class。生成这两个.class文件之后,执行javaTestStudent则,首先启动JVM,然后在硬盘上找到TestStudent.class文件,读入这个文件,并开始解释执行。此时,JVM中只有TestStudent.class这个类的信息。然后,执行主方法。在第11行时,遇到Studentstu1=newStudent();这句话。这个语句要求创建一个Student类的对象,但是此时,在JVM中,只有TestStudent类的信息,而没有Student类的信息!这个时候,JVM会自动的通过CLASSPATH环境变量,去硬盘上寻找相应的Student.class文件。当JVM找到这个文件之后,会把这个文件中所保存的Student类的信息读入到JVM中,并保