关于静态方法为什么不能被重写的一点思考以及overload的一些坑。

重写”只能适用于可观察的实例方法.不能用于静态方法和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) {
        SubClass s = new SubClass();
        Parent p = s;
        p.testStatic();// 打印父类方法。
        s.testStatic();// 打印子类方法。
    }
}
class Parent {
    public static void testStatic() {
        System.out.println("父类方法");
    }
}
class SubClass extends Parent {
    public static void testStatic() {
        System.out.println("子类方法 ");
    }
}

所以说从语义就可以看出static、final、private方法本身都是编译期绑定的(也叫前期绑定)这些方法不存在多态,他们是在还没有运行的时候,程序在编译器里面就知道该调用哪个类的哪个方法了,而其他可观察的普通方法的绑定是在运行的时候根据具体的对象决定的,因为从语义上看这些方法是可被继承的,有了多态而造成了不确定性。。。
所以不管是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平台类库一样,采用一些语义明确的方法名,那样代码会更清晰明了,使用者也不会犯迷糊。。。这里再补充一下,重载不是多态,重载方法的确定是编译期间就能确定的,是写代码的人手动选择的,不是运行时动态选择的。覆盖是多态,重载是例外。

欢迎大家来交流,或赞或喷皆可,说的好的我会感激,说的不好的我也不会介意。

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值