zoukankan      html  css  js  c++  java
  • 【细说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》

  • 相关阅读:
    单例模式
    建造者模式
    工厂方法模式
    原型模式
    适配器模式
    桥接模式
    装饰模式
    组合模式
    多线程的学习与GDI的学习
    我们复习.Net的这些日子里
  • 原文地址:https://www.cnblogs.com/xiaozhang2014/p/5323213.html
Copyright © 2011-2022 走看看