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的,因为无法覆盖它们。