Java 多态之“绑定”

在刚开始学习 JavaSE 时,学习多态时对下面几段程序输出的结果感觉到迷惑

public class Sub extends Super{
    public int num = 1; 

    public int getNum(){
        return num;
    }

    public static void main(String[] args) {

        Super sup = new Sub();
        System.out.println("sup.num = "+sup.num);  //调用父类的属性
        System.out.println("sup.getNum() = "+sup.getNum());  //调用子类的方法
    }
}

class Super{
    public int num = 0;

    public int getNum(){
        return num;
    }
}

输出:
sup.num = 0
sup.getNum() = 1


当将方法定义为静态时输出又会是什么样的呢?

public class Sub extends Super{
    public static int num = 1;

    public static int getNum(){
        return num;
    }

    public static void main(String[] args) {

        Super sup = new Sub();
        System.out.println("sup.num = "+sup.num);  //调用父类的属性
        System.out.println("sup.getNum() = "+sup.getNum());  //调用父类的方法
    }
}

class Super{
    public static int num = 0;

    public static int getNum(){
        return num;
    }
}

输出:
sup.num = 0
sup.getNum() = 0


如果将方法定义成私有的又会是什么结果呢?

public class Sub extends Super{

    private void f(){
        System.out.println("Sub f run........... ");
    }

}

class Super{

    private void f(){
        System.out.println("Super f run........... ");
    }

    public static void main(String[] args) {

        Super sup = new Sub();

        sup.f();  //调用父类的方法
    }
}

输出:
Super f run………..


在当时刚开始学 Java 时,当时的疑惑主要有:

  • 为什么第一个程序在向上转型的过程中调用的是父类的字段,以及子类的方法
  • 为什么第二个程序在向上转型的过程中调用的是父类的字段,以及父类的方法

后来在网上搜了一下知道了“绑定”这个概念,但是也只是很浅显的知道了有这个概念,最近在看《Java 编程思想》对“绑定”这个概念有了更深刻的认识,现在分享出来供大家参考:

绑定:将一个方法调用同一个方法主题关联起来被称为绑定。

  • 静态绑定:若程序执行前绑定 (如果有的话,由编译器和链接程序实现),叫做前期绑定。
  • 后期绑定:在运行时根据对象的类型进行绑定。后期绑定也叫做动态绑定或运行时绑定。

在 Java 中除了 static 方法和 final 方法 (private 方法属于 final 方法) 之外,其他的方法都属于后期绑定。为什么要将一个方声明为 final 的呢?因为这样做可以有效“关闭”动态绑定,或者说告诉编译器不需要对其进行动态绑定。

Java 中所有的方法都是通过动态绑定实现多态。因此子类在向上转型时调用的是子类的方法 (此方法时非静态的),这就可以解释为什么在第一段程序中调用 getNum() 方法输出的是子类的方法。

只有普通的方法调用是多态的,因此如果你要访问某个作用域或静态方法,这个访问将在编译器进行解析。正如第一个程序中输出的是父类的成员,以及第二个程序中将方法修饰成 static 时输出的就是父类的成员和父类的方法了 (失去了多态的特性)。

静态方法是与类,而并非与单个对象相关联的。如果你并不想将方法定义成静态的还想要得到 Super.num,必须显示的指明 super.num,就像下面这样:

public class Sub extends Super{
    public int num = 1;

    public int getNum(){
        return super.num;
    }

    public static void main(String[] args) {

        Super sup = new Sub();

        System.out.println("sup.getNum() = "+sup.getNum());
    }
}

class Super{
    public int num = 0;

    public int getNum(){
        return num;
    }
}

输出:
sup.getNum() = 0


对于第三段程序将方法定义成 private 的,我们期望是输出的是子类的方法运行,但是结果却出我们所料,这是动态绑定的缺陷所造成的结果,以及包括上面的域与静态方法都是它的缺陷造成的 (也就是说这两种缺陷是前期绑定)。

结论

只有非 private 方法才可以被覆盖,但是还是要注意覆盖 private 方法的现象,这时编译器虽然不会报错,但是它不会按照我们期望的那样执行。确切的说,在导出类中,对于基类中的 private 方法最好不要采取一样的名字。

在这里再强调一下这句话:在 Java 中除了 static 方法和 final 方法 (private 方法属于 final 方法) 之外,其他的方法都属于后期绑定。

其实在实践过程中,这种混淆的问题通常不会发生,因为你通常会将所有的域都设成 private 的,因此不能直接访问它们,其副作用是只能通过方法获取它们。另外,你几乎不可能将基类中的域和导出类的域赋予相同的名字。

参考资料
《Java 编程思想》Bruce Eckel 著 陈昊鹏 译

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值