10.3 方法复写

上一节,我们有这样的例子
public class Rectangle{
    protected double length;
    protected double height;
    public double sqr(){
        return length*height;
    }

    public double cc(){
        return 2*(length+height);
    }
}
public class Square extends Rectangle{
    public Square(double length){
        this.height=length;
        this.length=length;
    }
}
public static void main(String[]args){
    Square s=new Square(4);
    System.out.println(s.sqr());
}
即使Square类并没有sqr方法,对象s也能调用,因为Rectangle有这个方法,而s实际上也是一个Rectangle实例,也就是说s既是一个Square对象也是一个Rectangle对象。 子类由于在逻辑上是父类的一种,所以它能直接调用父类的所有方法。假如子类的方法,和父类名字一样,参数一样,这样的话这个方法会覆盖掉父类。例如Square现在是这样的话:
public class Square extends Rectangle{
    public Square(double length){
        this.height=length;
        this.length=length;
    }
 
    @Override
    public double sqr(){
        System.out.println("调用Square的sqr方法");
        return length*length;
    }
    public static void main(String[]args){
        Square s=new Square(4);
        System.out.println(s.sqr());
    }
}
上面的15行代码main函数s.sqr调用的是父类Rectangle的sqr方法,还是子类Square的sqr方法呢?
答案是子类的sqr方法,如果父类和子类有同名同参数的方法,那么子类的对象使用的是子类的方法。这称为复写,可以理解成子类重新定义父类的某个方法。
我们观察到sqr方法的前面有这样的代码:@Override 。这种称为标签,用来描述方法的特征,在这里我们可以看做sqr是复写父类的。
这个Override标签不是必须的!就算不写只要条件达到,就是复写。 什么样的条件才能达到复写呢,或者说子类里什么方法前面加上@Override没有语法错误呢?子类的方法需要满足以下条件:
1.父类有同名方法。
2.该方法参数必须和父类方法的参数一致。
3.该方法的返回类型和父类方法一致。
4.子类方法不能降低父类方法的访问级别。这是指访问控制,父类方法如果是public,那么子类必须是public;父类方法如果是protected或者没有(没有意味着同一个包的类可见,不同包不可见),那么子类方法可以是protected或者public(private不能复写)。
5.静态方法不能被复写。 就算对象的声明部分是父类,仍然调用的是子类的方法。下面的例子s声明的类型是Rectangle,但是s.sqr()调用的是子类Square的sqr:
public static void main(String[]args){
    Rectangle s=new Square(4);
    System.out.println(s.sqr());
}
复写和重载有类似的,但实际上区别比较大:

重载
复写
应用场景
同一个类
子类和父类
区别
相同方法名,不同参数子类复写的方法必须和父类的方法同名、同参数、同返回类型、同访问控制级别
最后,就算发生了复写,子类还是可以显式调用父类的方法,子类可以在调用父类某个方法的基础上,再加上自己的代码。例如上面的矩形和正方形的面积计算sqr,实际上是相通的。我们可以这样写:
public class Square extends Rectangle{
    public Square(double length){
        this.height=length;
        this.length=length;
    }

    @Override
    public double sqr(){
        System.out.println("调用Square的sqr方法");
        return super.sqr();
    }
}
这里的super代表的就是父类。事实上,我们可以使用这个关键字,显式调用父类的所有方法

