第5章 类的继承性与多态性 主要任务: 介绍类的继承性和多态性,以及一些 相关问题。
本章主要内容 1.类的继承 2.类的多态
5.1 类的继承 什么是类的继承? 新类可从现有的类中产生,将保留现有类的状态属性和方法并可根据需要加以修改。新类还可添加新的状态属性和方法,这些新增功能允许以统一的风格处理不同类型的数据。这种现象就称为类的继承。
5.1.1 父类和子类 当建立一个新类时,不必写出全部成员变量和成员方法。只要简单地声明这个类是从一个已定义的类继承下来的,就可以引用被继承类的全部成员。被继承的类称为父类或超类(superclass),这个新类称为子类。
在类的声明语句中加入extends关键字和指定的类名即可实现类的继承,例如: public class MyApplet extends java.applet.Applet public class MyApplication extends Frame public class MyApp1 extends Object public class MyApp2 第一条语句声明子类MyApplet的父类是Applet,并指明Applet的层次结构;第二条语句声明子类MyApplication的父类是Frame;第三条语句声明子类MyApp1的父类是Object,但编程中通常会省略extends Object子句;第四条语句在字面上没有extends,但实际上等价于public class MyApp2 extends Object。
类的继承的模拟图
图反映了什么? 图5.1反映了Java类的层次结构。最顶端的类是Object,它在java.lang中定义,是所有类的始祖。一个类可以有多个子类,也可以没有子类,但它必定有一个父类(Object除外)。 子类不能继承父类中的private成员,除此之外,其它所有的成员都可以通过继承变为子类的成员。另一方面,对继承的理解应该扩展到整个父类的分支,也就是说,子类继承的成员实际上是整个父系的所有成员。例如,toString这个方法是在Object中声明的,被层层继承了下来,用于输出当前对象的基本信息。
结论: 子类只能有一个父类。如果省略了extends,子类的父类是Object。子类继承了父类和祖先的成员,可以使用这些成员。在需要的时候,子类可以添加新的成员变量和方法,也可以隐藏父类的成员变量或覆盖父类的成员方法。
5.1.2 成员变量的继承和隐藏 1.成员变量的继承 2.成员变量的隐藏
Point(int a, int b) {setPoint(a, b);} 例5.1 下面的三个程序说明从点Point类扩展到线Line类和圆Circle类的方法,这是三个公共类,不能放在同一个文件中。它们都没有输出语句,如果运行看不到什么结果。 public class Point { protected int x, y; Point(int a, int b) {setPoint(a, b);} public void setPoint(int a, int b) { x=a; y=b; } public int getX() {return x;} public int getY() {return y;}
public class Line extends Point { protected int x, y, endX, endY; Line(int x1, int y1, int x2, int y2) {setLine(x1, y1, x2, y2);} public void setLine(int x1, int y1, int x2, int y2) { x=x1; y=y1; endX=x2; endY=y2; }
public int getX() {return x ;} public int getY() {return y ;} public int getEndX() {return endX;} public int getEndY() {return endY;} public double length() { return Math.sqrt((endX-x) * (endX-x) + (endY-y) * (endY-y)); }
public class Circle extends Point { protected int radius; Circle(int a, int b, int r) { super(a, b); setRadius(r); } public void setRadius(int r) {radius=r;} public int getRadius() {return radius;} public double area() {return 3.14159*radius*radius;}
Point 的成员 x, y // 受保护的成员变量,代表点的坐标 Point // 点的构造方法 setPoint // 设定点的坐标值的方法 getX, getY // 返回坐标x和y的值的方法
x, y, endX, endY // 子类受保护的成员变量,代表线的两个端点坐标 x, y // 继承父类的受保护成员变 量,但被子类隐藏 Line 的成员 x, y, endX, endY // 子类受保护的成员变量,代表线的两个端点坐标 Line // 线的构造方法 setLine // 设定线的两个端点坐标值的方法 getX, getY // 返回起点坐标x和y的值的方法 getEndX, getEndY // 返回终点坐标endX和endY的值的方法 length // 返回线的长度的方法 x, y // 继承父类的受保护成员变 量,但被子类隐藏 setPoint // 继承父类的方法 getX, getY // 继承父类的方法,但被子类覆盖
radius // 子类受保护的成员变量,代表圆的半径 Circle 的成员 radius // 子类受保护的成员变量,代表圆的半径 Circle // 圆的构造方法 setRadius // 设定半径值的方法 getRadius // 返回半径值的方法 area // 返回圆面积的方法 x, y // 继承父类的受保护成员变量 setPoint // 继承父类的方法 getX, getY // 继承父类的方法
2.成员变量的隐藏 所谓隐藏是指子类重新定义了父类中的同名变量,如子类Line重新定义了x为x1,y为y1,隐藏了父类Point中的两个成员变量x和y。子类执行自己的方法时,操作的是子类的变量,子类执行父类的方法时,操作的是父类的变量。在子类中要特别注意成员变量的命名,防止无意中隐藏了父类的关键成员变量,这有可能给你的程序带来麻烦。 Line还覆盖了Point的两个方法getX和getY。
5.1.3 成员方法的覆盖 已知通过继承子类可以继承父类中所有可以被子类访问的成员方法,但如果子类的方法与父类方法同名,则不能继承,此时称子类的方法覆盖了父类的那个方法,简称为方法覆盖。方法的覆盖(override),为子类提供了修改父类成员方法的能力。例如,子类可以修改层层继承下来的toString方法,让它输出一些更有用的信息。下面的例子显示了在子类Circle中添加toString方法,用来返回圆半径和圆面积信息。
例5.2 对Object的toString方法的覆盖。结果如图所示。
class Circle { private int radius; Circle(int r) {setRadius(r);} public void setRadius(int r) {radius=r;} public int getRadius() {return radius;} public double area() {return 3.14159*radius*radius;} public String toString() { return "圆半径:"+getRadius()+" 圆面积:"+area(); }
public class O1 { public static void main(String args[]) { Circle c=new Circle(10); System.out.println("\n"+c.toString()); } 程序中改写了上一节介绍的Circle,添加了toString方法并修改了它的返回值。由于toString和继承下来的方法名相同、返回值类型相同,因此就覆盖了父类中的toString方法。而父类Point中的toString方法尽管没有显示出来,它仍然存在,它是从Object中继承下来的。
方法覆盖时要特别注意: 用来覆盖的子类方法应和被覆盖的父类方法保持同名、同返回值类型,以及相同的参数个数和参数类型。如果被覆盖的方法没有声明抛出异常,子类的覆盖方法可以有不同的抛出异常子句。 有时,可能不需要完全覆盖一个方法,可以部分覆盖一个方法。部分覆盖是在原方法的基础上添加新的功能,即在子类的覆盖方法中添加一条语句:super.原父类方法名,然后加入其它语句。
5.1.4 this和super 例5.3 this和super的使用。运行结果如图所示。
class Point { protected int x, y; Point(int a, int b) {setPoint(a, b);} public void setPoint(int a, int b) { x=a; y=b; }
class Line extends Point { protected int x, y; Line(int a, int b) { super(a, b); setLine(a, b); } public void setLine(int x, int y) { this.x=x+x; this.y=y+y;
public double length() { int x1=super.x, y1=super.y, x2=this.x, y2=this.y; return Math.sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1)); } public String toString() { return "直线端点:[" + super.x + "," + super.y + "] [" + x + "," + y + "] 直线长度:" + this.length();
public class ThisDemo { public static void main(String args[]) { Line line=new Line(50, 50); System.out.println("\n"+line.toString()); }
this和super 的作用: (1)this实际代表的是当前类或对象本身。 在一个类中,this表示对当前类的引用,在使用类的成员时隐含着this引用,尽管可以不明确地写出,例如length和toString中对x和y的使用。当一个类被实例化为一个对象时,this就是对象名的另一种表示。通过this可顺利地访问对象,凡在需要使用对象名的地方均可用this代替。
(2)super代表着父类 如果子类的变量隐藏了父类的变量,使用不加引用的变量一定是子类的变量,如果使用父类的变量,就必须加上super引用。同样道理,如果有方法覆盖的发生,调用父类的方法时也必须加上super引用。
例5.4 了解下面两个类中super的使用。运行结果如图所示。
(3)super() 可用来调用父类的构造方法。 这也是唯一由程序员间接调用类的构造方法的途径,因为Java规定类的构造方法只能由new操作符调用,程序员不能直接调用。同理,this() 也可用来间接调用当前类或对象的构造方法。 类的构造方法是不能继承的,因为构造方法不是类的成员,没有返回值,也不需要修饰符。又由于父类的构造方法和父类同名,在子类中继承父类的构造方法肯定和子类不同名,这样的继承是无意义的。
5.2 类的多态 类的继承发生在多个类之间,而类的多态只发生在同一个类上。在一个类中,可以定义多个同名的方法,只要确定它们的参数个数和类型不同。这种现象称为类的多态。 多态使程序简洁,为程序员带来很大便利。在OOP中,当程序要实现多个相近的功能时,就给相应的方法起一个共同的名字,用不同的参数代表不同的功能。这样,在使用方法时不论传递什么参数,只要能被程序识别就可以得到确定的结果。 类的多态性体现在方法的重载(overload)上,包括成员方法和构造方法的重载。
5.2.1 成员方法的重载 方法的重载是指对同名方法的不同使用方式。 看下面这个例子。
例5.5 对不同的数进行排序输出,运行结果见图。 例5.5 对不同的数进行排序输出,运行结果见图。
import java.awt.Graphics; import java.applet.Applet; class IntSort { public String sort(int a, int b) { if (a>b) return a+" "+b; else return b+" "+a; }
public String sort(int a, int b, int c) { int swap; if (a<b) { swap=a; a=b; b=swap; } if (a<c) { a=c; c=swap;
if (b<c) { swap=b; b=c; c=swap; } return a+" "+b+" "+c; public String sort(int arr[]) { String s=" "; int swap;
for (int i=0; i<arr.length; i++) for (int j=0; j<arr.length-1; j++) if (arr[j]>arr[j+1]) { swap=arr[j]; arr[j]=arr[j+1]; arr[j+1]=swap; } s=s+arr[i]+" "; return s;
public class Class2 extends Applet { IntSort s=new IntSort(); public void paint(Graphics g) { int a=30, b=12, c=40; int arr[]={34,8,12,67,44,98,52,23,16,16}; g.drawString("两个数的排序结果:"+s.sort(a,b),30,30); g.drawString("三个数的排序结果:"+s.sort(a,b,c),30,60); g.drawString("数组的排序结果:"+s.sort(arr),30,90); }
程序中调用了IntSort类的构造方法,但这个构造方法并没有在IntSort中出现,它是怎么来的呢? 每一个类都有一个缺省的构造方法,这就是和类同名的无参构造方法。它实际上是父类的构造方法,创建子类时由父类自动提供。因此,每个类的对象都可以使用这种方式来初始化对象。如同为变量声明数据类型一样,可为对象声明类的类型。如果在初始化对象时需要对象具有更多的特性,可重载构造方法。
5.2.2 构造方法的重载 重载构造方法的目的: 提供多种初始化对象的能力,使程序员可以根据实际需要选用合适的构造方法来初始化对象。
例5.6 构造方法的重载。运行结果如图所示.
class RunDemo { private String userName, password; RunDemo() { System.out.println("All is null!"); } RunDemo(String name) { userName=name; RunDemo(String name, String pwd) { this(name); password=pwd; check();
void check() { String s=null; if (userName!=null) s="用户名:"+userName; else s="用户名不能为空!"; if (password!="ThisWord") s=s+" 口令无效!"; s=s+" 口令:********"; System.out.println(s); }
public class D1 { public static void main(String[] args) { new RunDemo(); new RunDemo("Bill"); new RunDemo(null,"Bill"); new RunDemo("Bill","ThisWord"); }
习 题 1.什么是类的继承性?子类和父类有什 么关系? 2.什么是类的多态性? 3.何为隐藏、覆盖、重载? 习 题 1.什么是类的继承性?子类和父类有什 么关系? 2.什么是类的多态性? 3.何为隐藏、覆盖、重载? 4.this和super类有什么作用? 5.什么是构造方法? 6.构造方法有何特点和作用?
7.分析下面这段程序,指出父类、子类以及它们的成员,成员的作用是什么? class Point { int x, y; Point(int a, int b) {setPoint(a,b);} public void setPoint(int a, int b) {x=a; y=b;} } class Circle extends Point { int radius; Circle(int a, int b, int r) {super(a,b); setRadius(r);} public void setRadius(int r) {radius=r;} public double area() {return 3.14159*radius*radius;}
8.给出下面的代码: class Person { String name,department; public void printValue(){ System.out.println("name is "+name); System.out.println("department is "+department); } } public class Teacher extends Person { int salary; ———————— System.out.println("salary is "+salary); 下面的哪些表达式可以加入到Teacher类的 printValue()方法中?为什么?(D) A. printValue(); B. this.printValue(); C. person.printValue(); D. super.printValue().
9.创建一个Fraction类执行分数运算。要求如下: ⑴ 用整型数表示类的private成员变量:f1和f2。 ⑵ 提供构造方法,将分子存入f1,分母存入f2。 ⑶ 提供两个分数相加的运算方法,结果分别存入f1和f2。 ⑷ 提供两个分数相减的运算方法,结果分别存入f1和f2。 ⑸ 提供两个分数相乘的运算方法,结果分别存入f1和f2。 ⑹ 提供两个分数相除的运算方法,结果分别存入f1和f2。 ⑺ 以a/b的形式打印Fraction数。 ⑻ 以浮点数的形式打印Fraction数。 ⑼ 编写主控程序运行分数运算。