返回列表 发新帖

【Android入坑系列教程】46.对象的多态性(二)---面向对象

[复制链接]

62

主题

722

回帖

2756

积分

大学生

Rank: 5Rank: 5

金币
2029
好评
12
信誉
100

考神

发表于 2019-4-4 12:26:16 | 显示全部楼层 | 阅读模式  来自 四川
本帖最后由 pansong291 于 2019-4-5 12:42 编辑

上篇:45.对象的多态性(一)---面向对象
下篇:47.异常(一)---面向对象

注意:如果你的回复只是“感谢分享”、“辛苦了”之类的与教程内容无关的字眼,还请不要回复本帖,谢谢合作!!!
注意:如果你的回复只是“感谢分享”、“辛苦了”之类的与教程内容无关的字眼,还请不要回复本帖,谢谢合作!!!
注意:如果你的回复只是“感谢分享”、“辛苦了”之类的与教程内容无关的字眼,还请不要回复本帖,谢谢合作!!!


面向对象的多态性
在前面讲到的子类自动转换成父类的例子中,我们的调用程序是这么写的:
class C
{
    public static void main(String [] args)
    {
        B b=new B();
        A a = b;
        callA(a);
        callA(new B());
    }
    public static void callA(A a)
    {
        a.func1();
        a.func2();
    }
}
程序运行结果是:
B func1 is calling
B func1 is calling
B func1 is calling
B func1 is calling

