1、Java程序设计系列讲座3OOP之继承性与多态性,黄绍辉厦门大学计算机科学系E-mail: ,来点轻松的话题:Java命名规范,包的命名规范,包的名称一般全部采用小写包名的前缀一般是域名单词序列的逆序实例com.sun.engcom.apple.quicktimeedu.cmu.cs.bovik.cheese,文件的命名规范,必须与该文件中public类的类名同名后缀必须是 .java,类/接口的命名规范,类名一般是名词/名词词组:每个单词的首字母大写,其它字母小写类名应当尽量简单,而且其含义能够尽量准确地刻画该类的含义一般采用全称尽量少用缩写词 (除非该缩写词被广泛使用)实例ClockTim
2、eImageSprite,方法的命名规范,方法名一般是动词/动词性词组首字母小写中间单词的首字母大写,其它字母小写尽量应用简单的、常用的单词实例run( );getBackground( );getTime( );,变量的命名规范,变量名的首字母小写中间单词的首字母大写,其它字母小写变量名的首字母尽量不要用字符: _ 或 $变量名应当简短、有含义、且便于记忆变量名常常由表示其所扮演的角色与数据类型组成实例int i; char c; double widthBox;Point startingPoint, centerPoint;Name loginName;,常量的命名规范,常量名一般全大写
3、,单词之间用下划线分隔 (“_”)实例:static final int MIN_WIDTH = 4;static final int MAX_WIDTH = 999;,编辑方法,尽量不要用TAB排版行数/每行的字符数缩排方式(Indentation)空白符/行友情提示:通常Java的开发环境都会提供源代码的格式化/重排功能,如果使用记事本编辑源代码,建议去下载一个叫astyle的源代码重排工具(DOS命令行工具)。UltraEdit有捆绑这个工具,并提供了GUI界面的调用方式。,文件组织,源程序文件一般采用如下的组织顺序:最开始一般是注释package 和 import 语句类和接口的定义,
4、OOP-Part II 继承性,类的继承和实现,一个类可以被继承,继承有什么好处呢?当一个类被继承的时候,它所有说明为protected和public的成员(变量和函数)都可以传递到它的下一代,这就是好处。例如图形界面的程序,一般都需要继承JFrame,因为JFrame这个类帮你做了很多事情:绘制一个界面,做一个标题栏,可以缩放要是你自己从头开始做一个类似功能的类,那难度可就太可怕了但是如果你继承JFrame的话,那么JFrame可以把它拥有的超过150个精心设计功能强大的函数转让给你使用!多好的JFrame啊,直接继承JFrame类,界面就出来了,一个类可以被继承,例如这个MyGUI类:im
5、port javax.swing.*;public class MyGUI extends JFrame public static void main(String args) MyGUI frame = new MyGUI(); frame.setTitle(早上好!); frame.setSize(500, 400); frame.setVisible(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);注意这个MyGUI类,其实除了继承,啥都没干,但是人家就是有界面,超类和子类,当一个类C1是从C2继承而来的,C1叫做
6、子类(subclass),而C2叫做超类(superclass)子类在OOP里还有一种叫法child class,或者扩展类(extended class),或者派生类(derived class)超类也叫双亲类、父类(parent class),或者基类(base class)子类通常具有比父类更强的功能,因为它不但能够继承父类的成员,还可以添加属于自己的成员,继承性,创建子类格式:class SubClass extends SuperClass 这里SubClass就可以继承所有SuperClass的public和protected的成员,当然,如果SubClass有同名的成员,那么Su
7、perClass的将被屏蔽。例如:class A public int a; protected int b; class B extends A int a; /此a将屏蔽父类的a void f() b+; /这里的b实际上是从A继承来的,又一个继承的例子,class A protected int x; public void f() x-; class B extends A B() x = 100; /这里直接使用的x,其实来自父类A f(); /f函数其实也是来自A,但现在可以直接使用 ,一个忠告,由于无参的构造函数会被子类自动调用,因此如果一个类将来有可能被继承,一定要提供一个无参
8、的构造函数。例如下面的代码会有编译错误: public class Apple extends Fruit class Fruit public Fruit(String name) System.out.println(Fruit的构造函数); ,super & this,Java中通过super来实现对父类成员的访问,super用来引用当前对象的父类。super 的使用有三种情况: 1)访问父类被隐藏的成员变量,如:super.variable;2)调用父类中被重写的方法,如:super.Method(paramlist);3)调用父类的构造函数,如:super(paramlist); t
9、his用于调用同一个类的成员变量或者成员方法,用法和super类似,其格式为:this.variable;this.Method(paramlist);,this的例子,class Point private int x,y; public void setX(int xx) this.x=xx; public int getX() return this.x; public void setY(int y) this.y = y; public int getY() return this.y; 注意只有斜体部分的this才是一定要使用this的场合,其它情况下并不需要this。,super
10、和this的例子,class A protected int x; public void f() x-; class B extends A int x; void f() this.x+; /这里用的是自己的x,也可以直接写x+ super.x = 100; /这里用的是父类继承来的x super.f(); /f函数其实也是来自父类 ,总结一下继承性,public:包内及包外的任何类均可以继承或访问; protected:包内的任何类,及包外的那些继承了此类的子类才能访问; default:包内的任何类都可以访问它,而包外的任何类都不能访问它(包括包外继承了此类的子类) ;private:
11、不能被继承,只能在类内部使用。,继承性的完整例子,题目,我们想做一个类Circle表示平面直角坐标系上的一个圆(解析几何还记得吧.)。然后我们想做一个求面积的成员函数。我们还要假定所有的Circle的成员变量都是private的,所有的成员函数都是public的,下面开始动手设计这个类。,操作步骤 1,先搭一个空架子总是没有问题的:class Circle,操作步骤 2,设计一下成员变量,这里三个足够了:class Circle private double r; /r是半径 private double x,y; /(x,y)是圆心坐标,操作步骤 3,然后是构造函数,这里先搞两个:class
12、 Circle private double r; private double x,y; public Circle() x=0; y=0; r=1; public Circle(double x, double y, double r) this.x=x; this.y=y; this.r=r; /亲,不小心又用到了this,还记得this干吗的不? ,操作步骤 4,然后是面积函数:class Circle private double r; private double x,y; public Circle() x=0; y=0; r=1; public Circle(double x,
13、 double y, double r) this.x=x; this.y=y; this.r=r; public double getArea() return (Math.PI*r*r); ,Eclipse测试程序,综合一下以上的所有步骤:先建一个工程,名字随意,再建一个类,名字叫Circle,然后把Circle的代码贴上;再建一个类,名字随意,例如叫Test,勾上自动生成main函数的选项;现在你的工程里应该有两个java源文件,两个类,其中一个是用来测试的类,所以它有main函数,Circle类由于没有main函数,所有它是不能运行的。因此启动运行的主类是Test。,Test类的写法,
14、public class Testpublic static void main(String args) Circle c1 = new Circle();System.out.println(c1.getArea();Circle c2 = new Circle(3.0,4.0,5.0);System.out.println(c2.getArea();,Circle的改进,Circle类显然不完善,例如你在Test类里,如果想知道c1的x,y,r三个成员分别是多少,可能吗?如果你想在Test类里,随时修改c1的半径,可能吗?答案显然不行,所以,Circle其实还有很多的成员函数需要添加。这
15、里就不演示了,自己动手试试。,研究一下对象的引用,一个类的成员函数(static除外),是不是一定要new才能被使用呢?例如GUI编程中经常有这种语句:Container container = getContentPane(); container.add(new Button(button1);这个container好像就没有new对吧?怎么就可以抓来用呢?难道随便get一下也能用?其实这个container已经被new过了,只是你没有看见而已。get一下相当于做对象的引用。这么说好像很复杂,看一个例子:,对象的引用 I,class A public int x;public class
16、Testpublic static void main(String args) A a = new A(); a.x = 1; System.out.println(a.x=+a.x); A b = a; /注意这里b不是new出来的 b.x = 10; System.out.println(b.x=+b.x); System.out.println(a.x=+a.x); a.x = 100; System.out.println(a.x=+a.x); System.out.println(b.x=+b.x);,这个例子的输出是:a.x=1b.x=10a.x=10a.x=100b.x=100
17、,对象的引用 II,总结一下,如果一个对象不是new出来的(例子中的b),而是直接用一个已经new出来的对象(例子中的a)给它赋值,那么这个对象b就叫做a的引用。引用的对象之所以可以直接使用,不需要new,其实是因为它所指向的对象和赋值给它的对象是同一个!也就是它只是给原来的对象起了一个别名而已。,对象的引用 III,总结一下,类似下面这样b=a的操作叫做对象引用:A a = new A(); A b=a;对象的引用乍看上去有点类似对象整体赋值,因为在某些高级语言中,确实会把a整体复制一遍给b,于是a和b仅仅内容相同而已,但是属于不同对象;但是Java对这句话的理解却是:b仅仅是a的别名,a和
18、b其实表示同一个对象。所以别把语言搞混了。,OOP-Part III 多态性,多态性,静态多态性体现在方法的重载在编译阶段,具体调用哪个被重载的方法,编译器会根据参数的不同来静态确定调用相应的方法。 动态多态性由于子类继承了父类所有的属性(私有的除外),所以子类对象可以作为父类对象使用。程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个对象可以通过引用子类的实例来调用子类的方法。 重写方法的调用原则(匹配原则):对子类的一个实例,如果子类重写了父类的方法,则运行时系统调用子类的方法;否则调用父类的方法。,静态多态性:函数重载,class A A() A(int i) i+; void
19、fun(int i) i+; void fun(double f) f+; void fun(int i, int j) i+; j+; 上例中函数A和fun都算重载。,重载的特征:函数名相同 重载的两个要素: 参数类型不同 参数个数不同 重载与返回值无关,动态多态性,当继承发生的时候,动态多态性就来了,运行时多态性是面向对象程序设计代码重用的一个最强大机制。动态性的概念也可以被说成“一个接口,多个方法”。Java实现运行时多态性的基础是动态方法调度,它是一种在运行时而不是在编译期调用重载方法的机制。,有个例子,class A public void fun() System.out.prin
20、tln(“A”); class B extends A public void fun() System.out.println(“B”); class C extends B public void fun() System.out.println(“C”); class Test public static void main(String args) A a; B b = new B(); C c = new C(); a=b; a.fun(); / 此处将输出Ba=c; a.fun(); / 此处将输出C ,解释一下,Java 的动态多态性这种机制遵循一个原则:当超类(父类)对象的引用
21、变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法;当然,这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。你可能会问,如果这个方法没有被覆盖呢?Good question,还有个例子,class A public void fun() System.out.println(“A”); class B extends A public void funB() System.out.println(“B”); class C extends B public void funC() System.out.println(“C”); class Test
22、 public static void main(String args) A a; B b = new B(); C c = new C(); a=b; a.fun(); / 此处将输出Aa=c; a.fun(); / 此处将输出A ,总结一下,Java在函数调用的时候,是按照这个顺序查找函数的:1. 先看看自己有没有这个函数,如有则调用;2. 向上一级,看看有没有这个函数,一直上溯到找到函数为止。Java的子类可以直接转化为父类( class B extends A )如:B b = new B(); A a = b; /注意这里a是引用Java的父类必须强制转化为子类( class B
23、extends A )如:A a = new A(); B b = (B)a; /这个操作没意义或:B b = new B(); A a = b; B bb = (B)a;/这个倒可以,但是绕了一圈,还不如直接用:bb = b,效果一样,抽象类与接口 I,是不是所有的类都可以new出来用?答案是:NO!因为抽象类就不可以。它只能被继承。接口是一种特殊的抽象类,它也不能被new出来,而只能被实现(implements)。抽象类的定义是在class前面加上一个abstract,而接口的定义是把class关键字换成了interface。,抽象类与接口 II,例如:class A /A是类 . abs
24、tract class B /B是抽象类 . interface C /C是接口,一种特殊的抽象类 . ,为什么需要抽象类?I,在面向对象的概念中,所有的对象都是通过类来描述的,但是反过来却不是这样:并不是所有的类都是用来描述对象的,如果一个类中没有包含足够的信息来描述一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。,为什么需要抽象类?II,比如:圆、三角形、矩形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因
25、为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。,为什么需要抽象类?III,如果我们要设计一个Shape的类,用来表示几何形状;内含一个函数getArea,表示这个形状的面积可是这个面积目前是无法求的,因为我们的信息太少,所以,这个Shape只能设计成抽象类。在Shape基础上,我们可以继承出一个类Rect,用来表示矩形;也设计一个类Circle,用来表示圆形;显然,这两个类的getArea函数体是可以实现的。,为什么需要抽象类?IV,abstractclass和interface在Java语言中都可以用来设计抽象类。abstarctclass在Java
26、语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在“isa”关系,即父类和派生类在概念本质上应该是相同的。例如上一个例子,Rect和Circle都是一种形状,因此用Shape做父类是很自然的。此时Shape设计成abstract class在概念上也比较好接受。,为什么需要抽象类?V,interface表示的是“likea”或者“hasa”关系。我们用一个例子来表明,尽管都可以表示抽象类,但是abstract class并不是万能的。例如我们设计一个类HaoJiao,用来描述动物的嚎叫。显然各种动物嚎法不同,所以这个类没法具体实现,因此只能设计成抽象类。可是,HaoJia
27、o好继承吗?难道有更高级的HaoJiao?如果我们还有一个类Animal,显然它应该会有HaoJiao的属性,但是Animal不能继承自HaoJiao(否则在概念上实在不好理解)。于是我们只能将HaoJiao设计成interface,然后类Animal可以实现(implements)这个接口。,抽象类与接口 III,抽象类有一个特殊的规定,它里面的抽象函数在继承的类中一定要被重写!(为什么呢?如果你接受不了这个事实,参见刚翻过的5页PPT)接口也有一个特殊的规定,它里面的抽象函数在实现的类中一定要被重写!(不过很不幸,接口里所有的函数都是抽象的,所以它是特殊抽象类)所以总结起来就是一点,抽象函
28、数在设计的时候,自己是不用写函数体的(其实是想写也写不了,呵呵);但是一旦被继承extends或者实现implements,函数体就一定要被具体实现。,抽象类和继承的例子,abstract class A abstract public void f(); /注意这里没有函数体class B extends A public void f() /注意这里已经实现了f函数,尽管只是空函数class C extends A double x; public void f() x=100; /注意这里已经实现了f函数 public void ff() x+; /这个是C自己的函数,接口和实现的例子,
29、interface A abstract public void f(); /注意这里没有函数体class B implements A public void f() /注意这里已经实现了f函数,尽管只是空函数class C implements A double x; public void f() x=100; /注意这里已经实现了f函数 public void ff() x+; /这个是C自己的函数,接口总结,接口是一种特殊的抽象类,所以它仍然具有类的特点。Java在继承机制上,只支持单一继承(也就是任何类的父类只有一个)。假设你现在想同时把A类和B类的遗产继承过来,就只能借助于接口了
30、。因为Java允许你实现多个接口,用逗号隔开就可以,当然前提是A和B你要先改成接口才行。顺便说一句,当你随手写一个class A,而没有继承任何类的时候,其实这个A并不是孤零零的,因为Java会认为A继承了java.lang.Object。,内部类 I,为什么需要内部类?Java中的内部类和接口加在一起,可以有效解决常被C+程序员抱怨Java中存在的一个问题没有多继承。实际上,C+的多继承设计起来很复杂,而Java通过内部类加上接口,可以很好的实现多继承的效果。内部类如何定义?简单一点说,就是在一个class A的内部,还包含着另外一个完整的class B,B就是内部类;当然B不是随便放哪里都
31、行,位置还是有讲究的。下次讲座我们会在适当的地方用内部类做例子说明这个问题。,内部类的造型,public class OuterClass private int data; public void m() InnerClass instance = new InnerClass(); / InnerClass完全处于OuterClass内部,一个标准内部类 class InnerClass public void mi() data+; m(); ,内部类 II,谈一下A包含B的时候的访问权限:如果B非静态,它可以访问A的一切资源(当然也包括private,自家人嘛,不用客气)A可以访问B的
32、成员,不过先要取得内部类的对象,并且取决于B成员的封装等级(B好歹也是一个完整类,所以总不能去访问它的private成员,坏了规矩)如果B非静态,它不能包含任何static成员如果B静态,那么它:1. 可以包含static或非static成员;2. 只能访问A的static成员A可以访问B的成员:对于static成员,用类名.成员即可访问,对于非static成员,访问规矩同条款2对于方法中的内部类B或块中内部类B,只能访问块中或方法中的final变量。,题外话,可执行的Java程序 I,Java可以编译成exe可执行文件吗?答案是:可以的。然而,坏消息是,就算你编译成exe文件,它仍然要在jr
33、e的环境中运行,而不能在没有安装jre的机器上独立运行。说白了,Sun公司是不主张将Java编译成exe的,所以将Java编译成exe的工具不是Sun公司开发的。至少有一个工具可以把Java搞成exe,那就是jsmooth(http:/ II,Java其实有一个jar命令可以做到双击运行的,jar的作用是把一堆的class打包成一个文件,同时,当你双击它的时候,它可以自动查找你指定的主类,然后帮你启动主类开始运行看看,这就是双击运行!jar命令的用法不算复杂,自己在命令行敲一下jar就有详细提示;其实你也可以用一些开发工具(例如Eclipse)的导出功能直接生成一个可执行的jar!,THE END,