java编程思想学习笔记 第七章 复用类

来源:互联网 发布:大数据营销培训课程 编辑:程序博客网 时间:2024/05/19 12:24
 

★    组合

★    继承

一、组合语法

★     组合的概念

◆     由现有类的对象组成新的类

★  class  WaterSource      // 水源
      {  private  String  s;   // 字符串对象
        
         WaterSource()
         {   System.out.println(“WaterSource()”);
              s=“Constructed”; }

         public  String  toString()
         {  return  s;}
      }

      public  class  SprinklerSystem   // 洒水系统 
      {  private  String  value1,value2,value3,
                                    value4;
         private  WaterSource  source =
                                                new  WaterSource();
         private  int  i;
         private  float  f;
         public  String  toString()
         {  return  “value1=”+value1+“”+.....
             “source=”+source;}   

      //  以下为简单的测试程序
      public  static  void  main(String[]  args)
      {
         SprinklerSystem  sprinklers =  new 
                                        SprinklerSystem();
         System.out.println(sprinklers);
      }

 

★  解释:toString()方法

◆  每一个非基本类型的对象都有一个toString方法

◆  调用时机:当编译器需要一个String(字符串)
                           而你却只有一个对象时,该对象中
                           的toString方法将被调用

 

二、继承语法

1、   继承的概念和语法

★     使用extends关键字

★  class  Cleaner
      { 
 private  String  s="Cleaner";

 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)
 {
  Cleaner  x=new  Cleaner();
  x.dilute();  x.apply();  x.scrub();
  System.out.println(x);
 }
      }

 

   public  class  Detergent  extends  Cleaner
      { 
 public  void  scrub()    // 改写
 {   append(“ Detergent.scrub()”);
               super.scrub();}

 public  void  foam()     // 增加
          { append(“foam()”);}

 

 public  static  void  main(String[]  args)
 {
     Detergent  x=new  Detergent();
     x.dilute();  x.apply();  x.scrub(); x.foam();
     System.out.println(x);
     System.out.println(“Testing  base  class:”);
     Cleaner.main(args);
 }
}

 

★  解释:

1、数据成员指定为private,方法指定为public

◆  如果数据成员指定为private,该数据成员同样
      是往下遗传的,只是在子类中不能访问父类的
      private对象而已

★  解释:

2、可以为每个类都创建一个main方法

◆  优点:方便调试

◆  注意:只有在命令行所调用的那个类的main方法
                  会被调用

二、继承语法

2、   初始化基类

⑴     当创建了一个导出类的对象时,该对象
         包含了一个基类的子对象

★     关键:该子对象与你直接用基类创建的
         对象是一样的

⑵     无参构造器情形

★     Java会自动在导出类的构造器中插入对
         基类构造器的调用

◆     首先调用基类构造器,再调用导出类的
         构造器(典例分析:P129)

⑶     有参构造器情形

★     必须用关键字super显示地调用基类的
         构造器

◆     典例分析:P130

★  class  Game
      { Game(int  i)
          {  print(“Game  constructor”);}
      }

     class  BoardGame  extends  Game
      { BoardGame(int  i)
          {  super(i);
              print(“BoardGame  constructor”);}
      }

三、组合使用组合和继承

1、   同时使用组合和继承

★     典例分析:P132~133

◆     关键:充分利用图形来分析和理解各个
         类之间的层次关系

 

★     虽然编译器强制你去初始化基类,并且
         要求你要在构造器起始处就就要这么做,
         但是它并不监督你必须将每个对象成员
         都初始化

◆     这就是为什么虽然有构造器,仍然需要
         自动初始化的原因

2、   重载(overload)

⑴     重载的定义

★     类对自身已有的同名方法的重新定义

⑵     重载的本质

★     利用不同的参数列表,包括形式参数的
         个数、类型、顺序的不同来区分重载

★  重载的典例:

◆  family(){}

◆  family(String ch){  address=ch;}

◆  family(String ch,float n)
                  { address=ch;pay=n;}

3、   覆盖(override)

⑴     覆盖的定义

★     子类对继承自父类方法的重新定义

⑵     覆盖的本质

★     是指在子类中定义了这样的一个方法:
         该方法具有与父类完全相同的返回值
         类型、方法名和参数列表(但是重新
         编写了方法体)

⑴  通过覆盖,父类中的方法被屏蔽掉,我们在
      子类中使用的是新定义的方法


⑵  如果在子类中需要调用父类的被覆盖的方法,
      那么我们该怎么做?

★  使用super关键字来实现

4、   重载与覆盖的区别

