一个人至少拥有一个梦想,有一个理由去坚强

心若没有栖息的地方,到哪里都是在流浪

java编程思想——第七章复用类

复用代码是java众多引人注目的功能之一。但要想成为极具革命性的语言,仅仅能够复制代码并对之加以改变是不够的,它还必须能够做更多的事情。

要想使用别人已经开发并调试好的类,此方法的窍门在于使用类而不破坏现有的程序代码。本章将介绍两种达到此目的的方法。

第一种方法非常直观:只需要在新类中产生现有类的对象。由于新的类是现有类的对象所组成,所以这种方法称为组合。该方法只是复用了现有程序代码的功能,而非它的形式。

第二种方法则更细致一些,它按照现有类的类型来创建新类。无需改变现有类的形式,采用现有类的形式并在其中添加新的代码。这种神奇的方式称为继承。

7.1  组合语法

class WaterSource {
    private String s;

    WaterSource() {
      System.out.println("WaterSource()");
      s = new String("Constructed");
    }

    public String toString() {
      return s;
    }
  }

  public class SprinklerSystem {
    private String valve1, valve2, valve3, valve4;
    WaterSource source;
    int i;
    float f;

    
    @Override
    public String toString() {
      return "SprinklerSystem [valve1=" + valve1 + ", valve2=" + valve2 + ", valve3=" + valve3 + ", valve4="
          + valve4 + ", source=" + source + ", i=" + i + ", f=" + f + "]";
    }

    public static void main(String[] args) {
      SprinklerSystem x = new SprinklerSystem();
      x.print();
    }
  } /// :~

组合方法就如上面代码,在SprinklerSystem 类中使用WaterSource类。

在类内作为字段使用的基本数据会初始化成零,对象句柄会初始化成null。
编译器并不只是为每个句柄创建一个默认对象,因为那样会在许多情况下招致不必要的开销。如希望句柄得到初始化,可在下面这些地方进行:
(1) 在对象定义的时候。这意味着它们在构建器调用之前肯定能得到初始化。
(2) 在那个类的构建器中。
(3) 紧靠在要求实际使用那个对象之前。这样做可减少不必要的开销——假如对象并不需要创建的话。

7.2 继承语法

当创建一个类时,总是在继承。因此,除非已明确指出要从其他类中继承,否则就是在隐式地从java地标准根类Object进行继承。

说明:extends关键字来实现继承。继承后,子类拥有父类地所有属性和方法。但是子类不能直接访问父类的私有属性,要通过public的getter和setter方法访问到,私有方法则不行。

(如果没有加任何权限修饰符,那么成员默认的访问权限是包访问权限,它仅允许包内的成员访问。

7.2.1 初始化基类

当创建一个子类的对象时,该对象包含一个基类的子对象。这个子对象与直接创建基类的对象是一样的。二者区别在于,后者来自于外部,而基类的子对象被包装在子类对象内部。

对基类的子对象的正确初始化至关重要,且仅有一种方法来保证这一点:在构造器中调用基类构造器来执行初始化,而基类构造器具有执行基类初始化所需要的所有知识和能力。java会自动在子类的构造器中插入对基类构造器的调用。

    // : Cartoon.java 
    // Constructor calls during inheritance
class Art {
  Art() {
    System.out.println("Art	constructor");
  }
}

class Drawing extends Art {
  Drawing() {
    System.out.println("Drawing	constructor");
  }
}

public class Cartoon extends Drawing {
  Cartoon() {
    System.out.println("Cartoon	constructor");
  }

  public static void main(String[] args) {
    Cartoon x = new Cartoon();
  }
}

输出:

Art   constructor 
Drawing	constructor 
Cartoon	constructor

构建过程是从基类“向外”扩散的,所以基础类会在子类构造器可以访问它之前,就已经完成初 始化。 即使没有为Cartoon()创建一个构建器,编译器也会为我们自动合成一个默认构建器, 该构造器将调用基类的构造器。

待参数的构造器

如没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显示地编写调用基类构造器的语句,并且配以适当的参数列表:

// : Chess.java 
// Inheritance, constructors and arguments
class Game {
  Game(int i) {
    System.out.println("Game	constructor");
  }
}

class BoardGame extends Game {
  BoardGame(int i) {
    super(i);
    System.out.println("BoardGame	constructor");
  }
}

public class Chess extends BoardGame {
  Chess() {
    super(11);
    System.out.println("Chess	constructor");
  }

  public static void main(String[] args) {
    Chess x = new Chess();
  }
} /// :

如果不调用BoardGames()内的基类构建器,编译器就会报告自己找不到Games()形式的一 个构建器。除此以外,在子类构建器中,对父类构建器的调用是必须做的第一件事情

7.3 代理

将一个成员对象置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。

例如,太空船需要一个控制模块:因为SpaceShip并非真正的SpaceShipControls类型。SpaceShip只是包含了SpaceShipControls,与此同时,SpaceShipControls的所有方法在SpaceShip中都暴露出来。

public class SpaceShipControls {
  void up(int velocity);
  void down(int velocity);
}

public class SpaceShipDelegation {
  private String name;
  private SpaceShipControls controls = new SpaceShipControls();
   SpaceShipDelegation(String name){
     this.name = name;
   }
   //Delegated methods
   public void up(int velocity) {
     controls.up(velocity);
   }
   public void down(int velocity) {
     controls.down(velocity);
   }
}

7.4 结合使用组合和继承

同时使用继承和组合是很常见的事。

class Plate {
  Plate(int i) {
    System.out.println("Plate	constructor");
  }
}

class DinnerPlate extends Plate {
  DinnerPlate(int i) {
    super(i);
    System.out.println("DinnerPlate	constructor");
  }
}

class Utensil {
  Utensil(int i) {
    System.out.println("Utensil	constructor");
  }
}

class Spoon extends Utensil {
  Spoon(int i) {
    super(i);
    System.out.println("Spoon	constructor");
  }
}

class Fork extends Utensil {
  Fork(int i) {
    super(i);
    System.out.println("Fork	constructor");
  }
}

class Knife extends Utensil {
  Knife(int i) {
    super(i);
    System.out.println("Knife	constructor");
  }
}

// A cultural way of doing something: 
class Custom { 
  Custom(int i) {
    System.out.println("Custom constructor"); 
    } 
}

public class PlaceSetting extends Custom {
  Spoon sp;
  Fork frk;
  Knife kn;
  DinnerPlate pl;

  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);
    System.out.println("PlaceSetting	constructor");
  }

  public static void main(String[] args) {
    PlaceSetting x = new PlaceSetting(9);
  }
}

