08-多态

1,什么是多态?

    多态是继数据抽象和继承之后的第三种基本特征。多态通过封装将接口(这里接口并非单指interface,而是包括抽象类,接口和普通父类)和实现分离开。多态能改善代码的组织结构和可读性。上篇笔记中写了“向上转型”,导出类(子类)可以被认为是基类(父类)。多态用它消除类型之间的耦合关系,代码中可以避免限定某个具体类型实例,而是以父类类型接收不同子类实例类型传入并调用。

2,向上转型和多态

    回顾一下向上转型,创建几何类,和四边形类,以及圆形类,四边形和圆形类都继承几何类,几何类拥有获取周长的方法,子类覆盖并实现。示例如下:
public class GeometricFraph {
    public static void main(String[] args) {
        perimeter(new Square());
        perimeter(new Circular());
    }

    /**
     * 多态的形式调用方法
     * @param geometric 
     */
    public static void perimeter(Geometric geometric){
        //根据实际的实例调用对应的方法
        geometric.perimeter();
    }
    public static void perimeter(Square square){
        System.out.println("Square");
        //根据实际的实例调用对应的方法
        square.perimeter();
    }
}
/**
 * 几何类
 */
class Geometric{
    /**
     * 获得周长
     */
    public void perimeter(){
        System.out.println("外围总长");
    }
}
/**
 * 四方形
 */
class Square extends Geometric{
    /**
     * 四边形获得周长的方式
     */
    @Override
    public void perimeter(){
        System.out.println("四边总长");
    }

    /**
     * 四方形的宽度
     * 扩展方法
     */
    public void width(){
        System.out.println("宽度");
    }
}

/**
 * 圆形
 */
class Circular extends Geometric{
    /**
     * 圆形获得周长的方式
     */
    @Override
    public void perimeter(){
        System.out.println("圆圈总长");
    }
    /**
     * 圆形的直径
     * 扩展方法
     */
    public void diameter(){
        System.out.println("直径");
    }
}
结果:

Square
四边总长
Geometric
圆圈总长

   如上两个perimeter方法参数分别是Geometric基类和Square导出类(子类),调用perimeter方法时如果传入的是Square实例,调则用的是perimeter(Square square)方法。实际上如果没有perimeter(Square square) 方,则默认会进入perimeter(Geometric geometric)方法,就比如传入的new Circular()调用的是perimeter(Geometric geometric)方法,而且geometric参数准确调用了Circular的perimeter方法。

    多态的好处已经看到了吧,正常情况下,我们只需要写一个perimeter(Geometric geometric)方法就可以了,它可以接收它的所有导出类,并能准确的执行导出类正确的方法,试想下如果没有多态时什么样子的,应该每个子类都要写个方法,每新增一个新的几何类,都要对应生成一个新的方法。对代码的维护和组织结构都是很大的破坏。

3,方法调用绑定

    正如上面示例,虽然传入的是Circular类型的实例,而父类Geometric参数正确调用其子类Circular的方法,其中原理涉及方法绑定。
3.1 前期绑定
    多态是针对从基类继承并在子类覆盖的方法,如果某些方法注定不会被覆盖,例如final修饰和static修饰的方法,private方法也是隐式的final方法,那这些方法就是前期绑定方法。程序编译期明确知道会被哪个类调用,所以这种在程序执行前绑定好的方法,就叫做前期绑定。又叫做静态绑定。而除了这些方法在java中所有方法都是后期绑定的, 后期绑定就是运行期绑定,在运行时根据具体对象的类型进行绑定。只有运行时才知道方法会被哪个类调用。
3.2 动态绑定
    大家都知道方法名+方法参数表组成方法签名,是调用方法识别方法的依据。而java如何实现在运行时把导出类识别到其基类参上呢?答案是RTTI,RTTI是运行时类型检查,java的每个.java文件编译运行后会在内存保存class信息,class信息里面包含方法,域等等信息,通过类型信息可以实现反射,动态加载等等功能,后面会有笔记写到。这里单指多态动态绑定方面。如下示例通过类信息判断实际的实例对象:
public static void main(String[] args) {
    getClassInfo(new Circular());
}
public static void getClassInfo(Geometric geometric) {
    Class clazz = geometric.getClass();
    System.out.println("Geometric参数实际的类" + clazz.getName());
    System.out.println(Arrays.deepToString(clazz.getDeclaredMethods()));
}
结果:
Geometric参数实际的类:com.javaApi.override.Circular
[public void com.javaApi.override.Circular.perimeter(), public void com.javaApi.override.Circular.diameter()]

    从上打印的结果可以看出java运行时通过参数获取其class信息并识别具体实例,以及拥有的可调用的方法集合信息。RTTI通过类型完成动态绑定工作,关于RTTI和类信息以后会有笔记详解讲。

