java课件第八版第九章中文版.ppt
不变对象与类不变对象与类:l不变对象:不变对象:指在实例化后其外部可见状态无法更改的对指在实例化后其外部可见状态无法更改的对象。象。l不变类:不变类:生成不可变对象的类。生成不可变对象的类。例如:例如:l不变类不变类:Boolean,Byte,Character,Double,Float,Integer,Long,Short,String.l可变类可变类:StringBuffer,java.util.Date 等等l不变对象的好处不变对象的好处:l只能处于一种状态,所以只要正确构造了它们,就决只能处于一种状态,所以只要正确构造了它们,就决不会陷入不一致的状态。不会陷入不一致的状态。l不必复制或克隆不变对象,不必复制或克隆不变对象,就能自由地共享和高速缓就能自由地共享和高速缓存对它们的引用;也可以高速缓存它们的字段或其方存对它们的引用;也可以高速缓存它们的字段或其方法的结果,而不用担心值会变成失效的或与对象的其法的结果,而不用担心值会变成失效的或与对象的其它状态不一致。它状态不一致。l它们本来就是它们本来就是线程安全的线程安全的,所以不必在线程间同步对它,所以不必在线程间同步对它们的访问。们的访问。这可以极大地简化编写并发程序的过程,并减少程序可能存在的潜在并发错误的数量。l线程安全问题发生在当多个线程正在试图并发地修改一个对象的状态(写写-写冲突写冲突)时,或当一个线程正试图访问一个对象的状态,而另一个线程正在修改它(读读-写和写和写写-读冲突读冲突)时。要防止这样的冲突,必须同步对共享对象的访问,以便在对象处于不一致状态时其它线程不能访问它们。l类不变条件:l所有成员都是private。l不提供对成员的修改器方法(setXXXX)。l没有可变成员的访问器方法(可变成员引用getXXXX)举例:三条件缺一不可public class T9ChangableClass public static void main(String args)ChangableClass c=new ChangableClass(original value);System.out.println(c.getSB().toString();c.getSB().append(is changed);System.out.println(c.getSB().toString();class ChangableClass private StringBuilder sb;public ChangableClass(String sb)this.sb=new StringBuilder(sb);public StringBuilder getSB()return sb;l本例的类具备前两条件,即类ChangableClass属性全部私有,且无setter。但对象构造后内容仍可变-状态可变!。this引用:l代指实例本身的引用。因而,与类的实例有关。l作用一:当方法中的局部变量与实例变量同名时,实例变量被隐藏,为访问该实例变量,需要在实例变量名前加this引用。l作用二:用在构造器中,调用重载的构造器。this(参数表);/调用有参构造或this();/调用无参构造l调用构造器时必须在其他语句前出现,否则编译错误!Super()也不行!l有参构造必须显式调用!l举例(作用二):测试如下内容l隐含this(用于限定构造器中的变量)l显式this调用重载构造器(无参中均先调用有参)l默认无参构造器调用及顺序l无参中均先调用有参是否导致父类构造器重复调用?!publicclassT9Thispublicstaticvoidmain(Stringargs)Sonson=newSon();/用无参构造器生成实例classGrandfatherprivateStringname=grandPa;publicGrandfather()/无参构造器,隐式调用this(grandfather);System.out.println(name);publicGrandfather(Stringname)/有参构造器,须显式调用System.out.print(name);classFatherextendsGrandfatherprivateStringname=dadi;publicFather()/无参构造器,隐式调用this(father);System.out.println(name);publicFather(Stringname)/有参构造器,须显式调用System.out.print(name);classSonextendsFatherprivateStringname=son;publicSon()this(son);System.out.println(name);publicSon(Stringname)System.out.print(name);使用无参构造时:调用父类无参构造器时刻使用有参构造时:调用父类无参构造器时刻l只使用无参构造器时的运行结果。l父类构造器隐式调用,并按继承层次上从上到下顺序构造。l子对象的构造包含父对象的构造。l父类无参构造器只会调用一次!子类无参构造器调用自己的有参构造器不会导致两次隐式调用父类无参构造。l将Sonson=newSon();换成:Sonson=newSon(“child”);运行结果。同样,隐式调用父类构造器。问题:下述程序执行结果?lclassTeacherintvar;Teacher(doublevar)this.var=(int)var;Teacher(intvar)this(hello);Teacher(Stringvar)this();System.out.println(var);Teacher()System.out.println(goodbye!);publicstaticvoidmain(Stringargs)Teachert=newTeacher(5);super关键字:l子类构造时要隐式调用父类的无参构造器,能否显式调用父类的构造器去初始化父类呢?如能,则对父类对象的初始化可依据需要灵活进行。lsuper指当前对象的父类的关键字。l一定想要在子类中访问继承父类的同名属性子类中访问继承父类的同名属性,使用super.属性名属性名来访问(问题:不同名如何?)l访问父类被子类覆盖的方法也用super.方法名方法名(问题:未被子类覆盖的方法如何?)lSuper和this不同,它不是引用!Super只限于子类内部使用;this可传到类的外部去!使用super:publicclassT9UsingSuperpublicstaticvoidmain(Stringargs)UsingChildt=newUsingChild();System.out.println(t.getSuperField();System.out.println(t.callSuperMethod();classUsingParentStringname;UsingParent(Stringname)this.name=name;StringgetName()returnname;class UsingChild extends UsingParent /子类全覆盖父类成分子类全覆盖父类成分 String name=child;UsingChild()super(parent);/显式调用父类有参构造器显式调用父类有参构造器 public String getName()return name;public String getSuperField()System.out.print(Return the field value of super:);return super.name;/访问父类同名属性访问父类同名属性 public String callSuperMethod()System.out.print(Calling the method of super:);return super.getName();/访问被子类覆盖的父类方法访问被子类覆盖的父类方法 完全同父类l可见不是子类构造器中使用super时,无第一条语句约束。l显式调用父类构造器则必须第一条语句。举例:测试this引用可赋值给其类类型的变量publicclassT9AssignThispublicstaticvoidmain(Stringargs)AssignThist=newAssignThis();AssignThiss=t.getThis();System.out.println(s.name);classAssignThispublicStringname=Test;AssignThisas;AssignThis()as=this;publicAssignThisgetThis()returnas;举例:测试super不是引用publicclassT9AssignSuperpublicstaticvoidmain(Stringargs)AssignSupert=newAssignSuper();AssignParents=t.getSuper();System.out.println(s.name);classAssignParentpublicStringname=Test;classAssignSuperextendsAssignParentpublicAssignParentgetSuper()returnsuper;如是引用,则本方法应能执行(同上例一样)l继承复用:如果想要一个类也具有另一个类的方法功能时,早期一般情况会采用将这个类去继承另一个类,这就是继承复用l继承复用问题:l继承一定是从一般到特殊的关系,父子类之间一定要是is-a的关系。而程序中的两个类不是/不一定是is-a的关系,不能直接设计为继承关系l如果被继承的另一个类中有protected属性,那么在子类中就可以随意访问和修改,数据不安全,继承复用破坏了封装。(public更不用说!)l对于被继承的另一个类中有些方法,子类不想要,但因继承却不得不要,破坏了子类的设计要求l继承复用也称白盒复用,其要破坏封装。继承复用和组合/聚合复用l组合组合/聚合复用聚合复用:在一个类中维护另一个类的对象作为属性,然后在自己的方法中不写自己实现,而去调用属性指示的另一个类的对象方法来实现自己的方法。l组合/聚合复用的好处l不破坏封装l不需要继承不想要的方法l可以复用别的类的方法来实现自己的方法l组合/聚合复用也称黑盒复用,不破坏封装l复用总则:尽可能使用组合/聚合复用,不采用继承复用。lA类组合/聚合复用B类的方法resuedMethod()ClassABb=newB();reusedMethod(.)b.resuedMethod(.);ClassBreusedlMethod()OO设计原则在Java中的体现:类的设计原则:类的设计原则:l除了上一章中介绍的除了上一章中介绍的Java类设计的原则外,类设计的原则外,OOD中更一中更一般的类设计原则:般的类设计原则:l依赖倒置原则DependencyInversionPrinciple(DIP)l里氏替换原则LiskovSubstitutionPrinciple(LSP)l接口分隔原则InterfaceSegregationPrinciple(ISP)l单一职责原则SingleResponsibilityPrinciple(SRP)l开闭原则TheOpen-ClosedPrinciple(OCP)依赖倒置原则DependencyInversionPrinciple(DIP)l两个原则,两重含义:l高层类模块不依赖低层类模块,两者均依赖于抽象。(传统的面向过程的方法中,高层模块一般依赖于低层的具体过程,使得两者均难于复用!)。l抽象不应依赖于细节,而细节应依赖于抽象。l具体实现:高层模块(抽象)低层模块高层模块低层模块抽象接口lclass A class B extends A class C extends A .以上是第一方式实现以上是第一方式实现lInterface C class A public void handle(C c)class B implements C Cb=newB();Aa=newA();a.handle(b);抽象抽象高层模块具体里氏替换原则LiskovSubstitutionPrinciple(LSP)l所有引用基类的地方必须能透明地使用其子类的对象。l2个条件:l不应该在代码中出现if/else之类对子类类型进行判断的条件。以下代码就违反LSP定义。if(objectinstanceofsubClass1)elseif(objectinstanceofsubClass2)l子类应当可以替换父类并出现在父类能够出现的任何地方,或者说如果我们把代码中使用基类的地方用它的子类所代替,代码还能正常工作。违反LSP原则举例:classRectangledoublewidth;doubleheight;publicdoublegetHeight()returnheight;publicvoidsetHeight(doubleheight)this.height=height;publicdoublegetWidth()returnwidth;publicvoidsetWidth(doublewidth)this.width=width;classSquareextendsRectanglepublicvoidsetHeight(doubleheight)super.setHeight(height);super.setWidth(height);publicvoidsetWidth(doublewidth)super.setHeight(width);super.setWidth(width);假如系统中存在以下业务逻辑代码:voidgetArea(Rectanglerect)rect.setWidth(6);rect.setHeight(4);System.out.println(rect.getWidth()*rect.getHeight();则使用Rectangle的子类Square,在调用上述既有业务逻辑时:Rectanglesquare=newSquare();getArea(square);给出不正确的结果。lLSP设计原则提示:设计原则提示:l符合LSP设计原则的类的扩展不会给已有的系统引入新的错误。l如果一个继承类的对象可能会在基类出现地方出现运行错误,则该子类不应该从该基类继承,或者说,应该重新设计它们之间的关系。l上述实现中,Square不应从Rectangle继承,而应为独立的类。接口分隔原则InterfaceSegregationPrinciple(ISP)l不能强迫用户去依赖那些他们不使用的接口。l包含了两方面的含义:l接口设计应该遵循最小接口原则最小接口原则,不要把用户不使用的方法塞进同一个接口里。如果一个接口的方法没有被使用到,则意味着应将其分割成几个功能专一的接口。l接口继承也应该遵循上述原则:继承的接口方法如未使用,则说明存在因继承不当导致的接口污染,应重新设计它们的关系。lJava实现的方式:lAdapter模式:将一个类的接口转换成客户希望的另外一将一个类的接口转换成客户希望的另外一个接口。个接口。Adapter模式使得原本由于接口不兼容而不能一模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。起工作的那些类可以一起工作。当接口存在不需要的方法时,当接口存在不需要的方法时,先建立一个适配器,将其转换成满足客户端需要的新类,继承并实先建立一个适配器,将其转换成满足客户端需要的新类,继承并实现需要的那部分接口即可!现需要的那部分接口即可!lpublicinterfaceMouseListenerextendsEventListenerpublicvoidmouseClicked(MouseEvente);publicvoidmousePressed(MouseEvente);publicvoidmouseReleased(MouseEvente);publicvoidmouseEntered(MouseEvente);publicvoidmouseExited(MouseEvente);lpublicinterfaceMouseWheelListenerextendsEventListenerpublicvoidmouseWheelMoved(MouseWheelEvente);lpublicinterfaceMouseMotionListenerextendsEventListenerpublicvoidmouseDragged(MouseEvente);publicvoidmouseMoved(MouseEvente);举例-接口分隔原则lpublicabstractclassMouseAdapterimplementsMouseListener,MouseWheelListener,MouseMotionListenerpublicvoidmouseClicked(MouseEvente)publicvoidmousePressed(MouseEvente)publicvoidmouseReleased(MouseEvente)publicvoidmouseEntered(MouseEvente)publicvoidmouseExited(MouseEvente)publicvoidmouseWheelMoved(MouseWheelEvente)publicvoidmouseDragged(MouseEvente)publicvoidmouseMoved(MouseEvente)单一职责原则SingleResponsibilityPrinciple(SRP)l一个类有且仅有一个职责。所谓类的一个职责是指引起该类变化的一个原因。如果一个类存在多个使其改变的原因,那么这个类就存在多个职责。lSRP说明了类(接口)抽象粒度的判断基准。l例如:File类与PrintWriter类是对文件的操作,合在一体似乎没什么不妥。但File类与文件的公共属性和管理有关,而PrintWriter与文件内容属性和操作有关。分开更好!开闭原则(OCP:Open-ClosedPrinciple)l设计类或其他程序单位时,应该遵循对扩展开、对修改关闭的原则。l为实现该原则l充分使用抽象,把类的不变部分抽象成接口,这些不变的接口可以应对未来的扩展;l接口最小化。不足部分可以通过定义新的接口来实现;l模块之间的调用通过接口进行,这样即使实现层发生变化,也无需修改调用方的代码。