Java之美[从初学者到高手演变]之类与对象(三)
类与对象之类的复用(继承、组合、代理)
作者:egg
微博:http://weibo.com/xtfggef
出处:http://blog.csdn.net/zhangerqing
此章我们主要谈下面向对象编程的代码复用机制。
继承
继承是OOP中最为重要的概念,达到了非常有效的代码重用效果,使得开发效率变得很高!同时也因此,造成了OOP语言执行效率低下,不免被C/C++程序员嘲笑。在Java语言中,两个类之间通过extends关键字实现继承。我们来看个继承的实例:
class A { public A() { System.out.println("A()!"); } } class B extends A { public B() { System.out.println("B()!"); } } public class ExtendsTest extends B { public ExtendsTest() { System.out.println("ExtendsTest()!"); } public static void main(String[] args) { new ExtendsTest(); } }
ExtendsTest继承自B,B继承自A,当实例化ExtendsTest的时候,却依次打印出了A、B、ExtendsTest构造器中的内容,说明:构造器被依次调用了,这是为什么?因为当类实现继承时,默认的会将基类的一个子对象传给子类,而子类需要对这个子对象进行初始化,所以需要调用父类的构造器,但是,这一切都是隐式进行的,我们看不到,不过可以从实验中得出结论:在对子类进行初始化的时候,会先调用父类的构造器(如果有学过C++的同学,肯定知道,在C++中除了有构造函数,还有析构函数,初始化的时候先调用父类的构造函数,析构的时候,先析构子类对象,再析构父类对象,一个从外到里,再由里到外的过程)。如果父类构造器需要传递参数,则使用super关键字来实现就行了。
class B extends A { public B(int n) { System.out.println("B()!"); } } public class ExtendsTest extends B { public ExtendsTest(int n) { super(n); System.out.println("ExtendsTest()!"); } public static void main(String[] args) { new ExtendsTest(1); } }
下面我们分几种情况讨论下继承:
1、子类不能继承父类私有的域或者方法。如果想要继承父类的私有对象,只能将private改成protected,因为protected的权限控制在包内。因此一般情况,用到继承的话,最好将父类中的域声明为私有(private,因为一般情况不需要继承成员变量),将方法声明为public,方便继承。
2、当子类对象调用一个方法时,如果子类没有,则去调用父类的同名方法,但是调用者保持是子类。
public class A { int a = 10; void a(){ System.out.println(a); System.out.println(getClass().getName()); } } class B extends A { int a = 20; // void a(){ // System.out.println(a); // System.out.println(getClass().getName()); // System.out.println(this.a); // System.out.println(super.a); // } public static void main(String[] args) { B b = new B(); b.a(); } }
输出:
10
B
a()B中被注释掉了,则调用的是父类A中的,所以输出的值是A中的成员变量。但是调用getClass()获取的仍然是B。当我们将上述代码中的注释去掉,则输出:
20
B
20
10
当B中有a()方法时,屏蔽了A中的a(),super关键字调用的是父类的信息,this关键字调用的是当前类的信息。
代理
代理的思想在我们讲得设计模式里面有体现,就是在一个类中持有另一个类的实例,从而代替原类进行一个操作,我们看个例子:
public class ProxyTest { Source source = new Source(); void p(int n){ source.a(n); } void p2(int n){ source.b(n); } public static void main(String[] args) { ProxyTest pt = new ProxyTest(); pt.p(20); pt.p2(50); } } class Source{ void a(int n){ System.out.println("this is : "+n); } void b(int n){ System.out.println("this is : "+n); } }
组合
如果大家还记得设计模式里的建造者模式,那么很容易联想到组合机制,就是将一系列的对象组合在一起,组合成一个功能丰富的类,当然,这些对象包括基本数据类型,也包括引用。来看个例子:
class Soap{ private String s; Soap(){ System.out.println("soap"); s = "constructor"; } public String toString(){ return s; } } public class CompronentTest { private String s1 = "happy",s2="Happy",s3,s4; private Soap castille; private int i; public CompronentTest(){ s3 = "joy"; castille = new Soap(); } { i = 88; } public String toString(){ if(s4 == null){ s4 = "Joy"; } return "s1 = " + s1 + "\n" + "s2 = " + s2 + "\n" + "s3 = " + s3 + "\n" + "s4 = " + s4 + "\n" + "i = " + i + "\n" + "castille = " + castille; } public static void main(String[] args) { CompronentTest ct = new CompronentTest(); System.out.println(ct); } }
该类就是一个普通的组合类,在组合类中我们应该注意这个对象的初始化方式,此处:1、s1和s2采用在声明的地方直接赋值,这样能够保证它们在构造器被调用之前被初始化(详细可见类与对象一中关于类的初始化顺序的介绍)。2、s3在构造器中初始化。3、s4采用的是懒加载(下面会讲)。4、i在非静态初始化块中。此处我们说下toString方法,就是一个将其它对象转为String对象的方法,除了非基本类型的对象,其它都有一个toString方法,这是因为toString方法是Object类的固有方法,在Java中任何类都隐式继承Object类,也就说都隐含toString方法。所以,在上述的例子中,当最后的字符串+ castille对象时,需要将castille对象以字符串的形式表现出来,因此调用了toString()。
懒加载因为涉及持有对象实例,所以会涉及到懒加载的机制,代码中的:
if(s4 == null){
s4 = "Joy";
}
就是一种懒加载的机制,这种机制就是解决当所需的对象比较庞大的时候,只有在用的时候才去初始化,节省空间,提高效率!
总结下:
1、初始化方面,注意一些特殊对象的初始化,可以在定义的时候直接初始化,或者在构造方法中或在方法块中,或者在用的时候懒加载。
2、toString方法,解决和字符串对象衔接出现的类型不匹配问题。
3、懒加载,提高效率,对于大的对象,延迟加载!
持续更新中...