抽象方法是没有方法体的方法,java提供的一种机制,仅有声明而没有方法体。语法:abstract void f();
包含抽象方法的类叫做抽象类。如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的。
抽象类无法创建对象,创建抽象类的对象是不安全的。
从抽象类继承产生一个新类,必须为基类中所有抽象方法提供方法定义。如果不这样做,那么意味着新类中还有至少一个抽象方法,根据抽象类的定义,这个新类也是抽象类,该类也必须用abstract声明为抽象类。
练习(1):修改第八章练习9中的Rodent,使其成为一个抽象类。只要有可能,就将Rodent的方法声明为抽象方法。
abstract class Rodent{
public Rodent(){
shanghai = 100;
System.out.println("Rodent");
}
private int shanghai;
public abstract void bite();
@Override
public abstract String toString();
}
class Mouse extends Rodent{
private int sh;
public Mouse(){
sh = 1000;
System.out.println("Mouse");
}
@Override
public void bite() {
System.out.println("造成伤害" +sh + "点" );
}
@Override
public String toString() {
return "Mouse";
}
}
练习(2):创建一个不包含任何抽象方法的抽象类,并验证我们不能为该类创建任何实例。
public class Test1 {
public static void main(String[] args) {
absTest abs = new absTest();//编译器报错:xxx is abstract ,cannot be instantiated;
}
}
abstract class absTest{}
练习(3):创建一个基类,让它包含抽象方法print(),并在导出类中覆盖该方法。覆盖后的方法版本可以打印出导出类中定义的某个整形变量的值。在定义该变量之处,赋予它非零值。在基类的构造器中调用这个方法。现在,在main()方法中,创建一个导出类对象,然后调用它的print()方法。请解释发生的情形。
public class Test3 {
public static void main(String[] args){
new PrinDrive().print();
}
}
abstract class PrinTest{
public PrinTest(){
print();
}
abstract void print();
}
class PrinDrive extends PrinTest{
private int id = 10;
@Override
void print() {
System.out.println("PrintDrive : " + id);
}
}
输出:
PrintDrive : 0
PrintDrive : 10
前面第8章已经说过初始化。
在类的初始化时,jvm会先给对象分配一块空间,所有的成员都被暂时赋值为0,对象引用赋值为null,这时int值还是0,然后从基类开始调用构造方法,在基类的构造方法中,调用了print()方法,注意此时调用的print()是导出类的方法,看内存示例图。
看似是调用了基类的print()的方法,其实是调用了自身的方法,调用某类的方法是需要该类的实例,但导出类中根本没有基类的实例成员。
在完成基类构造方法的调用之前,导出类的其他成员都不会初始化,此时int值是0,所以最终先输出:PrintDrive : 0
完成基类构造方法调用后,然后进行自身的成员变量的初始化,这时int值被赋为10。然后调用自身的构造方法,在构造方法里调用了重写的抽象方法print(),所以此时再输出:PrintDrive : 10
练习(4):创建一个不包含任何方法的抽象类,从它那儿导出一个类,并添加一个方法。创建一个静态方法,它可以接受指向基类的引用,将其向下转型到导出类,然后再调用该静态方法。在main()中,展示它的运行情况。然后,为基类中的方法加上abstract声明,这样就不再需要向下转型。
public class Test4 {
public static void main(String[] args){
Test4Abs t = new Child();
Child.g(t);//6,然后调用该静态方法
//7,运行后输出child created
}
}
abstract class Test4Abs{//1,创建一个不包含任何方法的抽象类
abstract void f();//8,为基类方法添加abstract
}
class Child extends Test4Abs{//2,从它那儿到处一个类
void f(){};//3,添加一个方法
public Child(){
System.out.println("child created");
}
public static void g(Test4Abs t4){//4,再添加一个静态方法,接受指向基类的引用
t4 = new Child();//5,向下转型到导出类
}
}
笔者实在不知道这个练习在将声明,想让读者理解什么,如果你能读懂题目的目的的话,请指导笔者。
abstract的类是至少包含一个抽象方法,而interface是完全的抽象类,它里面的所有方法必须是抽象的,不提供具体实现的。(可以规定参数列表返回值类型等,但不能有方法体)接口提供了形式,没有任何具体实现。
接口也可以包含域,但是这些域隐式的是static和final。
练习(5):在某个包内创建一个接口,内含桑方法,然后在另一个包中实现此接口。
创建接口同创建类相似,点new然后出来这个对话框,选interface即可
public interface Excample {
void fi();//方法是默认public的,如果是包访问权限,那么接口就有很大限制,失去了意义。
void g(int i);
boolean h(float f);
}
在另一个包内
public class InterfaceLearn implements Excample {
@Override
public void fi() {
}
@Override
public void g(int i) {
}
@Override
public boolean h(float f) {
return false;
}
}
练习(6):证明接口内所有的方法都是自动public的。
前面曾说到重写(覆盖)的几个规则,重写就是对父类方法的重新编写,接口的规则其实符合重写,与类的继承的重写相比,只不过接口是必须重写所有方法。
在重写的几个规则里,有一个规定:派生类重写的方法的权限不能低于基类方法的权限。我们可以运用这一规则完成题目。
public class InterfaceLearn implements Excample {
@Override
void fi() {//此时该方法的权限为包权限
}
如代码,当我们把重写自接口的方法fi()的权限改成包权限时,此时,编译器报错:
这是重写权限小于父类方法的错误,在最后,明确的指出了,原方法的权限:was “public”.
可见,接口的方法确实是默认public的。
练习(7):修改第八章中的练习9,使Rodent成为一个接口。
interface Rodent{
//没有构造方法
int shanghai = 10;//域必须是初始化有值的
public abstract void bite();//所有方法都是没有方法体的 public abstract 编译器是暗色显示,因为没有写的必要,是默认的
@Override
public abstract String toString();
}
练习(8):在polymorphism.Sandwich.java中,创建接口FastFood并添加合适的方法,然后修改Sandwich以实现接口。
public class Sandwich extends PortableLunch implements FastFood{
public Sandwich(){
System.out.println("Sandwich");
}
public static void main(String[] args){
new Sandwich().cook();
}
@Override
public void cook() {
Bread b = new Bread();
Cheese c = new Cheese();
Lettuce l = new Lettuce();
}
}
interface FastFood{
void cook();
}
其他简单代码略,请见书本P158
练习(9):重构Music5.java,将在Wind,Percussion和Stringed中的公共方法移入一抽象类中。
其实就是在接口中添加toString()方法。
interface Instrument{
int VALUE = 5;//static &final
void play(Note n);
void adjust();
String toString();
}
其它代码见书本P173
练习(10):修改Music5.java,添加Playable接口。将play()的声明从Instrument中移到Playable中。通过将Playable包括在Implements列表中,把Playable添加到导出类中。修改tune(),使它接受Playable而不是Instrument作为参数。
public class Music5 {
static void tune(Playable p){
p.play(Note.FIRST);
}
static void tuneAll(Playable[] e){
for (Playable p :
e) {
tune(p);
}
}
public static void main(String[] args){
Playable[] orchestra = {new Wind(),
new Percussion(),new Stringed(),
new Brass(),new Woodwing()};
tuneAll(orchestra);
}
}
enum Note{
FIRST,SECCOND,THIRD;
}
interface Instrument{
int VALUE = 5;//static &final
void adjust();
String toString();
}
interface Playable{
void play(Note n);
}
class Wind implements Instrument,Playable{
@Override
public void play(Note n) {
System.out.println(this + "play " + n);
}
@Override
public void adjust() {
System.out.println(this + "adjust");
}
@Override
public String toString() {
return "Wind";
}
}
可以看到,这个练习也许涉及到了设计模式的知识,暂时无法理解,记住这种做法。