输出:

      Custom constructor
Utensil	constructor
Spoon	constructor
Utensil	constructor
Fork	constructor
Utensil	constructor
Knife	constructor
Plate	constructor
DinnerPlate	constructor
PlaceSetting	constructor

虽然编译器强制初始化基类,并且要求在构造器起始处就要这么做,但是它并不监督你必须将成员对象也初始化,因此在这一点上要必须时刻注意。

7.4.1 确保正确清理

关于清理的,我都是看得云里雾里,因此就不做记录。记录一句:

若需要亲自进行清理时,最好是编写清理方法,但不要使用finalize().

7.4.2 @Override注解,实现方法重写

7.5 在组合和继承之间选择

一句话概括:当一个类与另一个类是“is-a”(是一个)的关系时用继承关系表达,而“has-a”(有一个)的关系则用组合来表达。

7.6 protected关键字

protected权限限定的,对于任何继承于此类的子类和任何位于同一个包内的类都是可以访问的。

7.7 向上转型

“为新的类提供方法”并不是继承技术中最重要的,最重要的方面是用来表现新类和基类之间的关系。这种关系可以用“新类是现有类的一种类型”这句话加以概括。

7.7.1 何为向上转型

子类转型为父类,在继承图上是向上移动的,一般称为向上转型。由于向上转型是从一个较专用类型向较通用类型转换,所以总是很安全。

7.7.2 再论组合和继承

在面向对象编程中,生成和使用程序代码最常用的方法是直接将数据和方法包装进一个类中,并使用该类对象。也可使用组合技术使用现有类来开发新的类,而继承技术其实不常用。因此,尽管在OOP的过程中多次强调继承,但这并不意味着要尽可能多的使用它。相反,应当慎用这一技术,其使用场合仅限于你确信使用该技术确实有效的情况。

到底是使用组合还是继承,一个最清晰的判断方法就是问一问自己是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的;但如果不需要,则应好好考虑自己是否需要继承。

7.8 final关键字

在java中,常量必须是基本数据类型,并以关键字final表示。在对常量进行定义的时候,必须对其赋值。

一个既是static又是final的域只占据一段不能改变的存储空间。

当一个对象用final修饰时,它的含义就稍微让人有点儿迷糊了。对于基 本数据类型,final会将值变成一个常数;但对于对象引用,final使引用(句柄)恒定不变。进行 声明时一旦引用被初始化指向一个对象,就永远不能将句柄变成指向另一个对象。 然而,对象本身是可以修改的。Java对此未提供任何手段,可将一个对象直接变成一个常数 (但是,我们可自己编写一个类,使其中的对象具有“常数”效果)。这一限制也适用于数组, 它也属于对象。

private final int valueOne = 88;
private static final int VALUE_TWO = 99;

valueOne和VALUE_TWO二者均可以用作编译期常量,并且没有重大区别。只是 VALUE_TWO是一种更加典型的对常量进行定义的方式。

注意:用final static修饰的基本类型是常量,用大写字母命名,多个字母之间用下划线隔开。

fical参数

方法的参数指明为final,意味着你无法在方法中更改参数指向的东西。

  // 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				
    g.spin();		
              }		
  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);		
  } 
}	///:~

方法f()和g()展示基本类型的参数被指明为final时:你可以读参数,但是不能修改参数。这一特性主要用来向匿名内部类传递数据。(第十章学习它)

7.8.2 final 方法

使用原因两个:1、把方法锁住,防止任何继承类修改它的含义。2、过去建议使用final方法原因是效率(内嵌调用)。现已不需要。

final和private关键字

类中所有的private方法都隐式指定为final。由于无法取用private,所以也就无法覆盖它。

7.8.3 final类

final修饰的类不能被继承。

7.9 初始化及类的加载

创建类的第一个对象,以及访问static域或static方法时,会发生类的加载。

初次使用之处也是static初始化发生之处。所有的static对象和static代码段都会在加载时依程序中的顺序(即定义类时的书写顺序)而依次初始化。当然,定义为static的东西只会加载一次。

 

 

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注