【细说Java】Java的重写与隐藏

  重写与隐藏,有些书上或介绍上可能名称不一样,但都大差不差。以前只了解重写,隐藏也听说过,但没有详细了解过,趁现在,整理一下这两方面的内容吧。

  首先,先说一下概念方面的东西。

重写

  重写:子类继承了父类,在子类中声明了与父类具有相同的方法名称与参数列表,并且具有相同的返回类型(或者子类的返回类型是父类的子类型)的实例方法,那么就说子类重写了父类中的同名方法(但父类的方法必须在子类中可见),而重写则是实现多态的前提;子类可以向上转型为父类类型,这样,当通过父类的引用来调用重写的方法时,就可以表现出子类的行为。

1. 如果子类重写了父类的方法,则有如下要求:

(1)子类与父类的方法必须都是实例方法,即都不是静态方法;

(2)子类与父类的方法需要具有相同的方法名称,参数列表,并且子类的返回类型与父类相同,或者是父类的子类型;

public class JavaTest extends Super {
    @Override
    public Integer print() {
        return null;
    }
}

class Super {
    public Object print() {
        return null;
    }
}

由于Integer是Object的子类,因此满足重写的条件。

(3)子类方法的访问权限不能小于父类的访问权限(可以有相同的访问权限);

public class JavaTest extends Super {
    @Override
    protected Integer print() {
        return null;
    }
}

class Super {
    public Object print() {
        return null;
    }
}

  访问权限由高到低依次为public,protected,包的访问权限,private。如果子类方法的访问权限低于父类的访问权限,那么编译器将给出错误信息。如上面的代码,编译器就会报错。

(4)子类方法不能比父类方法抛出更多的已检测异常(也称编译时异常),即子类方法抛出的异常必须是父类的子集或和父类是相同的异常;

public class JavaTest extends Super {
    @Override
    public Integer print() throws IOException {
        return null;
    }
}

class Super {
    public Object print() throws FileNotFoundException{
        return null;
    }
}

如上面代码,由于父类抛出的FileNotFoundException异常是子类抛出的IOException异常的子类,这是不允许的。

注意:如果是未检测异常(运行时异常),则不受此限制,子类也不用显示的使用throws抛出。

(5)父类的方法在子类中必须可见(子类可以使用super来访问父类中被重写的方法);

public class JavaTest extends Super {

    public Integer print() {
        return null;
    }
}

class Super {
    private Object print()  {
        return null;
    }
}

如上面代码,父类中的方法是private类型的,在子类是无法访问的,但这不属于重写;

2.重写方法的调用

  当子类向上转型为父类类型A,通过父类的引用来调用某个方法时,Java编译器会先检查当前类有没有实现同名方法,如果有,执行当前类的方法;若没有,则去执行父类的同名方法;若父类没有,则再检查父类的父类有没有实现这个方法,以此类推,直到父类A为止。

public class JavaTest {
    public static void main(String[] args) {
        SuperClass test = new ThatClass();
        test.print1();
        test.print2();
        test.print3();
    }
}

class ThatClass extends ThisClass {
    public void print1() {
        System.out.println("ThatClass print1()");
    }
}

class ThisClass extends SuperClass {
    public void print1() {
        System.out.println("ThisClass print1()");
    }

    public void print2() {
        System.out.println("ThisClass print2()");
    }
}

class SuperClass {
    public void print1() {
        System.out.println("SuperClass print1()");
    }

    public void print2() {
        System.out.println("SuperClass print2()");
    }

    public void print3() {
        System.out.println("SuperClass print3()");
    }
}

执行结果如下:

ThatClass print1()
ThisClass print2()
SuperClass print3()

  还有一种情况,当使用父类的引用调用某个方法(如A方法)时,如果A方法中又调用了另一个父类中的方法(假设为B方法),那么依然按照上述的原则来查找B方法(由子类到父类的查找顺序);

public class JavaTest {
    public static void main(String[] args) {
        SuperClass test = new ThatClass();
        test.print3();
    }
}

class ThatClass extends ThisClass {
    public void print1() {
        System.out.println("ThatClass print1()");
    }
}

class ThisClass extends SuperClass {
    public void print1() {
        System.out.println("ThisClass print1()");
    }

    public void print2() {
        System.out.println("ThisClass print2()");
    }
}

class SuperClass {
    public void print1() {
        System.out.println("SuperClass print1()");
    }

    public void print2() {
        System.out.println("SuperClass print2()");
    }

    public void print3() {
        System.out.println("SuperClass print3()");
        print2();
        print1();
    }
}

打印结果如下:

SuperClass print3()
ThisClass print2()
ThatClass print1()

  简单分析下,父类引用调用print3方法,由于子类都没有重写,所以调用父类的方法,而父类print3方法又调用了父类的其他两个方法,并且这两个方法在子类中均可见,因此按照由子类到父类的查找顺序进行。由于print1方法在子类中被重写,所以调用子类的print1方法,而print2在子类中没有被重写,而是在子类的直接父类中被重写,所以调用子类的直接父类的print2方法。

  注意:JDK5.0之后可以使用@Override注解来标明要重写的方法。如果没有重写,编译器就会报错,如下,编译器就会报错。

