重庆大学计算机学院 电子邮件:lizhx@126.com 《Java程序设计之网络编程》 教学课件 重庆大学计算机学院 电子邮件:lizhx@126.com
第5章 面向对象高级程序设计
主要内容 5.1 继承 5.2多态性 5.3 抽象类和抽象方法 5.4接口 5.5内部类和匿名类
5.1 继承 5.1.1 创建子类 5.1.2 成员变量的隐藏和方法的重写 5.1.3 super 5.1.4 对象的上转型对象
5.1.1 创建子类 继承是一种由已有的类创建新类的机制。利用继承,我们可以先创建一个拥有共同属性的一般类,根据该一般类再创建具有特殊属性的新类。由继承而得到的类称为子类(subclass), 被继承的类称为父类(或叫超类,superclass)。 直接或间接被继承的类都是父类。子类继承父类的状态和行为,同时也可以修改父类的状态或重写父类的行为,并添加新的状态和行为。Java中不支多重继承。
5.1.1 创建子类 通过在类的声明中加入extends子句来创建一个类的子类,其格式如下: class SubClass extends SuperClass{ …… } 上面的代码把SubClass声明为SuperClass的直接子类。如果SuperClass又是某个类的子类,则SubClass同时也是该类的(间接)子类。子类可以继承父类的成员变量和方法。如果缺省extends子句,则该类为java.lang.Object的子类。子类可以继承父类中访问权限设定为public、protected、default的成员变量和方法。但是不能继承访问权限为private的成员变量和方法。
5.1.1 创建子类
5.1.1 创建子类
5.1.1 创建子类 注意,MammalClass类拥有来自于DogClass和CatClass的相同属性,包括了name、eyeColor、age等。现在我们可以利用继承重写DogClass和CatClass。 public class DogClass extends MammalClass { boolean hasTail; // name,eyeColor已经从父类继承 public DogClass() { //隐式调用super() name="Chase"; eyeColor="Black"; age=2; hasTail=true; }
5.1.1 创建子类 【例5-1】 继承的简单例子 class Father{ //父类 private int money; float weight,height; String head; String speak(String s) { return s ; } class Son extends Father{ //子类 String hand ,foot; Weight,height,head, Speak(); Hand,foot
5.1.1 创建子类 【例5-1】 继承的简单例子 public class TestExtend { public static void main(String args[]){ Son boy=new Son(); boy.weight=120f; boy.height=1.8f; boy.head="一个头"; boy.hand="两只手"; boy.foot="两只脚"; System.out.println("我是儿子"); System.out.println("我有:"+boy.hand+"、"+boy.foot+"、"+ boy.head +"、重"+boy.weight+"、高"+boy.height); } 上面程序运行结果如下: 我是儿子 我有:两只手、两只脚、一个头、重120.0、高1.8
5.1.1 创建子类 如果子类和父类不在同一个包中,那么,子类可以继承了父类的protected、public修饰的成员变量做为子类的成员变量,并且也可以继承了父类的protected、 public 修饰的方法作为子类的方法。另外子类和父类不在同一个包中,则子类不能继承父类的default变量和default方法。
5.1.1 创建子类 【例5-2】继承不同包中的类的简单例子 // HouseHold.java package xing.house; public class HouseHold { //家类 protected String address; //地址 public String surnname; //姓 String givenname; //名 public HouseHold(String add) { address =add;} protected String getAddress(){return address;} void setMoney(String newadd) {address=newadd;} void setAddress(String add){address=add;} }
5.1.1 创建子类 // Mikey.java: package xing.friend; import xing.house.HouseHold; public class Mikey extends HouseHold { public Mikey(){ super("Star flight street 110"); } public static void main(String args[]){ Mikey mikey=new Mikey(); //mikey.givenname=“Johnson”; //非法 mikey.surnname="Math"; //合法. mikey.address="Star flight street 110"; //合法. String m=mikey.getAddress(); //合法 //mikey.setAddress("Star flight street 110"); //非法. System.out.println(mikey.surnname+":"+m); }
5.1.1 创建子类 // Mikey.java: package xing.friend; import xing.house.HouseHold; public class Mikey extends HouseHold { public Mikey(){ super("Star flight street 110"); } public static void main(String args[]){ Mikey mikey=new Mikey(); //mikey.givenname=“Johnson”; //非法 mikey.surnname="Math"; //合法. mikey.address="Star flight street 110"; //合法. String m=mikey.getAddress(); //合法 //mikey.setAddress("Star flight street 110"); //非法. System.out.println(mikey.surnname+":"+m); }
5.1.1 创建子类 程序编译和运行过程如下:
5.1.2 成员变量的隐藏和方法的重写 当我们在子类中定义的成员变量和父类中的成员变量同名时,此时称子类的成员变量隐藏了父类的成员变量。当子类中定义了一个方法,并且这个方法的名字,返回类型,参数个数以及类型和父类的某个方法完全相同时,父类的这个方法将被隐藏,这时我们说重写了父类的方法。 子类通过成员变量的隐藏和方法的重写可以把父类的状态和行为改变为自身的状态和行为。
5.1.2 成员变量的隐藏和方法的重写 例如下面的这段程序就是这样的情况: class SuperClass { //父类 int y; void setY(){ y=0; } class SubClass extends SuperClass{ int y; // 父类变量y被隐藏 void setY(){ // 重写父类的方法setY() y=1;
5.1.3 super 5.1.3 super 子类在隐藏了父类的成员变量或重写了父类的方法后,常常还要用到父类的成员变量,或在重写的方法中使用父类中被重写的方法以简化代码的编写,这时就要访问父类的成员变量或调用父类的方法,Java中通过super来实现对父类成员的访问。我们知道,this用来引用当前对象,与this类似,super用来引用当前对象的父类。 super的使用可以分为下面三种情况: 1)用来访问父类被隐藏的成员变量,如: super.variable 2)用来调用父类中被重写的方法,如: super.Method([paramlist]): 3)用来调用父类的构造方法,如: super([paramlist]);
5.1.3 super 【例5-3】调用父类的构造方法的例子 class A { //类A public int n; //公共类型的成员变量 public A(){ } public A(int n){ this.n = n; } int method(){ return n; } } public class B extends A { //类B public B(){ super(15); } public static void main(String args[]){ A aInstance = new B( ); int b=aInstance.method(); System.out.println("类A中的成员变量:"+b);
5.1.4对象的上转型对象 假设A 类是B 类的父类, class b extends B{} 当我们用子类创建一个对象,并把这个对象的引用放到父类的对象中时,例如 A a; A a=new B(); 或 B b=new B(); a=b; 称这个父类对象a,是子类对象b的上转型对象。
5.1.4对象的上转型对象 对象的上转型对象的实体是子类负责创建的,但上转型对象会失去原对象的一些属性和功能.上转型对象具有如下特点: 1)上转型对象不能操作子类新增的成员变量和子类新增的方法。 2)上转型对象可以操作子类继承或重写的成员变量,也可以使用子类继承的或重写的方法。 3)如果子类重写了父类的某个方法后,当对象的上转对象调用这个方法时一定是调用了这个重写的方法,因为程序在运行时知道,这个上转对象的实体是子类创建的,只不过损失了一些功能而已。 不要将父类创建的对象和子类对象的上转型对象相混淆。 上转型对象在Java编程中是常见的。 可以将对象的上转型对象再强制转换到一个子类对象,这时,该子类对象又具备了子类所给的所有属性和功能。
5.1.4对象的上转型对象 【例5-5】 上转型对象的使用 class Mammal{ //哺乳动物类 private int n=40; void crySpeak(String s) { System.out.println(s); } } public class Monkey extends Mammal{ // 猴子类 void computer(int aa,int bb) { int cc=aa*bb; System.out.println(cc);} void crySpeak(String s) {System.out.println("**"+s+"**");} public static void main(String args[]){ Mammal mammal=new Monkey(); // mammal是Monkey类的对象的上转型对象. mammal.crySpeak("I love this game"); // mammal.computer(10,10); Monkey monkey=(Monkey)mammal; //把上转型对象强制转化为子类的对象. monkey.computer(10,10);
5.1.4对象的上转型对象 上述程序的运行结果为: **I love this game** 100 在上述例子中,上转对象mammal调用方法: mammal.crySpeak("I love this game"); 得到的结果是”**I love this game**”.而不是 ”I love this game”。 因为mammal调用的是子类重写的方法crySpeak. 在main()中,如果出现下面的两行代码,那将是错误的: mammal.n=1000; //因为子类本来就没有继承n。 mammal.computer(10,10); //因为computer方法是子类新增的方法。
第5章 面向对象高级程序设计 5.1 继承 5.2多态性 5.3 抽象类和抽象方法 5.4接口 5.5内部类和匿名类
5.2多态性 多态(Polymorphism)的意思就是用相同的名字来定义不同的方法。在Java中,普通类型的多态为重载,这就意味着可以使几个不同的方法使用相同的名字,这些方法以参数的个数不同、参数的类型不同等方面来进行区分,以使得编译器能够进行识别。 也可以这样讲,重载是同一个方法具有不同的版本,每个版本之间在参数特征方面有差异。重载是Java实现多态性的方式之一。 例如:family()方法可以有三个版本,如下: family() { } family(String ch) { address=ch; } family(String ch,float n) { address=ch; pay=n; } 这些方法并存于程序中,编译时,编译器根据实参的类型和个数来区分从而调用那个方法。如果这些方法作为函数或过程同时出现在其它语言的程序中,如C,那将导致灾难性的错误。
5.2多态性 【例5-6】构造方法重载的例子 class person { String name="Johnson"; // 姓名 int age=45; // 年龄 person(){ } person(String a) {name=a; } person(String a,int b) { name=a; age=b; } public voiddisplay(){ System.out.println("Name="+ name+","+"Age="+age); } } public class Poly{ public static void main(String[] args) { person ko1=new person(); person ko2=new person("Mike"); person ko3=new person("Willian",50); ko1.display(); ko2.display(); ko3.display();
5.2多态性 改写后的方法不能比被重写的方法有更严格的访问权限。 改写后的方法不能比被重写的方法产生更多的异常。 在Java语言中,多态性主要体现在两个方面:由方法重载实现的静态多态性(编译时多态)和方法重写实现的动态多态性(运行时多态),如下: 1) 编译时多态 在编译阶段,具体调用哪个被重载的方法,编译器会根据参数的不同来静态确定调用相应的方法。 2) 运行时多态 由于子类继承了父类所有的属性(私有的除外),所以子类对象可以作为父类对象使用。程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个对象可以通过引用子类的实例来调用子类的方法。 如果子类重写了父类的方法,那么重写方法的调用原则如下:Java运行时系统根据调用该方法的实例,来决定调用哪个方法。对子类的一个实例,如果子类重写了父类的方法,则运行时系统调用子类的方法;如果子类继承了父类的方法(未重写),则运行时系统调用父类的方法。 另外,方法重写时应遵循的原则如下: 改写后的方法不能比被重写的方法有更严格的访问权限。 改写后的方法不能比被重写的方法产生更多的异常。 进行方法重写时必须遵从这两个原则,否则编译器会指出程序出错。
5.2多态性 【例5-7】方法重写的例子 class Parent{ public void function(){ System.out.println("I am in Parent!"); } class Child extends Parent{ private void function(){ System.out.println("I am in Child!"); public class RTpolyTest{ public static void main(String args[]){ Parent pl=new Parent( ); Parent p2=new Child( ); p1.function( ); p2.function( );
5.2多态性 编译过程如下: D:\user\chap05>Javac RTpolyTest.java RTpolyTest.java:8: function() in Child cannot override function() in Parent; attempting to assign weaker access privileges; was public private void function(){ ^ RTpolyTest.java:16: cannot find symbol symbol : variable p1 location: class RTpolyTest p1.function( ); 2 errors 可以看出,该程序中实例p2调用function()方法时会导致访问权限的冲突。
第5章 面向对象高级程序设计 5.1 继承 5.2多态性 5.3 抽象类和抽象方法 5.4接口 5.5内部类和匿名类
第5章 面向对象高级程序设计 5.1 继承 5.2多态性 5.3 抽象类和抽象方法 5.4接口 5.5内部类和匿名类
5.3 抽象类和抽象方法 Java语言中,用abstract关键字来修饰一个类时,这个类叫做抽象类。一个abstract 类只关心它的子类是否具有某种功能,并不关心该功能的具体实现,功能的具体行为由子类负责实现的。例如: public abstract class Drawing { public abstract void drawDot(int x, int y); public void drawLine(int x1, int y1,int x2, int y2) { ………… } 用abstract来修饰一个方法时,该方法叫做抽象方法。与final类和方法相反,abstract类必须被继承,abstract方法必须被重写。
5.3 抽象类和抽象方法 当一个类的定义完全表示抽象的概念时,它不应该被实例化为一个对象。例如Java中的Number类就是一个抽象类,它只表示数字这一抽象概念,只有当它作为整数类Integer或实数类Float等的父类时才有意义。 定义一个抽象类的格式如下: abstract class abstractClass{ …… } 由于抽象类不能被实例化,因此下面的语句会产生编译错误: new abstractClass(); 抽象类中可以包含抽象方法,为所有子类定义一个统一的接口,对抽象方法只需声明,而不需实现,因此它没有方法体。其格式如下: abstrac returnType abstractMethod([paramlist));
5.3 抽象类和抽象方法 【例5-8】使用abstract的另一例子 abstract class AA{ abstract void callme( ); void metoo( ){ System.out.println("InsideA's metoo() method"); } class BB extends AA{ void callme( ){ System.out.println("Inside B's callme() method"); public class AAbstract{ public static void main(String args[]){ AA cc=new BB(); //cc为上转型对象 cc.callme(); cc.metoo();
第5章 面向对象高级程序设计 5.1 继承 5.2多态性 5.3 抽象类和抽象方法 5.4接口 5.5内部类和匿名类
5.4 接口 5.4.1接口声明 5.4.2使用接口的优点
5.4.1接口声明 从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。通过接口使得处于不同层次,甚至互不相关的类可以具有相同的行为。接口其实就是方法定义和常量值的集合。 它的优点主要体现在下面几个方面: (1)通过接口可以实现不相关类的相同行为,而不需要考虑这些类之间的层次关系。 (2)通过接口可以指明多个类需要实现的方法。 (3)通过接口可以了解对象的交互界面,而不需了解对象所对应的类。 接口把方法的定义和类的层次区分开来,通过它可以在运行时动态地定位所调用的方法。同时接口中可以实现“多重继承”,且一个类可以实现多个接口。正是这些机制使得接口提供了比多重继承(如C++等语言)更简单、更灵活、而且更强劲的功能。
5.4.1接口声明 我们曾使用class关键字来声明类,接口通过使用关键自interface 来声明. 完整的接口定义格式如下: Java 不支持多继承性,即一个类只能有一个父类。单继承性使得Java类层次简单,易于程序的管理。为了克服单继承的缺点,Java使用了接口,一个类可以实现多个接口。使用关键字interface 来定义一个接口。接口的定义和类的定义很相似,分为接口声明和接口体两部分。 1、 接口声明 我们曾使用class关键字来声明类,接口通过使用关键自interface 来声明. 完整的接口定义格式如下: [public] interface interfaceName [extends listOfSuperInterface]{ …… } 其中public修饰符指明任意类均可以使用这个接口,缺省情况下,只有与该接口定义在同一个包中的类才可以访问这个接口。extends子句与类声明中的extends子句基本相同,不同的是一个接口可以有多个父接口,用逗号隔开,而一个类只能有一个父类。子接口继承父接口中所有的常量和方法。 通常接口名称以able或ible结尾,表明接口能完成一定的行为,例如Runnable、Serializable。
5.4.1接口声明 2、 接口体 接口体中包含常量定义和方法定义两部分。其中常量定义部分定义的常量均具有public、static和final属性。 其格式如下: returnType methodName([paramlist]); 接口中只能进行方法的声明,而不提供方法的实现,所以,方法定义没有方法体,且用分号(;)结尾,在接口中声明的方法具有public和abstract属性。另外,如果在子接口中定义了和父接口同名的常量,则父接口中的常量被隐藏。 例如: interface Summaryable { final int MAX=50; // MAX具有public、static、final属性 void printone(float x); float sum(float x ,float y); } 上面这段程序可以以Summaryable.java来保存,也可以写入其它Java程序中。
5.4.1接口声明 3、接口的使用 一个类通过使用关键字implements 声明自己使用(或实现)一个或多个接口。如果使用多个接口,用逗号隔开接口名。如 class Calculate extends Computer implements Summary,Substractable{ …… } 类Calculate使用了Summary 和Substractable接口,继承了Computer类。 如果一个类使用了某个接口,那么这个类必须实现该接口的所有方法,即为这些方法提供方法体。需要注意的如下: 1)在类中实现接口的方法时,方法的名字,返回类型,参数个数及类型必须与接口中的完全一致。 2)接口中的方法被默认是public ,所以类在实现接口方法时,一定要用public 来修饰。 3)另外,如果接口的方法的返回类型如果不是void 的,那么在类中实现该接口方法时,方法体至少要有一个return 语句。如果是void 型,类体除了两个大括号外,也可以没有任何语句.
5.4.2使用接口的优点 从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。通过接口使得处于不同层次,甚至互不相关的类可以具有相同的行为。接口其实就是方法定义和常量值的集合。 它的优点主要体现在下面几个方面: (1)通过接口可以实现不相关类的相同行为,而不需要考虑这些类之间的层次关系。 (2)通过接口可以指明多个类需要实现的方法。 (3)通过接口可以了解对象的交互界面,而不需了解对象所对应的类。 接口把方法的定义和类的层次区分开来,通过它可以在运行时动态地定位所调用的方法。同时接口中可以实现“多重继承”,且一个类可以实现多个接口。正是这些机制使得接口提供了比多重继承(如C++等语言)更简单、更灵活、而且更强劲的功能。
5.4.2使用接口的优点 // MultInterfaces.java 【例5-9】 使用多重接口的例子 // MultInterfaces.java interface I1 { abstract void test(int i); } interface I2 { abstract void test(String s); } public class MultInterfaces implements I1, I2 { public void test(int i) { System.out.println("In MultInterfaces.I1.test"); } public void test(String s) { System.out.println("In MultInterfaces.I2.test"); public static void main(String[] a) { MultInterfaces t = new MultInterfaces(); t.test(42); t.test("Hello");
第5章 面向对象高级程序设计 5.1 继承 5.2多态性 5.3 抽象类和抽象方法 5.4接口 5.5内部类和匿名类
5.5内部类和匿名类 5.5.1 内部类的定义 5.5.2 内部类特性 5.5.3 匿名类
5.5.1 内部类的定义 简单地说,一个类被嵌套定义于另一个类中,称为嵌套类。在大多数情况下,嵌套类( 静态的嵌套类除外)就是内部类(inner class)。包含内部类的类称为外部类。与一般的类相同,内部类具有自己的成员变量和成员方法。通过建立内部类的对象,可以存取其成员变量和调用其成员方法。 例如下面的例子: pubic class GroupOne{ int count; //外部类的成员变量 public class Student{ //声明内部类 String name; //内部类的成员变量 public void output(){ //内部类的成员方法 System.out.println(this.name+" "); }
5.5.1 内部类的定义 实际上,Java语言规范对于内部类有如下的规定: 在另一个类或者一个接口中声明一个类。 在另一个接口或者一个类中声明一个接口。 在一个方法中声明一个类。 类和接口声明可嵌套任意深度。 从上面的规定中我们可以看出,内部类的定义是非常灵活的。
5.5.2内部类特性 内部类有如下特性: 一般用在定义它的类或语句块之内,在外部引用它时必须给 出完整的名称。名称不能与包含它的类名相同。 可以使用包含它的外部类的静态成员变量和实例成员变量,也可以使用它所在方法的局部变量。 可以定义为abstract。 可以声明为private或protected。 若被声明为static,就变成了顶层类,不能再使用局部变量。 若想在内部类中声明任何static成员,则该内部类必须声明为static。 Java将内部类作为外部类的一个成员,就如同成员变量和成员方法一样。因此外部类与内部类的访问原则是:在外部类中,通过一个内部类的对象引用内部类中的成员;反之,在内部类中可以直接引用它的外部类的成员,包括静态成员、实例成员及私有成员。
5.5.2内部类特性 【例5-10】 内部类和外部类之间的访问 【例5-10】 内部类和外部类之间的访问 本例的类GroupTwo中声明了成员变量count、内部类Student、实例方法output和main方法,在内部类Student中声明了构造方法和output方法,构造方法存取了外部类GroupTwo的成员变量count。 程序运行结果: Johnson count=1 本例演示嵌套的两个类之间的访问规则,即在外部类GroupTwo中,通过一个内部类Student的对象s1可以引用内部类中的成员;反之,在内部类Student中可以直接引用它的外部类的成员,如count。 本例的外部类GroupTwo中有实例方法output(),内部类Student中也有实例方法output(),两者虽然同名,却表达不同含义。使用时,外部类GroupTwo的对象调用GroupTwo的output,如g2.output(),内部类Student的对象调用Student的output,如s1.output()。
5.5.2内部类特性 public class GroupTwo{ //例5-10 private int count; //外部类的私有成员变量 public class Student { //声明内部类 String name; public Student(String n1) { name=n1; count++; //存取其外部类的成员变量 } public void output(){ System.out.println(this.name); } } public void output(){ //外部类的实例成员方法 Student s1=new Student("Johnson"); //建立内部类对象" s1.output(); //通过s1调用内部类的成员方法 System.out.println("count="+this.count); public static void main(String args[]){ GroupTwo g2=new GroupTwo(); g2.output(); }}
5.5.2内部类特性 【例5-11】 内部类访问外部静态变量 【例5-12】 静态公用内部类 【例5-13】 抽象内部类 【例5-13】 抽象内部类 【例5-14】内部接口 【例5-15】局部内部类
5.5.3匿名类 要采用另一种形式的new语句,如下所示: new <类或接口> <类的主体> 从技术上说,匿名类可被视为非静态的内部类,所以它们具有和方法内部声明的非静态内部类一样的权限和限制。 有关匿名类的使用详见本书第12章本分内容。 内部和匿名类是Java为我们提供的两个出色的工具。它们提供了更好的封装,结果就是使代码更容易理解和维护,使相关的类都能存在于同一个源代码文件中(这要归功于内部类),并能避免一个程序产生大量非常小的类(这要归功于匿名类)。