,第二级,第三级,第四级,第五级,第,6,章,Java,中类、对象、接口及包的概念,第,6,章,Java,中类、对象、接口及包的概念,6.1,类的基本概念,6.2,对象,6.3,类的继承概念,6.4 Java,中接口与包的概念,6.1,类的基本概念,传统的过程式语言,如,C,,由于它的设计方式与客观世界之间存在差距,使得程序的编写首先必须定义所要实现的功能,然后确定需采取的步骤,即所谓的,“,逐步求精,”,的结构程序设计方法。实际开发中,当程序大到一定程度的时候,其调试和维护就变得很困难,使用过程式语言就会感到力不从心了。,前面我们说过,,Java,是一种纯面向对象的编程语言,而面向对象程序设计是近些年来才发展起来的程序设计方法,其基本思想是将现实世界中的事物抽象为对象,抽象出来的对象被赋给相应的状态和行为,通过对消息的响应完成一定的任务。在现实生活中,任何事物都可以被认为是对象,(Object),,如:, 电梯, 街头的自动售货机, 街上跑的汽车, 凳子, 人,上面列举的对象都有两个共性:,(1),具有一定的状态和特性。比如汽车有轮胎、发动机、方向盘等。,(2),每个对象对应一组特定的操作。比如汽车需保养、加油、清洗等。,面向对象程序设计方法就是把现实世界中对象的状态和操作抽象为程序设计语言中的对象,达到二者的统一。同一种对象的所有共性进行抽象,又得到了类的概念。,所以,面向对象程序设计中的对象是由描述状态的变量和对这些变量进行维护和操作的一系列方法组成的事务处理单位,而类相当于创建对象实例的模板,通过对其实例化得到同一类的不同实例。本章我们将讨论类的特性、成员变量,方法、对象的建立及初始化、对象的继承及接口与包等内容。,类是对一个或几个相似对象的描述,它把不同对象具有的共性抽象出来,定义某类对象共有的变量和方法,从而使程序员实现代码的复用,所以说,类是同一类对象的原型。创建一个类,相当于构造一个新的数据类型,而实例化一个类就得到一个对象。,Java,为我们提供了大量的类库,如果从已知类库入手来构造自己的程序,不仅能有效地简化程序设计,而且能很好地学习面向对象程序设计方法。,其实,前面很多例子已经对类的组成有了明确地说明,一个类的实现包含两部分内容:声明和实体。类的各部分组成如图,6.1,所示。,图,6.1,6.1.1,类的声明,类声明包括关键字,class,、类名及类的属性。类名必须是合法的标识符,类的属性为一些可选的关键字。其声明格式如下:,public|private|friendly|protected abstractfinal class className,extends superclassNameimplements interfaceNameList,.,其中,第一项属于访问控制符,它不仅针对于类,类的变量、方法的访问也有该项的限制,我们后面会做专门的介绍。其他的修饰符说明如下:, abstract,:声明该类不能被实例化。,final,:声明该类不能被继承,即没有子类。,class class Name,:关键字,class,告诉编译器表示类的声明以及类名是,class Name,。,extends super class Name,:,extends,语句扩展,super class Name,为该类的父类。, implements interface Name List,:声明类可实现一个或多个接口,可以使用关键字,implements,并且在其后面给出由类实现的多个接口名字列表,各接口之间以逗号分隔。如图,6.1,中的,public class stack,.,即为类的声明。,6.1.2,类的实体,类体是类的主要部分,包括变量的说明及该类所支持的方法,我们习惯称之为成员变量和成员方法。需要注意的是,除了类体中定义的变量与方法外,该类还继承了其父类的变量与方法。当然,对父类变量和方法的访问要受到访问控制条件的限制。类体说明的格式为,class class Name ,variable Declaration,method Declaration,读者可参照图,6.1,仔细体会类体所包含的内容。,1,变量,Java,中变量的说明可以分为两种:类成员变量的说明和方法变量的说明。其变量声明格式为,public|protected|private staticfinal transientvolatile,type variable Name,上述声明格式中,第一项指的是访问控制格式,(,我们后面会有介绍,),,另外的几项我们说明如下:, static:,成员控制修饰符,说明该类型的变量为静态变量,或者称之为类变量。说明静态变量类型后则该类的所有实例对象都可以对其共享,而且访问静态变量无须事先初始化它所在的类。,final,:常量声明修饰符,与,C/C+,类似,用该符号声明后,在程序的运行过程中不能再改变它的值。实际使用中,,final,往往与,static,结合在一起使用。比如:,final int INDEX = 1000;,static final int LOOP=10;, volatile,:异步控制修饰符,表示多个并发线程共享的变量,这使得各线程对该变量的访问保持一致。,transient,:存储控制临时变量修饰符,因为在缺省的情况下,类中所有变量都是对象永久状态的一部分,将对象存档时,必须同时保存这些变量。用该限定词修饰的变量指示,Java,虚拟机:该变量并不属于对象的永久状态。它主要用于实现不同对象的存档功能。,总之,从变量定义的不同位置及所使用的限定词不同来看,变量可以分为三类:实例变量、局部变量和静态变量。,如果在类的方法代码段之外声明且没有限定词,static,,则为实例变量。从它的定义我们可以看出,实例变量与类紧密相关,如果一个类有多个实例对象,那么每个实例对象都有自己的实例变量拷贝,之间并不影响。,如果在类的方法本体之中声明,则为局部变量,这有点与,C,语言函数中定义的局部变量相似。由于局部变量是在方法体内所定义,因而只能在本方法中使用,无所谓访问控制,也不能用,static,修饰符加以说明。另外,需要注意的是局部变量使用前必须初始化,这也是它与实例变量及后面要介绍的静态变量之间的不同之处。局部变量可以与实例变量同名而相互不影响。,如果将一个实例变量声明为,static,,则为静态变量,或称之为类变量。静态变量在类声明后就可以直接引用,但实例变量则不能,必须在实例化对象后才可以使用。,下面我们对实例变量与类变量加以详细地说明,以加深读者的理解。比如我们可以如下来声明一个成员变量:,class My Class ,public float variable1;,public static int variable2,该例中声明了一个实例变量,variable1,和一个类变量,variable2,。今后当我们创建类的实例的时候,系统就会为该实例创建一个类实例的副本,但系统为每个类分配类变量仅仅只有一次,而不管类创建的实例有多少。当第一次调用类的时候,系统为类变量分配内存。所有的实例共享了类的类变量的相同副本。在程序中可通过一个实例或者类本身来访问类变量。例如:,My Class A=new My Class();,My Class B=new My Class();,A.variable1=100;,A.variable2=200;,B.variable1=300;,B.variable2=400;,System.out.println(A.variable1= +A.variable1);,System.out.println(A.variable2= +A.variable2);,System.out.println(A.variable1= +A.variable1);,System.out.println(A.variable1= +A.variable1);,.,当我们从类实例化新对象的时候,就得到了类实例变量的一个新副本。这些副本跟新对象是联系在一起的。因此,每实例化一个新,MyClass,对象的时候,就得到了一个和,MyClass,对象有联系的,variable1,的新副本。当一个成员变量用关键字,static,被指定为类变量后,其第一次调用的时候,系统就会为它创建一个副本,之后,类的所有实例均共享了该类变量的相同副本。所以上述程序段的输出结果为,A.variable1= 100,A.variable2= 400,B.variable1= 300,B.variable2= 400,2,方法,Java,程序通过方法完成对类和对象属性的操作。方法定义了在类成员变量上的一系列操作,它只能在类的内部声明并加以实现,其他的对象通过调用对象的方法得到该对象的服务。方法的定义包含两部分内容:方法声明和方法体。,1),方法声明,方法声明的一般格式如下:,public/protected/privatestaticfinalabstract,nativesynchronized,return Type method Name(param List) throws exceptionList,.,在方法声明中应包括方法名、方法的返回值类型、方法的修饰词、参数的数目和类型及方法可能产生的例外。从其声明格式中可以发现,不一定要全部显示并指明所有的信息,方法最基本的声明格式为,return Type method Name(),.,一般声明格式中的第一项是访问控制属性,后面会介绍。其他几个修饰词我们说明如下:, static:,说明该方法为静态方法。与变量的定义类似,静态方法我们也称作类方法,与之对应,其他的方法就为实例方法。静态方法属于类,所以只要对类作了声明,就可以调用该类的类方法,即使用时无须类的初始化。当然,实例方法只能在类的实例或子类的实例中调用。类方法只能操作类变量而不能访问定义在类中的实例变量,这是实际使用过程中经常出错的地方。例如:,class A ,int x;,static public int x() ,return x;,static public void setX(int newX) ,x = newX;,.,A myX = new A ();,A anotherX = new A ();,myX.setX(1);,anotherX.x = 2;,System.out.println(myX.x = + myX.x();,System.out.println(anotherX.x = + anotherX.x();,.,当我们编译的时候,编译器会给出以下的错误信息:,A.java:4: Cant make a static reference to,nonstatic variable x in class A.,return x;,出现这个错误的原因是类方法不能访问实例变量,如果把类的定义改为,class AnIntegerNamedX ,static int x;,static public int x() ,return x;,static public void setX(int newX) ,x = newX;,类就可以成功编译了:,myX.x = 2,anotherX.x = 2,实例成员和类成员之间的另外不同点是类成员可以在类本身中访问,而不必实例化一个类来访问类成员。,下面再对上面的代码进行修改:,.,A.setX(1);,System.out.println(A.x = + A.x();,.,这里我们不用实例化类对象,myX,和,anotherX,就可以直接从类,A,中设置,x,并输出,x,。这样同样可以得到类变量,x,的值。, abstract:,说明一个方法是抽象方法,即该方法只有方法说明而没有方法体。抽象方法的实现须由该方法所在类的子类来实现。如果一个类包含一个或多个抽象方法,则该类必须为抽象类。抽象类不能被实例化。例如:,class Shape,abstract void draw();,该例中的说明方法,draw(),为抽象方法。, final,:,final,方法类似于常量的定义,它说明一个方法为终极方法,即它不能被子类重载。说明为,final,的方法往往与关键字,private,一起使用,避免出错。例如:,.,private final meth_final(),., native,、,synchronized,:程序中,native,指明本方法是用与平台有关的开发语言编写的,也就是说用来把,Java,代码和其他语言的代码集成在一起。,synchronized,主要用于多线程程序设计,说明某一方法是同步方法,用来控制多个并发线程对共享数据的访问。我们后面在讲线程的时候还要作介绍。,2),方法重载,Java,中方法的重载指的是多个方法共用一个名字,(,这样可实现对象的多态,),,同时,不同的方法要么参数个数各不相同,或者是参数类型不同。,Java,提供的标准类中包含了许多构造函数,并且每个构造函数允许调用者为新对象的不同实例变量提供不同的初始数值。比如,,java.awt.Rectangle,就有三个构造函数:,Rectangle();,Rectangle(int width, int height);,Rectangle(int x, int y, int width, int height);,当我们传递不同的参数时,构造出来的对象的实例具有不同的属性。,3),方法中参数的使用,在方法的声明格式中,需要指明返回值的类型。当一个方法不需要返回值的时候,其类型说明为,void,,否则方法体中必须包含,return,语句。返回值既可以是基本数据类型,也可以是复杂数据类型。,在,C,语言、,PASCAL,语言中,函数、过程的参数都存在值传递,/,参数传递的问题。比如,C,语言中如果参数是指针或数组名则为参数传递。我们知道,,Java,中由于取消了指针,不可能像,C,一样直接操作内存,但是由于对象的动态联编性,复杂数据类型作参数相当于指针的使用,即参数传递,而基本数据类型作参数传递则相当于值传递。比如下例:,例,6.1,class swapByValue ,int x,y;,public swapByValue (int x, int y),this.x=x;,this.y=y;,public void swap(int x,int y), int z;,z=x; x=y; y=z;,public static void main(String args) ,swapByValue s= new swapByValue (3,4);,Transcript.println(Before swap: x= +s.x+ y= +s.y);,s.swap(s.x,s.y);,Transcript.println(After swap: x= +s.x+ y= +s.y);,运行结果如图,6.2,所示。,图,6.2,例,6.2,class swapByAddress ,int x,y;,public swapByAddress (int x, int y),this.x=x;,this.y=y;,public void swap(Integer x,Integer y), Integer z;,z=x; x=y; y=z;,this.x=x.intValue();,this.y=y.intValue();,public static void main(String args) ,swapByAddress s= new swapByAddress (3,4);,Transcript.println(Before swap: x= +s.x+ y= +s.y);,s.swap(new Integer(s.x),new Integer(s.y);,Transcript.println(After swap: x= +s.x+ y= +s.y);, ,运行后的结果如图,6.3,所示。,图,6.3,在例,6.1,和例,6.2,中均出现了一个关键字,this,,它起什么作用呢?,Java,中关键字,this,表示当前对象。因为实际程序编写过程中,可能会出现局部变量名和成员变量名同名,如例,6.3,中有:,class swap By Address ,int x,y;,public swap By Address (int x, int y),this.x=x;,this.y=y;,其中,对象,swapByAdress,中定义了两个成员变量,x,、,y,,同时方法,swapByAddress,中也出现了以,x,、,y,命名的局部变量,为了避免由此可能造成的二义性,程序中我们用,this,关键字作前缀修饰词来指明是当前对象的实例变量。与此类似,用,this,关键字同样可以调用当前对象的某个方法。,4),构造方法,构造方法用来初始化新创建的对象。类可以包含一个或者多个构造方法,不同的构造方法根据参数的不同来决定要初始化的新对象的状态。所有的,Java,类都有构造方法,它用来对新的对象进行初始化。构造方法与类的名字是相同的。比如,,Stack,类的构造方法的名字为,Stack,,而,Rectangle,类的构造方法的名字为,Rectangle,,,Thread,类的构造方法的名字为,Thread,。下面给出,Stack,类的构造方法:,public Stack() ,items = new Vector(10); ,Java,支持对构造方法的重载,这样一个类就可以有多个构造方法,所有的构造方法的名字都是相同的,只是所带参数的个数和类型相异而已。下面是类,Stack,的另一个构造方法。这个构造方法是根据它的参数来初始化堆栈的大小。,public Stack(int initial Size) ,items = new Vector(initial Size); ,从上面可以看出,两个构造方法都有相同的名字,但是它们有不同的参数列表。编译器会根据参数列表的数目以及类型来区分这些构造方法。所以,当创建对象的时候,要根据它的参数是否与初始化的新对象相匹配来选择构造方法。根据传递给构造方法参数的数目和类型,编译器可以决定要使用哪个构造方法。如对于下面的代码,编译器就可以知道应该是使用单一的整型参数来初始化对象:,new Stack(10);,与此相同,当我们给出下面代码的时候,编译器选择没有参数的构造方法或者缺省的构造方法进行初始化:,new Stack();,另外,如果生成的类不为它提供构造方法,系统会自动提供缺省的构造方法。这个缺省的构造方法不会完成任何事情。下例给出类,AnimationThread,的构造方法,在其初始化过程中设置了一些缺省的数值,比如帧速度、图片的数目等。,class Animation Thread extends Thread ,int frames Per Second;,int num Images;,Image images;,Animation Thread(int fps, int num) ,super(Animation Thread);,this.frames Per Second = fps;,this.num Images = num;,this.images = new Imagenum Images;,for (int i = 0; i = num Images; i+) ,.,.,从该例来看,构造方法的实体跟一般方法的实体是相似的,均包含局部变量声明、循环以及其他的语句。该例的构造方法中出现了以下一条语句:,super(Animation Thread);,与关键字,this,相似,(,我们知道,,this,表示当前对象,),,关键字,super,表示当前对象的父对象,所以使用,super,可以引用父类被隐藏的变量和方法。本例中调用了父类,Thread,的构造方法。使用中我们须注意的是,父类的构造方法必须是子类构造方法的第一条语句,因为对象必须首先执行高层次的初始化。构造方法说明中只能带访问控制修饰符,即只能使用,public,、,protected,及,private,中的任一个。关于访问控制符,后面我们会介绍。,5),方法,finalize,finalize,在,Java,程序中相当于,C+,中的析构方法,它在对象退出的时候释放掉占用的资源。其声明格式如下:,protected void finalize(),.,有些面向对象语言需要保持对所有对象的跟踪,所以在对象不再使用的时候要将它从内存中清除,这个过程就是所谓的,垃圾收集,。,当对象不再有引用的时候,对象就需被清除,即作为垃圾收集的对象。保留在变量中的引用通常在变量超出作用域的时候被清除,比如当我们从某个方法调用中退出时,其局部变量就自动被清除。当然也可以通过设置变量为,null,来清除对象引用。需注意的是,程序中同一个对象可以有多个引用,对象的所有引用必须在对象被垃圾收集器清除之前清除。,Java,有一个立即垃圾收集器,它周期性地把不再被引用的对象从内存中清除。这个垃圾收集器是自动执行的,我们也可以通过调用系统类的,System.gc(),方法来显式地运行垃圾收集程序,(,比如在创建大量垃圾代码之后或者在需要大量内存代码之前运行垃圾收集器,),。在一个对象被系统垃圾收集器处理之前,对象也可调用自己的,finalize,方法进行析构处理,这个过程就是所说的最后处理,也有的参考资料称之为结束方法。,Java,中构造方法和结束方法在类中均是可选的,尤其是结束方法在一般情况下是不需要的,而提供结束方法,finalize,的目的是让程序员有机会释放掉不能被自动内存管理器直接使用的资源或是不能自动释放掉的资源。,6),变量和方法的访问控制,前面我们多次提到过对类、变量及方法的访问控制属性,比如有:,private,、,friendly,、,protected,及,public,。,Java,中最低访问控制范围是类的级别,与之对应也分为四种:同一个类、同一个包、不同包的子类及不同包的非子类。,表,6.1,给出了每一种访问指示的访问等级。,表,6.1,访问控制权限表,访问指示,类,子类,包,所有,private,protected,public,friendly,表,6.1,中,第二列给出了是否类本身可以访问它的成员,(,从上表可以知道,类总是可以访问它自己的成员,),;第三列给出了是否类的子类可以访问它的成员;第四列给出了是否在相同包中的类可以访问该类成员;第五列给出了是否所有的类可以访问该类成员。,类的访问控制权限只能为,public,和,friendly,,变量和方法的访问控制可以为上面四种的任何一种。, private,private,成员只能被它所定义的类或类的不同对象所访问。外部访问这个变量就会出错,因为如果,private,的方法被外部类调用,就会使得程序或对象处于不安全状态。,private,成员就像不能告诉任何人的秘密,所以,任何不需要他人直接访问的成员都应该定义为,private,类型。,下面的类,A,包含了一个,private,成员变量和一个,private,方法:,class A ,private int private Variable;,private void private Method() ,System.out.println(Test for private definition!); ,在做了以上的定义之后,,A,类型的对象可以调用或者修改,private Variable,变量以及调用,private Method,方法,但是其他类型的对象却不行。比如,以下的类,B,就不能访问,private Variable,变量或者调用,private Method,方法,因为类,B,不是,A,类型的。,class B ,void access Method() ,A a = new A ();,a. privateVariable = 10; /,非法,a.privateMethod(); /,非法,这时,编译器就会给出以下错误信息并拒绝继续编译程序:,B.java:9: Variable privateVariable in class A not accessible from class B.,/,在类,A,中的,privateVariable,变量不能从类,B,中进行访问,a. privateVariable = 10; /,非法,1 error /,程序中有一个错误,与此类似,如果程序试图访问方法,privateMethod(),时,将导致如下的编译错误:,B.java:12: No method matching privateMethod(),found in class A. /,在类,A,中没有匹配的方法,privateMethod(),a.privateMethod(); /,非法,1 error /,一个错误,下面我们再给出一个例子来解释同类对象调用,private,的方法。假如,A,类包含了一个实例方法,它用于比较当前的,A,对象,(this),同另外一个对象的,privateVariable,变量是否相等:,class A ,private int private Variable;,boolean is Equal To(A anotherA) ,if (this. Private Variable = another A. private Variable),return true;,else,return false;,结果是运行正常。可见,相同类型的对象可以访问其他对象的,private,成员。这是因为访问限制只是在类别层次,(,类的所有实例,),而不是在对象层次,(,类的特定实例,),上。,实际应用过程中,如果我们不想让别的类生成自己定义的类的实例,可以将其构造方法声明为,private,类型。比如,,java.lang.System,,它的构造方法为,private,,所以不能被实例化,但由于其所有方法和变量均定义为,static,类型,故可以直接调用其方法和变量。, protected,定义为,protected,的类成员允许类本身、子类以及在相同包中的类访问它。一般来说,需要子类访问的成员,可以使用,protected,进行限制。,protected,成员就像家庭秘密,家里人知道无所谓,但是却不让外人知道。现在我们看看,protected,是怎样限制使用在相同包内的类的。假如上面的那个类,A,现在被定义在一个包,Protect1,内,它有一个,protected,成员变量和一个,protected,方法,具体如下:,package Protext1;,public class A ,protected int protectVariable;,protected void protectMethod() ,System.out.println(Test for protected definition!);,现在,假设类,C,也声明为,Protect1,包的一个成员,(,不是,A,的子类,),。则类,C,可以合法地访问,A,对象的,protect Variable,成员变量并且可以合法调用它的,protect Method,,如下:,package Protect1;,class C ,void access Method() ,A a = new A ();,a. protect Variable = 10; /,合法,a.protect Method(); /,合法,下面我们探讨,protected,如何限制类,A,子类的访问。首先定义类,D,,它由类,A,继承而来,但处在不同的包中,设为,Protect2,。则类,D,可以访问其本身类实例成员,protect Variable,和,protect Method,,但不能访问类,A,对象中的,protect Variable,或者,protect Method,。,在下面代码中,,access Method,试图访问在,A,类型对象中的,protect Variable,成员变量,这是不合法的,而访问,D,类型对象则是合法的。与此相同,,access Method,试图调用,A,对象的,protect Method,方法也是非法的。见下例:,package Protect2;,import Protect1.*;,class D extends A ,void access Method(A a, D d) ,a. protect Variable = 10; /,非法,d. protect Variable = 10; /,合法,a.protect Method(); /,非法,d.protect Method(); /,合法, public,public,是,Java,中最简单的访问控制符。修饰为,public,的成员在任何类中、任何包中都可以访问,它相当于是无任何秘密可言,从其使用角度来看,有点相当于,C,语言中的外部变量。例如:,package Public1;,public class A ,public int public Variable;,public void public Method() ,System.out.println(Test for public definition!);,接下来,我们重新编写类,B,,将它放置到不同的包中,并且让它跟类,A,毫无关系:,package Public2;,import Public1.*;,class B ,void access Method() ,A a = new A ();,a. public Variable = 10; /,合法,a.public Method(); /,合法,从上面的代码段我们可以看出,这时类,B,可以合法地使用和修改在类,A,中的,public Variable,变量及方法,public Method,。, friendly,friendly,关键字我们并不陌生,在,C+,中其表示友元类,,Java,中如果不显式设置成员访问控制的时候,(,即缺省的访问控制,),,则隐含使用,friendly,访问控制。该访问控制允许在相同包中的类成员之间相互可以访问。就像在相同包中的类是互相信任的朋友。下例中,类,A,声明了一个单一包访问的成员变量和方法。它处在,Friend,包中:,package Friend;,class A ,int friend Variable; /,缺省为,friendly,void friend Method() /,缺省为,friendly,System.out.println(Test for friendly definition!);,这样,所有定义在和类,A,相同的包中的类也可以访问,friend Variable,和,friend Method,。,假如,A,和,B,都被定义为,Friend,包的一部分,则如下的代码是合法的:,package Greek;,class B ,void access Method() ,A a = new A ();,a. friend Variable = 10; /,合法,a.friend Method(); /,合法,6.2,对 象,6.2.1,对象的创建,Java,中创建新的对象必须使用,new,语句,其一般格式为,class Name object Name = new class Name( parameter List);,此表达式隐含了三个部分,即:对象说明、实例化和初始化。,对象说明:上面的声明格式中,,class Name object Name,是对象的说明;,class Name,是某个类名,用来说明对象所属的类;,object Name,为对象名。例如:,Integer I Variable;,该语句说明,I Variable,为,Integer,类型。, 实例化:,new,是,Java,实例化对象的运算符。使用命令,new,可以创建新的对象并且为对象分配内存空间。一旦初始化,所有的实例变量也将被初始化,即算术类型初始化为,0,,布尔逻辑型初始化为,false,,复合类型初始化为,null,。例如:,Integer I Variable = new Integer (100);,此句实现将,Integer,类型的对象,I Variable,初始值设为,100,的功能。,初始化:,new,运算符后紧跟着一个构造方法的调用。前面我们介绍过,,Java,中构造方法可以重构,因而通过给出不同的参数类型或个数就可以进行不同初始化工作。如例,6.3,,类,Rectangle,定义了一个矩形类,它有多个不同的构造方法,我们可以通过调用不同的构造方法来进行初始化。,例,6.3,public class Rectangle ,public int width = 0;,public int height = 0;,public Point origin;,public static void main(String args),Point p=new Point(20,20);,Rectangle r1=new Rectangle();,Rectangle r2=new Rectangle(p,80,40);,Transcript.println(The area of Rectangle1 is: +r1.area();,Transcript.println(The area of Rectangle1 is: +r2.area(); ,public Rectangle() ,origin = new Point(0, 0);,public Rectangle(Point p) ,origin = p;,public Rectangle(int w, int h) ,this(new Point(0, 0), w, h);,public Rectangle(Point p, int w, int h) ,origin = p; width = w; height = h;,public void move(int x, int y) ,origin.x = x;,origin.y = y;,public int area() ,return width * height;,public class Point ,public int x = 0;,public int y = 0;,图,6.4,public Point(int x, int y) ,this.x = x; this.y = y;,该例中,我们定义了两个类,Rectangle,、,Point,,并调用了类,Rectangle,中的,area(),方法来求矩形的面积。方法,Rectangle(),不带参数,因而只是初始化原点坐标为,(0,0),,矩形的长、宽各为,0,;方法,Rectangle(p,80,40),不仅初始化原点由类型,Point(),指定,同时还限定矩形的长、宽各为,80,、,40,。此程序的运行结果如图,6.4,所示。,6.2.2,对象的使用,前面我们花了很大的篇幅介绍类,其目的就是为了掌握如何使用它。类是通过实例化为对象来使用的,而对象的使用是通过引用对象变量或调用对象的方法来实现的。与,C+,相类似,对象变量和方法均是通过运算符,“,.,”,来实现的。,1,变量的引用,对象变量引用的一般格式为,object Name.variable Name,例如:,class example ,int x; ,example a = new example();,a.x=100;,变量的引用在,Java,中还有一种很特殊的情况,即可以使用表达式指定变量所在的对象,例如:,int z= new example().x;,这个语句创建了一个新的,example,对象,并且得到了它的成员变量,x,。需要注意的是,在这条语句被执行后,程序不再保留对象,example,的引用。这样,对象,example,就被取消引用,因而通过上述语句并不能达到初始化对象的作用。,2,对象方法的引用,与对象变量引用一样,对象方法的引用一般格式为,object Name.method Name(argument List);,例如我们在例,6.3,中调用对象,Rectangle,中的,area(),方法计算矩形的面积:,Transcript.println(The area of Rectangle1 is: +r1.area();,Transcript.println(The area of Rectangle1 is: +r2.area();,虽然通过直接引用对象变量可以改变对象的属性,但是它没有任何意义,(,比如,我们在例,6.3,中,使用,Rectangle,类的构造方法,可以创建不同的矩形,如果设置其高,height,、宽,width,是负的,程序并不认为其非法,),。所以,较好的做法是:不直接对变量进行操作,而由类提供一些方法,对变量的引用可以通过这些方法来进行,以确保给定变量的数值是有意义的。这样一来,,Rectangle,类将提供,setWidth,、,setHeight,、,getWidth,以及,getHeight,方法来设置或者获得宽度和高度。设置变量的方法将在调用者试图将,width,和,height,设置为负数的时候给出一个错误。这样能够更好地体现数据的封装和隐蔽。例如,加上上述的几个方法后,例,6.3,变为,public class Rectangle,.,public void setWidth(int width) ,if (width 0),System.out.println(Illegal number! );,else,this.width=width; ,public void setHeight(int height) ,.,public int getWidth() ,return width; ,public int getHeight(),return height;,public void move(int x, int y) ,origin.x = x; origin.y = y; ,public int area() ,return width * height;,6.3,类的继承概念,Java,通过子类实现继承。继承指的是某个对象所属的类在层次结构中占一定的位置,具有上一层次对象的某些属性。在,Java,中,所有的类都是通过直接或间接地继承,java.lang.Object,类得到的,如图,6.5,所示。,图,6.5,在类的继承过程中,被继承的类为父类或超类,继承得到的类为子类。父类包括所有直接或间接被继承的类。子类继承父类的状态和行为,也可以修改父类的状态或重写父类的行为,(,方法,),,同时也可以再添加新的状态和行为,(,方法,),。需要注意的是,,Java,与,C+,不同,不支持多重继承。同时,为了使继承更为灵活和完善,,Java,支持最终类和抽象类的概念。,所谓的最终类,同数结构中的树叶节点一样,就是不允许对它进行扩展的类,也就是说不可以有该类的子类。实际使用过程中,可以在定义类时用关键字,final,对它加以说明。引入最终类的好处是为了提高系统安全性,因为如果有重要的信息的类允许继承的话,就可能被不怀好意的攻击者加以利用,从而重要的数据就可能被非法修改或泄密。为了防止这些情况发生,可以将那些重要的类说明为最终类,避免非安全事件的发生。,Java,中,类层次的另一个概念就是抽象类,它与最终类相对,需要子类继承完善。在类的说明中我们已讨论过,这里不再详细说明,只是有几点注意事项希望读者留意:,(1),构造方法不能定义为抽象方法。,(2),最终方法不能说明为抽象方法。,(3) static,和,private,修饰符不能用于抽象方法。,(4),不能重载父类中的抽象方法。,6.3.1,子类的创建,通过关键字,extends,来创建某个类的子类,其语法如下:,class subclass Name extends superclass Name .,例如:,class Rectangle extends Shape .,这样,类,Rectangle,就可以继承父类,Shape,中的成员变量和方法。前面一些例子中,在作类的定义时,并没有指明继承于某个父类,比如:,public class Rectangle . ,此时,隐含认为类,Rectangle,缺省继承于类,Object,。当然,继承父类中的,private,属性的变量和方法也是受限制的,这是大家需要注意的地方。我们可以发现,通过继承的关系,使得成熟的代码可以获得重用的好处,大大提高了编程的效率。同时,由类封装而带来的数据隐藏,也可以提高程序的可维护性。,6.3.2,变量的隐藏,继承给我们带来方便,但如果使用中概念不清,也可能会给我们带来一些问题。假设我们实现了某个类的继承,当子类中的成员变量与父类中的成员变量同名时,应该怎么办呢?,Java,解决这一问题的办法是采用所谓的变量隐藏机制。也就是说,如果该种情况发生,则父类中的变量将被隐藏起来。例如:,class super Class ,int same Variable;,.,class sub Class extends super Class,int same Variable,/,此时,此处变量,same Variable,隐藏了父类的同名变量,.,可能有的读者会说,那我非要用父类中的同名变量,应该怎么办呢?其实我们前面讨论构造方法时已经提到过:当引用父类中的变量时,必须使用,super,关键字。,class sub Class extends super Class,int same Variable,/,此时,此处变量,same Variable,隐藏了父类的同名变量,.,System.out.println(Now output the Super Class variable! + super.sameVariable);,/,引用父类中的变量,6.3.3,方法置换,与我们前面所介绍的方法重载很相似,,Java,中的方法置换指的是子类中的方法名与父类中的某个方法名相同,此时,子类中的同名方法就被称为置换方法。置换方法与父类中的同名方法具有相同的方法名、相同的返回值类型和相同的参数表。如果要调用父类中的同名方法,也使用关键字,super,进行前缀修饰。例如:,例,6.4,package untitled4;,import genesis.*;,public class MyClass1 ,int x,y;,public MyClass1() ,public MyClass1(int x,int y) ,this.x=x; this.y=y; ,public void test(),Transcript.println(x,y in superclass is: +x+ +y);,package untitled4;,import genesis.*;,public class MyClass2 extends MyClass1 ,int x,y;,public MyClass2(int x,int y) ,this.x=x;,this.y=y; ,public static void main(String args),MyClass2 m2= new MyClass2(10,20);,MyClass1 m1=m2;,m1.test(); m2.test();,public void test(),super.test();,Transcript.println(x,y in subclass is: + x+ +y);,运行结果如图,6.6,所示。请读者仔细分析程序的执行结果。,图,6.6,6.4 Java,中接口与包的概念,在上一小节我们讨论了,Java,中的一个重要特征:继承机制。由于,Java,只支持单一继承,即一个子类只有一个父类,(,与,C+,不同,),,但实际的开发过程中,可能会碰到需要多重继承的情况,这应该怎么办呢?,Java,中的接口机制为我们提供了实现多重继承的可能。接口的概念较,C+,中的多重继承概念要简单、方便,它可以达到多个不相关的类具有相同的方法这一特殊效果。,6.4.1,接口,接口是一系列没有实现的方法和常量的组合,它提供方法协议的封装,但不限制子类如何实现这些方法,从而使得类的继承变得简单而灵活。通过接口我们可以使处于不同层次、互不相干的类具有相同的行为。总的来说,接口的功能可以归纳为如下几点:,(1),通过接口可以实现不相干类的相同行为而不需考虑这些类之间的层次关系。,(2),通过接口可以指明多个类需要实现的方法