前言

关于所有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中只要方法签名一致就可以达到重写,不过建议通过注解方法来显式声明重写以免引起不必要的问题,同时,即使基类方法私有,我们也可借助于内部类来实现重写。

Java入门系列之重写的更多相关文章

  1. Java入门系列-26-JDBC

    认识 JDBC JDBC (Java DataBase Connectivity) 是 Java 数据库连接技术的简称,用于连接常用数据库. Sun 公司提供了 JDBC API ,供程序员调用接口和 ...

  2. Java入门系列-19-泛型集合

    集合 如何存储每天的新闻信息?每天的新闻总数是不固定的,太少浪费空间,太多空间不足. 如果并不知道程序运行时会需要多少对象,或者需要更复杂方式存储对象,可以使用Java集合框架. Java 集合框架提 ...

  3. Java入门系列之类继承、抽象类、接口(五)

    前言 C#和Java关于类.抽象类.接口使用方式基本相似,只是对应关键字使用不同罢了,本节呢,我们只是对照C#和Java中关于这三个概念在具体使用时,看看有哪些不一样的地方. 类继承 C#和Java在 ...

  4. Java入门系列之hashCode和equals(十二)

    前言 前面两节内容我们详细讲解了Hashtable算法和源码分析,针对散列函数始终逃脱不掉hashCode的计算,本节我们将详细分析hashCode和equals,同时您将会看到本节内容是从<E ...

  5. Java入门系列之字符串创建方式、判断相等(一)

    前言 陆续从0开始学习Java出于多掌握一门语言以后的路也会更宽,.NET和Java兼顾,虽然路还很艰难,但事在人为.由于Java和C#语法相似,所以关于一些很基础的内容不会再重头讲,Java系列中所 ...

  6. Java入门系列(九)Java API

    String,StringBuilder,StringBuffer三者的区别 1.首先说运行速度,或者说是执行速度 在这方面运行速度快慢为:StringBuilder > StringBuffe ...

  7. Java入门系列(七)Java 集合框架(JCF, Java Collections Framework)

    Java 集合概述 List.Set.Map可以看做集合的三大类 java集合就像一个容器,可以将多个对象的引用丢进该容器中. Collection和Map是java集合的根接口. List List ...

  8. Java入门系列(五)JVM内存模型

    概述 根据<Java 虚拟机规范>中的说法,Java 虚拟机的内存结构可以分为公有和私有两部分. 公有指的是所有线程都共享的部分,指的是 Java 堆.方法区.常量池. 私有指的是每个线程 ...

  9. Java入门系列(四)内部类

    为什么需要内部类? 真正的原因是这样的,java中的内部类和接口加在一起,可以的解决常被C++程序员抱怨java中存在的一个问题没有多继承.实际上,C++的多继承设计起来很复杂,而java通过内部类加 ...

随机推荐

  1. idea针对有外联jar包的项目如何编译成可运行的jar包

    1.打开file-->project structure 2.如下图所示,创建 3.在空白处右键点击“create directory”创建一个“”“libs”文件夹 4.把项目所需的jar吧, ...

  2. 【分区】使用 GPT 分区表分区并格式化 (FreeBSD 系统)

    1. 查看磁盘列表 使用命令 diskinfo -v /dev/vtbd1 查看磁盘设备列表. 2. 创建 GPT 分区 1). 执行命令 gpart create -s gpt vtbd1.2). ...

  3. PE文件介绍 (2)-DOS头,DOS存根,NT头

    PE头 PE头由许多结构体组成,现在开始逐一学习各结构体 0X00 DOS头 微软创建PE文件格式时,人们正广泛使用DOS文件,所以微软充分考虑了PE文件对DOS文件的兼容性.其结果是在PE头的最前面 ...

  4. pip超时问题解决

    阿里云 http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣(douban ...

  5. Area.js下载

    因为vant AddressEdit 地址编辑的必要组件area.js网站经常进不去,所以存在这里,area.js 代码如下: export default { province_list: { 11 ...

  6. 线程间配合:Condition、Semaphore、CountDownLatch、CyclicBarrier

    1 重入锁的好搭档:Condition条件 如果大家理解了Object.wait()和Object.notify()方法的话,那么就能很容易理解Condition接口了.它和wait()和notify ...

  7. Linux-基于公私钥实现免密码登录

    STEP1 在任意一个Linux机器上利用ssh-keygen 命令选择一种加密算法,生成一个密钥对.输入保存密钥对的位置和密码,输入完毕会在指定的目录,默认为/root/.ssh/下生成密钥对 语法 ...

  8. Python 简明教程 --- 9,Python 编码

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 当你选择了一种语言,意味着你还选择了一组技术.一个社区. -- Joshua Bloch 目录 1, ...

  9. 入门大数据---Hive是什么?

    这篇文章主要介绍Hive的概念. 简介: Hive中文名叫数据仓库管理系统,之前我们操作MapReduce必须通过编写代码或者通过特殊命令来实现,有了Hive我们通过常用的SQL语句就能操作MapRe ...

  10. Linux系统如何使用Fuser命令

    本文不再更新,可能存在内容过时的情况,实时更新请访问原地址:Linux系统如何使用Fuser命令: 什么是Fuser命令? fuser命令是一个非常聪明的unix实用程序,用于查找正在使用某个文件.目 ...