尽管在 callA(A a)方法定义中,我们从字面上看是通过类 A(父类)的引用类型变量 a,
去调用其中的 func1(),但实际上执行的是类 B(子类)中的 func1 方法,这是因为我们实
际传递进来的对象确实是子类 B 的实例对象,所以程序调用的是类 B 中的 func1 方法。如果
我们再做一个类 A 的子类 D,让 D 也覆盖类 A 中的 func1()方法,然后产生一个类 D 的实例
对象,并传递给 callA(A a)方法,程序在 callA 方法中的 a.func1()调用,也将是类 D 中的
func1 方法。这就好比我们有一个“叫某人来吃饭”的函数,在该该函数内部,我们是这样
写的:
void 叫某人来吃饭(人 p)
{
    p.吃饭();
}
当叫来一个中国人时,我们看到的是用筷子在吃饭,但是当叫来的是一个美国人时,我
们看到的就是另外一番景象了,用的是叉子和小刀。同一段程序代码(单指“叫某人来吃饭”
这个函数),却有两种截然不同的结果,这就是面向对象的多态性。可见,多态性有如下特
点:
1). 应用程序不必为每一个派生类(子类)编写功能调用,只需要对抽象基类进行处理
即可。这一招叫“以不变应万变”,可以大大提高程序的可复用性。
2). 派生类的功能可以被基类的方法或引用变量调用,这叫向后兼容,可以提高程序的
可扩充性和可维护性。以前写的程序可以被后来程序调用不足为奇,现在写的程序(如 callA
方法)能调用以后写的程序(以后编写的一个类 A 的子类,如类 D)就很了不起了。
我们在深入分析多态性的另外一个方面,细心的读者可能已经注意到:在前面的程序中,
子类 B 并没有覆盖父类 A 的 func2()方法,直接从类 A 中继承了该方法,类 A 中的 func2()
方法调用的是类 A 自己的 func1 方法,但上面的结果显示,类 B 从类 A 继承来的 func2 里面
调用的 func1 变成了子类 B 的 func1。还记得我说过,编程序的时候,眼睛就盯着内存去想
问题,而不是盯着程序代码,肯定能想明白这个问题。
独家见解:
接口在面向对象的设计与编程中应用的非常广泛,特别是实现软件模块间的插接方面有
着巨大优势。其实,我们生活中也经常碰到接口的概念,大家想一想,我们在北京中关村的
电子市场随便挑选了一块计算机主板和一块 PCI 卡(网卡,声卡等),结果,这块 PCI 卡能
够很好地用到这块主板上,大家想过没有,这是什么原因造成的呢?主板厂商和 PCI 卡厂商
是同一家吗?他们相互认识吗?答案是否定的。但他们都知道同一个标准,那就是 PCI 规范。
做 PCI 卡的厂商严格按照 PCI 规范去实现他们的 PCI 卡,也就是与主板插槽的连接处是固定
的格式,包括卡的尺寸与连接电路线的排列顺序,但卡内部如何制造,就无所谓了,可以做
成网卡,也可以作成声卡。做主板的厂商也要知道 PCI 规范,他们只要保留一个能使用 PCI
卡的插槽,也就是按照 PCI 卡的尺寸与连接电路线的排列顺序去使用可能插进来的 PCI 卡,
而不必知道 PCI 卡的内部具体实现。
我们通过编写一段程序来模拟上述过程的实现,PCI 卡中的每个方法名称(相当于 PCI
卡的尺寸与连接电路线的排列顺序)必须是固定的,主板才能根据自己想执行的命令找到
PCI 卡中对应的方法,PCI 卡也必须具有主板可能调用到的所有命令方法。这正是“调用者
和被调用者必须共同遵守某一限定,调用者按照这个限定进行方法调用,被调用者按照这个
限定进行方法实现”的应用情况,在面向对象的编程语言中,这种限定就是通过接口类来表
示的,主板和各种 PCI 卡就是按照 PCI 接口进行约定的。
程序清单:Interface.java
interface PCI
{
    void start();
    void stop();
}
class NetworkCard implements PCI
{
    public void start()
    {
        System.out.println("Send ...");
    }
    public void stop()
    {
        System.out.println("Network Stop.");
    }
}
class SoundCard implements PCI
{
    public void start()
    {
        System.out.println("Du du...");
    }
    public void stop()
    {
        System.out.println("Sound Stop.");
    }
}
class MainBoard
{
    public void usePCICard(PCI p)
    {
        p.start();
        p.stop();
    }
}
class Assembler
{
    public static void main(String [] args)
    {
        MainBoard mb=new MainBoard();
        NetworkCard nc=new NetworkCard();
        mb.usePCICard(nc);
        SoundCard sc=new SoundCard();
        mb.usePCICard(sc);
    }
}
在上面的程序代码中,类 Assembler 就是计算机组装者,他买了一块主板 mb 和一块网
卡 nc,一块声卡 sc,无论是网卡还是声卡,他们都使用的是主板的 usePCICard 方法。由于
NetworkCardSoundCard 都是 PCI 接口的子类,所以,他们的对象能直接传递给 usePCICard
方法中的 PCI 接口的引用变量 p,在参数传递的过程中发生了隐式自动类型转换。
通过这个例子,读者应该明白了一个类必须实现接口中的所有方法的原因,因为调用者
有可能会用到接口中的每个方法,所以,被调用者必须实现这些方法。
思考题:如果 SoundCard 类中具有 PCI 接口中的所有方法,但没有明确声明它实现了 PCI
接口,SoundCard 的对象实例能不能直接传递给 MainBoard 的 usePCICard 方法使用?
编译器并不能根据一个类中有哪些方法,就知道它是某个类的子类的,编译器只能从
extendsimplements 关键字上来了解。MainBoard 的 usePCICard 方法要求的是 PCI 类型
的对象,虽然 SoundCard 中具有 PCI 接口中的所有方法,但编译器并不能知道它就是 PCI
接口的子类对象,编译器又是严格进行语法检查的,所以我们不能将 SoundCard 的对象实例
直接传递给 MainBoard 的 usePCICard 方法。即使我们将 SoundCard 的实例对象强制转换成
PCI 类型,编译时能够通过,但运行时会产生类型转换异常

匿名内部类
第41篇中我们已经讲到了内部类,印象不深的读者请回到第41篇复习一下,不然在这里
你可能会更迷惑的。我们首先来看看内部类继承另外一个类的应用情况。下面的例子在类
Outer 中定义了一个内部类 InnerInner 类又继承了类 A
abstract class A
{
    abstract public void fun1();
}
class Outer
{
    public static void main(String [] args)
    {
        class Inner extends A
        {
            public void fun1()
            {
                System.out.println("implement for fun1");
            }
        }
        new Outer().callInner(new Inner());
    }
    public void callInner(A a)
    {
        a.fun1();
    }
}
在上面的例子中,我们在 Outer.main 方法中调用了 Outer.callInner 方法,传递给
callInner方法的参数要求是一个实现了类 A 中的方法的子类对象,为此,我们在 Outer.main
方法中定义的一个继承了类 A 的内部类 Inner,类 Inner 仅在此被使用了一次。内部类可以
声明是抽象类或是一个接口,它可以被另外一个内部类来继承或实现。内部类可以继承外部
类,也可以用 final 关键字修饰。
对于上面的程序,我们可以简写,在调用 callInner 方法时,不用事先定义类 Inner
我们可以在给 callInner 方法传递参数时,临时创建一个类 A 的匿名子类的实例对象。
class Outer
{
    public static void main(String [] args)
    {
        new Outer().callInner(new A()
        {
            public void fun1()
            {
                System.out.println("implement for fun1");
            }
        });
    }
    public void callInner(A a)
    {
        a.fun1();
    }
}
上面的写法,初看起来,有些复杂,上面的程序相当于定义了一个类 A 的子类,但没给
这个子类起名字,接着又创建了这个子类的一个实例对象,这两步需要合在一块写。读者只
要按下面的步骤去走,这个过程就很简单了。
首先,在 callInner()方法调用中,写上 new A(){},也就是在 new A()的后面加上一
对大括号,就表示要产生一个类 A 的一个匿名子类的实例对象,并传递给 callInner 方法。
写完的效果如下:
new Outer().callInner( new A(){} );
匿名子类的所有实现代码(包括成员方法和成员变量)都要在那对大括号间增加。
接着,为了有个直观的程序代码层次,我们先用回车键将那对大括号间分开成两行,效
果如下:
new Outer().callInner(new A()
{
});
最后,在那对大括号间增加匿名子类的所有实现代码,效果如下:
new Outer().callInner(new A()
{
    public void fun1()
    {
        System.out.println("implement for fun1");
    }
});
就这么简单,见多了,用多了,你就非常习惯这种写法了,通过这种方式定义的类就是
匿名内部类。

