主要内容 子类与父类 子类的继承性 子类与对象 成员变量的隐藏和方法重写 重点与难点: 重点:重点是类的继承性、方法重写。 难点:难点是理解子类继承过程中成员变量的隐藏与方法重写。 §5.1 子类与父类 继承是一种由已有的类创建新类的机制。利用继承,我们可以先创建一个共有属性的一般类,根据该一般类再创建具有特殊属性的新类,新类继承一般类的状态和行为,并根据需要增加它自己的新的状态和行为。由继承而得到的类称为子类,被继承的类称为父类(超类)。 Java不支持多重继承(子类只能有一个父类)。 5.1.1 声明子类 (重点) 使用关键字extends来定义一个类的子类,格式如下: class 子类名 extends 父类名 { … } 例如: class Student extends People { … } 说明:把Student类定义为People类的子类、People类是Student类的父类 5.1.2类的树形结构(简介) Java的类按继承关系形成树形结构这个树形结构中,根节点是Object类(Object是java.lang包中的类),即Object是所有类的祖先类。 除了Object类,每个类都有且仅有一个父类,一个类可以有多个或零个子类。如果一个类(除了Object类)的声明中没有使用extends关键字,这个类被系统默认为是Object的子类,即类声明“class A”与“class A extends Object”是等同的。 §5.2 子类的继承性 类可以有两种重要的成员:成员变量和方法。子类的成员中有一部分是子类自己声明定义的,另一部分是从它的父类继承的。 所谓子类继承父类的成员变量就是把继承来的变量作为自己的一个成员变量,就好象它们是在子类中直接声明一样,可以被子类中自己定义的任何实例方法操作。 所谓子类继承父类的方法就是把继承来的方法作为子类中的一个方法,就好象它们是在子类中直接定义了一样,可以被子类中自己定义的任何实例方法调用。 §5.2.1 子类和父类在同一包中的继承性(重点) 如果子类和父类在同一个包中,那么,子类自然地继承了其父类中不是private的成员变量作为自己的成员变量,并且也自然地继承了父类中不是private的方法作为自己的方法,继承的成员变量或方法的访问权限保持不变。 下面的例子1中有4个类:People,Student.java,UniverStudent.java和Example5_1,这些类都没有包名(需要分别打开文本编辑器编写、保存这些类的源文件,比如保存到C:\ch5目录中),其中UniverStudent类是Student的子类,Student是People的子类。程序运行效果如图5.1。 例子1 People.java
public class People { int age,leg = 2,hand = 2; protected void showPeopleMess() { System.out.printf("%d岁,%d只脚,%d只手\t",age,leg,hand); } }
Student.java
public class Student extends People { int number; void tellNumber() { System.out.printf(“学号:%d\t”,number); } int add(int x,int y) { return x+y; } }
UniverStudent.java
public class UniverStudent extends Student { int multi(int x,int y) { return x*y; } }
Example5_1.java
public class Example5_1 { public static void main(String args[]) { Student zhang = new Student(); zhang.age = 17; //访问继承的成员变量 zhang.number=100101; zhang.showPeopleMess(); //调用继承的方法 zhang.tellNumber(); int x=9,y=29; System.out.print(“会做加法:”); int result=zhang.add(x,y); System.out.printf("%d+%d=%d\n",x,y,result); UniverStudent geng = new UniverStudent(); geng.age = 21; //访问继承的成员变量 geng.number=6609; //访问继承的成员变量 geng.showPeopleMess(); //调用继承的方法 geng.tellNumber(); //调用继承的方法 System.out.print(“会做加法:”); result=geng.add(x,y); //调用继承的方法 System.out.printf("%d+%d=%d\t",x,y,result); System.out.print(“会做乘法:”); result=geng.multi(x,y); System.out.printf("%d×%d=%d\n",x,y,result); } } §5.2.2 子类和父类不在同一包中的继承性 如果子类和父类不在同一个包中,那么,子类继承了父类的protected、public成员变量做为子类的成员变量,并且继承了父类的protected、public方法为子类的方法,继承的成员或方法的访问权限保持不变。 §5.2.3 继承关系(Generalization)的UML图(简介) 如果一个类是另一个类的子类,那么UML通过使用一个实线连接两个类的UML图来表示二者之间的继承关系,实线的起始端是子类的UML图,终点端是父类的UML图,但终点端使用一个空心的三角形表示实线的结束。 §5.2.4 protected的进一步说明(了解,课堂不讲,学生自主学习) §5.3 子类与对象 类继承了父类的很多东西,那么子类在创建对象的时候,他又是怎么生成自己的对象的呢。子类生产的对象会有哪些东西呢。 5.3.1 子类对象的特点 子类创建对象时,子类的构造方法总是先调用父类的某个构造方法,完成父类部分的创建;然后再调用子类自己的构造方法,完成子类部分的创建。如果子类的构造方法没有明显地指明使用父类的哪个构造方法,子类就调用父类的不带参数的构造方法 。 子类在创建一个子类对象时,不仅子类中声明的成员变量被分配了内存,而且父类的所有的成员变量也都分配了内存空间,但子类只能操作继承的那部分成员变量 。 子类可以通过继承的方法来操作子类未继承的变量和方法 . 例子2 Example5_2.java
class People { private int averHeight = 166; public int getAverHeight() { return averHeight; } } class ChinaPeople extends People { int height; public void setHeight(int h) { //height = h+averHeight; // 非法,子类没有继承averHeight height = h; } public int getHeight() { return height; } } public class Example5_2 { public static void main(String args[]) { ChinaPeople zhangSan = new ChinaPeople(); System.out.println(“子类对象未继承的averageHeight的值是:”+zhangSan. getAverHeight()); zhangSan.setHeight(178); System.out.println(“子类对象的实例变量height的值是:”+zhangSan.getHeight()); } } 5.3.2 关于instanceof运算符(了解,简单介绍) instanceof运算符是Java独有的双目运算符,其左面的操作元是对象,右面的操作元是类,当左面的操作元是右面的类或其子类所创建的对象时,instanceof运算的结果是true,否则是false 。 §5.4 成员变量的隐藏和方法重写(重点) §5.4.1 成员变量的隐藏 对于子类可以从父类继承的成员变量,只要子类中声明的成员变量和父类中的成员变量同名时,子类就隐藏了继承的成员变量。 在子类中要操作这个与父类同名的成员变量时,子类操作的是子类重新声明的这个成员变量。而不是被隐藏掉的。 例子3 Goods.java
public class Goods { public double weight; public void oldSetWeight(double w) { weight = w; System.out.println(“double型的weight=”+weight); } public double oldGetPrice() { double price = weight*10; return price; } }
CheapGoods.java
public class CheapGoods extends Goods { public int weight; public void newSetWeight(int w) { weight = w; System.out.println(“int型的weight=”+weight); } public double newGetPrice() { double price = weight*10; return price; } }
Example5_3.java
public class Example5_3 { public static void main(String args[]) { CheapGoods cheapGoods = new CheapGoods(); //cheapGoods.weight=198.98; 是非法的,因为子类对象的weight变量已经是int型 cheapGoods.newSetWeight(198); System.out.println(“对象cheapGoods的weight的值是:”+cheapGoods.weight); System.out.println(“cheapGoods用子类新增的优惠方法计算价格:”+ cheapGoods.newGetPrice()); cheapGoods.oldSetWeight(198.987); //子类对象调用继承的方法操作隐藏的double //型变量weight System.out.println(“cheapGoods使用继承的方法(无优惠)计算价格:”+ cheapGoods.oldGetPrice()); } } §5.4.2 方法重写(Override) 同样,子类通过重写可以隐藏已继承的实例方法。 重写的语法规则 如果子类继承了父类的实例方法,那么子类就有权利重写这个方法。 方法重写是指:子类中定义一个方法,这个方法的类型和父类的方法的类型一致或是父类方法的类型的子类型,且这个方法的名字、参数个数、参数的类型和父类的方法完全相同. 重写的目的 子类通过方法的重写可以隐藏继承的方法,子类通过方法的重写可以把父类的状态和行为改变为自身的状态和行为。 例子4 University.java
public class University { void enterRule(double math,double english,double chinese) { double total = math+english+chinese; if(total >= 180) System.out.println(total+“分达到大学录取线”); else System.out.println(total+“分未达到大学录取线”); } }
ImportantUniversity.java
public class ImportantUniversity extends University{ void enterRule(double math,double english,double chinese) { double total = math+english+chinese; if(total >= 220) System.out.println(total+“分达到重点大学录取线”); else System.out.println(total+“分未达到重点大学录取线”); } }
Example5_4.java
public class Example5_4 { public static void main(String args[]) { double math = 62,english = 76.5,chinese = 67; ImportantUniversity univer = new ImportantUniversity(); univer.enterRule(math,english,chinese); //调用重写的方法 math = 91; english = 82; chinese = 86; univer.enterRule(math,english,chinese); //调用重写的方法 } }
以下我们再看一个简单的重写的例子,并就该例子讨论一些重写的注意事项。在下面的例子5中,子类B重写了父类的computer()方法,运行效果如图5.6所示。 例子5 Example5_5.java
class A { float computer(float x,float y) { return x+y; } public int g(int x,int y) { return x+y; } } class B extends A { float computer(float x,float y) { return x*y; } } public class Example5_5 { public static void main(String args[]) { B b=new B(); double result=b.computer(8,9); //b调用重写的方法 System.out.println(result); int m=b.g(12,8); //b调用继承的方法 System.out.println(m); } } 重写后方法的调用 子类创建的一个对象,如果子类重写了父类的方法,则运行时系统调用的是子类重写的方法;子类创建的一个对象,如果子类未重写父类的方法,则运行时系统调用的是子类继承的方法。 重写的注意事项 重写父类的方法时,不允许降低方法的访问权限,但可以提高访问权限(访问限制修饰符按访问权限从高到低的排列顺序是:public、protected、友好的、private。) 本次课总结 继承是一种由已有的类创建新类的机制。 子类继承父类的成员变量。 子类继承父类的方法。 子类继承过程中可以实现变量隐藏与方法重写。
§5.5 super关键字 1 用super操作被隐藏的成员变量和方法 子类可以隐藏从父类继承的成员变量和方法,如果在子类中想使用被子类隐藏的成员变量或方法就可以使用关键字super。比如super.x、super.play()就是访问和调用被子类隐藏的成员变量x和方法play(). 例子7 Example5_7.java
class Sum { int n; float f() { float sum = 0; for(int i=1;i<=n;i++) sum = sum+i; return sum; } } class Average extends Sum { int n; float f() { float c; super.n = n; c = super.f(); return c/n; } float g() { float c; c = super.f(); return c/2; } } public class Example5_7 { public static void main(String args[]) { Average aver = new Average(); aver.n = 100; float resultOne = aver.f(); float resultTwo = aver.g(); System.out.println(“resultOne=”+resultOne); System.out.println(“resultTwo=”+resultTwo); } }
2 使用super调用父类的构造方法 子类不继承父类的构造方法,因此,子类如果想使用父类的构造方法,必须在子类的构造方法中使用,并且必须使用关键字super来表示,而且super必须是子类构造方法中的头一条语句。 例子8 Example5_8.java
class Student { int number;String name; Student() { } Student(int number,String name) { this.number = number; this.name = name; System.out.println(“我的名字是:”+name+ “学号是:”+number); } } class UniverStudent extends Student { boolean 婚否; UniverStudent(int number,String name,boolean b) { super(number,name); 婚否 = b; System.out.println(“婚否=”+婚否); } } public class Example5_8 { public static void main(String args[]) { UniverStudent zhang = new UniverStudent(9901,“何晓林”,false); } }
§5.6 final关键字 final关键字可以修饰类、成员变量和方法中的局部变量。 1 final类 可以使用final将类声明为final类。final类不能被继承,即不能有子类。 如: final class A { … … } 2 final方法 如果用final修饰父类中的一个方法,那么这个方法不允许子类重写。 3 常量 如果成员变量或局部变量被修饰为final的,就是常量。 例子9 Example5_9.java
class A { final double PI=3.1415926;// PI是常量 public double getArea(final double r) { //r = r+1; //非法,不允许对final变量进行更新操作 return PIrr; } public final void speak() { System.out.println(“您好,How’s everything here ?”); } } public class Example5_9 { public static void main(String args[]) { A a=new A(); System.out.println(“面积:”+a.getArea(100)); a.speak(); } }
§5.7 对象的上转型对象(重点) 假设,A类是B类的父类,当用子类创建一个对象,并把这个对象的引用放到父类的对象中时,称对象a是对象b的上转型对象。比如: 上转型对象的使用 上转型对象不能操作子类新增的成员变量;不能调用子类新增的方法。 上转型对象可以访问子类继承或隐藏的成员变量,也可以调用子类继承的方法或子类重写的实例方法。 如果子类重写了父类的某个实例方法后,当用上转型对象调用这个实例方法时一定是调用了子类重写的实例方法。 例子10 Example5_10.java
class 类人猿 { void crySpeak(String s) { System.out.println(s); } } class People extends 类人猿 { void computer(int a,int b) { int c=ab; System.out.println©; } void crySpeak(String s) { System.out.println("**"+s+"***"); } } public class Example5_10 { public static void main(String args[]) { 类人猿 monkey; People geng = new People(); monkey = geng ; //monkey是People对象geng的上转型对象 monkey.crySpeak(“I love this game”); //等同于geng.crySpeak(“I love this game”); People people=(People)monkey; //把上转型对象强制转化为子类的对象 people.computer(10,10); } }
§5.8 继承与多态 多态性就是指父类的某个方法被其子类重写时,可以各自产生自己的功能行为。 下面的例子11展示了多态,运行效果如图5.11。 例子11 Example5_11.java
class 动物 { void cry() { } } class 狗 extends 动物 { void cry() { System.out.println(“汪汪…”); } } class 猫 extends 动物 { void cry() { System.out.println(“喵喵…”); } } public class Example5_11 { public static void main(String args[]) { 动物 animal; animal = new 狗(); animal.cry(); animal=new 猫(); animal.cry(); } }
§5.9 abstract类和abstract()方法 用关键字abstract修饰的类称为abstract类(抽象类)。 例如:abstract class A { … … } 用关键字abstract修饰的方法称为abstract方法(抽象方法) 例如:abstract int min(int x,int y); abstract类有如下特点 和普通的类相比,abstract类里可以有abstract方法。也可以没有。对于abstract方法,只允许声明,不允许实现,而且不允许使用final修饰abstract方法。 对于abstract类,不能使用new运算符创建该类的对象,只能产生其子类,由子类创建对象。 如果一个类是abstract类的子类,它必须具体实现父类的所有的abstract方法。 理解抽象类 例子12 使用了abstract类 例子12 Example5_12.java
abstract class GirlFriend { //抽象类,封装了两个行为标准 abstract void speak(); abstract void cooking(); } class ChinaGirlFriend extends GirlFriend { void speak(){ System.out.println(“你好”); } void cooking(){ System.out.println(“水煮鱼”); } } class AmericanGirlFriend extends GirlFriend { void speak(){ System.out.println(“hello”); } void cooking(){ System.out.println(“roast beef”); } } class Boy { GirlFriend friend; void setGirlfriend(GirlFriend f){ friend = f; } void showGirlFriend() { friend.speak(); friend.cooking(); } } public class Example5_12 { public static void main(String args[]) { GirlFriend girl = new ChinaGirlFriend(); //girl是上转型对象 Boy boy = new Boy(); boy.setGirlfriend(girl); boy.showGirlFriend(); girl = new AmericanGirlFriend(); //girl是上转型对象 boy.setGirlfriend(girl); boy.showGirlFriend(); } }
§5.10 面向抽象编程 在设计一个程序时,可以先声明一个abstract类,通过在类中声明若干个abstract方法,表明这些方法在整个系统设计中的重要性,方法体的内容细节由它的非abstract子类去完成。 然后利用多态实现编程。使用多态进行程序设计的核心技术是使用方法重写和上转型对象,即将abstract类声明对象作为其子类的上转型对象,那么这个上转型对象就可以调用子类重写的方法。 所谓面向抽象编程,是指当设计某种重要的类时,不让该类面向具体的类,而是面向抽象类,即所设计类中的重要数据是抽象类声明的对象,而不是具体类声明的对象。 §5.11 开-闭原则 所谓“开-闭原则”(Open-Closed Principle)就是让设计的系统应当对扩展开放,对修改关闭。 在设计系统时,应当首先考虑到用户需求的变化,将应对用户变化的部分设计为对扩展开放,而设计的核心部分是经过精心考虑之后确定下来的基本结构,这部分应当是对修改关闭的,即不能因为用户的需求变化而再发生变化,因为这部分不是用来应对需求变化的。 如果系统的设计遵守了“开-闭原则”,那么这个系统一定是易维护的,因为在系统中增加新的模块时,不必去修改系统中的核心模块。
结合 5.10节例题说明开-闭原则 如果将5.10节中的Pillar类、Geometry类以及Circle和Rectangle类看作是一个小的开发框架,将Application.java看作是使用该框架进行应用开发的用户程序,那么框架满足“开-闭”原则,该框架相对用户的需求就比较容易维护,因为,当用户程序需要使用Pillar创建出具有三角形底的柱体时,系统只需简单的扩展框架,即在框架中增加一个Geometry的Triangle子类,而无需修改框架中的其他类,如图5.14所示 5.12 应用举例 用类封装手机的基本属性和功能,要求手机即可以使用移动公司的ISM卡也可以使用联通公司SIM卡(可以使用任何公司提供的SIM卡)。
总结 子类重写或新增的方法能操作子类继承和新声明的成员变量,但不能直接操作隐藏的成员的变量(需使用关键字super操作隐藏的成员变量)。 多态是面向对象编程的又一重要特性。子类可以体现多态,即子类可以根据各自的需要重写的父类的某个方法,子类通过方法的重写可以把父类的状态和行为改变为自身的状态和行为。在使用多态设计程序时,要熟练使用上转型对象以及面向抽象编程的思想,以便体现程序设计所提倡的“开-闭”原则。
|