java三大特性之多态

多态的定义

多态
程序中定义的引用变量和通过该引用变量调用的方法所指向的具体类型和具体类型的方法在编程时并不确定,而只有在程序运行期间某一刻才能具体确定的情况称之为多态。

写的有点饶,但是可以看到几个重点是引用的变量和该变量的方法在没运行时我们是不确定它的具体类型的。

那么为什么会有这种情况呢,根据在继承中的提现我们发现会发生这种情况的就是向下转型的问题。
即;我们可以确定猫是动物,但是反过来如果别人给你是一只被袋子装起来的动物(向上转型)你是无法判断它到底是猫还是小老虎(向下转型)不是么。

多态的三个必要条件

因此多态出现的情况就很明显了,它必须有一个前提场景才能发生;

实现多态有三个必要条件:继承(extent)、重写(overriding)、向上转型

通过实例说明

public class Animal {
    public void fun1(){
        System.out.println("Animal 的Fun.....");
        fun2();
    }

    public void fun2(){
        System.out.println("Animal 的Fun2...");
    }
}
public class Cat  extends Animal{
    /**
     * @desc 子类重载父类方法
     *        父类中不存在该方法,向上转型后,父类是不能引用该方法的
     * @param a
     * @return void
     */
    public void fun1(String a){
        System.out.println("Cat 的 Fun1...");
        fun2();
    }

    /**
     * 子类重写父类方法
     * 指向子类的父类引用调用fun2时,必定是调用该方法
     */
    public void fun2(){
        System.out.println("Cat 的Fun2...");
    }



    public static void main(String[] args) {
        Animal a = new Cat();
        a.fun1();
    }

}

Output

Animal 的Fun.....
Cat 的Fun2...

从程序的运行结果中我们发现,
a.fun1()首先是运行父类Animal中的fun1().然后再运行子类Cat中的fun2()。

所以对于多态我们可以总结如下:

指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用这些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。

多态的表现形式

java中的多态的表现形式一般分为两种:overriding(重写)和overloading(重载)

  • 重写overriding是父类和子类之间多态性的一种表现
  • 重载overloading是一个类中多态性的表现。

如果在子类中定义某方法与其父类有相同的名称和参数,我们就可以这样说此方法被子类重写overriding,子类中的对象使用这个方法的时候,将调用子类中中的定义,此时对子类来说,父类中的方法定义如同被”屏蔽”了一样(a不能调用fun1(String str)方法)。如果在一个类中定义了多个同名的方法,它们或有不同的参数或有不同的参数类型,则称为方法的重载overloading。并且overloading的方式是可以改变返回值类型的,但是父类中并没有这些重载的方法。

多态的种类

  • 分为编译时多态和运行时多态

多态的实现方式

两种实现方式:
- 基于继承
- 基于接口

基于继承实现的多态

上面的例子都是基于继承实现的,当然下面这个也是基于继承:

public class Wine {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Wine(){
    }

    public String drink(){
        return "喝的是 " + getName();
    }

    /**
     * 重写toString()
     */
    public String toString(){
        return null;
    }
}

public class JNC extends Wine{
    public JNC(){
        setName("JNC");
    }

    /**
     * 重写父类方法,实现多态
     */
    public String drink(){
        return "喝的是 " + getName();
    }

    /**
     * 重写toString()
     */
    public String toString(){
        return "Wine : " + getName();
    }
}

public class JGJ extends Wine{
    public JGJ(){
        setName("JGJ");
    }

    /**
     * 重写父类方法,实现多态
     */
    public String drink(){
        return "喝的是 " + getName();
    }

    /**
     * 重写toString()
     */
    public String toString(){
        return "Wine : " + getName();
    }
}

public class Test {
    public static void main(String[] args) {
        //定义父类数组
        Wine[] wines = new Wine[2];
        //定义两个子类
        JNC jnc = new JNC();
        JGJ jgj = new JGJ();

        //父类引用子类对象
        wines[0] = jnc;
        wines[1] = jgj;

        for(int i = 0 ; i < 2 ; i++){
            System.out.println(wines[i].toString() + "--" + wines[i].drink());
        }
        System.out.println("-------------------------------");

    }
}

OUTPUT:

Wine : JNC--喝的是 JNC
Wine : JGJ--喝的是 JGJ
-------------------------------

在上面的代码中JNC、JGJ继承Wine,并且重写了drink()、toString()方法,程序运行结果是调用子类中方法,输出JNC、JGJ的名称,这就是多态的表现。不同的对象可以执行相同的行为,但是他们都需要通过自己的实现方式来执行,这就要得益于向上转型了。

  我们都知道所有的类都继承自超类Object,toString()方法也是Object中方法,当我们这样写时:
Object o = new JGJ();

      System.out.println(o.toString());

所以基于继承实现的多态可以总结如下:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。

基于接口实现的多态

  继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。

  在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。

  继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

这里就不写代码了,接口继承,执行的方法根据具体的子类型来确定,借口本身是不能直接调用的,所以执行的具体方法根据传递的子类型来执行它的实现方法。

经典案例

这是一个非常变态的多态案例,作者写得非常详细:

http://blog.youkuaiyun.com/thinkGhoster/archive/2008/04/19/2307001.aspx

public class A {
    public String show(D obj) {
        return ("A and D");
    }

    public String show(A obj) {
        return ("A and A");
    } 

}

public class B extends A{
    public String show(B obj){
        return ("B and B");
    }

    public String show(A obj){
        return ("B and A");
    } 
}

public class C extends B{

}

public class D extends B{

}

public class Test {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();

        System.out.println("1--" + a1.show(b));
        System.out.println("2--" + a1.show(c));
        System.out.println("3--" + a1.show(d));
        System.out.println("4--" + a2.show(b));
        System.out.println("5--" + a2.show(c));
        System.out.println("6--" + a2.show(d));
        System.out.println("7--" + b.show(b));
        System.out.println("8--" + b.show(c));
        System.out.println("9--" + b.show(d));      
    }
}

答案

          ①   A and A
          ②   A and A
          ③   A and D
          ④   B and A
          ⑤   B and A
          ⑥   A and D
          ⑦   B and B
          ⑧   B and B
          ⑨   A and D

分析

    ①②③比较好理解,一般不会出错。④⑤就有点糊涂了,为什么输出的不是"B and B”呢?!!先来回顾一下多态性。

    运行时多态性是面向对象程序设计代码重用的一个最强大机制,动态性的概念也可以被说成“一个接口,多个方法”。Java实现运行时多态性的基础是动态方法调度,它是一种在运行时而不是在编译期调用重载方法的机制。

    方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写(Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。

当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。 (但是如果强制把超类转换成子类的话,就可以调用子类中新添加而超类没有的方法了。)

    好了,先温习到这里,言归正传!

多层父子调用的优先级

上面的案例实际上涉及方法调用的优先问题

优先级由高到低依次为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。让我们来看看它是怎么工作的。

    比如④,a2.show(b),a2是一个引用变量,类型为A,则this为a2,b是B的一个实例,于是它到类A里面找show(B obj)方法,没有找到,于是到A的super(超类)找,而A没有超类,因此转到第三优先级this.show((super)O),this仍然是a2,这里O为B,(super)O即(super)B即A,因此它到类A里面找show(A obj)的方法,类A有这个方法,但是由于a2引用的是类B的一个对象,B覆盖了A的show(A obj)方法,因此最终锁定到类B的show(A obj),输出为"B and A”。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值