重写”只能适用于可观察的实例方法.不能用于静态方法和final、private.对于静态方法,只能隐藏。一方面这是Java的规定,另一方面其实也有一定的道道这里边。

首先谈谈java方法是何时绑定的 
我们大家平时使用一个对象的方法时可能是这样的 
Shape shape = new Rectangle(); 
shape.getArea(); 
那么请问大家知道getArea是该调用父类Shape 的方法呢还是该调用Rectangle的方法呢。实际情况是:在运行期的时候取决于是哪个对象调用他的,规则是优先调用自己的这个getArea方法,如果自己没有这个方法就调用父类的getArea方法(这一切都是基于反射的,大家还记得设计模式里有一个模版方法模式吧,如果还不明白请往下看)。这里声明成Shape 类型变量,是让我们忘记它的具体实现类型,将做什么和怎么做分离,这就是java的多态。当然这些如果运用的好的话,将会非常有效的改善我们代码的组织结构和可读性并且提升程序的扩展性。

但是我们每个方法的调用者并不都是由对象来调用他的。比如说static方法,可能有人会觉得这个static方法调用者是类对象啊,但我告诉你类对象是Class类型,也就是每个类的class静态域,比如String.class、ArrayList.class,他们并不包含我们要调用的目标静态方法。。。想必大家也碰到过调用类的非静态方法时的NullPointException吧,那是因为我们的调用者是null变量,它里面没有我们的目标方法。现在大家想一想下面这个程序执行完成会是什么结果(我可以告诉大家,是可以正常运行的)

public class NullInvoker {
    public static void testNullInvoker(){
        System.out.println("in testNullInvoker");
    }
    public static void main(String[] args) {
        NullInvoker NULL = null;//虽然这里是空指针,但是下面依然可以执行下去
        NULL.testNullInvoker();
    }
}

在Java中我们静态方法的的选择是编译期就选择好的,是编译器自动根据声明类型帮我们选择的,它不依赖与任何对象。下面这个小例子(很重要!!很重要!!很重要!!)看明白就彻底会了:

public class TestStaticMethod {
    public static void main(String[] args) {
        Parent p = new Parent();
        SubClass s = new SubClass();
        Parent ps = new SubClass();
        
        p.testStatic();// 会打印“父类方法”。
        s.testStatic();// 会打印“子类方法”。
        ps.testStatic();// 会打印“父类方法”。而不是“子类方法”。
    }
}
class Parent {
    public static void testStatic() {
        System.out.println("父类方法");
    }
}
class SubClass extends Parent {
    public static void testStatic() {
        System.out.println("子类方法 ");
    }
}

 所以说从语义就可以看出static、final、private方法本身都是编译期绑定的(也叫前期绑定)这些方法不存在多态,他们是在还没有运行的时候,程序在编译器里面就知道该调用哪个类的哪个方法了,而其他可观察的普通方法的绑定是在运行的时候根据具体的对象决定的(也叫后期绑定),因为从语义上看这些方法是可被继承的,有了多态而造成了不确定性。。。 

方法重载(overload)实现的是编译时的多态性(也称为前绑定

方法重写(override) 实现的是运行时的多态性(也称为后绑定

所以不管是java语言的硬性规定还是java语言的一些关键字的语义,大家应该都会明白静态方法为什么不能被覆盖了吧。


上面说了这么多,现在再说说关于重载的一些坑吧 
首先我们看看代码,大家觉得这段代码会输出什么 

public class TestOverLoadMethod {
    public void testOverLoad(ArrayList<String> arrayList){
        System.out.println("ArrayList");
    }
    public void testOverLoad(LinkedList<String> linkedList){
        System.out.println("LinkedList");
    }
    public void testOverLoad(List<String> list){
        System.out.println("list");
    }

    public static void main(String[] args) {
        List<List<String>> lists = new ArrayList<List<String>>();
        lists.add(new ArrayList<String>());
        lists.add(new LinkedList<String>());
        lists.add(new CopyOnWriteArrayList<String>());
        TestOverLoadMethod obj = new TestOverLoadMethod();
        for (List<String> list : lists) {
            obj.testOverLoad(list);
        }
    }
}

下面是输出结果:
list 
list 
list  

明明传的参数是ArrayList和LinkedList,为什么最后调用的都是testOverLoad(List list)这个方法呢。。。这里大家注意一下,虽然我们具体方法的选择是运行时才确定的,但是我们方法签名的确定却是编译期就决定的。因为我们的编译器看到obj.testOverLoad(list);调用时候list的声明类型是List,所以这个调用生成的签名是testOverLoad(List list),而他在运行时根据反射调用这个方法会输出3个list字符串就不为怪了。

细心的朋友可能会注意到“java平台类库中”有些类中比如DataOutputStream有些方法叫:
writeInt(int v) 
writeLong(long v) 
writeFloat(float v)等等许多方法,现在大家应该能猜到是什么原因吧?参数类型不同,却还要重新命名方法名!

所以两个方法的签名如果能够完全区分时,可以使用overload。如果两个方法签名存在一些交集时,请慎用overload了,就像java平台类库一样,采用一些语义明确的方法名,那样代码会更清晰明了,使用者也不会犯迷糊。。。这里再补充一下,重载不是多态,重载方法的确定是编译期间就能确定的,是写代码的人手动选择的,不是运行时动态选择的。覆盖是多态,重载是例外。

 

 

因为重载是“(编译时)静态多态”,重写是“(运行时)动态多态”。