复用代码是java众多引人注目的功能之一,但要想成为极具革命性的语言,仅仅能复制代码并对之加以改变是不够的,它还必须能够做更多的事情
一.组合语法
组合只需要将对象引用置于新类中即可。
class WaterSource {
private String s;
WaterSource() {
System.out.println("WaterSource()");
s = "Constructed";
}
public String toString() { return s; }
}
public class SprinklerSystem {
private String valve1, valve2, valve3, valve4;
private WaterSource source = new WaterSource();
private int i;
private float f;
public String toString() {
return
"valve1 = " + valve1 + " " +
"valve2 = " + valve2 + " " +
"valve3 = " + valve3 + " " +
"valve4 = " + valve4 + "\n" +
"i = " + i + " " + "f = " + f + " " +
"source = " + source;
}
public static void main(String[] args) {
SprinklerSystem sprinklers = new SprinklerSystem();
System.out.println(sprinklers);
}
} /* Output:
WaterSource()
valve1 = null valve2 = null valve3 = null valve4 = null
i = 0 f = 0.0 source = Constructed
*///:~
每个非基本类型的对象都有一个toString()方法,当编译器需要一个String而你只有一个对象的时候,该方法会被调用,重写toString()可以返回你想要的格式的字符串。
编译器并不是为每一个引用都创建默认对象,这一点是很有意义的。因为若要真的那么做就会在许多情况下增加不必要的负担,如果想要初始化这些引用,可以再下列位置进行:
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
*///:~
二.继承语法
继承是所有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()
*///:~
可以发现在Detegent类中可以调用父类的方法。
初始化基类:在创建一个导出类的对象时,该对象包含了一个基类的子对象,这个子对象和直接用基类创建的对象是一样的。二者的区别在于后者来自外部,而基类的子对象被包装在导出类对象的内部。
从下面的例子可以看出三层继承是如何工作的:
//: 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
*///:~
构造过程是从基类向外扩散的,即加载一个类的时候会先加载他的父类。
带参数的构造器:
上例中各个类均含有默认构造器,即这些构造器都不带参数,编译器可以轻松地调用他们是因为不需要考虑传递了什么参数的问题,但是如果没有默认的构造器,或者想调用一个带参数的构造器就必须用关键字super()显式的编写调用基类构造器的语句:
//: 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
*///:~
三.代理
第三种关系称之为代理,java并没有提供对它的支持,这是继承与组合之间的中庸之道,因为我们将一个成员对象置于所要构造的类中。但是与此同时我们在新的类中暴露了该成员对象的所有方法。
//: 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);
}
} ///:~
观察这个例子,可以发现飞船类是借助于controls对象来调用模块的方法。使用代理可以拥有更多的控制能力,因为可以选择只提供在成员对象中的方法的某个子集。
四.结合使用组合和继承
同时使用组合和继承是很常见的事,下面的例子展示了使用这两种技术,并配以必要的构造器初始化,来创建更复杂的类:
//: 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
*///:~
关于try和finally的简单介绍:
try表示下面的是所谓的保护区,意味着它需要被特殊处理,其中一项特殊处理就是无论try块是怎么退出的,保护区后面的finally子句中的代码总是要被执行的,这里的finally子句表示的是“无论发生什么事”后面的语句总是要执行的。
五.组合与继承的选择
组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形,在新的类中嵌入某个对象,让其实现所需的功能,但新类的用户看到的只是为新类定义的接口,而非所嵌入对象的接口。
六.protected关键字
protected的意义在于:就类用户而言,这是private的,但是对于任何继承于此类的导出类或者其他任何位于同一个包内的类来说,它却是可以访问的。
尽管可以创建protected域,但是最好的方式还是将域保持为private,你应当一直保留更改底层实现的权利。然后通过protected方法来控制类继承者的访问权限。
七.向上转型
要转型,首先要有继承。继承是面向对象语言中一个代码复用的机制,简单说就是子类继承了父类中的非私有属性和可以继承的方法,然后子类可以继续扩展自己的属性及方法。
向上转型:子类对象转为父类,父类可以是接口。公式:Father f = new Son();Father是父类或接口,son是子类。
向下转型:父类对象转为子类。公式:Son s = (Son)f;
八.final关键字
根据上下文环境,java的关键字final含义存在细微区别,但是通常指“这是无法改变的”,不想改变出于两种理由:设计或者效率。
一下谈论三种情况:数据,方法和类。
final数据:
很多编程语言都有某种方法,来向编译器告知一块数据是恒定不变的,有时数据的恒定不变是很有用的
1.一个永远不改变的编译时常量。
2.一个在运行时被初始化的值,而你不希望它被改变。
对于编译期常量这种情况,编译器可以将该常量值代入任何可能用到它的计算式中,也就是说可以在编译时执行计算,这减轻了一些运行时的负担,在java中这类常量必须是基本数据类型,并且以final表示,在这个常量进行编译时,必须对其进行赋值。
一个即使static又是final的域只占据一段不能改变的存储空间。
当对对象引用而不是基本类型运用final时,final使引用不可改变,一旦引用被初始化指向一个对象,就无法再改变使它指向另一个对象,然而对象本身是可以修改的。
按照惯例,既是final又是static的域将用大写表示,并且使用下划线分隔各个单词。
定义为public则可以被用于包之外,定义为static则强调只有一份,定义为final则说明他是一个常量,带有恒定的初始值。
空白final:
java允许生成空白final,所谓空白final是指被声明为final但又没有给定初始值,无论什么时候编译器都会保证空白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);
}
} ///:~
必须在域的定义处或者每个构造器中用表达式对final进行赋值,这正是final域在使用前总是被初始化的原因所在。
final参数:
java允许在参数列表中以声明方式将参数声明为final,这意味着无法在方法中更改参数引用所指向的对象。可以读参数但是不可以修改参数,这个特性主要用来向匿名内部类传递数据。
final方法:
使用final方法原因有主要是讲方法锁定,以防止任何继承类修改它的含义。
final方法和private关键字:
类中的private方法都是隐式的指定为final的,由于无法取用private方法,所以也就无法覆盖他。
所以在继承类中“覆盖”private方法其实只是重新写了一个具有相同名称的方法。
final类:
当某个类定义为final时,不能继承这个类