第 5章 继承与多态
5.1 继承简介
5.2 继承与子类
5.3 关键字 super
5.4 方法覆盖
5.5 关键字 final
5.6 对象类型转换第 5章 继承与多态(续)
5.7 多态与动态绑定
5.8 抽象类和抽象方法
5.9 接口
5.10 内部类
5.11 习题
5.1 继承简介民用飞机飞 机军用飞机歼击机 轰炸机,...,.,
5.1 继承简介(续)
继承是从一个类派生出一个新类的过程。
通过继承,可以让一个类拥有另一个类的全部属性,也即让一个类继承另一个类的全部属性。
派生出来的新类称为子类(也称派生类或次类);而被继承的类称作父类(也称基类或超类)。
5.1 继承简介(续)
单重继承是指从一个父类派生出一个子类的过程,而多重继承是指从一个以上的父类派生出一个子类的过程。
Java语言仅支持单重继承,其多重继承只能通过接口等间接实现。
硬盘类计算机类台式机类输出设备类 输入设备类便携机类
5.2 继承与子类
5.2.1 继承的语法
5.2.2 关键字 protected
5.2.3 子类的构造方法
5.2.1 继承的语法
class 子类名 extends 父类名
{
定义子类新成员
}
例 5-1 继承源代码 编译运行
5.2.2 关键字 protected
具有 protected访问权限的成员能被其子类和同一包中的任何类访问 。
例 5-2 修饰符 protected
源代码
5.2.3 子类的构造方法
子类不能继承父类的构造方法 。 因此,创建子类对象时,为了初始化从父类中继承来的数据成员,系统需要调用其父类构造方法 。
例 5-3 默认构造方法例 5-4 调用父类的构造方法源代码 编译运行源代码 编译运行
5.3 关键字 super
关键字 super连接的是当前子类对象中的父类子对象 。
– 关键字 super可用来调用父类构造方法 。
– 关键字 super可用来在子类中访问从父类继承来的数据成员和方法 。
例 5-5 关键字 super
源代码 编译运行
5.4 方法覆盖
方法覆盖时,必须注意子类中的方法应该与父类中被覆盖的方法有相同的方法名,返回类型和参数列表 。
私有方法不能覆盖。
方法覆盖时,不能降低其访问权限。
例 5-6 方法覆盖源代码 编译运行
5.5 关键字 final
5.5.1 final变量
5.5.2 final方法
5.5.3 final类
5.5.1 final变量
使用关键字 final修饰一个基本数据类型变量,并在声明的同时给定其值,
那么该变量就成为一个常量。
例:
final double PI=3.1415;
PI=3; //错误
5.5.1 final变量(续)
使用关键字 final修饰的变量并不一定需要在声明的同时给定其值。
例:
public void g(double r)
{
final double PI;
PI=3.1415;
double area=PI*r*r;
System.out.println(area);
}
注意,任何一个变量被关键字 final 修饰后,一旦初始化,其值便不能改变。
5.5.1 final变量(续)
关键字 final修饰的变量用作类的数据成员时,系统并不会为它提供默认值。
必须在声明的同时指定其值(即定义常量)或利用构造方法初始化。
例 5-7 关键字 final
源代码 运 行
5.5.1 final变量(续)
当某个形参被 final修饰时,可以在方法中读取它的值,但不能改变。
例:
class A
{
public void f(final int i,int j)
{
i++; //错误
j++; //正确
}
public int g(final int j)
{
return j*2; //正确
}
}
5.5.1 final变量(续)
用关键字 final修饰一个对象变量时,实际修饰的是对象的引用,该引用一旦被初始化连接到某个对象后,便不能连接到另一对象。但对象自身成员的值可以改变。
例:
class A
{
int i=10;
}
class B
{
final A a=new A();
public static void main(String[] args)
{
B b=new B();
b.a.i++; //正确
b.a=new A(); //错误
}
}
5.5.2 final方法
使用关键字 final修饰的方法不能在继承过程中被子类覆盖。
例:
class A
{
final void f()//将方法声明为 final,它不能被覆盖
{
System.out.println("f() in A");
}
}
class B extends A
{
void f() //错误,覆盖了父类中的 final方法
{
System.out.println("f() in B");
}
void f(int i) //正确,final方法可以重载
{
System.out.println("f(int i) in B");
}
}
5.5.2 final方法(续)
类的私有方法不能被覆盖,因此,它们都是 final的。如果尝试在子类中覆盖它,实际上定义的是一个新的方法,
与原来的私有方法没有任何关系。
可以使用关键字 final明确修饰某个私有方法,不过,这并没有什么实际意义。
5.5.3 final类
使用关键字 final修饰的类不能被其他类继承。
例:
final class A{}
class B extends A{} //错误,final类不能被继承
由于 final类没有子类,其中的方法不可能被覆盖,因此,final类中的所有方法都是 final的。
5.6 对象类型转换
一个类的对象在一定条件下可以转换成继承链中的另一个类的对象:
– 一个子类对象的类型可以向上转换成它的父类类型,这种转换是安全的,Java编译器能自动进行。
– 一个父类对象的类型一般不能向下转换成它的子类类型。但有一种情况除外,如果一个父类对象引用连接的实际上是一个子类对象,就可以使用强制类型转换将这个父类对象转换成子类类型。
5.6 对象类型转换(续)
将一个父类对象的类型转换成它的子类类型时,必须确保其引用连接的实际上是该子类的一个对象,这可以利用运算符
instanceof来完成。运算符 instanceof的作用是检查某个对象的类型是否是某个指定类或其子类,如果是,且该对象不为 null,则返回 true,否则返回 false。
例 5-8 对象类型转换源代码 编译运行
5.7 多态与动态绑定
通过方法覆盖可以在具有继承关系的多个类中定义名称相同但操作不同的多个方法,多态指的正是程序运行时判断执行其中哪个方法代码的能力。
Java语言的多态性是通过动态绑定实现的。所谓绑定是指建立方法调用语句和方法之间的关系,而动态绑定是指在程序运行时,根据对象的实际类型调用相应的方法 。
5.7 多态与动态绑定(续)
应用多态,可使程序具有良好的可扩充性。
例 5-9 多态源代码 编译运行
5.8 抽象类和抽象方法
抽象方法只有方法头,没有具体的方法体。
例:
abstract void f();
含有抽象方法的类是抽象类,抽象类必须使用关键字 abstract修饰。不过,一个抽象类并不一定拥有抽象方法。
例:
abstract class A
{
public void f(){}
}
注意,抽象类只能用作其他类的父类,不能创建其对象。
5.8 抽象类和抽象方法(续)
一般来说,继承于抽象类的类应该实现父类中的所有抽象方法。如果没有,那么该子类也就成为抽象类。
例:
abstract class A
{
public abstract void f();
}
class B extends A //错误
{
public void g(){}
}
class C extends A
{
public void f(){} //实现类 A中的抽象方法
}
5.8 抽象类和抽象方法(续)
静态方法不能声明为抽象的。
私有方法不能声明为抽象的。
例 5-10 抽象类和抽象方法源代码 编译运行
5.9 接口
5.9.1 定义接口
5.9.2 实现接口
5.9.3 接口继承
5.9.1 定义接口
interface A
{
int I=1;
void f();
}
注意,接口中的数据成员都是公用静态常量,不需要明确声明;接口中的方法都是公用抽象方法,同样不需要明确声明。当然,也可以明确声明,不过,
这并没有什么实际意义。但是,不可以将它们声明为其他,否则,编译器会报告错误。
5.9.1 定义接口(续)
接口可以使用修饰符 public修饰或保持默认。
与类相同,每个接口经编译后都会生成一个独立的字节码文件。
可以将接口想象成一个纯粹的抽象类,不过,
抽象类中还可以包含变量和具体方法等 。 在使用上,接口与抽象类有许多相似之处,比如,
不能创建接口的对象;可以用作变量的数据类型;可以用作对象转换的类型;等等 。
5.9.2 实现接口
实现接口中的方法时,必须用修饰符 public修饰。
如果一个类没有实现接口中的全部方法,该类就成为抽象类。
例:
interface A
{
void f();
void g();
}
abstract class B implements A
{
public void f(){}
}
例 5-11 实现接口源代码 编译运行
5.9.2 实现接口(续)
Java语言中,为了得到多重继承的效果,允许在一个类中实现多个接口,
甚至,还允许同时继承一个类 。
例 5-12 间接多重继承源代码 编译运行
5.9.2 实现接口(续)
interface A
{
int I=10;
void f();
}
interface B
{
int I=2;
int f(int i);
}
class C
{
public void f(){}
}
class D extends C implements A,B
{
int i=A,I; //访问重名常量必须以接口名为前缀
public int f(int i)
{
return i;
}
}
5.9.2 实现接口(续)
interface A
{
void f();
}
interface B
{
int f();
}
无法定义一个类同时实现上述接口 A和 B。
5.9.3 接口继承
一个接口可以同时继承多个接口 。
例 5-13 接口继承源代码 编译运行
5.10 内部类
在其他类中定义的类叫做内部类。
例 5-14 内部类例 5-15 匿名内部类源代码 编译运行源代码 编译运行
5.10 内部类(续)
return new I()
{
private int j=1;
public int f()
{
return j;
}
};
可展开为:
class MyI implements I
{
private int j=1;
public int f()
{
return j;
}
}
return new MyI();
5.10 内部类(续)
每个类经编译后都会生成独立的字节码文件,
内部类也不例外 。 不过,内部类生成的字节码文件并不是直接取与内部类相同的名称,
而是以外部类名作前缀,形如:,外部类名
$ 内部类名,。 如果内部类没有名称 ( 匿名内部类 ),编译器就会自动产生数字,作为内部类的标识符 。
5.11 习 题