JAVA--OOP IV

本章目标

•方法重写
•引用转型(类型转换)
•了解多态,在Java中实现多态
•abstract关键字  (abstract 方法名  /   类名)
•抽象方法和抽象类
•接口和实现接口,interface关键字和implements关键字
•final关键字(final+类/属性/方法)
•类与类之间的关系(补充)


父/子类中的同名成员

•上次课在讲述super关键字时,提到了父类和子类中存在同名成员的情况;
•如果是同名属性(即成员变量同名),那么可以通过this和super显式地区分开来;
•如果是同名方法(即成员方法同名),情况将会相对复杂。

父/子类中成员方法仅仅同名   (重载)

class BaseClass     //定义基类
{
  public void fun()
  {
    ……  //具体实现代码略
  }
}

class DerivedClass extends BaseClass //派生类继承于基类
{
  public void fun(int x)  //跟父类中有相同名称的方法
  {
    ……  //具体实现代码略
  }
}

/*如果仅仅只是名称相同,但参数列表不同的话,则构成方法重载*/


父/子类中成员方法同原型   (重写 

class SuperClass     //定义父类
{
  public void fun()
  {
    ……  //具体实现代码略
  }
}

class SubClass extends SuperClass //子类继承于父类
{
  public void fun()  //与父类中的方法完全同原型
  {
    ……  //具体实现代码略
  }
}

/*如果不但名称相同,而且连方法原型也完全相同的话,则构成方法重写*/


方法重写

•在类的继承体系结构中,如果子类中出现了与父类中有同原型(方法的签名)的方法,那么认为子类中的方法覆盖了父类中的方法(也称为方法重写/覆写);
•通过子类的实例调用被覆盖的方法时,将总是调用子类中的方法,而父类中的方法将被隐藏。


(补充:)方法签名包括了方法的:A.形式参数列表  B.返回值类型  C.方法名      (不包含 : 访问控制修饰符)



方法重写示例

class ParentClass {  //定义父类
  public void fun() {
    System.out.println("这是父类中的方法。");
  }
}

class ChildClass extends ParentClass {//子类继承于父类
  public void fun() {  //子类覆盖父类中的方法
    System.out.println("这是子类中的方法。");
  }
}

class OverriddenTest {  //用于容纳main方法
  public static void main(String[] args) {
    ParentClass parObj = new ParentClass();
    parObj.fun();  //父类的实例调用此方法

    ChildClass chiObj = new ChildClass();
    chiObj.fun();  //子类的实例调用此方法
  }
}



区分方法重写和方法重载

•方法覆盖(重写)和方法重载是两个极易混淆的概念,必须严格区分;
•方法覆盖出现的前提条件之一是必须有继承发生的情况下,而且要求父类和子类中的方法必须同原型;
•方法重载时,继承并不是必需的,它只要求方法名称相同,而参数列表则必须不同,换言之,各方法的原型其实是不同的。



引用转型

•父类的引用可以指向 子类的对象,如:

  BaseClass obj = new DerivedClass();

  这样的语句是合法的;

•但是派生类的引用则不可以指向基类的对象,如:

  DerivedClass obj = new BaseClass();

  这样的语句将引发错误。


引用转型示例

class Person {  //定义人类
  ……
}

class Student extends Person {  //学生类继承于人类
  ……
}

public class OverriddenDemo {
  public static void main(String[] args) {

    //正确,所有的学生一定是人
    Person per = new Student();

    //错误,并不是所有的人都是学生
    Student std = new Person();
  }
}


问题

          既然父类的引用可以指向子类的实例,如果父类和子类中存在方法重写的情况,
                       那么通过父类的引用将会调用到哪个类中的方法呢?
                                      答案很简单,就是  new 谁 调用 谁
                                      Father f=new Son();   //  这里就调用Son中的方法

示例

class Shapes {  //基本形状类
  public void draw() {  //绘图的方法
    System.out.println("绘制了一个基本形状。");
  }
}

class Circle extends Shapes {  //圆形类继承于基本形状类
  public void draw() {  //覆盖父类的绘图方法
    System.out.println("绘制了一个圆形。");
  }
} 

class Square extends Shapes {  //正方形类继承与基本形状类
  public void draw() {  //覆盖父类的绘图方法
    System.out.println("绘制了一个正方形。");
  }
}

public class polymorphismDemo {
  public static void main(String[] args) {
    Shapes obj = new Shapes();  //父类的引用指向父类的实例
    obj.draw();                 //调用绘图方法
    obj = new Circle();         //父类的引用指向子类的实例
    obj.draw();                 //调用绘图方法
    obj = new Square();         //父类的引用指向子类的实例
    obj.draw();                 //调用绘图方法
  }
}


多态

•从上例中可以看出,父类的引用指向哪个类的实例就调用哪个类中的方法;(new谁调谁)
•同样是使用父类的引用,调用同一个名称的方法,却可以得到不同的调用结果,这就是Java中的多态,即:同一函数,多种形态
•实际上多态包括动态多态(重写)和静态多态(重载)。


静态多态

•静态多态也称为编译时多态,即在编译时决定调用哪个方法;
•静态多态一般是指方法重载;
•只要构成了方法重载,就可以认为形成了静态多态的条件;
静态多态与是否发生继承没有必然联系


动态多态

•动态多态也称为运行时多态,即在运行时才能确定调用哪个方法;
•形成动态多态必须具体以下条件:
>必须要有继承的情况存在;
>在继承中必须要有方法覆盖;
>必须由基类的引用指向派生类的实例,并且通过基类的引用调用被覆盖的方法;
•由上述条件可以看出,继承是实现动态多态的首要前提。


使用多态的优点

•我们在前面的学习过程,已经学习到了OOP的封装和继承。
•其中封装我们是为了隐藏细节,使代码模块化,而继承是为了代码重用,那么多态呢?我们来看一段需求!


示例:(飞行系统)

//具体实现代码省略
public class Plane {
    void fly(){…};      // 起飞
    void land(){…};     // 着陆 
}
public class Copter extends Plane {
    void fly(){…};      // 直升机起飞
    void land(){….};     //直升机着陆 
}
public class Jet extends Plane {
    void fly(){…};      // 喷气式飞机起飞
    void land(){….};     //喷气式飞机着陆 
}


多态的优点

•观察上面代码,我们会发现只要使用planeFly方法,可以让所有传给它的飞机(Plane的子类对象)正常起飞!不管是直升机还是喷气机,甚至是现在还不存在的,以后可能会增加的飞碟。
•可以看到planeFly()方法接受的参数是Plane类对象引用,而实际传递给它的都是Plane的子类对象,现在回想一下开头所描述的“多态”,不难理解,我们使用多态便于灵活地扩展我们开发的程序


抽象方法

•在某些情况下,基类无法(或者没有必要)提供被覆盖方法的具体实现,那么就可以将此方法声明成抽象方法
•使用关键字abstract声明抽象方法,一般语法:

  [访问权限] abstract 返回值类型 方法名称(参数列表);

•如:

  public abstract void draw();


抽象类

如果某个类中包含有抽象方法,那么该类就必须定义成抽象类;
•定义抽象类同样使用关键字abstract,一般语法:

  [访问权限] abstract class 类名 {

  成员列表

  }

•如:

  public abstract class Shapes {

  public abstract void draw();

  }



抽象类的注意事项

•抽象类不可以直接实例化,只可以用来继承
•抽象类的派生子类应该提供对其所有抽象方法的具体实现;
•可以这么认为,抽象方法实际上就是由抽象基类强制要求其派生子类必须实现的方法原型;
如果抽象类的派生子类没有实现其中的所有抽象方法,那么该派生子类仍然是抽象类,只能用于继承,而不能实例化
•抽象类中也可以包含有非抽象的方法;
构造方法和静态方法不可以修饰为abstract。


修改前面的示例

abstract class Shapes {  //基本形状类,抽象类
  public abstract void draw();  //绘图方法,抽象方法
}

class Circle extends Shapes {  //圆形类继承于基本形状类
  public void draw() {  //实现抽象父类的抽象绘图方法
    System.out.println("绘制了一个圆形。");
  }
} 

class Square extends Shapes {  //正方形类继承与基本形状类
  public void draw() {  //实现抽象父类的抽象绘图方法
    System.out.println("绘制了一个正方形。");
  }
}

public class abstractDemo {  //该类用于容纳main方法
  public static void main(String[] args) {
    Shapes obj;
    obj = new Circle();         //父类的引用指向子类的实例
    obj.draw();                 //调用绘图方法
    obj = new Square();         //父类的引用指向子类的实例
    obj.draw();                 //调用绘图方法
  }
}



接口

•如果某个类中的所有方法都是抽象方法,那么可以考虑将该类定义为接口
•定义接口使用关键字interface,一般语法:

  [访问权限] interface 接口名 {

      成员列表

  }

•如:

  public interface IMyInterface {

  ……

  }



实现接口

•与抽象类相似,接口同样不可以实例化,只能用于实现
•如果某类要实现接口,则使用implements关键字,一般语法:

  [访问权限] class 类名 implements 接口名 {

  成员列表

  }

•如:

  public class MyClass implements IMyInterface {

  ……

  }



接口示例




接口的注意事项

•接口中不能定义非抽象方法,也就是说接口中不能包含有函数实体;
•接口中的所有方法都默认为抽象方法,无需在每个方法前加abstract关键字;
•接口的实现类应该提供对接口中所有抽象方法的具体实现,否则将成为抽象类;
•与抽象类和它的继承类相似,也可以使用接口的引用指向其实现类的对象,从而达到动态多态的效果。


接口示例(续)

/*InterfaceDemo.java源文件*/
//导入必要的接口和类
import aaa.IMyInterface;
import bbb.MyClass;

//用于容纳main方法
public class InterfaceDemo
{
  public static void main(String[] args)
  {
    //使用接口的引用指向实现类的实例
    IMyInterface obj = new MyClass();
    System.out.println("两数的和是:" + obj.add(20, 30));
    System.out.println("两数的差是:" + obj.sub(30, 20));
  }
}


关于接口的更多知识

•Java只支持单继承,而不能象C++那样可以多重继承,接口正是为了弥补这一点;
•某个类只能继承于一个父类,但可以实现多个接口,如:

  public class 实现类名 implements 接口1,

                                  接口2,

                                  ……,

                                  接口n

  {

  成员列表

  }


关于接口的更多知识(续)

•Java中还允许一个接口继承于另一个接口,即由父接口派生出子接口,如:

  public interface 子接口名extends 父接口名 {

  成员列表

  }

•这样的话,甚至可以使用父接口的引用指向子接口的实现类的对象。


final关键字

•在Java中,final关键字表示最终的,不可修改的含义;
•final关键字有三种用途,可以分别应用于变量、成员方法和类。


final修饰变量

•如果将某个变量修饰为final,那么该变量就成为常量,一般语法:

   [访问权限] final 数据类型 常量名 = 值;

•如:

  final double PI = 3.14159;

  PI成为常量,其后任何试图对PI进行赋值的语句都将报错;

常量在声明时必须初始化


final修饰方法

•如果将某个成员方法修饰为final,则意味着该方法不能被子类覆盖,一般语法:

  [访问权限] final 返回值类型 方法名(参数列表) {

  ……

  }

•如:

  public final void fun() {

  ……

  }

如果在派生类中出现同原型的方法,将会报错。


final修饰类

•如果将某个类修饰为final,则说明该类无法被继承,一般语法:

  [访问权限] final class 类名 {

  成员列表

  }

•如:

  public final class MyClass {

  ……

  }

任何类想继承于MyClass类都将报错。



类与类之间的关系

•类与类之间的关系一般来说有两种:“有”关系和“是”关系;
•所谓“是”关系就是指类的继承关系。如:从动物类派生出哺乳动物类,那么可以说哺乳动物是动物;
•而“有”关系是指在一个类中包含了另一个类的对象,即一个类中有另一个类(的对象),可以理解为类的嵌套。


总结

•如果基类和派生类中有原型完全相同的方法,那么就形成了方法覆盖;
•引用转型是指父类的引用可以指向子类的实例,但反之不可以;
•在有方法覆盖的前提下,并且有引用转型的情况,就将形成动态多态;
•无法实现的方法可以定义成抽象方法,包含有抽象方法的类应该定义成抽象类;
•通过接口也能够实现多态,同时也可以弥补Java只支持单一继承的不足;
•final关键字有三种用途;
•类与类之间的关系:“是”关系和“有”关系。


作业

•在JAVA中开发一个飞行体系!该体系可以对任何飞行器进行控制起飞和降落!
•在前一章作业的基础上,进行修改。在Employee类中添加一个抽象方法以计算薪资总额。定义一个方法基于休假天数leave计算要从基本薪资中扣除的部分。计算薪资扣除部分的公式为:
>lessPay=(leave<=5)?(0.25*basic):(0.5*basic)

  Manager和Director类重写父类的抽象方法。

  计算Manager的薪资总额totalAmount的公式为:

>totalAmount=basic+houseRentAllowance+dearnessAllowance+medicalAllowance;
•其中:
>houseRentAllowance为basic的8%
>dearnessAllowance为basic的30%
>medicalAllowance为1500
•计算Director的薪资总额的公式为:
>totalAmount=basic+houseRentAllowance+dearnessAllowance+medicalAllowance+entertainmentAllowance+transportAllowance
>其中:
>houseRentAllowance为basic的20%
>dearnessAllowance为basic的50%
>medicalAllowance为4500
>entertainmentAllowance为5000
•改程序还应该计算应付薪资,其公式为:
>NetPay=totalAmount-lessPay
>请在show方法中显示name,address,basic,totalAmount和netPay属性中储存的值。

作业题效果





逻辑趣味思考题

•老板让工人为自己工作7天,给工人的回报是一根金条。金条平分成相连的7段,老板必须在每天结束时给他们一段金条。如果只允许把金条弄断两次,他该如何给工人支付报酬?



补充阅读

       内部类:http://download.csdn.net/detail/nisior/3848384

            抽象类和接口的区别:http://download.csdn.net/detail/nisior/3848397






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值