class ThatClass extends ThisClass {
    @Override
    public void print1() {
        System.out.println("ThatClass print1()");
    }
}

class ThisClass  {
    private void print1() {
        System.out.println("ThisClass print1()");
    }
}

隐藏

1. 什么是隐藏呢?

  隐藏其实和重写差不多,除了隐藏要求所有的方法都是静态方法,而重写是实例方法,其他的要求都是一样的。

  其实隐藏与重写最本质的区别是,重写是多态的前提,利用重写可以实现多态,而隐藏则无法实现多态。

下面通过一个简单的例子来了解一下:

public class JavaTest {
    public static void main(String[] args) {
        SuperClass superTest = new ThisClass();
        superTest.hiding();
        superTest.override();
        System.out.println("===================");
        ThisClass thisTest = (ThisClass)superTest;
        thisTest.hiding();
        thisTest.override();
    }
}

class ThisClass extends SuperClass {
    public void override() {
        System.out.println("ThisClass void override()");
    }

    public static void hiding() {
        System.out.println("ThisClass static void hiding()");
    }
}

class SuperClass {
    public void override() {
        System.out.println("SuperClass void override()");
    }

    public static void hiding() {
        System.out.println("SuperClass static void hiding()");
    }
}

结果如下:

SuperClass static void hiding()
ThisClass void override()
===================
ThisClass static void hiding()
ThisClass void override()

通过以上例子,我们发现了重写与隐藏的根本区别:

(1)如果子类重写了父类的方法,则通过父类的引用调用的是子类的方法;

(2)如果子类隐藏了父类的方法,则通过父类的引用调用的仍然是父类的方法;

(3)这表明了实例方法的调用是动态绑定的,是在运行的时候根据对象真正的类型来决定调用哪个方法,而静态方法的调用是静态绑定的,是根据引用的类型来决定调用哪个方法。

2. 成员变量的隐藏

  当子类继承了父类,在子类中声明了与父类具有相同名称的成员变量时,那么就说子类隐藏了父类中的成员变量。子类不能重写父类中的成员变量,只能隐藏。

  所以,不管是静态变量还是实例变量,打印的是父类还是子类的成员变量取决于引用的类型,而不是对象的类型,因为子类隐藏了父类的成员变量。

public class JavaTest {
    public static void main(String[] args) {
        SuperClass superTest = new ThisClass();
        System.out.println(superTest.min);
        System.out.println(superTest.max);
        System.out.println("===================");
        ThisClass thisTest = (ThisClass)superTest;
        System.out.println(thisTest.min);
        System.out.println(thisTest.max);
    }
}

class ThisClass extends SuperClass {
    public static int max = 8;
    public int min = 7;
}

class SuperClass {
    public static int max = 4;
    public int min = 5;
}

结果如下:

5
4
===================
7
8

3. 成员变量的继承

如果子类继承了父类的实例变量,子类将与父类共享该实例变量,在子类中修改将影响到父类,反之亦然。

public class JavaTest {
    public static void main(String[] args) {
        ThisClass superTest = new ThisClass();
        System.out.println("修改之前:");
        superTest.printMin();
        System.out.println("修改子类之后:");
        superTest.setMin(4);
        superTest.printMin();
        System.out.println("修改父类之后:");
        superTest.setSuperMin(7);
        superTest.printMin();
    }
}

class ThisClass extends SuperClass {

    public void setMin(int value) {
        min = value;
    }

    public void setSuperMin(int value) {
        super.min = value;
    }

    public void printMin() {
        System.out.println("父类的min: " + super.min);
        System.out.println("子类的min: " + min);
    }
}

class SuperClass {
    public int min = 5;
}

结果如下:

修改之前:
父类的min: 5
子类的min: 5
修改子类之后:
父类的min: 4
子类的min: 4
修改父类之后:
父类的min: 7
子类的min: 7

至于静态成员变量就不举例了,静态成员变量和实例变量类似,但实例变量是基于对象的,即每一个对象中子类与父类都共享同一份实例对象的拷贝,而不同的对象互不干扰。而静态成员变量是基于类的,这将会导致所有子类与父类都共享同一个静态变量,无论创建多少对象,都是同一份拷贝。

总结:Java的重写与隐藏其实差不多,最根本的区别就是重写可以实现多态,而隐藏则无法实现多态。

参考自:《细说Java》

时间: 2024-08-16 00:03:18

【细说Java】Java的重写与隐藏的相关文章

类与接口(五)java多态、方法重写、隐藏

