Java入门系列之重写
前言
关于所有Java系列文章面向有一定基础的童鞋,所写每一篇希望有一定含金量,有些内容可能会从Java整个语法全局考虑穿插后续要讲解的内容以成系统,若不理解,请看完后再学习。上一节我们讲解完了final关键字,本节我们继续来对比讲解Java和C#中的重写,二者语言的重写区分非常清晰,Java子类中基类方法签名一样或通过注解@Override显式声明,C#中基类通过virtual关键字修饰,子类通过ovveride关键字表示重写,具体细节请往下看。
重写
既然是重写必然就涉及到继承,我们首先来看看Java中的重写是怎样的呢?如下:
public class Main { public void f() { System.out.println("Main.f"); } public static void main(String[] args) { Main main = new Sub(); main.f(); } } class Sub extends Main { public void f() { System.out.println("Sub.f"); } }
当调用基类的f方法时,此时发现已被子类所重写,所以如上正常打印出Sub.f,要是我们将上述基类中方法修改为私有的呢
可能我们期待输出子类中的打印结果,但是修改为私有后,说明此时对子类不再可见,也就相当于使用了final,在这种情况下,子类中方法则是一个全新的方法,很显然说明:只有非私有方法才可以被重写。对于这种情况下编译不会报错, 但是也不会按照我们所期望的结果来执行,所以建议对于基类中的私有方法命名为和子类不同的名字,为了让重写一目了然或更加明确,在1.5版本发布了注解功能,我们可以通过注解来显式声明要重写基类方法,若基类为私有,此时通过注解则会编译报错,因为找不到要重写的方法,这种体验更加友好,比如如下:
public class Main { private void f() { System.out.println("Main.f"); } public static void main(String[] args) { Main main = new Sub(); main.f(); } } class Sub extends Main { //编译错误,未找到基类(超类)中要重写的方法 @Override public void f() { System.out.println("Sub.f"); } }
举一反三,我们来思考一个问题,是不是方法签名一致,子类就可以重写基类方法呢?很显然不是,若是静态方法,必然不能被重写,如果通过注解@Override声明那么必然编译报错,否则调用基类方法,对于接口中的静态方法同理。所以还是建议使用注解来声明重写。那么为什么通过注解显式声明重写基类方法或通过关键字final修饰方法就会在编译阶段报错呢?因为它们在编译阶段就完成了静态绑定,而不是运行时动态绑定。问题又来了,上述我们讲解到若在子类中不通过注解显式声明重写,同时在基类中方法私有,此时一定可以编译通过(上述已演示),并且会调用基类方法并打印出结果,事实情况一定是这样吗?很显然也不是如此,如下示例:
class Super { private void f() { System.out.println("Super.f"); } } class Derived extends Super { public void f() { System.out.println("Derived.f"); } public static void main(String[] args) { Super aSuper = new Derived(); //编译报错(因为基类方法私有) aSuper.f(); } }
初一看,这不是一样么,其实是不一样,上述可以那是因为调用方在基类里面,当然可以调用内部的私有方法,如上情况只对基类内部私有, 当然也就不能调用,这里就又引出一个问题,是不是声明为基类的私有方法,子类就无法进行重写呢?根据我们上述打印的结果来看,理论上不可行,事实情况是可以的,通过内部类(后续会出文章详细讲解)来实现。
class Main { private void f() { System.out.println("Main.f"); } class Inner extends Main { private void f() { System.out.println("Inner.f"); } } public static void main(String args[]) { //内部类实例必须通过外部类实例创建 Main outer = new Main(); Inner inner = outer.new Inner(); //内部类可以在内部访问外部的所有成员(包括私有) inner.f(); // 调用外部类方法 outer = inner; outer.f(); } }
C#若明确需要重写,那么基类方法声明为虚有的virtual,子类通过ovverride关键字修饰方法达到重写目的,若没有这两个关键字,和Java中一样只是方法签名一致,那么说明编译器会提醒是否通过new关键字来表明隐藏基类的方法
class Program { public void F() { Console.WriteLine("Main.f"); } static void Main(string[] args) { Program program = new Sub(); program.F(); Console.ReadKey(); } } class Sub : Program { public new void F() { Console.WriteLine("Sub.f"); } }
总结
Java和C#中的重写区分度非常清晰,Java中只要方法签名一致就可以达到重写,不过建议通过注解方法来显式声明重写以免引起不必要的问题,同时,即使基类方法私有,我们也可借助于内部类来实现重写。