五、abstract 类和 abstract 方法
用关键字 abstract 修饰的类称为 abstract 类(抽象类)。如:
abstract class A
{ ...
}
abstract 类有以下两个特点:
1.abstract 类中可以有 abstract 方法。对于 abstract 方法只允许声明不允许实现。而且不允许使用 final 和 abstract 同时修饰一个方法。
2.abstract 类不能用 new 运算符创建对象。
如果一个非抽象类是某个抽象类的子类,那么它必须重写父类的抽象方法,给出方法体,即在子类中将 abstract 方法去掉 abstract 修饰符重新声明,同时要保证声明的方法名、返回类型、参数个数和类型与父类的抽象方法完全相同。
注意:abstract 类可以没有 abstract 方法;如果一个 abstract 类是 abstract 类的子类,那么它可以重写父类的 abstract 方法,也可以继承这个 abstract 方法。
抽象类只关心功能,但不关心这些功能的具体实现的细节。如下例:
abstract class 机动车
{ abstract void 启动();
abstract void 加速();
abstract void 刹车();
}
class 手动挡轿车 extends 机动车
{ void 启动() //去掉abstract修饰符对方法重新声明
{ System.out.println("踏下离合器,换到一档");
System.out.println("然后慢慢抬起离合器");
}
void 加速()
{ System.out.println("踩油门");
}
void 刹车()
{ System.out.println("踏下离合器,踏下刹车板");
System.out.println("然后将档位换到一档");
}
}
class 自动挡轿车 extends 机动车
{ void 启动()
{ System.out.println("使用前进挡");
System.out.println("然后轻踩油门");
}
void 加速()
{ System.out.println("踩油门");
}
void 刹车()
{ System.out.println("踏下刹车板");
}
}
public class E
{ public static void main(String[] args)
{ 手动挡轿车 car1=new 手动挡轿车();
自动挡轿车 car2=new 自动挡轿车();
car1.启动();
car1.加速();
car1.刹车();
car2.启动();
car2.加速();
car2.刹车();
}
}
六、final 关键字
final 关键字可以修饰类、成员变量和方法中的局部变量。
1.final 类
可以使用 final 将类声明为 final 类。final 类不能被继承,即不能有子类。例如 Java 提供的 String 类对编译器和解释器的正常运行有很重要的作用,不能被轻易改变。因此它被修饰为 final 类。
2.final 方法
如果用 final 修饰父类中的一个方法,那么这个方法不允许子类重写,即不允许子类隐藏可以继承的 final 方法。
3.常量
如果成员变量或局部变量被修饰为 final,则该成员变量或局部变量就是常量。
使用了 final 关键字的例子:
class A
{ //final double PI; 非法,因为没有给常量指定值
final double PI=3,1415926; // PI是常量
public double getArea(final double r)
{ // r=89; 非法,因为不允许再改变r的值
return PI*r*r;
}
public final void speak()
{ System.out.println("您好");
}
}
class B extends A
{ /*非法,不能重写speak方法
public void speak()
{ System.out.println("你好");
}
*/
}
public class E
{ public static void main(String[] args)
{ B b=new B();
System.out.println("面积:"+b.getArea(100));
b.speak(); //调用继承的方法
}
}
七、对象的上传型对象
假设 A 类是 B 类的父类,当用子类创建一个对象,并把这个对象的引用放到父类的对象中时,如:
A a;
a=new B();
或
A a;
B b=new B();
a=b;
称这个父类对象 a 是子类对象 b 的上传型对象(父类是上传型)。
对象的上传型对象的实体由子类负责创建,但上传型对象会失去原对象的一些属性和功能。
1.上传型对象不能操作子类新增的成员变量(失掉了这部分的属性),不能使用子类新增的方法(失掉了一些功能)。
2.上传型对象可以操作子类继承或隐藏的成员变量,也可以使用子类继承的或重写的方法。如果子类重写了父类的某个方法,则当对象的上传型对象调用这个方法时一定是调用了这个重写的方法。
注意:可以将对象的上传型对象再强制转换成一个子类对象,此时该子类对象又具备了子类所有的属性和功能;不可以将父类创建的对象引用赋值给子类声明的对象(如不能说“哺乳动物是老虎”)。
下面的例子中,对象 monkey 是 People 类型的上转型对象。
class 类人猿
{ double m=12.58;
void crySpeak(String s)
{ System.out.println(s);
}
}
class People extends 类人猿
{ char m='A';
int n=60;
void computer(int a,int b)
{ int c=a+b;
System.out.println(a+"加"+b+"等于"+c);
}
void crySpeak(String s)
{ System.out.println(m+"**"+s+"**"+m);
}
}
class E
{ public void main(String[] args)
{ People wang=new People();
类人猿 monkey=wang; //monkey是wang对象的上转型对象
monkey.crySpeak("I love this game"); //等同于wang调用crySpeak方法
//monkey.n=100; //非法,n是子类新增的成员变量
//monkey.computer(12,19); //非法,computer是子类新增的功能
System.out.println(monkey.m); //操作隐藏的m,不等同于wang.m
People zhang=(People)monkey; //把上转型对象强制转化为子类的对象
zhang.computer(55,33); //zhang是子类的对象
zhang.m="T"; //操作子类声明的成员变量m
System.out.println(zhang.m);
}
}
运行结果:
D:\3000>java E
A**I love this game**A
12.58
55加33等于88
T
八、继承与多态
1.多态的体现
当上转型对象调用子类重写的某个方法时,可能具有多种形态。因为不同的子类在重写父类的方法时可能产生不同的行为。
多态性是指父类的某个方法被其子类重写时,可以各自产生自己的行为功能。例:
class 动物
{ void cry()
{
}
}
class 狗 extends 动物
{ void cry()
{ System.out.println("这是狗的叫声:汪汪");
}
}
class 猫 extends 动物
{ void cry()
{ System.out.println("这是猫的叫声:喵喵");
}
}
class E
{ public static void main(String[] args)
{ 动物 dongwu=new 狗(); //动物是狗的上转型对象
dongwu.cry();
dongwu=new 猫(); //动物是猫的上转型对象
dongwu.cry();
}
}
D:\3000>java E
这是狗的叫声:汪汪
这是猫的叫声:喵喵
2.多态的使用
使用多态进行程序设计的核心技术之一是使用上转型对象。将 abstract 类生命对象作为其子类的上转型对象,那么这个上转型对象就可以调用子类重写的方法。
(1)利用多态设计程序的好处是可以体现 “开-闭” 原则,即一个程序应当对扩展开放,对修改关闭。即当程序再增加一个子类时,上转型对象所在的类不需要做任何修改就可以调用该子类重写的方法。
(2)在程序设计好后,首先应当对 abstract 类的修改 “关闭”,一旦修改 abstract 类例如再为它增加一个 abstract 方法,那么该 abstract 类的所有子类都要作出修改。
(3)程序设计好后也应当对增加 abstract 的子类 “开放”,即在程序中再增加 abstract 的子类时,不需要修改其他重要的类。
例:
Geometry.java
public abstract class Geometry
{ public abstract double getArea();
}
Lader.java
public class Lader extends Geometry
{ double a,b,h;
Lader(double a,double b,double h)
{ this.a=a;this.b=b;this.h=h;
}
public double getArea()
{ return((1/2.0)*(a+b)*h);
}
}
Circle.java
public class Circle extends Geometry
{ double r;
Circle(double r)
{ this.r=r;
}
public double getArea()
{ return(3.14*r*r);
}
}
Cone.java
public class Cone
{ Geometry bottom;
double height;
Cone(Geometry bottom,double height)
{ this.bottom=bottom;
this.height=height;
}
void changeBottom(Geometry bottom)
{ this.bottom=bottom;
}
public double getVolume()
{ return (bottom.getArea()*height)/3.0;
}
}
Example5_9.java
public class Example5_9
{ public static void main(String[] args)
{ Cone zui;
Geometry tuxing;
tuxing=new Lader(2.0,7.0,10.7);
System.out.println("梯形的面积"+tuxing.getArea());
zui=new Cone(tuxing,30);
System.out.println("梯形底的锥的体积"+zui.getVolume());
tuxing=new Circle(10);
System.out.println("半径是10的圆的面积"+tuxing.getArea());
zui.changeBottom(tuxing);
System.out.println("圆形底的锥的体积"+zui.getVolume());
}
}
本例中如果再增加一个 Java 源文件(对扩展开放),该源文件有一个 Geometry 的子类负责计算三角形面积,那么 Cone 类不需要做任何修改(对 Cone 类的修改关闭)。