隐藏和覆盖的区别和用法

讲隐藏和覆盖之前先看两个概念:静态类型动态类型

任何一个引用变量都有两个类型:一个叫静态类型,也就是定义该引用变量的类型;另一个叫动态类型,也就是该引用实际指向的对象类型。

比如对于两个类A和类B,有:A a=new B();

那么,引用a的静态类型就是A,动态类型就是B。

java中引用的静态类型在编译的时候就可以确认,但是编译器无法得知这个引用的动态类型;只有当程序运行时,通过RTTI就可以检查出引用的动态类型。

再介绍一下,java中绑定的概念:对于一个程序,可以有很多的方法。这些方法的名称、参数类型和参数数量都可能相同或者不同,那么在调用一个方法的时候,如何将一个方法和调用该方法的主体关联起来,这就是绑定。java中的绑定分为静态绑定和动态绑定。

静态绑定:所有依赖于静态类型来将某方法和该方法所在的类关联起来的动作都是静态绑定。因为静态绑定在程序运行前发生,所有又叫前期绑定。

动态绑定:所有依赖于动态类型来将某方法和该方法所在的类关联起来的动作都是动态绑定。因为动态绑定是在程序运行时,通过RTTI实现,所以又叫后期绑定。

举例:假如有一个父类Father和一个子类Son,子类重写了父类中的某个方法method()。有以下语句:

Father father=new Son();

father.method();

对于这个例子,静态绑定的过程是:java文件编译时,编译器检查出引用father的静态类型是Father类,由于将method()方法和父类Father关联起来。也就是说,程序运行前编译器是无法检查出引用father的动态类型的,所以会直接调用静态类型中对应的方法。

而动态绑定的过程是:当这个java程序运行起来了,RTTI检查出引用father的动态类型是Son类时,会将method()方法和子类Son关联起来,也就是决定调用动态类型Son类中的method()方法。具体过程为:①JVM提取对象的实际类型的方法表;②JVM搜索方法签名;③调用方法。

另外,要补充的是:java中类的属性也都是静态绑定的,static、final、private(视为final,也是无法被覆盖的)是静态绑定,其余方法都是动态绑定。这是因为静态绑定是有很多的好处,它可以让我们在编译期就发现程序中的错误,而不是在运行期。这样就可以提高程序的运行效率!而对方法采取动态绑定是为了实现多态。

多态的含义是,“一个接口,多种形式”。通过继承,子类对象隐藏地持有一个引用名为super的父类对象,所以子类对象可以访问父类的属性和方法。又因为向上转型的安全性和自动转换的特点,所以这导致任何一个类的对象既可以视为该类自身的对象,又可以视为该类的基类的对象;一个基类和它的所有子类的对象都属于同一类型。这个特点表现的是不同类型之间的耦合关系,有很明显的优点:同一份代码可以无差别地运行在这些不同类型上(属于同一个基类):比如我只要把某方法的参数设为(object o),那么传递给该方法任意对象作为实参都可以调用该方法。而多态是为了消除类型之间的耦合关系,体现基类同一个接口(方法)被子类继承后表现的差异性。这种差异性需要通过重写父类方法来实现(不是重载,是对父类方法的重新解释以适应子类自身的需求)。

重写注意事项:①final修饰的父类方法不可以被重写 ②private视为final,但是又有所不同。你可以在子类中重写父类的private方法,但是实际上应当属于你新创建的一个方法,毕竟你压根无法访问该private方法。不过一般建议,不要重写父类的private方法。③方法重写时,可见性(访问控制权限)不能降低。比如public方法重写后设置为protected是不允许的!

考虑一个例子:父类Father一个方法move() 被子类继承后,子类即时重写了该方法,修改了其方法体的内容,有了属于自己的move()方法。但是当子类向上转型后,Father f=new Son(); f.move()你猜调用的是哪个方法?如果只有静态绑定,调用的必然是父类的move()方法,这就体现不了多态了!所以,必须引入动态绑定机制,正确的调用子类的move()方法。

所以,多态需要的条件是:①继承(复用类的重要方式) ②重写(没有重写则一成不变,没有差异性)③动态绑定(消除了同一基类的不同类型的耦合性)。

----------------------------------------------------------------------------------------------------------------分割线------------------------------------------------------------------------------------------------------------------

下面来说一下,java中的隐藏和覆盖的概念。我们知道,当子类继承父类时,除了继承父类所有的成员变量和成员方法之外,还可以声明自己的成员变量和成员方法。那么,如果父类和子类的成员变量和方法同名会发生什么?假设有一个父类Father和一个子类Son。父类有一个成员变量a=0;有一个静态成员变量b=0;有一个成员方法a,输出0;有一个静态成员方法b,输出0。子类分别重写这些变量和方法,只是修改变量的值和方法的输出,全部改为1.   我们再声明一个静态类型是父类,动态类型是子类的引用:

Father father=new Son();

通过这个引用访问子类的变量和调用子类的方法,那么,会有以下结论:

1、所有的成员变量(不管是静态还是非静态)都只进行静态绑定,所以JVM的绑定结果会是:直接访问静态类型中的成员变量,也就是父类的成员变量,输出0.

2、对于静态方法,也是只进行静态绑定,所以JVM会通过引用的静态类型,也就是Father类,来进行绑定,结果为:直接调用父类中的静态方法,输出0.

3、对于非静态方法,会进行动态绑定,JVM检查出引用father的动态类型,也就是Son类,绑定结果为:调用子类中的静态方法,输出1.

对于1和2这两种情况,子类继承父类后,父类的属性和静态方法并没有被子类抹去,通过相应的引用可以访问的到。但是在子类中不能显示地看到,这种情况就称为隐藏。

而对于3这种情况,子类继承父类后,父类的非静态方法被子类重写后覆盖上去,通过相应的引用也访问不到了(除非创建父类的对象来调用)。这种情况称为覆盖。

总结一下,就是,子类继承父类后:

父类的成员变量只会被隐藏,而且支持交叉隐藏(比如静态变量被非静态变量隐藏)。父类的静态方法只会被静态方法隐藏,不支持交叉隐藏。父类的非静态方法会被覆盖,但是不能交叉覆盖。

代码测试如下:

package test;
/* 隐藏和覆盖的区别 */
public class HideAndCover {
    public static void main(String[] args) {
        Father father=new Son();
        System.out.println(father.a);
        System.out.println(father.b);
        father.c();
        father.d();
    }

}
//声明父类
class Father{
    static int a=0;
    int b=0;
    void c() {
        System.out.println(0);
    }
    static void d() {
        System.out.println(0);
    }
}
//声明子类
class Son extends Father{
    static int a=1;
    int b=1;
    void c() {
        System.out.println(1);
    }
    static void d() {
        System.out.println(1);
    }
}

运行结果为:

0

0

1

0

总的来说:继承体现了同一个基类的不同子类之间的耦合性,而多态则是消除这种耦合体现这些类型之间的对于方法的差异性。前期绑定导致的后果就是隐藏;后期绑定导致的后果就是覆盖,继而实现多态。属性和静态方法(还有final、private)都是静态绑定,其余的实例方法是动态绑定。所以,只有实例方法可以体现多态性,域(属性)和静态方法体现不出多态。

——over。

原文地址:https://www.cnblogs.com/shakinghead/p/7445831.html