使用组合和继承,可以利用现有类型生成新类型来复用代码,而不必再从头开始编写,能够达到使用类而不破坏现有程序代码。
一、组合
只需在新的类中产生现有类的对象,新的类是由这些现有类的对象所组成,只是复用了现有程序代码的功能,而非它的形式。
只需将对象引用置于新类中即可。对于非基本类型的对象,必须将其引用置于新的类中;但是可以直接定义基本类型数据。
public class Springkler {
//将各种现有类的对象组合在一起,产生新类
private String value;
//可以在定义对象的时候,直接初始化
private WaterSource waterSource=new WaterSource();
//基本类型可以直接定义,有默认值
private int i;
public String toString(){
return "String value="+value+" "+
"Object Value="+waterSource+" "+
"Integer value="+i+" ";
}
public static void main(String[] args) {
Springkler sk=new Springkler();
System.out.println(sk.toString());
}
}
class WaterSource{
private String str;
WaterSource(){
str="Constructor";
}
public String toString(){
return str;
}
}
结果:
String value=null Object Value=Constructor
Integer value=0
每一个非基本类型的对象都有一个toString()方法,而且当编译器需要一个String而你只有一个对象时,该方法会被调用。
为了避免增加不必要的负担,编译器并不是为每个引用都创建默认对象,如果想初始化这些引用,可以:
1、在对象定义的时候。总能够在构造器被调用之前被初始化。
2、在类的构造器中。
3、惰性初始化,在使用这些对象之前进行初始化,可以减少额外的负担。
4、使用实例初始化。
public class Bath {
//在对象定义的地方初始化
private String value="This is a Test";
private Soap soup=new Soap();
private int i;
Bath(int i){
//在类的构造器中初始化
this.i=i;
}
public static void main(String[] args) {
//在使用这些对象之前进行初始化
String str="test only ";
System.out.println(str);
}
}
class Soap{
Soap(){
System.out.println("Soup Constructor");
}
}
二、继承
当创建一个类时,总是在继承,除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承。
public class Detergent extends Cleanser{
//覆写scrub()方法
@Override
public void scrub() {
append(" Detergent.scrub()方法 ");
//调用基类的方法
super.scrub();
}
public void form(){
append(" form() ");
}
public static void main(String[] args) {
Detergent x=new Detergent();
x.dilute();
x.apply();
x.scrub();
x.form();
System.out.println(x);
//直接调用基类中的main方法
Cleanser.main(args);
}
}
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();
System.out.println(x);
}
}
上例中,子类从基类导出,子类可以自动获得这些public方法,尽管并不能看到这些方法在子类中的显式定义。因此,继承可以视作是对类的复用。
导出类就像是一个与基类具有相同接口的新类,或许还会有一些额外的方法和域。但是继承并不只是复制基类的接口。当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。区别在于,后者直接通过基类创建对象,而前者是被包装在导出类对象内部。
在构造器中调用基类构造器来对基类子对象进行初始化操作。
Java会自动在导出类的构造器中插入对基类构造器的调用:
public class Cartoon extends Drawing {
public Cartoon(){
System.out.println("Cartoon Constructor");
}
public static void main(String[] args) {
Cartoon cartoon=new Cartoon();
}
}
class Drawing extends Art{
Drawing(){
System.out.println("Drawing Constructor");
}
}
class Art{
Art(){
System.out.println("Art Constructor");
}
}
结果:
Art Constructor
Drawing Constructor
Cartoon Constructor
构建过程是从基类向外扩散的,就像在导出类的构造函数中存在一个隐式的super()方法。基类在导出类构造器可以访问它之前,就已经完成了初始化。即使导出类使用默认的无参构造器,该构造器还是会调用基类的构造器。
如果想调用一个带参数的基类构造器,则必须用关键字super显式编写调用基类构造器的语句,并且配以适当的参数列表:
必须要将super()方法放在构造器的第一句。
public class Cartoon extends Drawing {
public Cartoon(){
super("test");
System.out.println("Cartoon Constructor");
}
public static void main(String[] args) {
System.out.println("进入main方法");
Cartoon cartoon=new Cartoon();
}
}
class Drawing extends Art{
Drawing(String str){
super(str);
System.out.println("Drawing Constructor");
}
}
class Art{
Art(String str){
System.out.println("str = [" + str + "]");
System.out.println("Art Constructor");
}
}
结果:
进入main方法
str = [test]
Art Constructor
Drawing Constructor
Cartoon Constructor
说明:基类的构造器与导出类的构造器要保持一致,也就是,当在导出类构造器中使用隐式或显式无参的super()方法时,基类中必须要有无参的构造器;当在导出类构造器中使用显式的super(Object o),那基类中必须要提供该有参构造器。
三、代理
Java并没有提供对代理的直接支持。可以将一个成员对象置于所要构造的新类中,但是通过选择只提供在该成员对象中的方法的某个子集,而不是在新类中暴露该成员对象的所有方法。
public class SpaceShipDelegation {
private String name;
private SpaceshipControl sc=new SpaceshipControl();
public SpaceShipDelegation(String name){
this.name=name;
}
//只提供成员对象sc中的方法的子集,而不是所有的方法
public void up(int velocity){
sc.up(velocity);
}
public void down(int velocity){
sc.down(velocity);
}
public void forward(int velocity){
sc.forward(velocity);
}
}
class SpaceshipControl{
void up(int velocity){};
void down(int velocity){};
void forward(int velocity){}
void back(int velocity){}
}
在java中,习惯只是忘掉而不是销毁对象,并且让垃圾回收器在必要时释放其内存。当不知道垃圾回收器何时将会被调用,或者是否将被调用。需要在其生命周期内执行一些必需的清理活动。
public class CADSystem extends Shape{
Circle c;
CADSystem(int i){
super(i);
c=new Circle(i);
System.out.println("CADSystem Constructor");
}
void dispose(){
//与构造器的顺序相反
System.out.println("CADSystem dispose");
c.dispose();
super.dispose();
}
public static void main(String[] args) {
CADSystem cad=new CADSystem(43);
try{
//do something
}finally{
cad.dispose();
}
}
}
class Circle extends Shape{
Circle(int i){
super(i);
System.out.println("Circle Constructor");
}
void dispose(){
//清理的顺序与构造器相反
System.out.println("Circle dispose");
super.dispose();
}
}
class Shape{
Shape(int i){
System.out.println("Shape Constructor");
}
//自定义的清理方法
void dispose(){
System.out.println("Shape dispose");
}
}
结果:
//基类Shape构造器方法
Shape Constructor
//类Circle中构造器方法
Shape Constructor
Circle Constructor
//类CADSyatem中构造器方法
CADSystem Constructor
//类CADSystem中清除方法
CADSystem dispose
//类Circle中清除方法
Circle dispose
Shape dispose
//基类Shape中清除方法
Shape dispose
将清理方法的调用放在finally子句中,表示该方法一定要被调用。
为了防止某个子对象依赖于另一个子对象的情形发生,执行清理顺序时:首先,执行类的所有特定的清理动作,其顺序同生成顺序相反;然后,调用基类的清理方法。
垃圾回收器可能永远也无法被调用,即使被调用,也可能以任何它想要的顺序来回收对象。最好的办法是除了内存以外,不能依赖垃圾回收器去做任何事。如果需要清理,最好编写自己的清理方法,但是不要使用finalize()方法。
名称屏蔽
基类拥有某个已被多次重载的方法名称,那么在导出类中重新定义该方法名称,并不会屏蔽其在基类中的任何版本。无论是在该层或者它的基类中对方法的定义,重载机制都可以正常工作:
public class Hide extends Home{
void doh(long l){
System.out.println("doh(long)");
}
public static void main(String[] args) {
Hide hide=new Hide();
//所有重载方法都是可用的
hide.doh(999l);
hide.doh('w');
hide.doh(1);
}
}
class Home{
void doh(char c){
System.out.println("doh(char)");
}
void doh(int i){
System.out.println("doh(int)");
}
}
结果:
doh(long)
doh(char)
doh(int)
新增加的@Override注解,它并不是关键字,但可以把它当做关键字使用。当想要覆写某个方法时,可以选择添加这个注解,但是不小心重载而非覆写该方法时,就会报错:
public class Hide extends Home{
void doh(long l){
System.out.println("doh(long)");
}
@Override
void doh(char c){
System.out.println("override the 方法");
}
public static void main(String[] args) {
Hide hide=new Hide();
hide.doh(999l);
hide.doh('w');
hide.doh(1);
}
}
//结果:
doh(long)
override the 方法
doh(int)
组合与继承之间的选择
组合和继承都允许在新的类中放置子对象,组合是显式地这样做,而继承是隐式的做。
组合技术通常用于想在新类中使用现有类的功能而非它的接口。即在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为新类所定义的接口,而非所嵌入对象的接口。
继承意味着你在使用一个通用类,并为了某种特殊需要而将其特殊化。
is-a(是一个)的关系是用继承来表达的;
has-a(有一个)的关系是用组合来表达的。
到底是使用组合还是继承的一个清晰的判断方法是:是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必须的。
protected关键字
就普通类用户而言,这是private的;但是对于任何继承于此类的导出类或其他位于同一个包内的类来说,它却是可以访问的。
向上转型
传统继承图的绘制如下:将根置于页面的顶端,然后逐渐向下:
由导出类转型为基类,在继承图上是向上移动,一次称为向上转型。
向上转型是从一个比较专用的类型向通用类型的转换,所以是安全的,也就是说导出类是基类的一个超集。它可能比基类含有更多的方法,但它必须至少具备基类中所含有的方法。它们之间的关系可以用”新类是现有类的一种类型”加以概括。
向上转型是安全的,因为基类不会具有大于导出类的接口,通过基类接口发送消息保证都能被接受。但是对向下转型,避免发出该对象无法接受的消息。
final关键字
1、final数据
向编译器告知一块数据是恒定不断的。
一个永不改变的编译期常量,编译器可以将该常量值代入到可能用到它的计算式中,也就是在编译时执行计算式,减轻运行时的负担。
java中这类常量必须是基本数据类型,并且以关键字final表示,对这个常量进行定义时,必须对其进行赋值。但是不能因为某数据是final,就认为在编译时可以知道它的值。可以在运行时知道它的值也行。
一个即使static又是fianl的域,只占据一段不能改变的存储空间。用大写字母命名,并且字与字之间用下划线隔开。
对于只有final的域,使用驼峰命名法。
对对象引用运用final,会使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。但是对象自身却是可以被修改的。数组也是对象,同样适用。
public class FinalData {
private int valueOne=9;
//final和static final都是带有编译时数值的final基本类型,都可以作为编译期常量,没有重大区别
private final int valueTwo=99;
private static final int VALUE_THREE=999;
//在运行时知道数值,而不是在编译时
private static final int VALUE_Four= new Random(23).nextInt(10);
private final int valueFive= new Random(53).nextInt(15);
//final使引用恒定不变
private Value obj=new Value(11);
private final Value obj2=new Value(22);
private static final Value OBJ_3=new Value(33);
FinalData(){
}
public static void main(String[] args) {
FinalData fd=new FinalData();
fd.valueOne++;
//final基本类型值不可改变
/**
fd.valueTwo++;
fd.VALUE_THREE++;
fd.VALUE_Four++;
fd.valueFive++;
*/
//final非基本类型,引用恒定,但是对象值可变。
fd.obj=new Value(666);
//引用不可以改变
/**
fd.obj2=new Value(7777);
fd.OBJ_3=new Value(8888);
*/
//对象的值可以改变
fd.OBJ_3.setI(787878);
int obj3Value=fd.OBJ_3.getI();
System.out.println("Value类型值="+obj3Value);
}
}
class Value{
int i;
public Value(int i){
this.i=i;
}
public void setI(int j){
i=j;
}
public int getI(){
return i;
}
}
结果:
Value类型值=787878
空白final
java允许生成空白final,空白final是指被声明为final但是又未给定初值的域。无论什么情况下,编译器都确保空白final在使用前必须被初始化。因此,必须在域的定义处或者每个构造器中对final进行赋值。
public class BlankFinal {
private final int i=0;
private final int j;
private final Poppet p;
public BlankFinal(){
j=1;
p=new Poppet();
}
}
class Poppet{
Poppet(){}
}
final参数
java允许在参数列表中以声明的方式将参数指明为final。意味着无法在方法中更改参数引用所指向的对象。
public class FinalAgs {
void with(final Game g){
//不能更改参数所指向的对象
//g=new Game();
}
void without(Game g){
g=new Game();
g.spin();
}
//可以读参数,但是无法修改参数
int find(final int i){
//无法修改
// i++;
//可以读参数
return i+1;
}
}
class Game{
public void spin(){}
}
final方法
想要确保在继承中使方法行为保持不变,并且不会被覆盖。把方法锁定,以防任何继承类修改它的含义。
final和private关键字
类中所有的private方法都是隐式的指定为final的。由于无法取用private方法,所以也就无法覆盖它。
final 类
当将某个类整体定义为final时,不打算继承该类,而且也不允许其他类继承。不希望它有子类。
final类的域可以自定义为是否是final。不论类是否被定义为final,相同的规则适用于final域。
初始化加载
每个类的编译代码都存在于它自己的独立的文件中,该文件只在需要使用程序代码时才会被加载。
类的代码在初次使用时才加载,是指加载发生于创建类的第一个对象之时,但是当访问static域或static方法时,也会发生加载。
构造器也是static方法,类是在其任何static成员被访问时加载的。
所有的static对象和static代码段都会在加载时以程序中的顺序而依次初始化。定义为static的东西只会被初始化一次。
public class Beetle extends Insect{
private int k=printInit("Beetle.k Initialized");
public Beetle(){
System.out.println("Beetle Constructor");
}
private static int x2=printInit("static Beetle.x2 initialized");
public static void main(String[] args) {
System.out.println("进入 main 方法");
Beetle b=new Beetle();
b.test();
b.free();
b.find();
}
public void test(){
System.out.println("test()");
}
public static void find(){
System.out.println("find()");
}
}
class Insect{
private int i=printInit("Insect.i initialized");
protected int j;
Insect(){
j=39;
System.out.println("Insect Constructor");
}
private static int x1=printInit("static Insect.x1 initialized");
static int printInit(String s){
System.out.println(s);
return 47;
}
public void free(){
System.out.println("free()");
}
}
//结果
//基类 static 域初始化,并且 类开始加载
static Insect.x1 initialized
//导出类 static 域初始化,并且 类开始加载
static Beetle.x2 initialized
//导出类main方法
进入 main 方法
//基类 域 初始化
Insect.i initialized
//基类构造器
Insect Constructor
//导出类 域 初始化
Beetle.k Initialized
//导出类构造器
Beetle Constructor
//按定义的顺序调用方法
test()
free()
find()