复用代码是java众多引人注目的功能之一。但是要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事情。
7.1组合语法
将对象引用置于新类中。每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你只有一个对象时,该方法便会被调用。
初始化方式:
1,在定义对象的地方。
2,在类的构造器。
3,使用对象之前(惰性初始化)。
4,使用实例化。
//: reusing/Bath.java
// Constructor initialization with composition.
import static net.mindview.util.Print.*;
class Soap {
private String s;
Soap() {
print("Soap()");
s = "Constructed";
}
public String toString() { return s; }
}
public class Bath {
private String // Initializing at point of definition:
s1 = "Happy",
s2 = "Happy",
s3, s4;
private Soap castille;
private int i;
private float toy;
public Bath() {
print("Inside Bath()");
s3 = "Joy";
toy = 3.14f;
castille = new Soap();
}
// Instance initialization:
{ i = 47; }
public String toString() {
if(s4 == null) // Delayed initialization:
s4 = "Joy";
return
"s1 = " + s1 + "\n" +
"s2 = " + s2 + "\n" +
"s3 = " + s3 + "\n" +
"s4 = " + s4 + "\n" +
"i = " + i + "\n" +
"toy = " + toy + "\n" +
"castille = " + castille;
}
public static void main(String[] args) {
Bath b = new Bath();
print(b);
}
} /* Output:
Inside Bath()
Soap()
s1 = Happy
s2 = Happy
s3 = Joy
s4 = Joy
i = 47
toy = 3.14
castille = Constructed
*///:~
7.2,继承语法
继承是所有OOP语言和Java语言不可或缺的组成部分。
//: reusing/Detergent.java
// Inheritance syntax & properties.
import static net.mindview.util.Print.*;
class Cleanser {
private String s = "Cleanser";
public void append(String a) { s += a; }
public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public String toString() { return s; }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute(); x.apply(); x.scrub();
print(x);
}
}
public class Detergent extends Cleanser {
// Change a method:
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Add methods to the interface:
public void foam() { append(" foam()"); }
// Test the new class:
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
print(x);
print("Testing base class:");
Cleanser.main(args);
}
} /* Output:
Cleanser dilute() apply() Detergent.scrub() scrub() foam()
Testing base class:
Cleanser dilute() apply() scrub()
*///:~
可以为每个类创建一个main方法。这样会让测试显得比较简单。
7.2.1 初始化基类
继承并不是复制基类的接口。当创建一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你使用基类直接创建的对象是一样的。二者区别在于后者来自于外部,而基类的子对象被包装在导出类对象内部。构造过程是从基类开始向外扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。即使你不为导出类创建构造器,编译器也会默认的为导出类合成一个构造器,该构造器将调用基类的构造器。
//: reusing/Cartoon.java
// Constructor calls during inheritance.
import static net.mindview.util.Print.*;
class Art {
Art() { print("Art constructor"); }
}
class Drawing extends Art {
Drawing() { print("Drawing constructor"); }
}
public class Cartoon extends Drawing {
public Cartoon() { print("Cartoon constructor"); }
public static void main(String[] args) {
Cartoon x = new Cartoon();
}
} /* Output:
Art constructor
Drawing constructor
Cartoon constructor
*///:~
//: reusing/Chess.java
// Inheritance, constructors and arguments.
import static net.mindview.util.Print.*;
class Game {
Game(int i) {
print("Game constructor");
}
}
class BoardGame extends Game {
BoardGame(int i) {
super(i);
print("BoardGame constructor");
}
}
public class Chess extends BoardGame {
Chess() {
super(11);
print("Chess constructor");
}
public static void main(String[] args) {
Chess x = new Chess();
}
} /* Output:
Game constructor
BoardGame constructor
Chess constructor
*///:~
7.3,代理
第三种关系称为代理,java并没有提供它的直接支持。这是继承与组合之间的中庸之道。因为我们将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露该成员对象的所有方法(就像继承)。
//: reusing/SpaceShipControls.java
public class SpaceShipControls {
void up(int velocity) {}
void down(int velocity) {}
void left(int velocity) {}
void right(int velocity) {}
void forward(int velocity) {}
void back(int velocity) {}
void turboBoost() {}
} ///:~
太空飞船
//: reusing/SpaceShip.java
public class SpaceShip extends SpaceShipControls {
private String name;
public SpaceShip(String name) { this.name = name; }
public String toString() { return name; }
public static void main(String[] args) {
SpaceShip protector = new SpaceShip("NSEA Protector");
protector.forward(100);
}
} ///:~
SpaceShip并非真正的SpaceShipControls类型,且SpaceShipControls的所有方法都暴露给了SpaceShip。代理可以解决这个问题。
//: reusing/SpaceShipDelegation.java
public class SpaceShipDelegation {
private String name;
private SpaceShipControls controls =
new SpaceShipControls();
public SpaceShipDelegation(String name) {
this.name = name;
}
// Delegated methods:
public void back(int velocity) {
controls.back(velocity);
}
public void down(int velocity) {
controls.down(velocity);
}
public void forward(int velocity) {
controls.forward(velocity);
}
public void left(int velocity) {
controls.left(velocity);
}
public void right(int velocity) {
controls.right(velocity);
}
public void turboBoost() {
controls.turboBoost();
}
public void up(int velocity) {
controls.up(velocity);
}
public static void main(String[] args) {
SpaceShipDelegation protector =
new SpaceShipDelegation("NSEA Protector");
protector.forward(100);
}
} ///:~
7.4,组合使用组合和继承
//: reusing/PlaceSetting.java
// Combining composition & inheritance.
import static net.mindview.util.Print.*;
class Plate {
Plate(int i) {
print("Plate constructor");
}
}
class DinnerPlate extends Plate {
DinnerPlate(int i) {
super(i);
print("DinnerPlate constructor");
}
}
class Utensil {
Utensil(int i) {
print("Utensil constructor");
}
}
class Spoon extends Utensil {
Spoon(int i) {
super(i);
print("Spoon constructor");
}
}
class Fork extends Utensil {
Fork(int i) {
super(i);
print("Fork constructor");
}
}
class Knife extends Utensil {
Knife(int i) {
super(i);
print("Knife constructor");
}
}
// A cultural way of doing something:
class Custom {
Custom(int i) {
print("Custom constructor");
}
}
public class PlaceSetting extends Custom {
private Spoon sp;
private Fork frk;
private Knife kn;
private DinnerPlate pl;
public PlaceSetting(int i) {
super(i + 1);
sp = new Spoon(i + 2);
frk = new Fork(i + 3);
kn = new Knife(i + 4);
pl = new DinnerPlate(i + 5);
print("PlaceSetting constructor");
}
public static void main(String[] args) {
PlaceSetting x = new PlaceSetting(9);
}
} /* Output:
Custom constructor
Utensil constructor
Spoon constructor
Utensil constructor
Fork constructor
Utensil constructor
Knife constructor
Plate constructor
DinnerPlate constructor
PlaceSetting constructor
*///:~
虽然编译器强制要求初始化基类,并且要求在构造器起始处就这么做,但是编译器并不要求初始化成员变量。这里要非常小心。
7.4.1,保持正确清理
java中没有c++中析构函数的概念。当你亲自回收垃圾时要非常小心。
7.4.2,名称屏蔽
java中继承类的方法名可以和基类的方法名相同,参数列表不同即可。c++中则不行,需要屏蔽积累方法 。
7.5,在组合和继承之间选择
组合和继承都允许在新的类中放置子对象,组合是显式的这样做,而继承式隐式的这样做。
组合技术通常用于想在新类中使用现有类的功能而非它的接口的这种情形。
通常情况下新类的用户看到的是为新类定义的接口,而非所有的对象接口。所以通常在新类中嵌入一个现有类的private对象。有的时候让新类用户直接访问组合成分也是极有意义的,例如下例:
//: reusing/Car.java
// Composition with public objects.
class Engine {
public void start() {}
public void rev() {}
public void stop() {}
}
class Wheel {
public void inflate(int psi) {}
}
class Window {
public void rollup() {}
public void rolldown() {}
}
class Door {
public Window window = new Window();
public void open() {}
public void close() {}
}
public class Car {
public Engine engine = new Engine();
public Wheel[] wheel = new Wheel[4];
public Door
left = new Door(),
right = new Door(); // 2-door
public Car() {
for(int i = 0; i < 4; i++)
wheel[i] = new Wheel();
}
public static void main(String[] args) {
Car car = new Car();
car.left.window.rollup();
car.wheel[0].inflate(72);
}
} ///:~
7.6,protected关键字
关键字protected提供类访问权限,它指明“就类用户而言,这是private的,但对于任何继承于此类的导出类或其他任何位于同一包内的类来说,它却是可以访问的”。
//: reusing/Orc.java
// The protected keyword.
import static net.mindview.util.Print.*;
class Villain {
private String name;
protected void set(String nm) { name = nm; }
public Villain(String name) { this.name = name; }
public String toString() {
return "I'm a Villain and my name is " + name;
}
}
public class Orc extends Villain {
private int orcNumber;
public Orc(String name, int orcNumber) {
super(name);
this.orcNumber = orcNumber;
}
public void change(String name, int orcNumber) {
set(name); // Available because it's protected
this.orcNumber = orcNumber;
}
public String toString() {
return "Orc " + orcNumber + ": " + super.toString();
}
public static void main(String[] args) {
Orc orc = new Orc("Limburger", 12);
print(orc);
orc.change("Bob", 19);
print(orc);
}
} /* Output:
Orc 12: I'm a Villain and my name is Limburger
Orc 19: I'm a Villain and my name is Bob
*///:~
changge()可以访问set()方法,因为set是包权限。
7.7,向上转型
“为新的类提供方法”并不是集成技术中最重要的方面,其最重要的方面是用来表示新类和基类之间的联系。
//: reusing/Wind.java
// Inheritance & upcasting.
class Instrument {
public void play() {}
static void tune(Instrument i) {
// ...
i.play();
}
}
// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
public static void main(String[] args) {
Wind flute = new Wind();
Instrument.tune(flute); // Upcasting
}
} ///:~
在tune()中,程序代码可以对instrument和它的导出类起作用,这种将wind引用转换为instrument引用的动作,我们称之为向上转型。
7.7.1,为什么称为向上转型
这个是根据传统的继承图绘制而来的。由于向上转型是从一个较专业的类型向较通用类型转换,所以总是很安全的。
7.7.2,组合和继承的关系
虽然面向对象中继承是一个很重要的特征,但是在使用的过程中要慎用。当涉及到需要向上转型是必须要使用继承的,其他的情况下要搞清楚父子类关系才能使用继承。
7.8,final关键字
java中final关键字根据不同的位置含义可能会有一些小的差异,但是大概的意思就是“这是无法改变的意思”。不想被改变是出于两个方面的理由:设计或效率。
7.8.1,final数据
java中类常量必须为基本数据类型,并且以final关键字修饰。在对这个常量进行定义的时候必须对其赋值。一个既是static又是final的域只占据一段不能改变的存储空间。当对象引用被final修饰,代表的是引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它指向另一个对象。而对象自身却是可以修改的。java并未提供使任何对象恒久不变的途径。这一限制同样适用于数组,数组也是对象。
//: reusing/FinalData.java
// The effect of final on fields.
import java.util.*;
import static net.mindview.util.Print.*;
class Value {
int i; // Package access
public Value(int i) { this.i = i; }
}
public class FinalData {
private static Random rand = new Random(47);
private String id;
public FinalData(String id) { this.id = id; }
// Can be compile-time constants:
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
// Typical public constant:
public static final int VALUE_THREE = 39;
// Cannot be compile-time constants:
private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value VAL_3 = new Value(33);
// Arrays:
private final int[] a = { 1, 2, 3, 4, 5, 6 };
public String toString() {
return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5;
}
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
//! fd1.valueOne++; // Error: can't change value
fd1.v2.i++; // Object isn't constant!
fd1.v1 = new Value(9); // OK -- not final
for(int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Object isn't constant!
//! fd1.v2 = new Value(0); // Error: Can't
//! fd1.VAL_3 = new Value(1); // change reference
//! fd1.a = new int[3];
print(fd1);
print("Creating new FinalData");
FinalData fd2 = new FinalData("fd2");
print(fd1);
print(fd2);
}
} /* Output:
fd1: i4 = 15, INT_5 = 18
Creating new FinalData
fd1: i4 = 15, INT_5 = 18
fd2: i4 = 13, INT_5 = 18
*///:~
java允许生成“空白final”,所谓空白final是指被声明为final但又未给定初值的域。无论什么情况,编译器都确保空白final在使用前完成初始化。
//: reusing/BlankFinal.java
// "Blank" final fields.
class Poppet {
private int i;
Poppet(int ii) { i = ii; }
}
public class BlankFinal {
private final int i = 0; // Initialized final
private final int j; // Blank final
private final Poppet p; // Blank final reference
// Blank finals MUST be initialized in the constructor:
public BlankFinal() {
j = 1; // Initialize blank final
p = new Poppet(1); // Initialize blank final reference
}
public BlankFinal(int x) {
j = x; // Initialize blank final
p = new Poppet(x); // Initialize blank final reference
}
public static void main(String[] args) {
new BlankFinal();
new BlankFinal(47);
}
} ///:~
java允许在参数列表中以声明的方式将参数指明为final。意味着你无法在方法中更改参数引用指向的对象。
//: reusing/FinalArguments.java
// Using "final" with method arguments.
class Gizmo {
public void spin() {}
}
public class FinalArguments {
void with(final Gizmo g) {
//! g = new Gizmo(); // Illegal -- g is final
}
void without(Gizmo g) {
g = new Gizmo(); // OK -- g not final
g.spin();
}
// void f(final int i) { i++; } // Can't change
// You can only read from a final primitive:
int g(final int i) { return i + 1; }
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
}
} ///:~
使用final方法的原因有两个。1,把方法锁定,防止任何继承类修改它的含义。2,确保在继承中方法的行为不变,并且不会被覆盖。
final和private关键字
类中所有的private方法都隐式的指定为final的。由于无法访问private方法,所以也无法覆盖它。在使用过程中我们可能会误解子类覆盖了父类的私有方法而编译器没有报错,其实这是我们在子类新建了一个父类名称和参数列表返回值相同的方法而已。
//: reusing/FinalOverridingIllusion.java
// It only looks like you can override
// a private or private final method.
import static net.mindview.util.Print.*;
class WithFinals {
// Identical to "private" alone:
private final void f() { print("WithFinals.f()"); }
// Also automatically "final":
private void g() { print("WithFinals.g()"); }
}
class OverridingPrivate extends WithFinals {
private final void f() {
print("OverridingPrivate.f()");
}
private void g() {
print("OverridingPrivate.g()");
}
}
class OverridingPrivate2 extends OverridingPrivate {
public final void f() {
print("OverridingPrivate2.f()");
}
public void g() {
print("OverridingPrivate2.g()");
}
}
public class FinalOverridingIllusion {
public static void main(String[] args) {
OverridingPrivate2 op2 = new OverridingPrivate2();
op2.f();
op2.g();
// You can upcast:
OverridingPrivate op = op2;
// But you can't call the methods:
//! op.f();
//! op.g();
// Same here:
WithFinals wf = op2;
//! wf.f();
//! wf.g();
}
} /* Output:
OverridingPrivate2.f()
OverridingPrivate2.g()
*///:~
final类
当某个类的整体定义为final时,就表明了你不打算继承该类,也不允许其它类这样做。注意:final类的域可以根据个人意愿选择是否为final,因为final类禁止继承,所以final类中所有方法都隐式指定为是final类的,因为无法覆盖它们。
7.8.4,有关final的忠告
1,通用类最好不要声明为final
2,子类继承自父类,但不能说子类是父类。
7.9,初始化及类的加载
7.9.1,继承与初始化
//: reusing/Beetle.java
// The full process of initialization.
import static net.mindview.util.Print.*;
class Insect {
private int i = 9;
protected int j;
Insect() {
print("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 =
printInit("static Insect.x1 initialized");
static int printInit(String s) {
print(s);
return 47;
}
}
public class Beetle extends Insect {
private int k = printInit("Beetle.k initialized");
public Beetle() {
print("k = " + k);
print("j = " + j);
}
private static int x2 =
printInit("static Beetle.x2 initialized");
public static void main(String[] args) {
print("Beetle constructor");
Beetle b = new Beetle();
}
} /* Output:
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
*///:~
7.10,总结
继承和组合都能形成新类型。组合一般是将现有类型作为新类型底层实现的一部分来加以复用,而继承复用的是接口。所以继承可以向上转型。