★     关键:发生的地点不同

◆     重载发生在同一个类中
◆     覆盖发生在基类与派生类中

5、   Java中的重载与覆盖

⑴     如果Java的基类拥有某个已被多次重载
         的方法,那么在导出类中重新定义该方
         法并不会覆盖在基类的任何版本

★     方法名相同,但参数列表不同

★  class  Homer
      {  char  doh(char  c)
          {   print(“doh(char)”);
               return  ‘d’;}
         
         float  doh(float  c)     //  重载
          {   print(“doh(float)”);
               return  1.0f;}
     }

★  class  Milhouse {}

      class  Bart  extends  Homer
      {  void  doh(Milhouse  m)  //  重载而非覆盖
          {   print(“doh(Milhouse)”);}
      }
         
        public  class  Hide      // 以下为测试程序
       {  .... }

★  分析:

◆  从前面的分析可以看出:重载是水平的(发生
      在类的内部),覆盖是垂直的(发生在基类与
      派生类之间)

◆  然而现在讨论的问题同上面两点有所不同:
      站在垂直的角度来探讨重载的问题

⑵     补充说明

★     Java SE5新增了@override注解

◆     当你试图覆盖该方法但却不小心重载时,
         编译器会产生一条错误信息

 

四、在组合和继承之间选择

★     is-a的关系是用继承来表达的
         has-a的关系是用组合来表达的

◆     典例:交通工具与车的关系(特殊化)

◆     典例:车与引擎、轮胎、车窗和车门的
                     关系(详见P137分析)

★  class  Engine                  // 引擎
      {
          public  void  start(){}           // 启动
          public  void  rev(){}              // 倒车
          public  void  stop(){} }          // 停止
         
      class  Wheel{...}             // 轮胎
      class  Window{...}         // 车窗
      class  Door{...}               // 车门

★  public  class  car{
          public  Engine  engine=new  Engine ();
          public  Wheel[]  wheel=new  Wheel[4];
          public  Door  left=new  Door(),
                                 right=new  Door();
          public  Car()
          {   for(int  i=0;i<4;i++)
                  wheel [i]=new  Wheel();}
          ....
      }

五、向上转型

1、   可以从两方面来理解继承

⑴     为新的类提供方法

⑵     更重要在于:用来表现新类和基类之间
         的关系,即新类是基类的一种类型

★  class  Instrument                                    //  乐器
      { 
          public  void  play()                        //  演奏
 {   System.out.println(“playing....!”);}
 
 static  void  tune(Instrument  i)   //  调音
 {   i.play();}
      }

★  public  class  Wind  extends  Instrument
      {   //  音乐分为管乐、弦乐、鼓乐等等
 public  static  void  main(String[]  args)
 {
     Wind  flute = new  Wind();  //  长笛
     Instrument.tune(flute);
 }
      }

 

2、   向上转型

★     这种将Wind引用转换为Instrument引用
         的动作,我们称之为向上转型

★  鉴于Java对类型的检查十分严格,这种接受某种
      类型的方法,同样也可以接受另外一种类型就会
      显得比较奇怪

★  向上转型是从一个较专用类型向较通用类型转换
      所以总是很安全
◆  导出类是基类的一个超集,它必须至少具备基类
      中所含有的所有方法

六、final关键字

 

1、   基本概念

⑴     final的基本概念

★     最终的,无法改变的

⑵     常量可以进一步细分为:

★     编译期常量
◆     编译期间就能确定它的值

★     非编译期常量
◆     直到运行期间才能确定它的值

★  典例分析

1、编译期常量
◆  private  final  int  valueOne = 9;

2、非编译期常量
◆   private  final  int  i4 = rand.nextInt(20);

2、   final数据

★     对于基本类型,final使数值恒定不变
         对于对象引用,final使引用恒定不变

1、一旦引用被初始化指向一个对象,就无法再把它
      指向另一个对象

★  然而,对象其自身却是可以修改的

★  与C++相应概念的对比分析:
◆  常引用与常对象

2、final关键字可以与static关键字一起使用

★  按照惯例:一个既是static又是final的域,将采用
      大写字母表示,并且单词之间用下划线分隔

★  典例:
◆  public  static  final  int  VALUE_THREE = 39;

★  典例分析:P140~141代码

class  Value
{   //  只有一个域,并且为包访问(缺省)
     int  i;

     //  仅含一个构造器
     public  Value(int  i)   {  this.i = i;}
}

public  class  FinalData
{   private  static  Random  rand
                   = new  Random(47); // 47为种子数
   
