Java学习(三).类与继承
类具有继承性。子类对父类的继承关系体现了现实世界中特殊和一般的关系。类的继承性大大简化了程序设计的复杂性。和类的继承性相联系的对象的动态绑定使对象的方法具有多态性。抽象类和最终类是两种特殊的类。接口和抽象类非常类似,Java语言只支持单继承,但接口使Java语言实际上实现了多继承。
1、面向对象的基本概念:继承
继承是面向对象程序设计的又一个重要特性。继承体现了类与类之间的一种特殊关系,即一般与特殊的关系。继承就是一个新的类拥有全部被继承类的成员变量和方法。继承机制使得新类不仅有自己特有的成员变量和方法,而且有被继承类的全部成员变量和方法。通过继承,可以从已有类模块产生新的类模块,从而使两个类模块之间发生联系。通过继承产生的新的类模块不仅重用了被继承类的模块资源,而且使两个类模块之间的联系方式和人类认识客观事物的方式一致。
面向对象程序设计的继承特性使得大型应用程序的维护和设计变得更加简单。一方面,大型应用程序设计完成并交付使用后,经常面临用户的需求发生变化,程序功能需要扩充等问题。这时程序的修改需要非常谨慎,因为某个局部的修改可能会影响其他部分,而一个正在使用中的系统要进行全面的测试,则既费时间又有很多实际的困难。另一方面,一个新的应用系统程序设计问题,在许多方面会和以前设计过的某个或某些系统的模块非常类似,怎样加快大型应用程序的开发速度,重用这些已经开发成功的程序模块,一直是软件设计中非常希望有效解决的问题。
传统的软件设计解决上述两类问题的方法主要有两种:
对于程序功能扩充问题,通常是直接对原代码进行改动。这种方法虽然可行,但有可能对正在使用的其他模块产生影响,通常靠测试的方法消除这种影响。但是,要对一个正在使用的系统进行全面测试,既非常困难,代价又很大。
对于模块重用问题,通常是对原模块进行复制。对复制的模块再根据需要进行改动,以支持新的功能。这种方法虽然可行,但仍然需要设计人员做很多工作,而且需要重新测试。
面向对象程序设计的继承机制可以很好地解决上述两类问题。面向对象程序设计的继承机制提供了一种重复利用原有程序模块资源的途径。通过新类对原有类的继承,既可以扩充旧的程序模块功能以适应新的用户需求,也可以满足新的应用系统的功能要求。从而既可以大大方便原有系统的功能扩充,也可以大大加快新系统的开发速度。另外,用这种软件设计方法设计的新系统较用传统的软件方法设计的新系统需要进行的测试工作少很多。
2、继承
1、子类和父类
利用面向对象程序设计的继承机制,我们可以首先创建一个包括其他许多类共有的成员变量和方法的一般类,然后再通过继承创建一个新类。由于继承,这些新类已经具有了一般类的成员变量和方法,此时只需再设计各个不同类特有的成员变量和方法。由继承而得到的新类称为子类,被继承的类称为父类或超类。子类直接的上层父类称作直接父类。Java不支持多继承,即一个子类只能有一个直接父类。
例如,设父类super已经定义,当类sub1继承类super时,就表明类sub1是类super的子类,或者说类super是类sub1的父类。子类sub1由两部分组成:继承部分和增加部分。继承部分是从父类super继承过来的,增加部分是子类sub1新增加的。这样,子类继承了父类的成员变量和方法,从而可以共享已设计完成的软件模块。不仅如此,父类super还可以作为多个子类的父类,如子类sub2也是父类super的子类。由于子类sub1和子类sub2有相同的父类,所以他们既有许多相同的性能,也有一些不同的功能。父类和子类之间的继承关系如下图所示:
从上图可知,具有继承关系的若干个类组成一棵类树。由于Java中所有的类都是从Object类继承(或称派生)来的,所以,Java中所有的类构成一棵类树。
注意:在图所示的有三层继承关系的类中,最下层的Sub11和Sub12子子类,不仅继承了直接父类Sub1的成员变量和方法,而且继承了间接父类Super的成员变量和方法。如果没有继承机制,则一个软件系统中的各个类是各自封闭的、相互无关的。当多个类需要实现相似的功能时,势必会造成成员变量和方法的大量重复。而有了继承机制,多个类就可相互关联,新类就可以从已有的类中通过继承产生。
继承有两种基本形式:多继承和单继承。多继承是指一个子类可以继承自多个直接父类。单继承是指一个子类只可以继承自一个直接父类。Java语言只允许单继承,不允许多继承。
2、创建子类
Java中的类都是Object类的子类(当然,很多类是Object类的间接子类)。Object类定义了所有对象都必须具有的基本成员变量和方法。Java中的每个类都从Object类继承了成员变量和方法,因而Java中的所有对象都具有Object类的成员变量和方法。
由于Java中的所有类都是Object的直接子类或间接子类,所以Java中的所有类构成一棵类的层次树结构。
定义类有两种基本方法:不指明父类和显式地指明父类。Java语言规定,若定义类时不指明父类,则其父类是Object类。本节介绍显式的指明父类的类定义方法。
显式的指明一个类的父类的方法是,在类定义时使用关键字extends,并随后给出父类名。类定义语句格式为:
[<修饰符>] class <子类名> extends <父类名>
例如: class Sub1 extends Super
就定义类Sub1继承自类Super。此时我们说类Sub1是类Super的子类,或者说类Super是类Sub1的直接父类,直接父类通常简称为父类。
1.子类继承父类的成员变量
子类继承了父类中的成员变量。具体的继承原则是:
(1)能够继承父类中那些声明为public和protected的成员变量。
(2)不能继承父类中那些声明为private和默认的成员变量。
(3)如果子类声明一个与父类成员变量同名的成员变量,则不能继承父类的同名成员变量。此时称子类的成员变量隐藏了父类中的同名成员变量。
因此,如果父类中存在不允许其子类访问的成员变量,那么这些成员变量必须以private修饰符声明该成员变量;如果父类中存在只允许其子类访问、不允许其他类访问的成员变量,那么这些成员变量必须以protected修饰符声明该成员变量。
2.子类继承父类的方法
子类继承父类方法的规则类似于子类继承父类成员变量的规则。具体的继承原规是:
(1)能够继承父类中那些声明为public和protected的方法。
(2)不能继承父类中那些声明为private和默认的方法。
(3)如果子类方法与父类方法同名,则不能继承。此时称子类方法重写了父类中的同名方法。
(4)不能继承父类的构造方法。
注意:和子类继承父类成员变量的继承原则不同的是,子类不能继承父类的构造方法。
3.this引用和super引用
(1)this引用。
Java中,每个对象都具有对其自身引用的访问权,这称为this引用。访问本类的成员变量和方法的语句格式为:
this.<成员变量名>
this.<方法名>
例如:下面定义的类X中有成员变量k,而在方法D中也用k作参数,这样两个不同含义的变量k就有可能产生混淆,此时必须用this.k指代对象的成员变量k。
class X
{
int k;
void D(int k)
{
this.k = 2*k; //this.k指成员变量k,k指参数k
}
}
(2)super引用。
使用关键字super可以引用被子类隐藏的父类的成员变量或方法,这称为super引用。super引用的语句格式为:
super.<成员变量名>
super.<方法名>
super引用经常用在子类的构造方法中。前面说过,子类不能继承父类的构造方法,但有时子类的构造方法和父类的构造方法相同时,或子类的构造方法只需在父类构造方法的基础上做某些补充时,子类构造方法中需要调用父类的构造方法时,此时的语句格式为:
super(<参数列表>)
其中,<参数列表>是调用父类构造方法所需的参数。
4.成员变量和方法的隐藏与覆盖
子类除了可以继承父类中的成员变量和方法外,还可以增加自已特有的成员变量和方法。当父类的某个成员变量不适合子类时,子类可以重新定义该成员变量。前面说过,此种情况下,子类隐藏了父类的成员变量(程序设计中这种情况很少,一般也不提倡);当父类的某个方法不适合子类时,子类可以重新定义它,这称为子类对父类方法的覆盖(overriding)。
子类对父类方法的覆盖是面向对象程序设计中经常使用的设计方法。在软件功能扩充和软件重用中,可以通过设计新的子类,以及通过子类方法对父类方法的覆盖,可以方便和快速地实现软件功能的扩充和软件的重用。
注意:方法的重写(overloading)和方法的覆盖(overriding)是两个不同的概念,在软件设计中实现的功能也不同。
5.举例
【例4.1】继承举例。
要求;设计一个Shape(形状)类,再设计Shape类的两个子类,一个是Ellipse(椭圆)类,另一个是Rectangle(矩形)类。每个类都包括若干成员变量和方法,但每个类都有一个draw()方法(画图方法),draw()方法中用输出字符串表示画图。
程序设计如下:
class Shape //定义父类Shape
{
protected int lineSize; //线宽
public Shape() //构造方法1
{
lineSize = 1;
}
public Shape(int ls) //构造方法2
{
lineSize = ls;
}
public void setLineSize(int ls) //设置线宽
{
lineSize = ls;
}
public int getLineSize() //获得线宽
{
return lineSize;
}
public void draw() //画图
{
System.out.println("Draw a Shape");
}
}
class Ellipse extends Shape //定义子类Ellipse
{
private int centerX; //圆心X坐标
private int centerY; //圆心Y坐标
private int width; //椭圆宽度
private int height; //椭圆高度
public Ellipse(int x, int y, int w, int h) //构造方法
{
super(); //调用父类的构造方法1
centerX = x;
centerY = y;
width = w;
height = h;
}
public void draw() //覆盖父类的draw()方法
{
System.out.println("draw a Ellipse");
}
}
class Rectangle extends Shape //定义子类Rectangle
{
private int left; //矩形左上角X坐标
private int top; //矩形左上角Y坐标
private int width; //矩形长度
private int height; //矩形宽度
public Rectangle(int l, int t, int w, int h) //构造方法
{
super(2); //调用父类的构造方法2
left = l;
top = t;
width = w;
height = h;
}
public void draw() //覆盖父类的draw()方法
{
System.out.println("draw a Rectangle");
}
}
public class Inherit //定义类Inherit
{
public static void main(String args[])
{
Ellipse ellipse = new Ellipse(30, 30, 50,60);
//创建子类Ellipse的对象
ellipse.setLineSize(2);
//调用父类方法重新设置lineSize 值为2
System.out.println("LineSize of ellipse : " + ellipse.getLineSize());
Rectangle rectangle = new Rectangle(0, 0, 20, 30);
//创建子类rectangle对象
rectangle.setLineSize(3);
//调用父类方法重新设置lineSize属性为3
System.out.println("LineSize of rectangle : " + rectangle.getLineSize());
ellipse.draw(); //访问子类方法
rectangle.draw(); //访问子类方法
}
}
程序运行结果如下:
LineSize of ellipse : 2
LineSize of rectangle : 3
draw a Ellipse
draw a Rectangle
程序设计说明:
(1)类Shape中定义了所有子类共同的成员变量lineSize(线宽),椭圆类Ellipse和矩形类Rectangle在继承父类成员变量的基础上,又各自定义了自己的成员变量。
(2)父类Shape中定义了画图方法draw(),子类Ellipse和子类Rectangle中由于各自形状不同,画图方法draw()也不同,所以子类Ellipse和Rectangle中重新定义了各自的draw()方法(即覆盖了父类的draw())。注意:子类覆盖父类方法时,参数个数和参数类型必须相同。
(3)当一个文件中包含有多个类时,源程序文件名应该和定义为public类型的类名相同。
3、方法的三种继承形式
上节讨论了子类对父类方法继承的一般形式,本节我们进一步总结子类对父类方法继承的三种不同形式,以及系统中子类对象访问方法的匹配原则和继承在面向对象程序设计中的作用。
1.方法的三种继承形式
子类对父类方法继承可以有三种不同形式:完全继承、完全覆盖和修改继承。
(1)完全继承。
完全继承是指子类全部继承父类的方法。
如果父类中定义的方法完全适合于子类,则子类就不需要重新定义该方法。子类对父类的继承允许子类对象直接访问父类的方法,这就是子类对父类方法的完全继承。
例如例4.1中,如果子类不重新定义draw()方法,则ellipse.draw()和rectangle.draw()访问的都是父类中定义的draw()方法。
(2)完全覆盖。
完全覆盖是指子类重新定义父类方法的功能,从而子类中的同名方法完全覆盖了父类中的方法。
如例4.1中,子类重新定义了父类的draw()方法,因此子类对象ellipse和rectangle访问的就是子类中重新定义的方法draw(),即ellipse.draw()和rectangle.draw()访问的都是子类中定义的draw()方法。
(3)部分继承。
部分继承是指子类覆盖父类的方法,但子类重新定义的方法中调用父类中的同名方法,并根据问题要求做部分修改。
【例4.2】修改继承举例。
class Shape //定义父类Shape
{
public void draw()
{
System.out.println("Draw a Shape");
}
}
class Ellipse extends Shape //定义子类Ellipse
{
public void draw() //覆盖父类的draw()方法
{
super.draw();
System.out.println("draw a Ellipse");
}
}
class Rectangle extends Shape //定义子类Rectangle
{
public void draw() //覆盖父类的draw()方法
{
super.draw(); //调用父类的draw()方法
System.out.println("draw a Rectangle"); //修改部分
}
}
public class CInherit //定义类Inherit
{
public static void main(String args[])
{
Ellipse ellipse = new Ellipse(); //创建子类Ellipse的对象
Rectangle rectangle = new Rectangle();//创建子类rectangle对象
ellipse.draw(); //访问子类方法
rectangle.draw(); //访问子类方法
}
}
程序运行结果如下:
Draw a Shape
draw a Ellipse
Draw a Shape
draw a Rectangle
程序设计说明:
(1)子类的draw()覆盖了父类的draw()方法,但子类的draw()方法首先调用了父类的draw()方法。子类draw()方法调用父类draw()方法的语句是:
super.draw();
(2)由于子类方法在调用父类方法的基础上,又增加了子类中需要补充修改的功能,所以子类对象ellipse和rectangle访问的draw()方法,完成的功能是在父类方法基础上的补充或修改。
2.系统中子类对象访问方法的匹配原则
在Java语言(以及在所有的面向对象程序设计语言)中,对象访问方法的匹配原则是:从对象定义的类开始,逐层向上匹配寻找对象要访问的方法。
在完全继承方式中,由于子类中没有定义draw()方法,所以系统自动到它的直接父类Shape中去匹配draw()方法,系统在父类Shape中匹配上了draw()方法,所以子类对象ellipse和rectangle访问的是父类定义的draw()方法。在完全覆盖方式中,由于子类中定义了draw()方法,所以子类对象ellipse和rectangle访问的是子类定义的draw()方法。在修改继承方式中,由于子类中定义了draw()方法,所以子类对象ellipse和rectangle访问的是子类定义的draw()方法,由于子类定义的draw()方法首先调用了父类中定义的draw()方法,然后又增加了需要修改或补充的功能,所以子类对象ellipse和rectangle访问的draw()方法,既包含了父类draw()方法的功能,又包含了子类修改或补充的功能。
3.继承在面向对象程序设计中的作用
继承在面向对象程序设计中有两方面的意义:
一方面,继承性可以大大简化程序设计的代码。我们可以把若干个相似类所具有的共同成员变量和方法定义在父类中,这样这些子类的设计代码就可以大大减少。
另一方面,继承(特别是部分修改继承和完全覆盖继承)使得大型软件的功能修改和功能扩充较传统的软件设计方法容易了许多。当要对系统的一些原有功能进行补充性修改或添加一些新的功能时,可以重新设计原先类的一个子类,利用部分修改继承方法重新设计子类中要补充性修改或添加的功能;当要废弃系统的一些原有功能,重新设计完全不同的新的功能时,可以重新设计原先类的一个子类,利用完全覆盖继承方法重新设计子类中的功能。
继承性是面向对象方法的一个非常重要的特点。这是因为继承性使得我们可以根据问题的特征,把若干个类设计成继承关系。而类的继承关系和人类认识客观世界的过程和方法基本吻合,从而使得人们能够用和认识客观世界一致的方法来设计软件。
4、方法的多态性
1.对象的动态绑定和方法的多态性
方法的多态性是面向对象程序的另一个重要特点。方法的多态(polymorphism)是指若以父类定义对象,并动态绑定对象,则该对象的方法将随绑定对象不同而不同。
在只定义对象、没有分配内存空间时,如下图(a)所示,对象名中并没有存放实际对象的首地址。在为已定义的对象分配了内存空间后,如下图(b)所示,对象名中存储的就是对象的内存空间的首地址。对象名和实际对象的这种联系称作对象的绑定(binding)。
Java语言还支持对象的动态绑定。所谓对象的动态绑定,是指定义为类树上层的对象名,可以绑定为所定义层类以及下层类的对象。这样,当对象动态绑定为哪一层子类对象时,其方法就调用那一层子类的方法。因此,对象的动态绑定和类的继承相结合就使对象的方法具有多态性。
【例4.3】方法的多态性示例。
class Shape //定义父类 Shape
{
public void draw() //父类的draw()方法
{
System.out.println("Draw a Shape");
}
}
class Circle extends Shape //定义子类Circle
{
public void draw() //覆盖父类的draw()方法
{
System.out.println("draw a Circle");
}
}
class Ellipse extends Circle //定义子类Ellipse
{
public void draw() //覆盖父类的draw()方法
{
System.out.println("draw a Ellipse");
}
}
public class FInherit //定义类FInherit
{
public static void main(String args[])
{
Shape s= new Shape(); //动态绑定为类Shape对象
Shape c = new Circle(); //动态绑定为类Circle对象
Shape e = new Ellipse(); //
动态绑定为类Ellipse对象
s.draw(); //访问父类方法
c.draw(); //访问一级子类方法
e.draw(); //访问二级子类方法
}
}
程序运行结果如下:
Draw a Shape
draw a Circle
draw a Ellipse
程序说明:
(1)类Shape是父类, 类Circle是类Shape的直接子类,类Ellipse是类Circle的直接子类。这三个类中都定义了draw()方法。子类中的draw()方法覆盖了父类中的同名方法。
(2)FInherit 类的main()方法中,定义了三个对象,三个对象s、c和e都定义为Shape类,但对象s动态绑定为Shape类的对象,对象c动态绑定为Circle类的对象,对象e动态绑定为Ellipse类的对象。这样,语句s.draw()调用的就是Shape类的方法draw(),语句c.draw()调用的就是Circle类的方法draw(),语句e.draw()调用的就是Ellipse类的方法draw()。
2.方法多态性的用途
方法的多态性在程序设计中非常有用。例如Java API语言包的Vector类,Vector类中定义的一个方法如下:
copyInto(Object[] anArray)
该方法的功能是把当前对象的一个成分复制给对象数组anArray。其参数anArray定义为Object类的数组,由于Object类是所有类的根(即最上层的类),所以,该方法可用于任何类的对象。例如,程序中可以像下面这样使用Vector类的copyInto()方法:
Vector v = new Vector(); //定义并创建Vector类的对象v
String[] s = new String[v.size()]; //定义并创建String类的对象s
v.copyInto(s); //把对象v的当前成分复制给对象s
上面语句段的最后一句将把对象v的当前成分复制给对象s。如果没有方法的多态性,若要定义Vector类的copyInto()方法适合所有类的对象时,就要把该方法用不同类的参数重载很多个;而方法的多态性支持Vector类的copyInto()方法用Object类参数(Object [] anArray)定义一次,就可以适合于所有类的对象了。
3、抽象类和最终类
在类的定义中,除了可说明该类的父类外,还可以说明该类是否是最终类或抽象类。
1、抽象类
类中允许定义抽象方法。所谓抽象方法是指只有方法的定义,没有方法的实现体的方法。Java语言用关键字abstract来声明抽象方法。例如:
abstract void draw()
则声明类中的draw()方法为抽象方法。但是,需要说明的是:
(1)构造方法不能被声明为抽象的。
(2)abstract和static不能同时存在,即不能有abstract static方法。
包含抽象方法的类称为抽象类。换句话说,任何包含抽象方法的类必须被声明为抽象类。因为抽象类中包含没有实现的方法,所以抽象类是不能直接用来定义对象。Java语言用关键字abstract来声明抽象类,例如:
abstract class Shape
则声明类Shape为抽象类。
在程序设计中,抽象类主要用于定义为若干个功能类同的类的父类。
【例4.4】抽象类举例。
问题描述:设计椭圆类Ellipse和矩形类Rectangle,要求这两个类都包含一个画图方法draw()。
设计分析:椭圆类Ellipse和矩形类Rectangle有许多成员变量和方法相同,因此,可以先设计一个它们的共同的父类(也称基类)Shape,并把画图方法draw()定义在父类中。但是,由于父类Shape只是抽象的形状,画图方法draw()无法实现,所以,父类中的画图方法draw()只能定义为抽象方法,而包含抽象方法的Shape类也只能定义为抽象类。
abstract class Shape //定义抽象类 Shape
{
public abstract void draw(); //定义抽象方法
}
class Ellipse extends Shape //定义子类Ellipse
{
public void draw() //实现draw()方法
{
System.out.println("draw a Ellipse");
}
}
class Rectangle extends Shape //定义子类Rectangle
{
public void draw() //实现draw()方法
{
System.out.println("draw a Rectangle");
}
}
public class AInherit //定义类Inherit
{
public static void main(String args[])
{
Ellipse ellipse = new Ellipse(); //创建子类Ellipse的对象
Rectangle rectangle = new Rectangle();//创建子类rectangle对象
ellipse.draw(); //访问子类ellipse的方法
rectangle.draw(); //访问子类rectangle的方法
}
}
上述例子说明:
(1)在一个软件中,抽象类一定是某个类或某些类的父类。
(2)若干个抽象类的子类要实现一些同名的方法。
在后面讨论的Java API中,系统的许多类都是用上面形式的结构定义和实现的。
2、最终类
最终类是指不能被继承的类,即不能再用最终类派生子类。在Java语言中,如果不希望某个类被继承,可以声明这个类为最终类。最终类用关键字final来说明。例如:
public final class C
就定义类C为最终类。
如果创建最终类似乎不必要,而又想保护类中的一些方法不被覆盖,可以用关键字final来指明那些不能被子类覆盖的方法,这些方法称为最终方法。例如:
public class A
{
public final void M();
}
就在类A中定义了一个最终方法M(),任何类A的子类都不能重新定义方法M()。
在程序设计中,最终类可以保证一些关键类的所有方法,不会在以后的程序维护中,由于不经意的定义子类而被修改;最终方法可以保证一些类的关键方法,不会在以后的程序维护中,由于不经意的定义子类和覆盖子类的方法而被修改。
需要注意的是:一个类不能既是最终类又是抽象类,即关键字abstract和final不能合用。在类声明中,如果需要同时出现关键字public和abstract(或final),习惯上,public放在abstract(或final)的前面。
4、接口
面向对象程序设计语言的一个重要特性是继承。继承是指子类可以继承父类的成员变量和方法。如果子类只允许有一个直接父类,这样的继承称作单继承。如果子类允许有一个以上的直接父类,这样的继承称作多继承。单继承具有结构简单,层次清楚,易于管理,安全可靠的特点。多继承具有功能强大的特点。
Java语言只支持单继承机制,不支持多继承。一般情况下,单继承就可以解决大部分子类对父类的继承问题。但是,当问题复杂时,若只使用单继承,可能会给设计带来许多麻烦。Java语言解决这个问题的方法是使用接口。
接口和抽象类非常相似,都是只定义了类中的方法,没有给出方法的实现。
Java语言不仅规定一个子类只能直接继承自一个父类,同时允许一个子类可以实现(也可以说继承自)多个接口。由于接口和抽象类的功能类同,因此,Java语言的多继承机制是借助于接口来实现的。
1、定义接口
接口的定义格式为:
<修饰符> interface<接口名>
{
成员变量1 = <初值1>;
成员变量2 = <初值2>;
……
方法1;
方法2;
……
}
其中,<修饰符>可以是public,也可以缺省。当为缺省时,接口只能被与它处在同一包中的方法访问;当声明为public时,接口能被任何类的方法访问。<接口名>是接口的名字,可以是任何有效的标识符。例如,
public interface PrintMessage
{
public int count = 10;
public void printAllMessage();
public void printLastMessage();
public void printFirstMessage();
}
就定义了一个接口PrintMessage。接口中的方法(printAllMessage()等)只有方法定义,没有方法实现。所以接口实际上是一种特殊的抽象类。
需要说明的是:
(1)若接口定义为默认型访问权限,则接口中的成员变量全部隐含为final static型。这意味着它们不能被实现接口方法的类改变,并且为默认访问权限。
(2)接口中定义的所有成员变量都必须设置初值。
(3)若接口定义为public型访问控制,则接口中的方法和成员变量全部隐含为public型。
(4)当接口保存于文件时,其文件命名方法和保存类的文件命名方法类同。即保存接口的文件名必须与接口名相同。一个文件可以包含若干个接口,但最多只能有一个接口定义为public,其他的接口必须为默认。
2、实现接口
一旦定义了一个接口,一个或更多的类就能实现这个接口。为了实现接口,类必须实现定义在接口中的所有方法。每个实现接口的类可以自由地决定接口方法的实现细节。
定义类时实现接口用关键字implements。一个类只能继承一个父类,但可以实现若干个接口。因此,类定义的完整格式是:
[<修饰符>]class<类名> [extends<父类名>] [implements <接口名1>,<接口名2>,……]
其中,关键字implements后跟随的若干个接口名表示该类要实现的接口;如果要实现多个接口,则用逗号分隔开接口名。
【例4.5】编写一个实现接口PrintMessage(为简化设计代码,去掉其中的成员变量定义)的类,并编写一个测试程序进行测试。
程序设计如下:
//接口文件PrintMessage.java
public interface PrintMessage
{
public int count = 10;
public void printAllMessage();
public void printLastMessage();
public void printFirstMessage();
}
//实现接口的文件MyInter.java
public class MyInter implements PrintMessage //实现接口的类MyInter
{
private String[] v; //类中的成员变量v
private int i; //类中的成员变量i
public MyInter() // MyInter类的构造方法
{
v = new String[3];
i = 0;
this.putMessage("Hello world!"); //使用MyInter类的方法
this.putMessage("Hello China!");
this.putMessage("Hello XSYU!");
}
public void putMessage(String str) //类中的方法
{
v[i++] = str;
}
public void printAllMessage() //实现接口中的方法
{
for(int k = 0; k < v.length; k++)
{
System.out.println(v[k]);
}
}
public void printLastMessage() //实现接口中的方法
{
System.out.println(v[v.length - 1]);
}
public void printFirstMessage() //实现接口中的方法
{
System.out.println(v[0]);
}
public static void main(String[] args)
{
MyInter mi=new MyInter(); //定义MyInter类的对象
System.out.println("print all messages");
mi.printAllMessage(); //使用实现了的接口方法
System.out.println("print the first messages");
mi.printFirstMessage(); //使用实现了的接口方法
System.out.println("print the last messages");
mi.printLastMessage(); //使用实现了的接口方法
}
}
程序的运行结果如下:
print all messages
Hello world!
Hello China!
Hello XSYU!
print the first messages
Hello world!
print the last messages
Hello XSYU!
程序说明:在定义类MyInter时,后边跟有implements PrintMessage,表示该类中要实现接口PrintMessage。此时类MyInter中必须实现接口PrintMessage中定义的三个方法。由于类MyInter隐含继承了类Object,现在又实现了接口PrintMessage,所以类MyInter是一个多继承。可见,接口支持了Java的多继承。
3、系统定义的接口
Java API中定义了许多接口,一旦安装了JDK运行环境,就可以像使用用户自己定义的接口一样使用系统定义的接口。例如,Enumeration是系统定义的一个接口。Enumeration接口的定义如下:
public interface Enumeration
{
Object nextElement(); //返回后续元素
boolean hasMoreElements(); //是否还有后续元素
}
许多系统定义的类都实现了Enumeration接口。如有必要,用户自己定义的类也可以实现Enumeration接口。