Java-面向对象编程.ppt
Java程序设计,2,面向对象程序设计的特点,面向对象程序设计具有三大特征:封装继承多态,封装,封装:隐藏内部细节,控制外部访问的方式恰当的封装可防止程序员相互干扰,实现了模块化和信息隐藏通过控制对(内部)属性和方法的访问方式来实现封装不要依赖于你不需要知道的知识类和包是Java语言中的基本封装机制类实现了数据和方法的封装包是对一组相互耦合度较高的类型的封装,访问控制修饰符,JAVA中的访问控制修饰符 public,protected,package(缺省),private,关联和聚合,对象不是孤立的关联(Association)两个对象间存在某种语义联系方向性关联可以是单向的,也可以是双向的单向关联:A“知道”B,但B不“知道”A双向关联:A“知道”B,B也“知道”A依赖性不完全依赖A“知道”B,因此A依赖于B这种依赖一般是松散的,即A和B的存在都不需要以对方的存在为基础实现时,一般通过保存对方的对象标识来表达,关联和聚合,关联(Association)的UML表示,关联和聚合,聚合(Aggregation)表示两个对象间有整体部分关系一种特殊的关联对象间的聚合关系总是单向的依赖性依赖较强B是A的一部分,如果A聚合B(has-A关系)将B从A中取出,则A往往变得不完整一般情况下,B可以独立于A而存在换句话说,在系统内部存在一个A和B同时都独立存在的暂时状态(当然此时聚合对象的功能不完整)。实现时,一般通过在A中保存B的对象标识来表达,关联和聚合,聚合(Aggregation)的UML表示,aStudent,aSchedule,关联和聚合,复合(Composition)一种更强的聚合关系(特殊的聚合)对象间的复合关系总是单向的依赖性部分的生命周期依赖于整体B不能独立于A而存在B的生命周期受A的控制(B会随着A的创建而创建,随A的消亡而消亡)实现时,一般通过在A中保存完整的B对象来表达B对象的实例化在A中完成,关联和聚合,复合(Composition)的UML表示,aTree,aLeaf,关联和聚合,练习:以下各组对象之间应是哪种关联关系?学生课本教师学生书前言书作者汽车引擎PCCPU深圳大学深圳大学软件学院顾客合同,继承概述,类与类之间还有一种Is-A关系,子类自动获得父类的方法与属性,我们称之为继承 子类拥有父类的一些公有的属性和方法,并且子类可能也会有自己的方法和属性 派生与抽象、特化与泛化除了添加父类中没有的新方法外子类还可以通过重写父类的方法来重定义自己的行为方式,继承基础,重写与重载不同,重载需要不同的参数来区别相同方法名的方法,重写是发生在子类与父类上,重写父类方法必须与父类的方法名同名同参并且同返回值。当访问修饰符为private时,表明该方法不能被重写,同样当方法具有final关键字时该方法不能被重写。子类虽然重写了父类的某个方法,但仍然需要用到父类的这个方法,这时使用super关键字仍然可以调用父类的方法。,/TestOstrich.javapublic class TestOstrich public static void main(String args)Bird ostrich1=new Ostrich();/向上转型 ostrich1.fly();class Bird public static void fly()System.out.println(Bird Flying!);class Ostrich extends Birdpublic static void fly()System.out.println(I cant fly!);输出结果:Bird Flying!,Static方法不能重写,Bird类派生了鸵鸟Ostrich类,以上程序声明了一个Bird类型的鸵鸟对象,象基本类型数据的自动提升一样,鸵鸟自动提升为Bird类,这叫做向上转型。static方法是不会被重写如TestOstrich.java所示:子类中定义了与父类同名的fly方法,父类的fly方法不会被重写,而是被遮蔽因此得出结果鸵鸟会飞!,构造方法的调用顺序,子类在产生对象实例时首先会调用父类的构造方法,如果没有显示的指定调用父类自定义的构造方法,那么编译器会调用默认的父类构造方法:super()。但是父类如果自定义了带参数的构造方法,就必须在子类的构造方法中指定调用它,否则会因找不到super()而报错。注意,如果需要显示的调用父类构造方法,必须将其写在构造方法里的第一行。接着按声明的顺序调用成员初始化。最后调用本构造方法的方法体,Object类,Java中所有的类都是继承自一个老祖宗,Object类,它们都是Object类的子类。如果一个类在声明时没有用extends关键字显式的指定继承自某个基类,则Java隐式的指定它的默认基类为Object类。这种强制的继承模式叫做单根继承模式。单根继承使得Java简化了许多操作,基于单根继承,所有的对象都可以像基本数据类型一样向上转型到根层次上而使其操作上一视同仁 单根继承的优势还在于可以在Object类定义所有对象都通用的方法,所有类都会自动继承这些方法,toString方法,Object类定义了 public String toString()方法,返回值是String类型,每当个对象都会有个toString方法,作用就是描述对象的信息要想使用toString方法必须按自己的需求重写这个方法,对象的比较,Java中比较两个基本数据类型变量的值时很简单使用“=”操作符 Object类中的equals方法提供了对象内容的比较方法同样我们要是用这个方法必须重写它 如果不重写equals方法,将调用object类的equals方法。Object类的equals方法相当于“=”,为什么要继承,“继承”概念的引入最早出现于Simula67语言需要继承,因为它支持更丰富、更强大的建模,有利于重用它可以在一个类中定义信息和行为,在相关子类中共享这些定义继承是很自然的使用继承,可以在已有类的基础上扩充,建立新类抽取一组类中的共同信息和行为,并同时保持类型安全进行抽象。对类进行约束,设计类层次结构,自顶向下根据需要逐步扩充自底向上寻找相同点和相异点逐级抽象继承的表示法:一条带有空心大箭头的有向实线,箭头指向父类标识继承关系的原则:LSP“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”Liskov,1987,多重继承,为什么引入多重继承?多重继承的优点功能强大允许私有继承更接近真实世界允许混合(mix-in)多重继承的缺点复杂,有时难以理解导致名字冲突(二义性)重复继承编译器和运行时实现困难,示例:摘自ARM(,The Annotated C+Reference Manual,Addison-Wesley,1990)class Lottery public:virtual int draw();.;class GraphicalObject public:virtual int draw();.;class LotterySimulation:public Lottery,public GraphicalObject./没有声明draw;LotterySimulation*pls=new LotterySimulation;pls-draw();,/编译错误,class B.;class C.;class D:public B,public C.;,往往最后会发展成象下面这样:class A.;class B:virtual public A.;class C:virtual public A.;class D:public B,public C.;,问题是:设计类A和类B时,能否知道它们会被同时继承?,多重继承,如何替代多重继承(Java、C#等语言中的)接口聚合面向方面编程(AOP)范型(类属),继承与聚合,继承与聚合都可用于扩展类的功能和进行重用继承通过扩展一个已有类的实现,从而获得新功能泛化类(超类)可以显式地捕获那些公共的属性和方法特殊类(子类)则通过附加属性和方法来进行实现的扩展聚合通过创建一个聚合了其它对象的对象,从而获得新功能通过将功能委托给所聚合的一个对象,从而实现新功能,继承与聚合,例子:class Stack extends ArrayList private int stack_pointer=0;public void push(Object article)add(stack_pointer+,article);public Object pop()return remove(-stack_pointer);public void push_many(Object articles)for(int i=0;i articles.length;+i)push(articles);,Stack a_stack=new Stack();a_stack.push(1);a_stack.push(2);a_stack.clear();,继承与聚合,继承优点容易进行新的实现,因为其大多数可继承而来易于修改或扩展那些被复用的实现聚合破坏了封装性,因为这会将父类的实现细节暴露给子类。白盒复用,因为父类的内部细节对于子类而言通常是可见的当父类的实现更改时,子类也不得不会随之更改从父类继承来的实现将不能在运行期间进行改变,继承与聚合,聚合优点黑盒复用,因为被包含对象的内部细节对外是不可见封装性好实现上的相互依赖性比较小每一个类只专注于一项任务。通过获取指向其它的具有相同类型的对象引用,可以在运行期间动态地定义(对象的)聚合类(子类)则通过附加属性和方法来进行实现的扩展缺点易于导致系统中的对象过多为了能将多个不同的对象作为组合块(composition block)来使用,必须仔细地对接口进行定义,继承与聚合,Coad规则仅当下列的所有标准被满足时,方可使用继承:1.子类表达了是一个的特殊类型,而非是一个由所扮演的角色。2.子类的一个实例永远不需要转化(transmute)为其它类的一个对象。3.子类是对其父类的职责(responsibility)进行扩展,而非重写或废除。4.子类没有对那些仅作为一个工具类(utility class)的功能进行扩展。5.对于一个位于实际的问题域(Problem Domain)的类而言,其子类特指一种角色(role),交易(transaction)或设备(device)。,继承与聚合,例:,1.是一个的特殊类型,而非是一个由所扮演的角色-失败。乘客是人所扮演的一种角色。代理人亦然。2.永远不需要转化-失败。随着时间的发展,一个Person的子类实例可能会从Passenger转变成Agent,再到Agent Passenger。3.扩展,而非重写和废除-通过。4.不要扩展一个工具类-通过。5.在问题域内,特指一种角色,交易或设备-失败。Person不是一种角色,交易或设备。继承并非适用于此处!,继承与聚合,例:,1.是一个的特殊类型,而非是一个由所扮演的角色-通过。乘客和代理人都是特殊类型的人所扮演的角色。2.永远不需要转化-通过。一个Passenger对象将保持不变;Agent对象亦然。3.扩展,而非重写和废除-通过。4.不要扩展一个工具类-通过。5.在问题域内,特指一种角色,交易或设备-通过。PersonRole是一种类型的角色。继承适用于此处!,继承与聚合,例:,1.是一个的特殊类型,而非是一个由所扮演的角色-通过。预订和购买都是一种特殊类型的交易。2.永远不需要转化-通过。一个Reservation对象将保持不变;Purchase对象亦然。3.扩展,而非重写和废除-通过。4.不要扩展一个工具类-通过。5.在问题域内,特指一种角色,交易或设备-通过。是一种交易。继承适用于此处!,继承与聚合,小结1.聚合与继承都是重要的重用方法2.在OO开发的早期,继承被过度地使用3.随着时间的发展,我们发现优先使用聚合可以获得重用性与简单性更佳的设计4.当然可以通过继承,以增加可复合的类(enlarge the set of composable classes)。5.因此聚合与继承可以一起工作6.但是我们的基本法则是:优先使用对象聚合,而非(类)继承,区分接口继承与实现继承*,深入分析继承机制,会发现它由两个可分的部分组成:函数接口的继承函数实现的继承作为类的设计者有时希望派生类只继承成员函数的接口(声明)有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现有时则希望同时继承接口和实现,并且不允许派生类改写任何东西,区分接口继承与实现继承,例:abstract class Shape public abstract void draw();virtual void error(String msg)int objectID().;class Rectangle extends Shape.;class Ellipse extends Shape.;,区分接口继承与实现继承,定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现非虚成员函数表明了一种特殊性上的不变性,因为它表示的是不会改变的行为,区分接口继承与实现继承,Tips:如果需要简单虚函数,不应将其声明为公有的决不要重新定义继承而来的非虚函数在C+中,如果要使用实现继承,尽可能使用private继承面向接口编程接口表达了对象在交互中的“角色”“应该尽可能的避免实现继承”Jams Gosling实现继承打破了类的封装实现继承增加了耦合避免“向下转换”继承层次,类型系统,类型一组值作用于这些值上的操作在OO世界中,类型(Type)=类(Class)类型系统一组禁止误用值的规则类型表达式、定型断言、定型规则、类型检查算法可以在运行时检查(动态类型系统),也可在编译时检查(静态类型系统),类型系统,动态类型系统 编译快,编译器实现简单程序员修改方便支持Test-Modify方式最终仍可以发现错误静态类型系统编译仍相当快运行性能高可以找出拼写错误程序自描述性较好,多态性,多态性(polymorphism)多态变量一个值在不同上下文中表示不同类型可以把多态变量看作是一个占位符多态消息一个消息不同上下文中有不同的行为(实现)为限制对“多种类型”的滥用,需要使用继承来附加约束仅允许将子类型对象的类型“改变”为父类型,多态是和继承密切相关的,正因为有继承,才会有多态出现。多态从字面上的意思应该是多种形态。更进一步,延伸到继承里来,那么多态就应该是具有相同的操作不同的行为的一种特征。,多态性,多态性,问题为什么要引入多态?为了支持对类型扩展中涉及的行为扩展早期的想法现在呢?,多态性,多态的应用代替分支语句使得不同的行为(实现)可以由相同的消息(接口)来引发,以提供更利于重用的封装留下“扩展点”,便于今后进行扩展代替“函数指针”,向上转型(upcasting),publicclassShape voiddraw()staticvoidstart(Shapes)s.draw();publicstaticvoidmain(Stringagrs)start(newCircle();start(newSquare();classCircleextendsShape voiddraw()System.out.println(“drawCircle”);classSquareextendsShape voiddraw()System.out.println(“drawSquare”);,向上转型(upcasting),在start()方法中,通过传入的参数类型不同,表现出来的行为也会不同。但是传入后的参数都统一转型为Shape这个基类型,这里就表现为向上转型。由导出类到基类,从继承图上面我们可以看出是向上移动的,因此我们一般称之为“向上转型”。向上转型是从一个较具体的类型转换为一个较抽象的类型的过程,所以说是安全的。在导出类中至少包含了基类的方法。在向上转型的过程中,由于在导出类中可能含有基类中没有的方法。类接口会丢失一些方法,将一个方法调用同一个方法主体关联起来被称之为绑定,若在程序执行前绑定就被称为早期绑定。晚期绑定也被称为动态绑定,即编译器始终都不会知道对象的类型,只有当方法在运行期间被调用时,方法的调用机制根据实参类型找到正确的方法体。,动态绑定,动态绑定,动态绑定运行时将消息关联到实现代码上处理多态的机制“真正的多态”多态性规则总是使用尽可能高的抽象级别来编程总是把字段、局部变量、方法参数的类型声明为继承层次中最高的类,构造器中多态方法的行为,publicclassShape3 Shape3()System.out.println(“thisisashape”);draw();voiddraw()publicstaticvoidmain(Stringagrs)Shape3circle=newCircle();classCircleextendsShape3 inti=100;Circle()draw();voiddraw()System.out.println(“drawCircle”+i);,最后的输出结果是:this is a shapedraw Circle0draw Circle100 不是我们希望的结果。,构造器中多态方法的行为,I、在其他任何事物发生之前,将分配给对象的存储空间初始化为二进制的零。II、如前所述的那样调用基类构造器。因此我们会发现,虽然在基类的构造器中调用了其子类的draw方法,而里面的i值还并没有被初始化,但由于步骤1的原因,i的值被置为0。III、按照声明顺序调用成员的初始化方法。IV、调用子类的构造器主体。,抽象类和接口,抽象类,使用abstract修饰的类叫做抽象类 使用abstract修饰的方法叫做抽象方法 抽象类的意图是为从它派生出去的所有类都创建一个通用接口。从而实现运行时多态。抽象类不一定要包含抽象方法。如果一个类中包含了一个以上的abstract方法,那么这个类必须 是abstract。abstract类不能被实例化,只能作为父类来派生子类。由抽象类派生的非抽象子类必须实现父类中的所有抽象方法。抽象类、抽象方法的表现形式。,接口,是一个更“纯”的抽象类。(不能含有非abstact方法,变量)接口这样描述自己:“对于实现我的所有类,看起来都应该象我现在这个样子”。接口只包含常量和方法的定义,没有变量和方法的实现。接口可以实现多重继承的功能。“面向接口设计”,1.关于接口的几个问题,接口声明格式:public interface 接口名 extends 接口列表 接口体接口体包含常量和方法的定义,2.接口的定义,3.接口的实现,使用implements关键字,接口,