1、第四章 继承和多态,继承和多态,4.1继承基础4.2构造器的调用顺序4.3 继承还是组合?4.4 Object类4.5 toString方法4.6 对象的比较,4.1继承基础,类与类之间还有一种父与子的关系,子类继承父类方法与属性,这个我们就称之为继承 子类拥有父类的一些公有的属性和方法,并且子类可能也会有自己的方法和属性 除了添加父类中没有的新方法外子类还可以通过重写父类的方法来重定义自己的行为方式,继承基础,重写与重载不同,重载需要不同的参数来区别相同方法名的方法,重写是发生在子类与父类上,重写父类方法必须与父类的方法名同名同参并且同返回值 。当访问修饰符为private时,表明该方法不能
2、被重写,同样当方法具有final关键字时该方法不能被重写 。子类虽然重写了父类的某个方法,但仍然需要用到父类的这个方法,这时使用super关键字仍然可以调用父类的方法 。程序4.1 TestExtends.java示例继承机制,程序4.2 TestOstrich.javapublic class TestOstrich public static void main(String args) Bird ostrich1 =new Ostrich(); /向上转型 ostrich1.fly(); class Bird public static void fly()System.out.prin
3、tln(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方法不会被重写,而是被遮蔽因此得出结果鸵鸟会飞!,4.2构造器的调
4、用顺序,子类在产生对象实例时首先会调用父类的构造方法,如果没有显示的指定调用父类自定义的构造方法,那么编译器会调用默认的父类构造方法:super()。但是父类如果自定义了带参数的构造方法,就必须在子类的构造方法中指定调用它,否则会因找不到super()而报错。注意,如果需要显示的调用父类构造方法,必须将其写在构造方法里的第一行 。接着按声明的顺序调用成员初始化。最后调用本构造方法的方法体 例:程序4.3 TestInheritanceConstructor.java,4.3 继承还是组合?,判断继承关系的一条定律是看它是否符合is-a的定义 继承打破了类的封装子类继承了父类的成员就意味着父类成
5、员有所改变时子类必须随之变化,这削弱了子类的独立性,增加了软件维护的难度。继承的层次不宜过多,设计时良好的继承体系应该保持在三层以内。我们复用类时的指导原则是优先选择组合,例子:程序4.4 CallOffNumbers.java,4.4 Object类,Java中所有的类都是继承自一个老祖宗,Object类,它们都是Object类的子类。如果一个类在声明时没有用extends关键字显式的指定继承自某个基类,则Java隐式的指定它的默认基类为Object类。这种强制的继承模式叫做单根继承模式。单根继承使得Java简化了许多操作,基于单根继承,所有的对象都可以像基本数据类型一样向上转型到根层次上而
6、使其操作上一视同仁 单根继承的优势还在于可以在Object类定义所有对象都通用的方法,所有类都会自动继承这些方法,4.5 toString方法,Object类定义了 public String toString()方法,返回值是String类型,每当个对象都会有个toString方法,作用就是描述对象的信息要想使用toString方法必须按自己的需求重写这个方法,例子:程序4.6 TestToString.java,4.6 对象的比较,Java中比较两个基本数据类型变量的值时很简单使用“=”操作符 Object类中的equals方法提供了对象内容的比较方法同样我们要是用这个方法必须重写它 如果
7、不重写equals方法,将调用object类的eaquals方法。Object类的equals方法相当于“=”例:程序4.7 TestEquals.java,4.7多态,4.7.1向上转型(upcasting)4.7.2动态绑定4.7.3构造器中多态方法的行为4.7.4向下转型,4.7多态,多态是和继承密切相关的,正因为有继承,才会有多态出现。多态从字面上的意思应该是多种形态。更进一步,延伸到继承里来,那么多态就应该是具有相同的操作不同的行为的一种特征。,4.7.1向上转型(upcasting),publicclassShape voiddraw() staticvoidstart(Shape
8、s) s.draw(); publicstaticvoidmain(Stringagrs) start(newCircle(); start(newSquare(); classCircleextendsShape voiddraw()System.out.println(“drawCircle”); classSquareextendsShape voiddraw()System.out.println(“drawSquare”); ,4.7.1向上转型(upcasting),在start()方法中,通过传入的参数类型不同,表现出来的行为也会不同。但是传入后的参数都统一转型为Shape这个基
9、类型,这里就表现为向上转型。 由导出类到基类,从继承图上面我们可以看出是向上移动的,因此我们一般称之为“向上转型”。向上转型是从一个较具体的类型转换为一个较抽象的类型的过程,所以说是安全的。在导出类中至少包含了基类的方法。在向上转型的过程中,由于在导出类中可能含有基类中没有的方法。类接口会丢失一些方法,4.7.2动态绑定,将一个方法调用同一个方法主体关联起来被称之为绑定,若在程序执行前绑定就被称为前期绑定。 后期绑定也被称为动态绑定,编译器始终都不会知道对象的类型,只有当方法在运行期间被调用时方法的调用机制就能找到正确的方法体。,4.7.3构造器中多态方法的行为,publicclassShap
10、e3 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 不是我们希望的结果。,4.7.3构造器中多态方法
11、的行为,I、在其他任何事物发生之前,将分配给对象的存储空间初始化为二进制的零。II、如前所述的那样调用基类构造器。因此我们会发现,虽然在基类的构造器中调用了其子类的draw方法,而里面的i值还并没有被初始化,但由于步骤1的原因,i的值被置为0。III、按照声明顺序调用成员的初始化方法。IV、调用子类的构造器主体。,4.7.4向下转型,由于向上转型会丢失一些具体的类型的信息,因此我们考虑用向下转型的方式,也就是和向下转型相反的方向转型。,4.7.4向下转型,class A public void f() ; public void g() ; class B extends A public v
12、oid f() ; public void g() ; public void h() ; class C extends A public void f() ; public void g() ; public void u() ;class D extends A public void f() ; public void g() ; ,public class testRtti public static void main(String args) A a1 = new A(); A a2 = new B(); A a3 = new C(); a1.f(); a2.g(); a3.g(); /a2.h(); (B)a2.h();/向下转型 (C)a2.h();/异常 ,4.7.4向下转型,类B、类C和类D有共同的父类 。类D和基类A看作是一种纯粹的“is-a”的关系 。类B和类C中我们对基类进行了扩展,类B、C和A是is-like-a的关系。将他们向上转型后,他们特有的方法和属性就会丢失,如果需要找回这些属性和方法我们就需要进行强制的类型转换。使用(B)a2.h()这样的方式来强制转换成B这个类型 ,会抛出ClassCastException 。,