第5 章类的高级特性 5.1 static关键字 static关键字用来声明静态变量和静态方法。例如: class MyClass { static int i; static void increase(){ i++; } } 静态变量和静态方法为类中所有对象所公有,可以不创建对象,直接 引用,也称为类变量和类方法。 引用方式:类名.静态变量/静态方法,如: MyClass.i; MyClass.increase(); 如果在声明时不用static关键字修饰,则为实例变量和实例方法。 一个类通过使用new运算符可以创建多个不同的对象,这些对象将被 分配不同的内存空间,准确地说,就是不同的对象的实例变量将被分配不 同的内存空间,如果类中的成员变量有类变量,所有的对象的这个类变量 都分配给相同的一处内存。也就是说,对象共享类变量,改变其中一个对 象的这个类变量会影响其他对象的这个类变量。 静态变量可以通过类名直接访问,也可以通过对象来调用。采用这两 种方法取得的结果是相同的。如果是public静态变量,则其他类可以不通 过实例化访问它们。 类方法不能访问实例变量,只能访问类变量。类方法可以由类名直接 调用,也可由实例对象进行调用。类方法中不能使用this或super关 键字。 1 20 JavaEE 零基础入门 对于实例变量必须先生成实例对象,通过该对象访问实例变量。实例方法可以对当 前对象的实例变量进行操作,也可以对类变量进行操作,实例方法由实例对象调用。下面 图5-1 实例变量与静态变量关系 的代码及图5-1 说明了实例变量与静态变量的 关系。 class ABCD { char data; static int st_data; }c lass Demo { ABCD a,b,c,d } 例5-1 关于实例成员和类成员的例子。 程序清单:ch05\MemberTest.java package ch05; class Member { static int classVar; int instanceVar; static void setClassVar(int i) { classVar=i; //instanceVar=i; //类方法不能访问实例变量 } static int getClassVar(){ return classVar; } void setInstanceVar(int i){ classVar=i; //实例方法不但可以访问类变量,也可以访问实例变量 instanceVar=i; } int getInstanceVar() { return instanceVar; } } public class MemberTest{ public static void main(String[] args) { Member m1=new Member(); Member m2=new Member(); m1.setClassVar(1); m2.setClassVar(2); System.out.println("m1.classVar="+ m1.getClassVar()+" m2.ClassVar="+ m2. getClassVar()); m1.setInstanceVar(11); m2.setInstanceVar(22); 第5 章 类的高级特性 1 21 System.out.println("m1.InstanceVar="+ m1.getInstanceVar()+" m2.InstanceVar= "+m2.getInstanceVar()); } } 程序运行结果如下: m1.classVar=2 m2.ClassVar=2 m1.InstanceVar=11 m2.InstanceVar=22 分析一个不正确的变量引用实例: class StaticError{ String mystring="hello"; //实例变量 public static void main(String[] args) { System.out.println(mystring); //静态方法访问实例变量出错 } } 错误信息:can’tmakeastaticreferencetononstaticvariable。因为只有对象的方法 可以访问对象的变量。 解决的办法如下。 (1)将实例变量mystring改为类变量: class StaticError{ static String mystring="hello"; public static void main(String[] args) { System.out.println(mystring); } } (2)将实例变量mystring改为局部变量: class NoStaticError{ public static void main(String[] args) { String mystring="hello"; System.out.println(mystring); } } 例5-2 下面例子中的梯形对象共享一个static的下底。 程序清单:ch05\CommonLader.java package ch05; class 梯形{ float 上底,高; //类的变量 static float 下底; //类的变量 梯形(float x,float y,float h) { //构造方法 1 22 JavaEE 零基础入门 上底=x; 下底=y; 高=h; } float 获取下底() { return 下底; } void 修改下底(float b) { 下底=b; } }p ublic class CommonLader{ public static void main(String[] args){ 梯形laderOne=new 梯形(3.0f,10.0f,20); 梯形laderTwo=new 梯形(2.0f,3.0f,10); System.out.println("laderOne 的下底:"+laderOne.获取下底()); System.out.println("laderTwo 的下底:"+laderTwo.获取下底()); laderTwo.修改下底(60); System.out.println("laderOne 的下底:"+laderOne.获取下底()); System.out.println("laderTwo 的下底:"+laderTwo.获取下底()); } } 程序运行结果如下: laderOne 的下底: 3.0 laderTwo 的下底: 3.0 laderOne 的下底: 60.0 laderTwo 的下底: 60.0 当Java程序执行时,类的字节码文件被加载到内存,如果该类没有创建对象,类的实 例成员变量不会被分配内存。但是,类中的类变量,在该类被加载到内存时,就分配了相 应的内存空间。如果该类创建对象,那么不同对象的实例变量互不相同,即分配不同的内 存空间,而类变量不再重新分配内存,所有的对象共享类变量,即所有的对象的类变量是 相同的一处内存空间,类变量的内存空间直到程序退出运行,才释放所占有的内存。 实例方法和类方法的区别如下。 对于类中的类方法,在该类被加载到内存时,就分配了相应的入口地址。从而类方法 不仅可以被类创建的任何对象调用执行,也可以直接通过类名调用。类方法的入口地址 直到程序退出才被取消。 当类的字节码文件被加载到内存时,类的实例方法不会被分配入口地址。当该类创 建对象后,类中的实例方法才分配入口地址,从而实例方法可以被类创建的任何对象调用 执行。需要注意的是,当创建第一个对象时,类中的类方法就分配了入口地址,当再创建 对象时,不再分配入口地址,也就是说,方法的入口地址被所有的对象共享,当所有的对象 都不存在时,方法的入口地址才被取消。 无论是类方法还是实例方法,当被调用执行时,方法中的局部变量才被分配内存空 第5 章 类的高级特性 1 23 间,方法调用完毕,局部变量即刻释放所占的内存。在一个方法被调用执行完毕之前,如 果该方法又被调用,那么,方法的局部变量会再次被分配新的内存空间,例如,方法在递归 调用时,方法中的局部变量会再次被分配新的内存空间。 5.2 this关键字 this关键字可以出现在类的实例方法中,代表使用该方法的当前对象。 为了说明this的用法,下面例子中的“三角形”的构造方法中,有意使用了this。当使 用构造方法来创建对象时,构造方法中的this就代表当前对象。 例5-3 “三角形”的构造方法中的this就代表当前对象。 程序清单:ch05\Triangle.java package ch05; class 三角形{ double a,b,c; 三角形(double a,double b,double c) { //构造方法 setABC(this,a,b,c); //调用实例方法 } void setABC(三角形trangle,double a,double b,double c) { //实例方法,供构造方法调用初始化实例对象 trangle.a=a; trangle.b=b; trangle.c=c; } } class Triangle{ public static void main(String[] args) { 三角形tra=new 三角形(3,4,5); System.out.print("三角形的三边是:"+tra.a+","+tra.b+","+tra.c+","); } } 运行结果如下: 三角形的三边是: 3.0,4.0,5.0, 实例方法可以操作类的成员变量,实际上,当成员变量在实例方法中出现时,默认的 格式是“this.成员变量”。如: class A{ int x,y; void fun(int x){ this.x=x; y=x; 1 24 JavaEE 零基础入门 } } 在上述A 类中的实例方法fun()中出现了this,this就代表使用fun()的当前对象。 所以,“this.x”就表示当前对象的变量x,当对象调用方法fun()时,将函数的局部变量x 赋给该对象的变量x。当一个对象调用方法时,方法中的成员变量就是指分配给该对象 的成员变量。 通常情况下,可以省略成员变量名字前面的“this.”,如成员变量y。但是,当成员变 量的名字和局部变量的名字相同时,成员变量前面的“this.”就不可以省略。 同样,类的实例方法可以调用类的其他方法,调用的默认格式是“this.方法”,如: class B{ void fun() { this.get(); //可省略this. } void get() { System.out.println("ok"); } } 在上述B类中的方法fun()中出现了this,this代表使用方法fun()的当前对象。所 以,方法fun()的方法体中this.get()就是调用当前对象的方法get()。也就是说,当某个 对象调用方法fun()的过程中,又调用了方法get()。由于这种逻辑关系非常明确,一个 方法调用另一个方法时可以省略方法名字前面的“this.”。 但是,this不能出现在类方法中,因为类方法可以通过类名直接调用,这时可能还没 有任何对象诞生。 例5-4 创建一个有两个方法的类,其中第一个方法使用this,第二个方法不使 用this。 程序清单:ch05\Rectangle.java package ch05; class Rectangle{ //矩形类 int width; //矩形的宽 int usethis(int width){ //返回宽度的函数 this. width=width; //this 指自己这个对象 return width; } int unusethis(int width){ int w=width; return w; } public static void main(String[] args){ Rectangle r=new Rectangle(); //类对象的实例化 System.out.println("r.width="+ r.width+" r.usethis(1)="+ r.usethis(1)+ 第5 章 类的高级特性 1 25 "r.width="+r.width); System.out.println("r.width="+r.width+" r.unusethis(2)="+ r.unusethis(2) +" r.width="+r.width); } } 运行结果如下: r.width=0 r.usethis(1)=1 r.width=1 r.width=1 r.unusethis(2)=2 r.width=1 例5-5 编译并运行下面的程序,分析运行结果,进一步了解super和this的作用。 程序清单:ch05\SuperDemo.java package ch05; mport java.io.*; class SuperClass{ //定义父类 int x; SuperClass(){ //父类的构造方法 x=10; } void doClass(){ System.out.println("SuperClass.doClass()"); } } class SubClass extends SuperClass{ //定义子类 int x; SubClass(){ //子类的构造方法 super(); //调用父类的构造方法 x=100; } void doClass(){ //重写父类的doClass 方法 System.out.println("SubClass.doClass()"); } void doDemo(){ //演示super 和this 的方法 int x; x=1000; super.doClass(); //调用父类的doClass 方法 doClass(); //调用本类的doClass 方法 System.out.println("super.x="+super.x); //父类的x System.out.println("this.x="+this.x); //本类的x System.out.println("x="+x); //本方法的x } }public class SuperDemo{ public static void main(String[] args){ //主方法 1 26 JavaEE 零基础入门 SubClass s=new SubClass(); s.doDemo(); } } 运行结果如下: SuperClass.doClass() SubClass.doClass() super.x=10 this.x=100 x=1000 分析:此程序中定义了一个父类,子类SubClass继承了父类SuperClass,在主函数 中定义SubClass类对象s时,自动调用类SubClass的构造函数SubClass(),在此构造函 数中先执行“super();”语句,这样就调用类SuperClass的构造方法SuperClass(),因为 super来指明超类中的方法。同样在子类方法doDemo()中,执行语句“super.doClass();”时, 则调用父类的方法doClass()。如不用super来指定,则调用的是子类的方法doClass(), 这里子类SubClass()的成员方法doClass()重写了父类SuperClass()的成员方法 doClass()。 语句“System.out.println("super.x="+super.x);”中super 调用的是父类 SuperClass的变量x,而语句“System.out.println("this.x="+this.x);”中this调用子类 SubClass的变量x,关键字this和super分别用来指明子类和父类中同名的成员变量。 这里父类SuperClass的成员变量x、子类SubClass的成员变量x和类方法doDemo()中 使用的局部变量x三者同名,则要使用关键字this和super来指定所要使用的变量。如 不用则输出类方法的局部变量,如语句“System.out.println("x="+x);”输出的就是类 方法doDemo()的局部变量。这里子类SubClass()的成员变量x隐藏了父类SuperClass()的 成员变量x。 5.3 静态导入 在JDK1.5后新加了导入静态方法和静态域的功能。在类中使用静态导入,可以使 用其他类中定义的类方法和类变量,而且这些类方法和类变量就像在本地定义的一样。 换句话说,静态导入允许在调用其他类中定义的静态成员时,可以不通过类名直接使用类 中的静态方法。 导入一个类一般都用importxxx.ClassName;而静态导入语句是importstaticxxx. ClassName.*;这里多了static和.*,意思是导入xxx.ClassName这个类里的所有静态 方法和静态域。当然,也可以只导入某个静态方法,只要把.* 换成静态方法名就行了。 以后在这个类中,就可以直接用方法名调用静态方法,而不必用“ClassName.方法名”的 方式来调用。 静态导入语句看起来和普通的import语句非常相似。但是,普通import语句从某 第5 章 类的高级特性 1 27 个包中导入的是一个或所有的类,而静态import语句从某个类中导入的却是一个或所有 的类方法以及类变量。需要注意的是,针对一个给定的包,不可能用一行语句静态地导入 所有类的所有类方法和类变量。也就是说,不能这样编写代码: import static java.lang.*; //编译出错! 如果一个本地方法和一个静态导入的方法有着相同的名字,那么本地方法将被 优先调用。 例如,源文件顶部添加:importstaticjava.lang.System.*;那么就可以使用System 类 的静态方法和静态域,而不必加类前缀。如: out.println("hello world"); //相当于System.out.println("hello world"); exit(0); //相当于System.exit(0); 导入静态方法和导入静态域有以下两个实际的应用。 (1)算术函数,对Math类使用静态导入,就能更自然地使用算术函数。如: sqrt(pow(x,2)+pow(y,2)); (2)笨重的常量,如果需要使用大量带有冗长名字的常量,就应该使用静态导入。如: Date d=new Date(); if (d.get(DAY_OF_WEEK)==MONDAY) 看起来比if(d.get(Calendar.DAY_OF_WEEK)==Calendar.MONDAY)清晰。 例5-6 导入静态方法举例,计算直角三角形的斜边。 程序清单:ch05\StaticImportTest.java package ch05; import static java.lang.Math.sqrt; //导入静态方法sqrt() import static java.lang.Math.pow; //导入静态方法pow() class Hypot { public static void main(String args[]) { double side1, side2; double hypot; side1=3.0; side2=4.0; //静态导入后,不再需要通过类名Math 来调用方法sqrt()和pow() hypot=sqrt(pow(side1, 2)+pow(side2, 2)); System.out.println("给定RT△的两边边长为:" + side1+"和"+ side2+", 其斜 边边长为:"+hypot); } } 程序运行结果如下: 给定RT△的两边边长为: 3.0 和4.0, 其斜边边长为: 5.0 1 28 JavaEE 零基础入门 5.4 final关键字 final关键字可以修饰类、类的成员变量和成员方法,但final的作用不同。 (1)final修饰成员变量,则成员变量成为常量。为了声明一个final变量,可以在类 型之前的变量声明使用final关键字,例如: final type piVar=3.14159; 可以在任何作用域声明一个final变量。修饰成员变量时,定义时同时给出初始 值,而修饰局部变量时不做要求。这个语句声明了一个final变量并对它进行了初始 化。如果在后面还想给piVar赋其他的值,就会导致编译错误,因为final变量的值不能 再改变。 (2)final修饰成员方法,则该方法不能被子类重写。有些方法希望始终保持它在父 类中的定义而不会被子类修改以保证安全,此时可以使用final关键字来阻止方法重写。 例如: public final returnType methodName(paramList){ … } (3)final修饰类,则类不能被继承。由于安全性的原因或者是面向对象的设计上的 考虑,有时希望一些类不能被继承,例如,Java中的String类,它对编译器和解释器的正 常运行有很重要的作用,不能对它轻易改变,因此把它修饰为final类,使它不能被继承, 这就保证String类型的唯一性。同时,如果认为一个类的定义已经很完美,不需要再生 成它的子类,这时也应把它修饰为final类,定义一个final类的格式如下: final class finalClassName{ … } 如果一个类被声明为final的,则这个类中的所有方法也自动成为final的。 (4)final修饰引用类型变量,针对引用类型变量的final修饰符也是容易混淆的地 方。实际上final只是修饰引用变量自身的值,如限定类变量保存的实例地址不能变。至 于该变量所引用的对象,内容是否能变,那就管不着了。所以,对于如下语句: final StringBuffer strConst=new StringBuffer(); 可以修改它指向的对象的内容,如: strConst.append(" "); 但是不能修改它的值,如: strConst=null;