前言
代码复用是Java众多引人注目的功能之一。但要想成为极具革命性的语言,仅仅能够复用代码并对之加以改变是不够的,它必须还能够做更多的事情。Java解决问题都是围绕类展开的,对于复用代码,可以创建新的类来复用,也可以使用别人已经开发并调试好的类。方法的关键在于使用类而不破坏现有程序代码。有两种方式达成此方法的目的:组合和继承。下面将介绍这两种代码重用机制。
组合和继承的实现
在新的类中产生现有类的对象,即为组合。该方法只是复用了现有程序代码的功能,而不是它的形式。
按照现有类的类型创建新类,无需改变现有类的形式,采用现有类的形式并在其中添加新的代码,此方式就是继承。
组合的实现
只需要将对象的引用置于新类中即可。
class A{
...
}
class B{
private A;
...
}
在Java中,类中域为基本类型时会被自动初始化为对应的“零”,对象引用会被初始化为null。编译器并不是简单地为每一个引用都创建默认对象。若是想初始化对象引用,可以在代码中的下列位置进行:
- 在定义对象的地方。这也意味着它们会在构造器被调用之前被初始化。
- 在类的构造器中。
- 在临近使用这些对象之前,再初始化,这种方式也叫做
惰性初始化
。 - 使用实例初始化。
使用《Java编程思想》上面的例子说明:
class Soap{
private String s;
public Soap() {
System.out.println("Soap()无参构造器");
s = "Constructed"; //在类构造器中初始化
}
@Override
public String toString() { return s;}
}
public class Bath {
private String s1 = "Happy";//在定义对象的地方初始化
private Soap castille = new Soap();
private String s2;
private int i;
public Bath() { System.out.println("Bath() 无参构造器");}
//实例初始化
{
i = 31;
System.out.println("初始化i为31");
}
@Override
public String toString() {
if(s2 == null) { //惰性初始化
s2 ="Java";
}
return s1 + "\t"+ s2 + "\t" + i + "\t" + castille;
}
public static void main(String[] args) {
System.out.println(new Bath());
}
}
/*
output:
Soap()无参构造器
初始化i为31
Bath() 无参构造器
Happy Java 31 Constructed
*/
继承的实现
Java中的继承是使用关键字extends
实现的,使用继承子类将会得到父类中所有的域和方法。父类中的私有域和方法将被隐式继承,非私有方法和域将被显式继承。隐式继承可以通过一定方式被间接访问到。
下面将以一个例子说明继承中出现的一些问题。
class Person {
private String name; //私有域
public Person() {
System.out.println("Person() 无参构造器");
name = "noName";
}
public Person(String name) {
System.out.println("Person(String name) 带参构造器");
this.name = name;
}
public String getName() { return name;} //让子类可以间接访问到私有域name
public String toString() { return "Person: "+name; }
}
class Student extends Person{
public Student() {
//默认调用 super(); 表示调用父类的无参构造器
System.out.println("Student() 无参构造器");
}
public Student(String name) {
super(name); //调用父类的带参构造器
System.out.println("Student(String name) 带参构造器");
}
public String toString() { return "Student: " + getName();}
}
public class ExtendsExample {
public static void main(String[] args) {
System.out.println(new Student());
System.out.println(new Student("sakura"));
}
}
/*
output:
Person() 无参构造器
Student() 无参构造器
Student: noName
Person(String name) 带参构造器
Student(String name) 带参构造器
Student: sakura
*/
子类和父类的构造器调用问题
关于构造器的调用顺序在前面提过,这里再着重介绍下子类和父类的构造器调用问题。
先有父才有子,Java中使用super关键字表示超类也就是父类。若是子类中没有显式调用父类的构造器,那么编译器就会默认调用super(),即父类的默认构造器;若是父类没有默认构造器,则子类中就必须显式调用父类的带参构造器,并且调用必须放在构造器中的第一行。
super()和this()可以同时存在吗?
super()和this()是不能同时出现在构造器中,因为二者都要求被放在构造器的首行。
组合和继承之间的区别
组合和继承都允许在新的类中放置子对象,组合是显式这样做,而继承则是隐式这样做。
组合技术通常是用于想在新类中使用现有类的功能而非其接口的情况。即,在新类中嵌入某个对象,让其实现所需要的功能;新类的用户看到的只是为新类所定义的接口,而非所嵌入类的接口。
继承是使用某个现有类并开发其特殊版本,会拥有现有类的接口并且可以为新类开发新的接口。
所以,继承关系为“is-a”(是一个),组合关系为“has-a”(有一个)。
protected关键字
在实际的项目中,经常会想要将某些事物尽可能对这个世界隐藏起来,但是仍然允许导出类的成员访问它们。protected
关键字就是实现这个作用。
它表明:对于任何继承于此类的导出类或者任何与此类在同一个包中的类来说,被protected修饰的方法和域是可以访问的,但是对于除这两种类的其他类则是不可访问的。
继承的局限
Java中继承的局限便是不允许类似C++那样的多继承,至于为什么没有多继承最直接的原因就为简单编程,不用去考虑父类的父类是谁、父类的父类的父类是谁、以及自己包含的方法继承自谁的。比如B和C继承自A,D又继承B、C,当D调用A中的一个方法时,是继承自B的还是继承自C的呢?
为了实现多继承的效果的话可以使用多层继承(A继承B,B继承C),但是最好不要超过三层,否则代码会太乱。后面会介绍Java使用接口实现多继承。一个类虽然不能从继承多个基类但是可以声明继承多个接口(interface),从而达到多继承效果。
小结
继承和组合都可以从现有类型生成新类型。组合一般是将现有类型作为新类型的底层实现的一部分来加以复用,而继承复用的是接口。在使用继承时,由于导出的类具有基类的接口,因此它可以向上转型至基类,这对后面要介绍的多态是至关重要的。