上篇:45.对象的多态性(一)---面向对象
下篇:47.异常(一)---面向对象
回复

使用道具 举报

40

主题

3367

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
6202
好评
10
信誉
101

考神MT论坛最佳新人MT论坛帅哥MT论坛活跃会员

QQ
发表于 2019-4-4 12:50:34 来自手机  | 显示全部楼层  来自 广东
原谅我看不懂占了个沙发
回复

使用道具 举报

2

主题

1136

回帖

4768

积分

大学生

Rank: 5Rank: 5

金币
3112
好评
19
信誉
100
发表于 2019-4-4 13:53:11 来自手机  | 显示全部楼层  来自 河南
真厉害...楼主是初学者还是大牛??
回复

使用道具 举报

62

主题

722

回帖

2756

积分

大学生

Rank: 5Rank: 5

金币
2029
好评
12
信誉
100

考神

发表于 2019-4-4 15:09:51 | 显示全部楼层  来自 四川
Jinjunjie 发表于 2019-4-4 13:53
真厉害...楼主是初学者还是大牛??

初学者         
回复

使用道具 举报

1

主题

2283

回帖

4494

积分

大学生

Rank: 5Rank: 5

金币
2610
好评
1
信誉
100
发表于 2019-4-4 18:02:50 | 显示全部楼层  来自 河北
打好基础,才能写好程序
回复

使用道具 举报

22

主题

3020

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
8869
好评
1
信誉
100
发表于 2020-3-13 14:41:07 | 显示全部楼层  来自 河南
可以出一个完整版吗?一个个看太麻烦了。
回复

使用道具 举报

62

主题

722

回帖

2756

积分

大学生

Rank: 5Rank: 5

金币
2029
好评
12
信誉
100

考神

发表于 2020-3-13 18:32:31 来自手机  | 显示全部楼层  来自 四川
aoc2225 发表于 2020-3-13 14:41
可以出一个完整版吗?一个个看太麻烦了。

第一篇开头就说了,有pdf文档。https://bbs.binmt.cc/thread-14350-1-1.html
回复

使用道具 举报

7

主题

2323

回帖

4081

积分

大学生

Rank: 5Rank: 5

金币
634
好评
2
信誉
100
发表于 2020-6-15 16:33:02 | 显示全部楼层  来自 广东
楼主发贴辛苦了,谢谢楼主分享
回复

使用道具 举报

7

主题

2323

回帖

4081

积分

大学生

Rank: 5Rank: 5

金币
634
好评
2
信誉
100
发表于 2020-6-16 09:34:01 | 显示全部楼层  来自 广东
感谢楼主分享,支持一下
回复

使用道具 举报

7

主题

2323

回帖

4081

积分

大学生

Rank: 5Rank: 5

金币
634
好评
2
信誉
100
发表于 2020-6-16 11:33:01 | 显示全部楼层  来自 广东
小白来学习了,感谢大佬分享
回复

使用道具 举报

7

主题

2323

回帖

4081

积分

大学生

Rank: 5Rank: 5

金币
634
好评
2
信誉
100
发表于 2020-6-16 13:05:01 | 显示全部楼层  来自 广东
楼主发贴辛苦了,谢谢楼主分享
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表