1. 组合语法

在新的类中产生现有类的对象,这种方法称为组合,该方法只是复用了现有程序代码的功能,而非它的形式,例如:

class Test1 {
  public void sayHello() {
    System.out.println("Hello World");
  }
}
class Test2 {
  private Test1 t1 = new Test1();
  public void getString() {
    t1.sayHello();
  }
}
public class Main {
  public static void main(String[] args) {
    Test2 t2 = new Test2();
    t2.getString();
  }
}

每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而却只有一个对象时,该方法便会被调用,例如:

class Test1 {
  public String toString() {
    return "Hello World";
  }
}
class Test2 {
  private Test1 t1 = new Test1();
  public void getString() {
    System.out.println("Say: " + t1);
  }
}
public class Main {
  public static void main(String[] args) {
    Test2 t2 = new Test2();
    t2.getString();
  }
}


2. 继承语法

继承是所有OOP语言和Java语言不可缺少的组成部分。当创建一个类时,如果没有明确指出要从其他类中继承,那么就是在隐式地从Java标准根类Object进行继承。在继承过程中,需要用关键字extends实现,例如:

class Test1 {
  public void sayHello() {
    System.out.println("Hello World");
  }
  public void sayBye() {
    System.out.println("Bye");
  }
}
class Test2 extends Test1 {
  public void sayHello() {
    super.sayHello();
    sayBye();
  }
}
public class Main {
  public static void main(String[] args) {
    Test2 t2 = new Test2();
    t2.sayHello();
  }
}

由于Test2是由关键字extends从Test1导出的,它可以自动获得Test1中非private的方法,因此,可以将继承视作是对类的复用。修改基类中定义的方法是可行的,Java用super关键字表示超类,可以在导出类中使用super来访问基类版本的方法。

如果一个程序中含有多个类,可以为每个类设置一个main()方法,这种技术可使每个类的单元测试变得简便。只有命令行所调用的那个类的main()方法会被调用,比如命令行是"java Test1",那么Test1.main()将会被调用,即使不是public类,public main()方法仍然会被调用,例如:

class Test1 {
  public void sayHello() {
    System.out.println("Hello World");
  }
  public void sayBye() {
    System.out.println("Bye");
  }
  public static void main(String[] args) {
    Test1 t1 = new Test1();
    t1.sayHello();
    t1.sayBye();
  }
}
class Test2 extends Test1 {
  public void sayHello() {
    super.sayHello();
    sayBye();
  }
  public static void main(String[] args) {
    Test2 t2 = new Test2();
    t2.sayHello();
    t2.sayBye();
  }
}
public class Main {
  public static void main(String[] args) {
    Test2 t2 = new Test2();
    t2.sayHello();
  }
}

导出类就像是一个与基类具有相同接口的新类,或许还有一些额外的方法和域,当创建一个导出类的对象时,该对象包含了一个基类的子对象,子对象被包装在导出类对象内部,并会被恰当初始化,例如:

class Test1 {
  public Test1() {
    System.out.println("Test1 constructor");
  }
}
class Test2 extends Test1 {
  public Test2() {
    System.out.println("Test2 constructor");
  }
}
public class Main {
  public static void main(String[] args) {
    Test2 t2 = new Test2();
  }
}

执行结果是先后输出"Test1 constructor"和"Test2 constructor",可以发现构建过程是从基类向外扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化,即使不为Test1创建构造器,编译器也会合成一个默认的构造器。


3. 代理

除了组合和继承外,代理是第三种关系,介于继承与组合之间。如果将一个成员对象置于所要构造的类中,与此同时我们在新类中暴露了该成员对象的所有方法以,例如:

public class SpaceShipControls {
  void up(int velocity) {}
  void down(int velocity) {}
}
public class SpaceShip extends SpaceShipControls {
  public static void main(String[] args) {
    SpaceShip ship = new SpaceShip();
    ship.up(100);
  }
}

SpaceShip包含SpaceShipControls,与此同时,SpaceShipControls的所有方法在SpaceShip中都暴露了出来,代理解决了此问题,例如:

public class SpaceShipControls {
  void up(int velocity) {}
  void down(int velocity) {}
  void reboot() {}
}
public class SpaceShip extends SpaceShipControls {
  private SpaceShipControls controls = new SpaceShipControls();
  public void up(int velocity) {
    controls.up(velocity);
  }
  public void down(int velocity) {
    controls.down(velocity);
  }
  public static void main(String[] args) {
    SpaceShip ship = new SpaceShip();
    ship.up(100);
  }
}


4. final

根据上下文环境,Java的关键字final的含义存在细微的区别,但通常指的是“无法改变的”。不想做改变可能出于两种理由:设计或效率。

许多编程语言都有某种方法告知编译器一块数据是恒定不变的,比如一个永不改变的编译时常量或一个在运行时被初始化的值,而不希望它被改变。对于编译期常量的情况,常量必须是基本数据类型,并且以关键字final表示,在对这个常量进行定义的时候,必须对其进行赋值。一个既是static又是final的域只占据一段不能改变的存储空间。当对对象引用而不是基本类型运用final时,表示final使引用恒定不变,一旦引用被初始化指向一个对象,就无法再指导它改为指向另一个对象,然而对象自身却是可以被修改的,例如:

class Value {
  int i;
  public Value(int i) {
    this.i = i;
  }
}
public class FinalData {
  private final int i = 10;
  private final Value v = new Value(20);
  public static void main(String[] args) {
    FinalData fd = new FinalData();
    // Error : fd.i++
    fd.v.i++;
    System.out.println(fd.i);
    System.out.println(fd.v.i);
  }
}

Java允许生成空白fina。所谓空白final是指被声明为final但又未给定初值的域,无论什么情况,编译器都确保空白final在使用前必须被初始化,但是,空白final在关键字final的使用上提供了更大的灵活性,例如:

class Poppet {
  private int i;
  Poppet(int i) {
    this.i = i;
  }
}
public class BlankFinal {
  private final int i;
  private final Poppet p;
  public BlankFinal(int x) {
    i = x;
    p = new Poppet(x);
  }
  public static void main(String[] args) {
    new BlankFinal(10);
  }
}

Java允许在参数列表中以声明的方式将参数指明为final,这意味着你无法在方法中更改参数引用所指向的对象,例如:

class Gizmo {
  public void spin() {}
}
public class FinalArguments {
  void with(final Gizmo g) {
    // Error : g = new Gizmo();
  }
  void without(Gizmo g) {
    g = new Gizmo();
    g.spin();
  }
  public static void main(String[] args) {
    FinalArguments fa = new FinalArguments();
    fa.without(null);
    fa.with(null);
  }
}

使用final方法可以把方法锁定,防止任何继承类修改它的含义。类中所有的private方法都隐式地指定为final的,可以对private方法添加final修饰词,但这并不能给方法增加任何额外含义,例如:

class Test1 {
  final void f() {}
}
class Test2 extends Test1 {
}
public class Main {
  public static void main(String[] args) {
    Test2 t = new Test2();
    t.f();
  }
}

当将某个类整体定义为final时,表示不能继承该类,例如:

final class Test {
  void f() {}
}
public class Main {
  public static void main(String[] args) {
    Test t = new Test();
    t.f();
  }
}

final类的域可以根据个人意愿选择为是或不是final,然后final类中所有的方法都隐式指定为是final的,因为无法覆盖它们。