练习题

  • 客观题
    1.下面关于复写的说法,错误的是: (2分) [选择]
    A.
    B. 子类可以复写父类任何方法
    C. @Override标签不是必须的
    D. 如果方法参数不一样,那么并不是复写而是重载
    E. 父类方法如果是protected,那么子类方法复写可以用public
    2.子类可以使用super调用父类所有protected或者public的属性和方法 (2分) [判断]
    3.一个子类对象,同时也是父类对象 (2分) [判断]
    4.假如在Square里面有一个这样的函数,Square里面也有sqr函数 (2分) [判断]
    test方法调用的是父类的方法sqr
    (注意代码里面是两个类,必须用两个java文件保存)
    /** Rectangle  */public class Rectangle{    protected double length;    protected double height;    public double sqr(){        return length*height;    }    public double cc(){        return 2*(length+height);    }} /** Square.java */public class Square extends Rectangle{    public Square(double length){        this.height=length;        this.length=length;    }    public double sqr(){        return length*height;    }    public void test(){        double s=super.sqr();        System.out.print(s);    }}
    
    5.假如在Square里面有一个这样的函数,Square里面没有sqr函数 (2分) [判断]
    super.sqr()调用的是父类的方法sqr
    (注意代码里面是两个类,必须用两个java文件保存)
    /** Rectangle.java */public class Rectangle{    protected double length;    protected double height;    public double sqr(){        return length*height;    }    public double cc(){        return 2*(length+height);    }} /**Square.java */public class Square extends Rectangle{    public Square(double length){        this.height=length;        this.length=length;    }    public void test(){        double s=super.sqr();        System.out.print(s);    }}
    
    6.假如在Square里面有一个这样的函数test,Square里面也有sqr函数 (2分) [判断]
    test里面调用的sqr函数是父类的Rectangle的sqr
    (注意代码里面是两个类,必须用两个java文件保存)
    /**Rectangle.java */public class Rectangle{    protected double length;    protected double height;    public double sqr(){        return length*height;    }    public double cc(){        return 2*(length+height);    }} /**Square.java */public class Square extends Rectangle{    public Square(double length){        this.height=length;        this.length=length;    }    public double sqr(){        return length*height;    }    public void test(){        double s=sqr();        System.out.print(s);    }}
    
    7.关于下面的方法的调用,错误的是: (2分) [选择]
    A.
    B. 对象调用的方法,可能是来自父类,也可能是父类的父类
    C. 当调用一个方法的时候,如果类本身没有此方法,那么就往父类去寻找这个方法,如果在整个继承体系都没找到,那么会有语法错误
    D. 子类可以调用父类的方法,但这个方法必须是protected
    E. 子类可以复写父类的方法,所谓复写是指子类方法名字、参数和父类的方法一样
    8.完成下面的填空 (4分) [编程填空]
    已知两个类,代码如上所示。现在有下面的代码:(注意代码里面是两个类,必须用两个java文件保存)
    /* Rectangel.java  */public class Rectangle{    protected double length;    protected double height;    public double sqr(){        return length*height;    }    public double cc(){        return 2*(length+height);    }}/* Square.java  */public class Square extends Recangle{    public Square(double length){        this.height=length;        this.length=length;    }    public static void main(String[]args){        int a=new Scanner(System.in).nextInt();        Square s=new Square(a);        System.out.println("面积是:"+        +",周长是:"+        );    }}
    
    9.super关键字 [阅读]
    super关键字是用来指代父类对象,我们知道一个子类被创建,是在父类的基础上,也就是先创建父类对象,然后扩展这个父类对象成为子类对象。所以这个对象即时父类的对象,也是子类的对象。而这个super,就是父类对象。我们可以通过它访问父类的方法。
    例如下面的代码,Square的sqr方法,调用了父类的sqr方法
    public class Square extends Rectangle{    public double sqr(){        return super.sqr();    }}
    
  • 编程题
    1.设计下面的类和测试代码 (10分) [Java多类]
    父类鸟类Bird,它有一个无参数方法fly,代表飞行。此方法调用后打印这样一句话“开始飞行”。还有一个方法land,代表着陆,此方法调用后打印“降落”。
                
    子类麻雀类Sparrow。
                
    写一个main函数来测试这两个类,要求定义一个Sparrow对象,然后调用fly和land方法。
                
    2.给Rectangle和Square增加修改边长的功能。 (10分) [Java多类]
    1.首先给Rectangle的两个属性height和length增加getter和setter方法
                
    2.在Square里复写这两个属性的setter方法,使得正方形永远能保持,具体做法就是只要设置一条边,另一条边也设置成这个值。
                

    Square类必须包含带一个参数的构造函数,下面的main函数是测试代码


    本题需要Recangle类有sqr函数以计算面积
    public static void main(String[]args){    Rectangle s=new Rectangle();    s.setHeight(6);    s.setLength(7);    System.out.println(s.sqr());    Square s1=new Square(5);    s1.setHeight(6);    System.out.println(s1.sqr());    s1.setLength(7);    System.out.println(s1.sqr());}