一.Java多态性 面向对象的三大特性:封装.继承.多态. 多态的类型,分为以下两种: 编译时多态: 指的是 方法重载.编译时多态是在编译时确定调用处选择那个重载方法,所以也叫 静态多态,算不上真正的多态.所以,一般说的多态都是运行时的多态. 运行时多态: 由于 方法重写,所以想要确定引用变量所调用的方法的入口,必须根据运行时的引用变量所指向的实例对象来确定.从而使得同一个引用变量调用同一个方法,但不同的实例对象表现出不同的行为.再简单点来说,就是在运行时,可以通过指向基类的指针,来调用实现子类

java线程:单例隐藏ThreadLocal实现线程数据共享

问题: 给定的二叉查找树中,有两个节点不小心被调换了位置,现在需要将其修正,不改变树的结构. 分析: 二叉排序树的中序遍历是有序的,所以这个问题又是建立在中序遍历模板上的问题,所以我们可以对其进行中序遍历,并用一个pre指针指向当前遍历结果中的最后一个结点,即下次遍历前的前一个结点.然后就可以通过将当前结点与pre结点进行比较,来判断是否有序了.若乱序,就将这两个结点都放入到预先定义的容器中. 错误的形式就这两种,我们看到,在乱序容器中,最多就存了四个元素,所以空间复杂度还是满足O(n)的,当然

Java之方法重写、方法重载、多态

Java之方法重写.方法重载.多态博客分类: java语言Java重写重载多态 一.方法重写(Override) 子类可以重写父类的方法. 方法的返回值类型.参数类型.参数个数都不能改变,只能重写方法体. 方法的重写规则 参数列表必须完全与被重写方法的相同: 返回类型必须完全与被重写方法的返回类型相同: 访问权限不能比父类中被重写的方法的访问权限更低. 例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected. 解释:用于多态(父类引用子类对象). 声明

[Java] JAVA面试题

1.String是最基本的数据类型吗?基本数据类型包括byte.int.char.long.float.double.boolean和short.java.lang.String类是final类型的,因此不可以继承这个类.不能修改这个类.为了提高效率节省空间,我们应该用StringBuffer类 2.String 和StringBuffer的区别JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据.这个String类提供了数 值不可

[Java]#从头学Java# Java大整数相加

重操旧业,再温Java,写了个大整数相乘先回顾回顾基本知识.算法.效率什么的都没怎么考虑,就纯粹实现功能而已. 先上代码: 1 package com.tacyeh.common; 2 3 public class MyMath { 4 5 public static String BigNumSum(String... n) { 6 int length = n.length; 7 StringBuilder result = new StringBuilder(); 8 //这里判断其实不需

类中的同名函数关系,重载,覆盖/重写,隐藏

对于类中的同名函数,重载,覆盖/重写,隐藏 C++特性,不是类亦可以实现重载,与virtual无关联.重载:同一类域抑或无类域,相同函数名,不同形参个数或类型:由形参决定调用哪个函数实现.同类域同名函数相同形参会引起编译错误. virtual修饰函数需要相同的返回值与形参类型,多态特性则需要该修饰符才能体现. 重写/覆盖:基类与派生类关系,同样的函数名,相同形参个数与类型,基类函数有virtual修饰:派生类执行派生类函数而不是基类函数实现,指针与引用的多态体现出来了,即基类指针或引用可根据指向

(原创)c#学习笔记10--定义类成员02--类成员的其他议题02--调用重写或隐藏的基类方法

10.2.2  调用重写或隐藏的基类方法 无论是重写成员还是隐藏成员,都可以在派生类的内部访问基类成员.这在许多情况下都是很有用的,例如: 要对派生类的用户隐藏继承的公共成员,但仍能在类中访问其功能. 要给继承的虚拟成员添加实现代码,而不是简单地用重写的新执行代码替换它. 为此,可以使用 base 关键字,它表示包含在派生类中的基类的实现代码(在控制构造函数时,其用法是类似的,如第9所述),例如: public class MyBaseClass { public virtual void Do

微信表情过滤 java "java.sql.SQLException: Incorrect string value: '\"

iOS 5.0之前,苹果都是采用3个字节来承接emoji表情,Java的普通char可以支持显示.但iOS 5.0之后, 苹果升级了系统自带的emoji表情输入法,用的Unicode 6标准来统一,是采用4个bytes来承接一个 emoji表情.如果不做处理的话,这种表情直接存储到mysql5.5以下的数据库是会报错的. 就像这两个表情一样:, 在Windows 8以下估计都不支持显示,可能会显示成框框,可能压根就是空白, 你可以在Mac中使用Safari浏览器中,就可以看到.经过测试,在Mac

新手如何学习java(java学习建议路线图)

怎么学习Java,这是很多新手经常会问我的问题,现在我简单描述下一个Java初学者到就业要学到的一些东西:     首先要明白Java体系设计到得三个方面:J2SE,J2EE,J2ME(KJAVA).J2SE,Java 2 Platform Standard Edition,我们经常说到的JDK,就主要指的这个,它是三者的基础,属于桌面级应用开发,这部分如果学得好很容易拓展J2EE和J2ME.J2ME,The Micro Edition of the Java 2 Platform.主要用于嵌入