    private  String  id;

    // 构造器
    public  FinalData(String  id)
    {  this.id = id;}

// 编译期常量
private  final  int  valueOne = 9;
private  static  final  int  VALUE_TWO = 99;
// final与static  final的区别在于: 尽管都属于常量,
   但后者是属于类这个层次的,将在类被装载的时候
   被初始化,而不是每次创建对象时都初始化

// 典型的公共常量,可以被包以外访问
public  static  final  int  VALUE_THREE = 39;

// 非编译期常量
private  final  int  i4 = rand.nextInt(20);
static  final  int  INT_5 = rand.nextInt(20);

// 对象引用(可看出final与static final的区别)
private  Value  v1 = new  Value(11);
private  final  Value  v2 = new  Value(22);
private  static  final  Value  VAL_3
                         = new  Value(33);

// 数组
private  final  int[]  a = { 1,2,3,4,5,6 };

// 再次注意到toString方法的应用
public  String  toString()
{
     return    id + ": " + "i4 = " + i4
                        + ", INT_5 = " + INT_5;
}

// 测试程序
public  static  void  main(String[]  args)
{
    FinalData  fd1 = new  FinalData(“fd1”);
   
    //! fd1.valueOne++;  // 错误
    fd1.v2.i++;                 // 对象本身可以改变
    fd1.v1 = new  Value(9); // OK,非常量

for(int i = 0;i < fd1.a.length;i++)
         fd1.a[i]++;  // 对象本身可以改变

//  错误,引用不能改变(不能指向另一个对象)
//! fd1.v2 = new  Value(0);
//! fd1.VAL_3 = new Value(1);
//! fd1.a = new int[3];

print(fd1);
print(“Creating new FinalData”);
FinalData  fd2 = new  FinalData(“fd2”);
print(fd1);
print(fd2);
}
} /* Output:

//  通过i4与INT_5分析:final与static final的区别

2、   final方法

⑴     使用final方法的原因之一

★     将方法锁定,以防止任何继承修改它的
         含义(慎用:参见P145分析Vector类与
         ArrayList类)

⑵     使用final方法的原因之二

★     效率:编译器将方法的所有调用转换为
         内嵌调用
◆     副作用:使代码膨胀(最新版本的Java
         已不再需要final来进行优化了)

⑶     对private方法添加final修饰词没有意义

★     问题:如果试图覆盖一个private方法?
                 
◆     覆盖只有在某方法是基类接口一部分时
         才会出现(即此时你并没有覆盖该方法,
         仅是生成了一个新的方法)

★     Java的加载方式与C/C++不一样

◆     每个类的编译代码都存在于它自己独立
         的文件中,该文件只有在需要使用程序
         代码时才会被加载
                 
◆     典例分析(P146)

★     Java的加载方式与C/C++不一样

◆     每个类的编译代码都存在于它自己独立
         的文件中,该文件只有在需要使用程序
         代码时才会被加载
                 
◆     典例分析(P146)

★  public  class  Beetle  extends  Insect
      { private  int  k=printInit(“....”);
 public  Beetle()
          {  print(“k=” + k);print(“j=”+j);}
          private  static  int  x2=printInit(“.....”);
         
          public  static  void  main(String[]  args)
          {   print(“Beetle  constructor”);
               Beetle  b=new  Beetle();}
       }

★  解释:假设在Java上运行Beetle

1、首先访问Beetle.main()方法(static方法)

◆  加载器开始启动并找出Beetle类的编译代码

◆  加载过程中,编译器注意到它有一个基类,于是
      继续加载基类(如果基类上还有其自身的基类,
      继续加载,以此类推)

、向上加载到根基类

◆  首先执行根基类中的static初始化,然后是它的
      导出类,以此类推

⑴  因为做的原因在于:导出类的static初始化可能
      会依赖于基类成员能否被正确初始化

⑵  这也再次证明了初始化第二基本原则的推论:
      静态初始化是在类的加载时期完成的

3、到现在为止,必要的类都已经加载完毕,可以
      开始创建对象了

⑴  首先创建基类对象(按照初始化第一基本原则)

◆  先进行自动初始化:对象中的所有基本类型都会
      被设为默认值,而对象引用设为null
◆  其次,进行指定初始化
◆  最后,调用基类的构造器进行初始化

⑵  当基类对象创建完毕,接下来要创建的是派生类
      对象

◆  仍然按照初始化的第一基本原则进行

◆  即:首先进行自动初始化,再指定初始化,最后
      利用构造器进行初始化

 

 

原创粉丝点击