女儿毕业学期选了门OOP的课程。作为一个统计专业和BA专业的学生,学这门课就是挑战自己。由于缺乏系统的编程学习,有些基础概念理解不是非常透彻,比如类,类实例,方法调用,递归调用等。
我结合她的一个日常作业,简单写下OO的方法调用过程,不是非常严谨,有些步骤(比如出入栈的内容)是原理性的,算是科普吧。
题目是在学习组合类(CompositeClass)的时候的练习题。
有两类形状(Shape):容器类Container 和 圆Circle,这两个形状都继承自Shape(抽象类)。每个容器类下可以有多个形状(可以是子容器或者圆),圆不是容器,不可以有子形状。
以下是部分代码:
abstract class Shape{ protected Rectangle rect; protected String color; public Shape(int x, int y, int w, int h, String c){ rect = new Rectangle(x, y, w, h); this.color = c; } @Override public String toString() { return getClass().getSimpleName() + ":(" + rect.x +"," + rect.y + ")," + color; } abstract void draw(String indent); } class Circle extends Shape{ public Circle(int x, int y, int w, int h, String c){ super(x, y, w, h, c); } @Override public void draw(String indent) { System.out.println(indent + toString()); } } class Container extends Shape { private ArrayList<Shape> elements; public Container(int x, int y, int w, int h, String c) { super(x, y, w, h, c); elements = new ArrayList<Shape>(); } public void add(Shape s) { elements.add(s); s.color = this.color; } public void remove(Shape s) { elements.remove(s); s.color = "black"; } @Override public void draw(String indent) { System.out.println(indent + this.toString()); Iterator<Shape> i = elements.iterator(); while (i.hasNext()) { Shape d = i.next(); d.draw(indent+" "); } }
下面是调用类:
public class ShapeComposite { public static void main(String args[]) { Container Container1 = new Container(10,20,300,300, "yellow"); Container Container2 = new Container(20,30,100,100, "red"); Shape Circle1 = new Circle(25,35,20,20, "green"); Container1.add(Container2); Container2.add(Circle1); Container1.draw(""); } }
实例的关系如下图:Container1里包含Container2,Container2中包含Circle1。
调用Container1的Draw()方法,打印container1 以及其下字形状的输出,如下样式:
Container:(10,20),yellow --无缩进(container1) Container:(20,30),yellow --缩进1字符(container2) Circle:(25,35),yellow --缩进2字符(circle1)
通过缩进表示上下级关系。
方法的调用关系:Container1.Draw() ->Container2.Draw()->Circle1.Draw()。
用图表示出来:
下面详细说明。
1、调用 Container1.Draw()
缩进参数 indent=“”,表示不缩进。
首先把自己打印出来:System.out.println( indent + this.toString());
Container:(10,20),yellow -- 无缩进
因为container 有子形状,然后挨个拿出来,调用子形状的 draw。在这个例子中,container1下有一个 子container2,调用 其 draw:
d.draw( indent + " " );
这里d 就是 containier2。注意因为 container2 和 container1 类型相同,都是Container,因此draw方法是同一个方法。因此d.draw(indent+" " ) 会再次进入同一个方法(等同递归调用),只不过方法运行的上线文(context)不同了:
在第一次调用draw方法,类的实例时 container1,indent = “”,没有缩进。我们记为 container1.draw。
在第二次调用draw方法,实例时 container2,indent = “ ”,缩进增加了一个空格,我们记为 container2.draw。(因为container2 是 container1 的下级,因此缩进比container的缩进(“””)增加1个空格:indent+“ ”)。
在这里补充下方法调用的过程(假设M1调用M2),分以下几步:
(1)保存现场。就是保存M1当前的运行上下文,将M1的临时变量入栈(push stack),准确讲是将“寄存器”的值、返回地址入栈保存起来
(2)调用方法。将cp当前执行位置修改为被调用方法M2的入口地址
(3)执行被调用方法。顺序执行M2的指令,直到结束。这期间可能还会还会调用其他方法(调用过程一样)。执行完后,将M2的返回值压栈。
(4)返回。
- cpu执行位置返回到M1方法中调用M2指令的下一条指令(从堆栈中取出调用前的执行位置+1)
- 取出返回值。从堆栈中取出m2的返回值。
- 恢复M1的上下文。从堆栈中恢复临时变量的值(寄存器的值),恢复调用前的“现场”。
- 继续执行M1的剩余指令。
以上就是方法调用的大概过程。
在第二次调用container2.draw方法时,计算机会现将 container1.draw方法里的临时变量 indent 和 d 压到栈中(stack)暂存。此时堆栈的内容是:
然后调用container2.draw(执行代码跳到方法的第一行代码)
2、调用 container2.draw()
container2.draw()和 container1.draw() 是同一个方法。只不过这次调用的indent = “ ”,是一个空格,表示缩进一个空格。
同样,因为container2仍然是个容器类,它会首先把自己打印出来:
Container:(10,20),yellow -- 缩进1字符。
Container:(10,20),yellow -- 缩进了一个字符。
到这里,总的输出是:
Container:(10,20),yellow -- 无缩进 Container:(10,20),yellow -- 缩进1字符。
然后继续遍历其下的字形状,调用子形状的 draw方法。
这个例子中container2下只有一个字形状:Circle1。因此会调用circle1.draw()。
同样,计算机会把container2.draw中的临时变量 indent 和 d 压栈。栈的内容如下:
3、调用 Circle1.draw()
第三次调用是调用 circle2的draw方法,indent 又加了个“ ” 空格,这时候的indent 是2个空格了:“ ”。也就是 circle的输出将缩进2个空格。
因为Circle不是容器类,其下没有子形状,以内draw方法只是把自己打印出来:
Container:(10,20),yellow -- 缩进了2个空格。
此时总的输出为:
Container:(10,20),yellow -- 无缩进。 Container:(10,20),yellow -- 缩进1字符。 Container:(10,20),yellow -- 缩进2字符。
至此,Circle1.draw方法执行完毕,即将返回到 Container2.draw方法的第6行代码(循环体结束代码)。
4、返回container2.draw
Circle1.draw方法执行完毕后,返回到Container2.draw方法的第6行代码(循环体结束代码)。计算机将调用circle.draw方法前压栈的两个临时变量 indent=“ ”(1字符) 和 d = circle1 出栈,赋值给container2.draw 的indent 和 d(恢复现场),继续执行下面的指令。此时栈的状态为:
因为container2只有一个字形状,因此打印为 Circle1后,方法执行完毕。
执行完毕后,返回container1.draw 的 第6行代码。
5,返回container1.draw方法,继续执行。
container2.draw 执行完返回后,计算机将调用circle.draw方法前压栈的两个临时变量 indent=“” (0字符)和 d = Container2 出栈,赋值给container2.draw 的indent 和 d。此时栈的内容清空了。
继续执行container2.draw下面的代码。因为 container1下也只有一个 字形状:container1,因此循环结束,方法返回。
最终的输出结果为:
Container:(10,20),yellow --无缩进 Container:(20,30),yellow --缩进1字符 Circle:(25,35),yellow --缩进2字符