继承与多态
类的继承
一、继承
- 类之间的关系
- HAS-A关系(子对象或对象成员):
- 类之间的包含关系、聚合和分解关系,即类A的对象作为类B的成员变量
- IS-A关系(继承或派生):
- 继承关系,概括和特化的关系,即类B继承类A的属性和行为
- HAS-A关系(子对象或对象成员):
- 继承的含义
- 继承机制提供了一种充分利用现有资源的方法
- 它既体现了经济思想,又体现了谦虚精神
- 派生类是父类的特殊化,是对父类在功能、内涵方面的扩展和延伸;在多继承时,派生类是父类的组合
- 继承的优点
- 继承使得在创建新类时,只需说明新类与父类的区别, 从而大量原有代码都可复用
- 提高了程序代码的重用性和程序的可扩充性
- 缩短了软件开发的周期
- 使得程序结构清晰
- 降低了编码和维护的工作量,提高了程序开发效率
- 提高了程序的抽象程度
- 更接近人类的习惯思维方式
- 继承机制
- 继承:子类继承父类的状态和行为
- 覆盖隐藏改造修改:子类也可以修改父类的状态或重载父类的行为
- 扩充:子类可以添加新的状态和行为
继承的概念和特点
Java中,所有的类都是通过直接或间接地继承java.lang.Object得到的
- 类继承并不改变成员的访问权限,父类中的公有和保护成员被继承到子类后,在子类中仍然作公有和保护成员。
- 父类中的私有成员在子类中不可见(不可使用)
- 子类将继承其直接父类中的所有非私有成员变量和成员方法
- 派生类有两种成员:
- 从父类继承来的成员(父类子对象),须由父类构造方法初始化
- 派生类自定义成员,由派生类自己的构造方法初始化。
创建子类对象时,系统先调用父类构造方法将从父类继承来的成员初始化,再调用子类构造方法初始化子类自己新定义的成员
三、类继承的实现
格式: [修饰符] class 类名 extends 父类名 {类体;}
- 说明:
- 类体:即子类自定义新成员
- 若一个类没有显式继承其他类,则默认继承Object类
- 须先定义父类,再定义子类
- 子类对象和成员方法不能直接访问父类的私有成员
```java 例://D.java class A{ int x=2; void f(){System.out.println(x);} } class B extends A { int y=3; void g(){System.out.println(“x=”+x+”y=”+y);} } public class D { public static void main(String []args) { B b=new B();//b.x=2 b.y=3 b.f();//2 b.g();//x=2y=3 A a=new A();//a.x=2 a.f();//2 //a.g();非法 } }例://D.javaclass A {int x=2;void f(){System.out.println(x);}}class B extends A{}public class D {public static void main(String []args) {A a=new A();//a.x=2a.f();//2B b=new B();//b.x=2b.x=3;b.f();//3}}
例://D.java class A { int x=2; A(){System.out.println(“x=”+x);} } class B extends A { int y=3; B(){System.out.println(“y=”+y);} } class D { public static void main(String []args) { A a=new A();//x=2 B b=new B();//x=2y=3 B m=new B();//x=2y=3 } }
<a name="btGCR"></a>####<a name="Bh8Fw"></a>### this和super<a name="TQuJZ"></a>#### 一、改造父类成员1. 子类在继承父类成员的基础上,可以通过自定义新 成员来扩充功能2. 控制父类成员在子类中的访问权限1. 类继承并不改变成员的访问权限,所以调节父类中成员的访问权限,即可控制父类成员在子类中的访问 权限2. 若父类不允许子类访问它的某些成员,则须将其声明为private成员3. 对父类成员进行同名覆盖1. 父类成员不一定能完全适合子类的需要2. 继承有用成员的同时,子类也继承了它不需要的父类成员3. 当一个父类成员不适合该子类时,子类可以覆 盖和重新定义它4. 同名覆盖可扩充和改造父类成员4. 同名覆盖1. 即在派生类中声明一个与父类成员同名的新成员以屏 蔽父类同名成员2. 在子类中或通过子类对象只能访问到子类中新定义的 同名成员3. 同名覆盖隐藏成员变量时,要求父类和子类同名成员 变量的类型和名字相同4. 同名覆盖成员方法时,要求父类和子类同名成员方法 的方法头(函数原型)完全相同,否则为方法重载或 编译错误5. 同名覆盖是一种多态6. 同名覆盖后,父类同名成员还存在,只是看不见(被隐藏,被屏蔽)7. 在子类中通过super可改变被覆盖的父类同名成员的可见性,即在子类中通过super可显式调用父类的同名覆盖成员```java例://D.javaclass A {void f(){System.out.println("AA");}void g(){System.out.println("aa");}}class B extends A {void f(){System.out.println("BB");}void g(int x){System.out.println("bb"+x);}}class D {public static void main(String []args) {B b=new B();b.f();//BBb.g();//aab.g(2);//bb2}}例://D.javaclass A {int x=2;void f(){System.out.println(x);}}class B extends A {int x=3;void g(){System.out.println(x);}void h(){f();}}class D {public static void main(String []args) {B b=new B();b.f();//2b.g();//3b.h();//2 调用方法的时候,对子类不可见}}
二、this
- Java系统默认,每个类都具有null,this和super三个域, 在任何类中都可以不加说明而直接使用它们
- null:空引用 /this:代表当前对象的引用 /super:代表父类对象的引用
- this:
- 表示当前对象本身,代表了当前对象的一个引用(别 名)
- 一个对象可以有若干个引用,this就是其中之一
- 利用this可以访问当前对象的成员
- 常用来把当前对象的引用作为参数传递给其他的对象 或方法
- 由系统自动创建,自动使用,用户也可以显式使用
- 不能在程序中显式修改它或给它赋值
- 静态方法(即类方法)中不能使用this
- 它始终代表当前对象(正在被某个成员方法操 作的对象)
使用this来访问当前对象的成员
- 在所有非static方法中,都隐含了一个参数this
- 提高了程序的可读性
- this作这种用法时,通常可以省去this(系统默认为this)
- 格式:this·成员变量 /this·成员方法(参数值);
例://B.javaclass A {int x=2;A(int y){x=y;} //等价于this.x=y;void f(A c){System.out.println("this.x="+this.x+"c.x="+c.x);}}class B {public static void main(String []args) {A a=new A(3); //a.x=3A b=new A(4); //b.x=4a.f(b); //this.x=3 c.x=4}}
使用this解决局部变量与成员变量同名的问题
- 在Java的成员方法中,当局部变量与成员变量同名时,成员变量被隐藏(看不见)
- 通过this可以改变成员变量的可见性
- 在成员变量前面加上this,以此区别于局部变量
例://B.javaclass A {int x=2;void f(int x){this.x=x;}void g(int x){x=this.x;}void s(){System.out.println(x);}}class B {public static void main(String []args) {A a=new A();//a.x=2a.f(3);//a.x=3a.s();//3a.g(4);//a.x=3a.s();//3}}
构造方法中,用this调用本类另一个构造方法
- 常用于重载的构造方法之间的相互调用
- 可提高代码的重用率,提高程序的抽象封装程度,减 少程序维护的工作量
- 此时,this调用语句必须是构造方法中的第一个可执行语句
例://B.javaclass A {int x=2;A(int y){x=y;}A(){this(3);}A(int m,int n){this(m+n);}void f(){System.out.println(x);}}class B {public static void main(String []args) {A a=new A(); //a.x=3a.f();//3A b=new A(4,5); //b.x=9b.f(); //9}}
this的其他应用
- this作方法的返回值或方法的实在参数时,代表当前对象
例://A.javapublic class A {private int n=0;A f(){n++;return this;}void s(){System.out.println(n);}public static void main(String []args) {A a=new A(); //a.n=0a.f().f().f().s();//3}}
- this作方法的返回值或方法的实在参数时,代表当前对象
三、super
- super:
- 表示当前对象的直接父类对象,是当前对象的直接父 类对象的引用
- 由系统自动定义,可直接使用
- 静态方法(即类方法)中不能使用super
- 不能在程序中显式修改它或给它赋值
使用super访问子类对象中从父类 继承来的成员
- 使用super标记标明是当前对象访问它从父类中继承来 的成员
- 此时,通常super可以省略
- 它强调是当前对象(子类对象)的super
- 格式:super·父类成员变量名 /super·父类成员方法名(参数值)
例://D.javaclass A {int x=2;}class B extends A {void s(){x=3;this.x=4;super.x=5;}void f(){System.out.println(x+","+this.x+","+super.x);}}class D {public static void main(String []args) {B b=new B();//b.x=2b.f();//2,2,2b.s();//b.x=5b.f();//5,5,5}}
使用super访问被子类同名覆盖的 父类成员
使用super调用父类的构造方法
- 在子类的构造方法中,可以用super来调用父类的构造 方法
- 此时,super调用语句必须是子类的构造方法中的第一 条可执行语句,以保证“先兄长再自己”
- 即先调用执行父类构造方法初始化从父类继承来的成 员
- 再执行本类构造方法初始化自定义新成员
- 格式:super(参数值);
例://D.javaclass A{int x=0;A(int y){x=y;}}class B extends A {//B(){super();} 非法B(int m,int n){super(m+n);}B(){super(2);}void f(){System.out.println(x);}}class D {public static void main(String []args) {B b=new B(); //b.x=2b.f(); //2B c=new B(3,4); //b.x=7c.f(); //7}}
类的多态
一、方法重载和同名覆盖
同一个类的成员方法间重载:要求方法形式参数的个数、类型、顺序不完全相同
- 不同类之间的成员方法重载:系统根据调用类对象名进行区分
- 子类和父类的成员方法之间也可以重载
同名覆盖只发生在有继承关系的类之间
例://D.javaclass A {void f(){System.out.println("AA");}void f(int x){System.out.println("AA"+x);}}class B extends A{void f(){System.out.println("BB");}}class D {public static void main(String []args) {A a=new A();a.f(); //AAa.f(2);//AA2B b=new B();b.f(); //BBb.f(3); //AA3}}
二、赋值相容原理
子类对象可以赋值给父类对象引用,反之则不行
- 即派生类对象可自动隐式地转换为一个父类对象
- 在需要父类对象的任何地方,都可以使用其子类对象替代之
- 赋值相容性不可逆
- 是多态性的基础(运行时的多态),提高了编程效率
- 例:中国人是父类,成都人是其子类,则成都人必定是中国人,但反之中国人不一定是成都人
- 父类对象引用引用子类实例对象时,该引用只能访问与父类成员同名的成员
- 若成员变量被同名覆盖,访问的是从父类继承来得成员变量
- 若成员方法被同名覆盖,访问的是子类自定义的成员方法
- 当将子类对象赋值给父类对象引用时,该引用调用成员方法的规则是:
- 若子类同名覆盖了父类方法,则运行时系统就调用子类自定义的成员方法
- 若子类未同名覆盖父类方法,则运行时系统就调用从父类继承来的成员方法
- 当将子类对象赋值给父类对象引用时,该引用访问的成员变量,不论是否被同名覆盖,都是访问从父类继承来的成员变量
例://E.javaclass A{int x=2;void f(){System.out.println("AA");}}class B extends A {void g(){System.out.println("bb");}void f(){System.out.println("BB");}}class D extends A{int x=4;}class E {public static void main(String []args) {A a=new B();a.f(); //BB//a.g(); 非法a=new D();System.out.println(a.x); //2}}
final和abstract
一、final
- final修饰变量
- 该变量只能读,不能写,即为常量
- 只能赋一次值
- 通常用static与final一起使用来定义一个常量
final修饰对象
- 该对象引用将不能再指向其他对象,但它所指对象的成员仍然可以改变
例://B.javaclass A {int x=2;}class B {public static void main(String []args) {final A a=new A(); //a.x=2a.x=3;System.out.println(a.x); //3//a=new A(); 非法}}
- 该对象引用将不能再指向其他对象,但它所指对象的成员仍然可以改变
final修饰方法
- 该方法将不能被覆盖,即最终方法
- 但final方法可以被重载
例://D.javaclass A {final void f(){System.out.println("AA");}}class B extends A {//void f(){System.out.println("BB");} 非法void f(int x){System.out.println("BB"+x);}}class D {public static void main(String []args) {B b=new B();b.f();//AAb.f(2);//BB2}}
final修饰类
- abstract类必须被继承派生/abstract方法必须被覆盖(重写)
abstract方法
- 被abstract修饰的方法叫抽象方法
- 抽象方法的目的是为其所有子类定义一个统一的接口界面
- 抽象方法只需要声明,不需要实现,即用”;”表示方法体
- 抽象方法的实现由其派生类提供
- 抽象方法在子类中必须被实现,否则子类继承的仍为抽象方法
- 构造方法、静态方法(即类方法)和私有方法不能定义为抽象方法
- abstract不能与private、static、final或native并列修饰同一个方法
- 定义格式:abstract 类型符 抽象方法名(参数值表);
例://D.javaabstract class A {abstract void f();void g(){System.out.println("AA");}}class B extends A {void f(){System.out.println("BB");}}class D {public static void main(String []args) {A a=new B();a.f(); //BBa.g();//AA}}
abstract类
- 凡是被abstract修饰的类称为抽象类
- 它是没有具体对象的概念类(如:鸟类,它相对麻雀、鸽子、燕子等即为抽象类)
- 它是其所有子类的共性的集合
- 可提高程序开发和维护的效率
- 因为抽象类是不完整的类,故抽象类不能实例化对象
- 抽象类只能用作父类来派生子类
- 虽然抽象类不能实例化对象,但可以声明其对象引用
- 抽象类中可以包含抽象方法,也可以不包含abstract方法
- 包含抽象方法的类一定是抽象类,即abstract方法必须位于abstract类中
- 一旦某个类中包含了abstract方法,则该类必须声明为abstract类
- 子类必须实现父类中的全部抽象方法,否则子类仍然是抽象类,仍不能实例化对象
例://E.javaabstract class A{abstract void f();}abstract class B extends A {void f(int x){System.out.println("BB"+x);}}class D extends B {void f(){System.out.println("DD");}}class E {public static void main(String []args) {A a=new D();a.f();//DD//a.f(2); 非法B b=new D();b.f(2); //BB2}}
类定义的完整格式
[public][abstract∣final] class 类名 [extends 父类名][implements 接口名表] {[public∣protected∣private][static] [final][transient][volatile] 类型符 成员变量名;[public∣protected∣private][static] [final∣abstract][native][synchronized] 类型符 成员方法名([参数表])[throws 异常列表] {方法体;}}
包和接口
包
1. 名字空间
- 是能用名字区别不同事物的范围:包是一个类名空间,同一个包中的类(或接口)不能重名,不同包中的类可以重名
- java没有全局变量和全局方法
- 全限定名字:包名·类名·成员名
2. 包
- 是若干相关的类、接口、(子)包的集合(容器)
- 可以理解为文件夹,一个包对应一个文件夹
- 是一种封装机制
- 可以避免大量的类名冲突,扩大名字空间
- java类库一包的形式储存
- 同一个包中类名不能重复,不同包可以
- 包名有层次关系,各层之间以·分隔,左大右小
3. 包的创建
- package语句必须作为Java源文件的第一条语句
- 表示该源文件中定义的全部类都属于这个包
- 一个源文件中最多只能有一条package语句
- 若缺省该语句,系统会为每一个*.java源文件创建一个无名包
- 无名包中的类(因为没有包名)不能被其他包中的类所引用和复用,无名包也不能有子包
- 创建包就是在当前文件夹下创建一个子文件夹, 以便存放该包中包含的所有类的*.class文件
- Java要求包声明的层次和实际保存类的字节码文件的目录结构相对应
- package语句的作用域是包含该语句的整个源文件
- 格式:package 包名; 例:package a; 例:package java·awt;
4. 包的引用
- 若要使用包中的类或接口,则必须将该包引入 (装载)到本源文件中
- import语句的三种形式
- import 包名;
- 例:import java.lang;
- 说明:该语句后面的程序凡涉及该lang包中的类和方法都可用简略形式书写
- 例:java.lang.System类可略写为lang.System
- import 包名·类名;
- 例:import java·lang·String;
- 说明:该语句后面的程序中可直接使用该类名
- 例 : 可 直 接 使 用 类 名 String 代替 java·lang·String
- 该方式适用于在源文件中只使用另一个包中的某一个类
- import 包名·*;
- 例:import java·applet·*;
- 说明:“*”为通配符,表示该包中的所有类
- 其后的程序中可直接使用该包中的所有类名
- 该方式适用于在源文件中使用另一个包中的多个类
- 注意:“*”只能表示本层次包中的所有类,而不包括其子层次下的类
- 例:import java·awt·;import java·awt·event·;
- import 包名;
- 包的引用说明
- import语句的数量没有限制
- import语句在源文件中必须位于package语句之后,类定义之前
- 若在源文件中偶尔使用另一个包中的类,也可以不用import语句,而直接在要引入的类名前加包名
- 例:class A extends java·applet·Applet{…} 等价于 import java·applet·Applet;class A extends Applet{…}
- 用import引入包,这种方式适用于在源文件中多次使用另一个包中的类
- Java 系统为所有源程序文件自动引入包 java·lang
- 若引入的几个包中含有相同类名的类,则须在类名前加上包名前缀,以示区别
- 例:创建a·b·D类的对象 a·b·D d=new a·b·D();
- import语句的三种形式
5. JDK中主要的包
- 单继承和多继承
- 单继承
- 子类只能有一个直接父类,类层次为树状结构
- 类间关系简单,类间耦合度小
- 描述复杂问题的能力有限,当类层次较深时,间接父类多,子类成员数量庞大,难于控制和管理
- 多继承
- 子类可以有多个直接父类与人类的自然习惯思维一致,描述问题的能力强,但复杂
- 为了简化程序结构和类间的关系,Java不直接支持多继承
- Java通过接口来实现多继承
- 单继承
- 接口
- 是方法定义(没有实现)和常量的集合
- 它定义了若干个抽象方法和常量,是一种引用数据类型
- 是一种特殊的抽象类
- 提高了程序的抽象程度和简洁性
- 一个类可以同时实现多个接口
- 接口也具有继承性,且支持多继承
- 一个接口可以有多个直接父接口,它们之间用逗号分隔
- 子接口将继承所有父接口中的属性和方法
- 接口中的属性都是用final修饰的常量
- 接口中的方法都是用abstract修饰的抽象方法
- 接口中不能实现任何方法(注意区别:抽象类中可以有方法实现)
- 接口的作用
- 可以实现不相关类的相同行为,而不需考虑这些类之间的层次关系
- 可以指明多个类需要实现的方法
- 可以了解对象的交互界面,而不需了解对象所对应的类
- 可以实现多继承
二、接口的定义
格式:
[public] interface 接口名 [extends 父接口名列表] {//接口体
//常量域声明 [public][static][final] 类型符常量名=常量值;
//抽象方法声明 [public][abstract][native] 类型符 方法名(参数列表) [throws 异常列表];
}
说明:
源文件名保存为:接口名·java
系统默认接口中的属性为:public static final
系统默认接口中的方法为:public abstract
例:interface A {int m=0; void f(); }interface B {int n=1; void g(); }interface D extends A,B {int k; public void s(); }interface E extends A {}
三、接口的实现
- 所谓一个类实现一个接口,是指该类给出这个接口的所有行为的具体实现过程
- 一个类在实现某接口的抽象方法时,必须使用完全相同的方法头
- 接口中的抽象方法均默认为public,故在实现时必须显式地使用public修饰符
- 在实现接口时,系统默认的修饰符(如public)是不能省略的
- 一个类实现接口后,将继承接口中的所有静态常量,为该类所用
- 若一个类只实现了接口中部分方法,则该类只能声明为abstract类
- 仅当一个类实现了一个接口中的所有方法时,才能实例化
格式:[修饰符] class 类名 [extends 父类名] [implements 接口A,接口B,…] {
类自定义新成员;
实现接口A(为A中的所有方法编写方法体)
实现接口B(为B中的所有方法编写方法体)
…… }
例://E.javainterface A{void f();}interface B{void g();}abstract class M {abstract public void s();}class D extends M implements A,B {public void f(){System.out.println("AA");}public void g(){System.out.println("BB");}public void s(){System.out.println("MM");}}class E {public static void main(String []args) {D d=new D();d.f();//AAd.g();//BBd.s();//MM}}
java源程序文件
完整的Java源文件格式:
package packageName; //指定文件中类所属的包,0句或1句
import packageName·[className∣*]; //指定引入的类,0个或多个
public classDefinition //定义public类,0个或1个
interfaceDefinition and classDefinition //接口或类定义,0个或多个