4,缺陷:"覆盖"私有方法(private方法)
    上面提到多态是针对从基类继承并在子类覆盖的方法,private修饰的方法不能被继承,所以它不适用多态,虽然它是隐式的final但它不像final限制子类不能重写,也就是说private修饰的方法,子类也可以再写一份一样的方法,只不过它们之间是没有任何关系。
   实例如下:
/**
 * 几何类
 */
class Geometric {

    /**
     * 对角线
     */
    private void diagonal(){
        System.out.println("几何总长");
    }
}
/**
 * 圆形
 */
class Circular extends Geometric {
    /**
     * 对角线
     */
    public void diagonal(){
        System.out.println("几何总长");
    }
}

    注意,父类和子类都有diagonal方法,但它们俩没有任何关系,而且此方法也不支持多态,如果有访问权限(private只能本类调用访问权限),Geometric参数只会调用自身的diagonal方法。

4,缺陷:域和静态方法
    java的多态只是针对方法,而不是适用于域。原因是因为父类的域和子类的域分配不同的存储空间,虽然域可能是一模一样的,但它们是相互隔离的,子类默认调用自身的域,而调用父类域的话通过super.域。示例如下:
class Geometric {
    public String name="几何";
    public void getName(){
        System.out.println("name:"+name);
    }
}
class Circular extends Geometric {
    public String name="圆形";

    @Override
    public void getName(){
        System.out.println("name:"+name+",super.name:"+super.name);
    }
}
public static void main(String[] args) {
    getName(new Circular());
}
public static void getName(Geometric geometric) {
    geometric.getName();
}
结果:
name:圆形,super.name:几何
4.1静态方法不支持多态
    多态是针对从基类继承并在子类覆盖的方法,所以静态方法不支持多态的。构造器也不适用于多态,因为构造器也是隐式的static方法
5,构造方法和多态
    构造方法是隐式static方法,所以它本身不是多态方法,但是它能够影响到多态,如果不加谨慎的话可能会导致程序发生问题。构造器的调用流程在之前的笔记中已经写过,简单回顾下,在没有静态域和静态块的前提下,构造器调用会按照继承层次逐渐向上链接,每个基类的构造器都能被调用,构造器调用过程会检查对象是否被正确地构造。基类会先进行域的初始化再调用构造方法,然后子类进行域的初始化,再调用子类的构造方法。但是子类和父类的域都会先设定默认值。
示例如下:
class Geometric {
    public String name="几何";
    public void getName(){
        System.out.println("name:"+name);
    }
    /**
     * 父类构造器
     */
    public Geometric(){
        //调用getName()方法,且getName方法支持多态
        getName();
    }
}
class Circular extends Geometric {
    public String name="圆形";

    @Override
    public void getName(){
        System.out.println("name:"+name+",super.name:"+super.name);
    }
}
public static void main(String[] args) {
    getName(new Circular());
}
public static void getName(Geometric geometric) {
    geometric.getName();
}
结果:
name:null,super.name:几何
name:圆形,super.name:几何

    通过结果分析,为什么第一次name为null,第二次又有值了呢?就上像提到的构造器调用顺序,new Circular()时会先调用父类的构造器并在父类构造器中调用了getName方法,而此时子类name值还没有初始化所以其默认值是null,而getName方法支持多态,所以在父类构造器中调用的是子类的getName方法,所以这就是结果name为null的原因。

    而第二次name有值是因为父类构造器执行完后,子类开始初始化域先初始化,name被赋值为"圆形",再调用getName方法时,name已经初始化后的值。

6,协变返回类型

    协变返回类型表示导出类中方法可以返回基类类型。示例如下:
public static Geometric getGeometric() {
    return new Circular();
} 

7,向下转型

    向下转型及父类引用转型成子类,通过强制性转换,但转换前最好先进行类型判断,否则强转后可能会抛ClassCastException异常,示例如下:
//类型判断,co是否是Circular类型
 if (co instanceof Circular){ 
  Circular cc= (Circular)co;//向下转型 cc.getName();
}

8 模板方法设计模式

    多态在java程序开发中是非常重要的功能,程序中大量的使用多态的方式。我觉得要说最能体现多态的用法,模板方法设计模式最能体现。模板方法设计模式用于先将代码的逻辑算法结构搭建起来,将公共的方法提供给子类覆盖实现,而具体实现由子类完成,详情可以看去网上下模板方法设计模式,我后面也会写java设计模式笔记。

9 总结

    为了让程序有效的运行多态乃至面向对象的技术,必须扩展自己的编程视野,尽量在少量的代码中实现更多的功能,可以减少后期维护以及大量维护带来的风险,以及更快的程序开发,更好的代码组织,更好的扩展程序等。

转载于:https://www.cnblogs.com/likejiu/p/10089228.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值