Java学习笔记-多态

本文深入探讨了Java中的多态概念,解释了多态如何通过向上转型和动态绑定增强类的灵活性与可扩展性。通过具体代码示例展示了多态的工作原理,包括普通方法调用的多态性以及构造过程中可能出现的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

其实在看Java编程思想之前,我对多态并没有什么概念,也没觉得多态有多重要。但是看完之后才发现,多态的存在能够使得继承更加灵活。甚至可以说,如果没有多态,子类的向上转型会更困难,父类方法的调用也会更加复杂。

多态的存在,可以更好的消除类之间的耦合关系,更方便的构建可扩展的程序,使得从同一父类继承的子类表现出一种共性。从这一点上说,称多态是OOP中即数据抽象和继承之后的第三种基本特征,实在是名副其实

向上转型

假设有一个父类A,一个从A继承的子类B。那么当存在一个接受A对象的方法时,该方法也能接受B或者其他继承自A的对象类

// A.class
public class A {
    public void show(){
        System.out.println("This show from A") ;
    }
}
// B.class
public class B extends A {
    public void show(){
        System.out.println("This show from B") ;
    }
    public static void test(A item){
        item.show() ;
    }
    public static void main(String[] args){
        A a = new A() ;
        A b = new B() ; // 注意这里
        test(a) ;
        test(b) ;
    }
}
// =======Console output========
//This show from A
//This show from B

这样做的合理性在于B是继承自A,所以A中的接口在B中一定存在,所以在对B执行test的过程中,并不会出现问题。这样就能将A,B的功能切分开来,可以在A中实现那些比较通用的方法,而在B中实现通用性不那么强的方法。如果没有多态,就要写2个test函数,分别接受A,B生成的对象,这不但繁琐,而且也不便于同步

动态绑定

动态绑定又称为后期绑定或运行时绑定。也就是说,声明为A的变量其实不一定真属于A类,也有可能属于B或者其他A的子类。而这些在静态编译的时候编译器是不知道的,所以称为动态绑定。

在上面那个例子中,虽然b在声明变量是是A,但如果调用b.show(), 就会发现其实是调用的B.show() 函数。 如果使用getClass函数,就会发现其实b是B类型的变量。

但是!只有普通方法的调用可以是多态的,对于域或者静态方法,是不会使用多态的。还有对于私有方法,因为无法继承,所以即使子类中有相同名字的方法,也不属于从父类继承所得,所以还是会使用父类中的对应方法。

//A.class
public class A {
    public int field = 0 ;
    public void show(){
        System.out.println(this.getClass().getSimpleName()) ;
    }
    public void getField(){
        System.out.println("The field value is "+this.field);
    }
}
//B.class
public class B extends A{
    public int field = 1 ;
    public void show(){
        System.out.println(this.getClass().getSimpleName()) ;
    }
    public static void test(A item){
        item.show() ;
    }
    public void getField(){
        System.out.println("The field value is "+this.field);
    }
    public static void main(String[] args){
        A a = new A() ;
        A b = new B() ;
        System.out.println("=====a======");
        System.out.println("this.field "+a.field);
        a.getField();
        System.out.println("=====b======");
        System.out.println("this.field "+b.field);
        b.getField();
    }
}
// result
//=====a======
//this.field 0
//The field value is 0
//=====b======
//this.field 0
//The field value is 1

可以看到,如果直接调用b.field,返回的是A类中的field值0。即没有发生动态绑定。而如果调用 b.getField() , 则会输出1,也就是此时发生了动态绑定。

构造时发生多态的问题

这是一个潜在的问题,即如果B继承自A,A,B都有相同的方法假设为test,且A在初始化时调用了test,那么由于多态的存在,此时会调用B.test,如果在test调用了变量,那么就会发生问题。

A在初始化时,B是没有初始化的。当A调用test时,由于多态,会访问B中的域,这就是问题所在。 举例如下

// A.class
public class A {
    public void test(){
        System.out.println("test from A");
    }
    A(){
        test(); // 此时B.field = 0
    }
}
// B.class
public class B extends A {
    private int value = 1 ;
    B(){
        //(调用构造函数前value被赋值为1)
        test(); // 此时B.field = 1     
        this.value = 5 ;
        test(); // 此时B.field = 5 (value被赋值为5)
    }
    public void test(){
        System.out.println("test from B, value = "+this.value);
    }
    public static void main(String[] args){
        B b = new B() ;
    }
}
// output
//test from B, value = 0
//test from B, value = 1
//test from B, value = 5

需要养成的编程习惯

  • 变量尽量设为private,转而使用public方法来调用
  • 尽量不要对基类的域和导出类的域赋予相同的名字
  • 不要滥用继承,尽量使用组合和代理的方法来实现功能
  • 注意在基类构造函数中调用子类同名函数的情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值