组合就是把别的类的实例,放到一个新的类里去。
Create a simple class. Inside a second class, define a reference to an object of the first class. Use lazy initialization to instantiate this object.
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
/**
* Compose of simple objects
*/
public class IniObj {
public String toString(){
return this.obj1.s;
}
public SimpObj obj1;
public static void main(String[] args){
IniObj testIniObj = new IniObj();
//惰性初始化
testIniObj.obj1=new SimpObj(); //需要先创建SimpObj实例
testIniObj.obj1.s="Now I need to use it!"; 才能对其中的值s赋值
System.out.println(testIniObj.toString());
}
}
class SimpObj {
//not initialized
String s;
}
为了继承,一般规则是将所有数据成员设置成private,而将所有成员方法都设置成public,或protected。
这个原则还是延续了一贯的“数据不可见,仅接口方法可见”的原则。这个时候,继承的子类对类中的数据是无法直接操作的,唯一可用的就是继承下来的饿接口方法。
Inherit a new class from class Detergent. Override scrub( ) and add a new method called sterilize( ).
/**
* Tide is a famous brand of laundry detergent
* inherit from Detergent class in the book
*/
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
public class Tide extends Detergent {
public void sterilize(){append(" sterilize() ");}
public void scrub(){append(" Tide."); super.scrub();}
/**
* main method
* @param args void
*/
public static void main(String[] args){
Tide t = new Tide();
t.dilute();
t.apply();
t.scrub();
t.foam();
t.sterilize();
System.out.println(t);
}
}
//Output: Cleanser dilute() apply() Tide.Detergent.scrub() scrub() foam() sterilize()
!!注意:前面说基类数据一般都设成private。但这样的话,继承后子类是无法访问自己的数据的。
比如在练习2中,Tide
类是继承自Detergent
类。Detergent中有私有字段,private String s;
。但有一点非常奇怪:当我们创建Tide型新对象t
的时候,t.s
还是受private保护,无法访问的。
原因就涉及到Java对继承特性的实现方法:当创建一个子类对象的时候,子类对象里就包含着一个父类对象。也就是说:Java自动在子类构造器中,插入父类构造器。
//爷爷
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 //造儿子
//所以实际上儿子构造器干了这些事儿:
public Cartoon() {
Drawing() {
Art() {
print("Art constructor");
}
print("Drawing constructor");
}
print("Cartoon constructor");
}
必须显式地写出来,而且必须是在构造器的起始处,不然默认调用无参数的构造器。
//爷爷
class Art {
Art() { print("Art constructor"); }
Art(String name) { print(name+" Art constructor"); }
}
//爸爸
class Drawing extends Art {
Drawing() { print("Drawing constructor"); }
Drawing(String name) { super("爷爷:"); print(name+" Drawing constructor"); }
}
//儿子
public class Cartoon extends Drawing {
public Cartoon() { print("Cartoon constructor"); }
public Cartoon(String name) { super("爸爸:"); print(name+" Cartoon constructor"); }
public static void main(String[] args) {
Cartoon x = new Cartoon("儿子");
}
}
//输出:
//Output:
// 爷爷: Art constructor
// 爸爸: Drawing constructor
// 儿子: Cartoon constructor
Exercise 3: (2) Prove the previous sentence.
Exercise 4: (2) Prove that the base-class constructors are (a) always called and (b) called before derived-class constructors.
Exercise 5: (1) Create two classes, A and B, with default constructors (empty argument lists) that announce themselves. Inherit a new class called C from A, and create a member of class B inside C. Do not create a constructor for C. Create an object of class C and observe the results.
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
//MacBook is a kind of Laptop
public class MacBook extends Laptop {
//constructor
MacBook(){
System.out.println("of Apple Inc.");
}
private RetinaScreen macScreen=new RetinaScreen();
//main method
public static void main(String[] args){
MacBook macDeWei=new MacBook();
}
}
//base class
class Laptop{
//constructor
Laptop(){
System.out.print("I am a Laptop ");
}
}
class RetinaScreen{
RetinaScreen(){
System.out.println("This is a Retina screen. ");
}
}
// 练习3,4 output:
// I am a Laptop of Apple Inc.
// 练习5: 用标注去掉MacBook的构造函数以后:
// Output: I am a Laptop This is a Retina screen.
Prove that if you don’t call the base-class constructor in BoardGame( ), the compiler will complain that it can’t find a constructor of the form Game( ).
/**
* 证明子类会自动调动父类构造器
* @author wei.shen
* @version 1.0
*/
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
//public class
public class Chess extends BoardGame {
Chess() {
super(11);
System.out.println("Chess constructor");
}
public static void main(String[] args) {
Chess x = new Chess();
}
}
//only visible in package
class BoardGame extends Game {
BoardGame(int i) {
//super(i); //如果父类构造器带参数,必须显式调用
System.out.println("BoardGame constructor");
}
}
//only visible in package
class Game {
Game(int i) {
System.out.println("Game constructor");
}
}
中间一行super(i)
被注释掉了,BoardGame
无法调用Game
的构造器。如果取消注释,就会恢复正常。
##系统报错
/Users/Wei/java/com/ciaoshen/thinkinjava/chapter7/Chess.java:25: error: constructor Game in class Game cannot be applied to given types;
BoardGame(int i) {
^
required: int
found: no arguments
reason: actual and formal argument lists differ in length
1 error
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
//MacBook is a kind of Laptop
public class MacBook extends Laptop {
//default constructor: exercise 8
MacBook(){
super(0);
this.owner="???";
System.out.println(" of Apple Inc. ");
System.out.println(this.owner+" bought me. ");
this.macScreen=new RetinaScreen(0);
}
//constructor: exercise 7
MacBook(int macId, int screenPixels, String theOwner){
super(macId);
this.owner=theOwner;
System.out.println(" of Apple Inc. ");
System.out.println(theOwner+" bought me. ");
this.macScreen=new RetinaScreen(screenPixels);
}
private String owner;
private RetinaScreen macScreen;
/**
* main method
* @param args void
*/
public static void main(String[] args){
//execise 7
MacBook macDeQui=new MacBook(12345, 8000000, "Wei");
//exercise 8
MacBook macDeWei=new MacBook();
}
}
//base class
class Laptop{
//constructor
Laptop(int id){
System.out.print("I am the Laptop No."+id);
}
}
//one of the MacBook component
class RetinaScreen{
RetinaScreen(int pixels){
System.out.println("I have a Retina screen with "+pixels+" pixels!");
}
}
输出结果:
//练习7
I am the Laptop No.12345of Apple Inc.
Wei bought me.
I have a Retina screen with 8000000 pixels!
//练习8
I am the Laptop No.0of Apple Inc.
??? bought me.
I have a Retina screen with 0 pixels!
Exercise 9: (2) Create a class called Root that contains an instance of each of the classes (that you also create) named Component1, Component2, and Component3. Derive a class Stem from Root that also contains an instance of each “component.” All classes should have default constructors that print a message about that class.
Exercise 10: (1) Modify the previous exercise so that each class only has non-default constructors.
/**
* Stem(树干)代表一棵树,继承自父类Root(根茎)
* Stem(树干)开始有自己的“name(名字)”
* 父类Root(根茎)有三个组成部分:树皮,木质部,和木髓
* @author wei.shen@iro.umontreal.ca
* @version 1.0
* 这个类写完,编译一次过,拥吻自己,撒花
*/
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
//树干
//the only visible class
public class Stem extends Root{
//default constructor
Stem(){}
//constructor with params
Stem(String phloemColor,int xylemLayer,int pithDiameter,String treeName){
super(phloemColor,xylemLayer,pithDiameter);
this.name=treeName;
System.out.println("It is called "+this.name+".");
}
//private fields
private String name="Tree"; //default name
/**
* MAIN
* @param args void
*/
public static void main(String[] args){
//Exercise 9
Stem newTree=new Stem();
//Exercise 10:雪曼将军树是目前世界上最大的树
Stem generalSherman=new Stem("red",2500,500,"General Sherman");
}
}
//根茎
//base class of Stem
class Root {
//default constructor
Root(){
this.treePhloem=new Phloem();
this.treeXylem=new Xylem();
this.treePith=new Pith();
}
//constructor with params
Root(String phloemColor,int xylemLayer,int pithDiameter){
this.treePhloem=new Phloem(phloemColor);
this.treeXylem=new Xylem(xylemLayer);
this.treePith=new Pith(pithDiameter);
}
//private fields
private Phloem treePhloem;
private Xylem treeXylem;
private Pith treePith;
}
//树皮(韧皮部)
//one of the three components of Root
class Phloem {
//friendly default constructor
Phloem(){System.out.println("The outer of a tree is the Phloem.");}
//constructor with param
Phloem(String color){System.out.println("This tree has "+color+" Phloem.");}
}
//木质部(木导管纤维)
//one of the three components of Root
class Xylem {
//friendly default constructor
Xylem(){System.out.println("The inner of a tree is the Xylem.");}
//constructor with param
Xylem(int numLayer){System.out.println("We can estimate the age of this tree by its Annual Growth Rings. This tree is "+numLayer+" years old.");}
}
//木髓(最里层)
//one of the three components of Root
class Pith {
//friendly default constructor
Pith(){System.out.println("The core of a tree is the Pith.");}
//constructor with param
Pith(int diameter){System.out.println("The diameter of its Pith is "+diameter+"CM.");}
}
输出:
##Exercise 9:
The outer of a tree is the Phloem.
The inner of a tree is the Xylem.
The core of a tree is the Pith.
##Exercise 10:
This tree has red Phloem.
We can estimate the age of this tree by its Annual Growth Rings. This tree is 2500 years old.
The diameter of its Pith is 500CM.
It is called General Sherman.
代理其实更像组合,而不是继承。用组合的方法,给原先的类穿个衣服,实现了继承的功能。而且比继承强大,因为可以选择性继承。 Java不直接支持继承。在Java里,继承更像是一种设计模式,把旧类包装成一个新类。
//给飞船控制器贴个名字,就变一艘飞船了
public class SpaceShipDelegation {
private SpaceShipControls controls =
new SpaceShipControls(); //飞船就只有一个控制器
private String name; //只不过贴个名字而已
//所有控制器的方法都重新起个名字
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);
}
Exercise 11: (3) Modify Detergent.java so that it uses delegation.
/**
* Encapsulate the Detergent Class using Delegation
*/
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
public class DetergentDelegation {
//全新接口
//Encapsulate the public methods of Cleanser
public void append(String a) { theCleanser.append(a); }
public void dilute() { theCleanser.dilute(); }
public void apply() { theCleanser.apply(); }
public String toString() { return theCleanser.toString(); }
public void scrub() {
append(" Detergent.");
theCleanser.scrub(); // Call base-class version
}
// Add a new method to the interface:
public void foam() { theCleanser.append(" foam()"); }
//private fields: 对用户完全隐藏Cleanser
//compose of Cleanser class
private Cleanser theCleanser=new Cleanser();
/**
* main method
* @param args void
*/
public static void main(String[] args) {
DetergentDelegation x = new DetergentDelegation();
x.dilute();
x.apply();
x.scrub();
x.foam();
System.out.println(x);
//System.out.println(x.theCleanser); //内部theCleanser对用户不可见
}
}
//Output: Cleanser dilute() apply() Detergent. scrub() foam()
很明显,继承的子类可以重载父类方法。只要子类的方法不完全和父类中的方法重合(返回值,参数,方法名完全相同),新方法会被重载,而不是重写。
真的需要重写覆盖父类方法的时候,为了确保我们不是仅仅重载了方法,可以在需要重写的方法前,加一个@Override
标签。这样系统会自动检察是不是重写覆盖成功,如果没有会报错。
Exercise 13: Create a class with a method that is overloaded three times. Inherit a new class, add a new overloading of the method, and show that all four methods are available in the derived class.
三个重载方法。!!注意:仅返回值不同,不能重载方法。编译器通不过,不知道加载哪个。
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
public class TestOverload {
//public method
public void overloadedMethod(){
System.out.println("overloadedMethod(): No parameter");
}
public void overloadedMethod(int i){
System.out.println("overloadedMethod(): int");
}
public void overloadedMethod(String s){
System.out.println("overloadedMethod(): String");
}
/******************************************************
*
* 这样以不同的返回值,区分不同方法,编译器不接受。
*
public int overloadedMethod(int i){
System.out.println("overloadedMethod(): int; return int");
return i;
}
*
******************************************************/
}
继承类继续重载:
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
public class TestOverride extends TestOverload{
//another overload
public void overloadedMethod(char c){
System.out.println("overloadedMethod(): char");
}
//now I want to override
//取消下面@Override的注释,会提示重写失败。
//@Override
public void overloadedMethod(String s, int a){
System.out.println("overloadedMethod(): String is overrided");
}
/**
* MAIN
* @param args void
*/
public static void main(String[] args){
TestOverride myOverride=new TestOverride();
myOverride.overloadedMethod();
myOverride.overloadedMethod(1);
myOverride.overloadedMethod("Hello");
myOverride.overloadedMethod((char)1);
}
}
结果四个方法都重载成功:
overloadedMethod(): No parameter
overloadedMethod(): int
overloadedMethod(): String
overloadedMethod(): char
但如果取消@Override
的注释,@Override
会检查最后那个方法是否重写成功。如果重写失败,抛出错误:
/Users/Wei/java/com/ciaoshen/thinkinjava/chapter7/TestOverride.java:20: error: method does not override or implement a method from a supertype
@Override
^
1 error
到底是用组合好,还是继承好。无法简单地下结论。作者给出两者形象化的思考:
IS-A
关系。就是使用某个现有通用类,有通用的一组接口,只是开发出一个特定版本。比如通用类是车->公共汽车。HAS-A
关系。本质不是同一个东西,接口基本不一样,我们只是想用某些功能。比如发动机->车。!!!注意:继承要慎用!作者提醒我们:“继承”在实战中并不太常用。不应该尽可能地使用继承,而是要慎重。用之前需要问自己一个问题:是否需要从导出类向基类进行向上转型?只有必须向上转型的情况才需要用继承。
所以正确的习惯是:优先考虑“组合”或“代理”。只在确实必要时才考虑“继承”。
无论是对于组合还是继承,作者反复强调需要遵守的谨慎的设计风格:
这里提到的“接口”的概念,我觉得很好。类的本质是对一组数据和流程的封装。外部可见的应该只有我们定义的想让人看到的接口。所以理想的情况就是断掉所有数据字段的访问权。只给几个公开的访问接口。
private的好处就是,无论是对于组合还是继承,private部分的代码都是完全不可见的。就算是被继承,在子类里也没有这些字段或方法。
这时候,应该讨论一下protected的意义。在尽可能多的private的前提下,哪些方法是我希望外部子类能够继承的接口,可以法外开恩设成protected。
构造函数随意设成public不是一个好习惯。比较谨慎的方法可以设成private,然后写一个public接口方法,方便控制创建实例。
!!!注意:需要被继承的基类,构造函数不要设成private,否则子类无法访问基类构造函数。!一般可以设成默认frendly或者protected。
chapter5包里的InPackageTank类,私有字段,protected接口方法。
package com.ciaoshen.thinkinjava.chapter5;
import java.util.*;
public class InPackageTank {
//default constructor
public InPackageTank(String name){
this.name=name;
}
//shot one bullet
protected void shot(){
if (this.bullet>0){
this.bullet --;
} else {
System.out.println("Out of bullet! Please recharge!");
}
}
//recharge the bullet
protected void charge(int inNum){
this.bullet+=inNum;
System.out.println("Charge successfully!");
}
//reture the number of reste bullet
protected int howManyBullet(){return this.bullet;}
//private field
private int bullet = 100;
private String name = new String();
}
在chapter7包里直接调用我们的坦克:
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
import com.ciaoshen.thinkinjava.chapter5.*;
public class CallTank {
/**
* MAIN
* @param args void
*/
public static void main(String args[]){
InPackageTank myTank = new InPackageTank("Tank1");
System.out.println(myTank.howManyBullet());
tank1.shot();
tank1.shot();
System.out.println(myTank.howManyBullet());
}
}
结果调用不动,报错:方法是受保护的。
/Users/Wei/java/com/ciaoshen/thinkinjava/chapter7/CallTank.java:21: error: howManyBullet() has protected access in InPackageTank
System.out.println(myTank.howManyBullet());
^
/Users/Wei/java/com/ciaoshen/thinkinjava/chapter7/CallTank.java:22: error: cannot find symbol
tank1.shot();
^
继承了包外的坦克类之后,调用成功。
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
import com.ciaoshen.thinkinjava.chapter5.*;
public class CallTank extends InPackageTank{
//inherit constructor
public CallTank(String name){super(name);}
/**
* MAIN
* @param args void
*/
public static void main(String args[]){
//没继承之前
//InPackageTank myTank = new InPackageTank("Tank1");
//继承以后可以调用Protected方法
CallTank myTank=new CallTank("Panzerkampfwagen VI Tiger"); //二战德军最强坦克
System.out.println(myTank.getName()+" has "+myTank.howManyBullet()+" bullets left.");
myTank.shot();
myTank.shot();
System.out.println(myTank.getName()+" has "+myTank.howManyBullet()+" bullets left.");
}
}
最后输出:
Panzerkampfwagen VI Tiger has 100 bullets left.
Panzerkampfwagen VI Tiger has 98 bullets left.
Exercise 16: (2) Create a class called Amphibian. From this, inherit a class called Frog. Put appropriate methods in the base class. In main( ), create a Frog and upcast it to Amphibian and demonstrate that all the methods still work.
Exercise 17: (1) Modify Exercise 16 so that Frog overrides the method definitions from the base class (provides new definitions using the same method signatures). Note what happens in main( ). 基类–两栖类:
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
public class Amphibian {
//constructor
public Amphibian(){this.onLand=false;}
//public methods
public void swim(){
if(onLand){
this.onLand=false;
System.out.println("PuPu...");
}
}
public void landings(){
if(!onLand){
this.onLand=true;
System.out.println("DuangDuang...");
}
}
//private fields
boolean onLand;
}
导出类–青蛙:
package com.ciaoshen.thinkinjava.chapter7;
import java.util.*;
public class Frog extends Amphibian{
//public methods
public void swim(){
if(onLand){
this.onLand=false;
System.out.println("GuaGuaGua...");
}
}
public void landings(){
if(!onLand){
this.onLand=true;
System.out.println("PiaPia...");
}
}
//private fields
/**
* MAIN
* @param args void
*/
public static void main(String[] args){
Amphibian someAmphibian=new Frog();
someAmphibian.landings();
someAmphibian.swim();
}
}
青蛙类里不重写swim()
和landings()
方法的话,照搬两栖类的结果:
DuangDuang...
PuTong...
要是重写swim()
和landings()
方法,马上变输出青蛙的叫声:
PiaPia...
GuaGua...
这其实证明了java具有多态性。因为java属于后期绑定,编译完之后,并没有把两栖类的方法都加进去,而是根据main线程的实际调用再去确认具体行为。
final
关键字,意味着这个字段,只能被赋值一次!
因为final关键字规定了此字段只能被赋值一次:
!!!注意:所以Final
基本型字段声明的时候系统不会给默认值。因为系统给了默认值的话,我们无法再赋值了。
运行结果报错:
public class StaticFinalClass {
//FINALFINAL 1号赋值点:声明的时候
final public int FINALFINAL;
//FINALFINAL 2号赋值点:构造函数
public StaticFinalClass() {
this.FINALFINAL=101;
//this.FINALFINAL=102; //一次赋值以后,不能再重新赋值
//this.FINALFINAL=103; // 这就是Final的意义
}
//FINALFINAL 3号赋值点:block
{
//this.FINALFINAL=104; //一次赋值以后,不能再重新赋值
// 这就是Final的意义。
}
}
!!!注意:Final static
全局静态常量,不能在构造函数里声明。因为,static意味着构造函数每创建一个实例都会为其赋值。但final意味着它只能被赋值一次,所以矛盾。
public class StaticFinalClass {
//FINALSTATIC 1号赋值点:声明的时候
final public static int FINALSTATIC;
//FINALSTATIC 2号赋值点:static block
static {
FINALSTATIC=1000;
}
//构造函数里不能声明final static静态常量
public StaticFinalClass() {
//StaticFinalClass.FINALSTATIC=1001; //报错
}
}
一般像这样带public static final
关键字修饰基本型,就是典型的公开静态常量。不但编译时就确认它的值,而且全局独一份儿,而且所有包都可以访问。
public static final int CONSTANT_VALUE=100;
java传递参数的时候传递的不是参数的拷贝,函数内部可以直接改变参数的值。如果给函数传递参数的饿时候加上final,函数内不能改变参数的值。这个特性主要用来向匿名内部类中传递参数用。
void with(final Gizmo g) {
g = new Gizmo(); //g是final,不许改变g对对象的引用
}
void with(final int i) {
i=5; //基本型就彻底不许改了
}
方法被声明称final方法,继承它的子类就不能对父类中的此方法进行重写,覆盖。
//子类尝试重写父类方法
public class Son extends Father {
public String toString(){return "I am son.";}
}
class Father {
//父类的final方法,拒绝被重写
final public String toString(){return "I am father.";}
}
运行后系统报错:
/Users/Wei/java/com/ciaoshen/thinkinjava/chapter7/Son.java:21: error: toString() in Son cannot override toString() in GrandFather
public String toString(){return super.toString()+" I am the son.";}
^
overridden method is final
1 error
其实类中的private方法,都相当于final方法,因为它对外部类来说不可见,所以没人能够重写它。
类前被加上final之后,此类无法再被继承。
练习16中的基类两栖类,被加上final限定之后,final public class Amphibian
,无法被Frog
继承。系统抛出错误:
/Users/Wei/java/com/ciaoshen/thinkinjava/chapter7/Frog.java:11: error: cannot inherit from final Amphibian
public class Frog extends Amphibian{
^
1 error