第4章 对象与类

4.1 面向对象程序设计概述
4.1.1 类
  • 类(class)是构造对象的模版或蓝图。由类构造(construct)对象的过程称为创建类的实例(instance)。
  • 标准Java库提供几千个类供使用,但还是需要创建自己的类,以描述应用程序所对应的问题域中的对象。
  • 封装(encapsulation,也称为数据隐藏),对对象的使用者隐藏了数据的实现方式。实现封装的关键就是绝不能让其他类的方法访问本类的实例域。封装给对象赋予了黑盒特性。这是提高重用性和可靠性的关键。
  • 对象状态:每个特定对象都有一组特定的实例域(instance field)值,这些值的集合就是这个对象的当前状态。
4.1.2 对象
  • 对象的三个主要特征
          1)对象的行为(behavior)
          2)对象的状态(state)  ---对象状态的改变,必须通过调用方法实现
          3)对象的标识(identity)
 
4.1.3 识别类
  • 名次对应类
  • 动词对应类的方法
4.1.4 类之间的关系
  • 依赖 dependence(uses-a):如果一个类的方法操作另一个类的对象,我们就说一个类依赖于另一个类。
          1)应尽可能将相互依赖的类减至最小。即类之前的耦合度最小。
          2)如果类A不知道B的存在,B的改变不会引起A的错误。
  • 聚合aggregation(has-a):类A的对象包含类B的对象。
          1)继承inheritance(is-a):用于标识一种特殊与一般的关系。
 
4.2 使用预定义类
4.2.1 对象与对象变量
  • 首先构造对象 -> 指定其初始状态 -> 对对象应用方法
  • 使用构造器(constructor)构造新实例。用来构造和初始化对象。
  • new操作符返回的也是一个对象的引用。
  • 方法中的局部变量不会自动地初始化为null,而必须通过调用new或将他们设置为null进行初始化。
4.2.2 Java类库中的Gregorian-Calendar类
  • 标准Java类库分别包含了两个类:一个是用来表示时间点的Date类,另一个是用来表示日历表示法的GregorianCalendar
4.2.3 更改器方法和访问器方法
  • 对实例域进行修改的方法称为更改器方法。
  • 仅访问实例域而不进行修改的方法称为访问器方法。
 
4.3 用户自定义类
  • 在一个Java源文件中,只能有一个public类,但可以有任意多个非公有类。源文件的文件名必须和public类的类名相同。
  • 实例域通常标记为private
  • 类通常包括类型属于某个类类型的实例域
  • 构造器与类同名,并在构造类的对象时,将实例域初始化为希望的状态。
  • 每个类可以有一个以上的构造器
  • 构造器可以有0个、1个及1个以上的参数
  • 构造器没有返回值
  • 构造器总是伴随着new操作一起调用
  • 在类的所有方法中(包括构造器),不要命名与实例域同名的变量,否则在方法内部,会屏蔽掉实例域。
  • 隐式参数与显示参数
          public void raiseSalary(double byPercent)
          {
               double raise = this.salary * byPercent / 100;
                this.salary += raise;
          }
         //raiseSalary方法有两个参数。第一个参数为隐式(implicit)参数,表示该对象本身,第二个参数是在方法名后的参数列表中的数值,称为显示(explicit)参数。在一个方法中,关键字this表示隐式参数。使用this可以将实例域和局部变量明显的区分开来。    
  • 不要编写返回引用可变对象的访问器方法,否则会破坏封装性。
           class Employee
           {
                 private Date hireDay;
                 .....
                 public Date getHireDay(){
                       return hireDay;
                 }
           }
 
           如果需要返回一个可变对象的引用,应该首先对它进行克隆(clone),对象克隆是指存放在另一个位置上的对象副本。
            class Employee
            {
                     ......
                     public Date getHireDay(){
                             return hireDay.clone();
                      }
            }
 
  • 基于类的访问权限
          方法可以访问所调用对象的私有权限,一个方法可以访问所属类的所有对象的私有数据。
          class Employee
          {
               ..........
               public  boolean  equals(Employee other){
                     return name.equals(other.name);
               }
          }
 
          典型的调用方法,if(harry.equals(boss))....
          这个方法访问了harry的私有域,而且还访问了boss的私有域,这是合法的,因为boss是Employee对象,而Employee类的方法可以访问Employee类的任何一个对象的私有域。
  • 只要方法是私有的,类的设计者可以确信,它不会被外部的其他类操作调用,可以将其删除。如果方法是公有的,就不能将其删除,因为其他的代码很可能已经依赖它了。
  • final实例域
          可以将实例域定义为final,构建对象时必须初始化这样的域。即,必须确保在每个构造器执行完后,这个域的值被设置。且在后面的操作中不能再对它进行修改。
          final修饰符大都应用于基本(primitive)类型域,或不可变类的域。用于可变的类,通常会引起混乱。
  • 不可变(immutable)类的域:如果类中的每个方法都不会改变其对象,这种类就是不可变类。如String类就是不可变类。
 
4.4 静态域与静态方法
4.4.1 静态域
  • 如果将域定义为static,每个类只有一个这样的域。而每个对象对于所有的实例域却都有自己的一份拷贝。  
          class Employee
          {
               private static int nextId = 1; //没这个类的所有实例所共享
               private int id;  //每一个雇员对象都有一个自己的id域
 
               public setId(){
                     id = nextId;
                     nextId++;
               }
          }
 
4.4.2 静态常量
  • 静态变量用的比较少,但静态常量用得比较多
          public class Math{
               ......
               public static final double PI = 3.1415926;
               ......
          }
         //程序中可以直接采用Math.PI的形式获得这个常量。
 
  • 另一个经常使用的静态常量是System.out
          public class System
          {
                public static final PrintStream out = ...;
          }
 
4.4.3 静态方法
  • 静态方法是一种不能向对象实施操作的方法。
  • 静态方法没有隐式的参数。
  • 可以认为静态方法是没有this参数的方法。
  • 因为静态方法不能操作对象,所以不能在静态方法中访问实例域。但是静态方法可以访问自身类中的静态域。
           public static int getNextId()
           {
                 return nextid; //return static field
           }
           可以通过类名调用这个方法:
           int n= Employee.getNextId();
  • 可以使用对象调用静态方法,但不推荐这样做,容易引起混淆,建议使用类名来调用静态方法。
  • 在下面两种情况下使用静态方法:
          1)一个方法不需要访问对象状态,其所需参数都是通过显示参数提供。
          2)一个方法只需要访问类的静态域。
 
4.4.4 工厂方法
  • 静态方法还有一个常见的用途,使用工厂方法产生不同风格的格式对象。
4.4.5 main方法
  • main方法也是一个静态方法。main方法不对任何对象进行操作。事实上,在启动程序时,还没有任何一个对象。静态的main方法将执行并创建程序所需的对象。
  • 每一个类都可以有一个main方法,这是一个常用于对类进行单元测试的技巧。
 
4.5 方法参数
  • Java总是采用按值调用,即,方法得到的是所有参数值的一个拷贝,特别的,方法不能修改传递给它的任何参数变量的内容。
4.6 对象构造
4.6.1 重载
  • 重载(overloading):多个方法,有相同的名字,不同的参数,便产生了重载。
  • 方法签名(signature):Java允许重载任何方法,而不只是构造器方法。因此完整地描述一个方法,需要指出方法名以及参数类型。这叫做方法签名。
  • 返回类型不是方法签名的一部分,因此,不能有两个名字相同、参数类型也相同,但返回类型不同的方法。
4.6.2 默认域初始化
  • 如果在构造器中,没有显示的给域赋初始值,那么就会被自动地赋予默认值:0、false、null,但这不是好的编程习惯。
  • 但方法中的局部变量,则必须明确的进行初始化,不会像域变量那样被赋予初始值。否则会报错。
4.6.3 无参数的构造器
  • 如果在编写一个类时,没有编写构造器,那么系统就会提供一个无参数的构造器。这个构造器将所有的实例域设置为默认值(0,false,null)。
  • 如果在类中已经提供了至少一个非无参数的构造器,则系统不再自动为该类提供一个无参数的构造器。此时,就不能使用无参数构造器来构造对象。除非自己在类中手动显示地添加一个构造器。
          public ClassName()
          {
 
          }
          //上述构造器构造对象时,会将所有域赋予默认值(0,false,null)
4.6.4 显式域初始化
  • 由于类的构造方法可以重载,所以可以采用多种形式设置类的实例域的初始状态。确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值。
  • 可以在类定义中,直接将一个值赋给任何域。
          class Employee
          {
              private string name = "";
              ...........
          }
          //在构造器之前,先执行赋值操作
  • 当一个类的所有构造器,都希望将一个特定的值赋予某个特定的实例域时,这种方式特别有用。
4.6.5 参数名
        1) 构造器的参数名用单个字符命名
            public Employee(String n, double s)
            {
                  name =  n;
                  salary = s;
            }
            //这种方式的缺点是,参数的可读性不佳
        2) 构造器的参数名在域名称的基础上加上一个前缀 a
             public Employee(String aName,double aSalary)
             {
                   name = aName;
                   salary = aSalary;
             }
        3) 构造器的参数名和域名称完全一样
             public Employee(String name,double salary)
             {
                   this.name = name;
                   this.salary = salary;
             }
            //这种方式会使参数将实例域在构造器内部屏蔽起来,但可以采用this.salary的形式访问实例域。this指示的是方法调用的隐式对象,也就是被构造的对象。    
 
4.6.6 调用另一个构造器
  • 如果构造器的第一个语句形如this(...),这个构造器将调用同一个类的另一个构造器。
          public Employee(double salary)
          {
               this("emp" + nextId, salary);
               nextId++;
          }
  • 一般都是参数个少的构造器调用参数个数多的构造器。
  • 采用这种方式非常有用,这样对公共的构造器代码编写一次即可。
4.6.7 初始化块
  • 第三种初始化域的机制,初始化块(initialization block),在一个类声明中,可以包含多个代码块,只要构造类的对象,这些块就会被执行。
     class Employee
     {
            private static int nextId;
 
            private int id;
            private String name;
            private int salary;
 
           {
                id = nextId;
                nextId++;
           }
 
           public Employee(String n, double s)
           {
                name = n;
                salary =s;
           }
 
          public Employee()
          {
               name = "";
               salary = 0;
          }
     }
     //无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。首先运行初始化块,然后再运行构造器的主体部分。
 
4.6.8 对象析构与finalize方法
  • 由于Java有自动的垃圾回收器,不需要人工回收内存,所以Java不支持析构器。
  • 但,某些对象使用了内存之外的其他资源,如文件或系统资源的另一个对象的句柄。在这种情况下,当资源不再需要时,将其回收或再利用显得十分的重要。
  • 可以为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象之前调用。在实际应用中,不要依赖于使用finalize方法回收任何短缺的资源。这是因为很难知道这个方法什么时候才能够调用。
  • 如果某个资源需要在使用完毕后立刻被关闭,那么就需要由人工来管理。对象用完时,可以应用close方法来完成相应的清理操作。
4.7 包
  • Java允许使用包(package)将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。
  • 所有标准的Java包都处于java和javax包层次中。
  • 使用包的主要原因是确保类名的唯一性。Sun公司建议将公司的英特网域名(显然是独一无二的)以逆序的形式作为包名,并且对于不同的项目使用不同的子包。
  • 从编译器的角度来看,嵌套的包之前没有任何关系。如:java.util和java.util.jar包毫无关系,每一个都拥有独立的类集合。
4.7.1 类的导入
  • 一个类能够使用所属包中的所有类,以及其他包中的公有类(public class)。
  • import语句是一种引用包含在包中的类的简明描述。一旦使用了import语句,在使用类时,就不必写出包的全名了。
4.7.2 静态导入
  • import语句不仅可以导入类,还增加了导入静态方法和静态域的功能。
4.7.3 将类放入包中
  • 想将一个类放入包中,就必须将包的名字放在源文件的开头,包中定义类的代码之前。
          package com.horstmann.corejava;
 
          public class Employee
          {
              .....
          }
  • 如果没有在源文件中放置package语句,这个源文件中的类就被放置在一个默认包(default package)中。默认包是一个没有名字的包。
  • 将包中的源文件放到与完整的包名匹配的子目录中。例如,com.horstmann.corejava包中的所有源文件应该被放置到com/horstmann.corejava目录中。编译器将class类文件也放在相同的目录结构中。
4.7.4 包作用域
  • 在默认情况下,包不是一个封闭的实体。也就是说,任何人都可以向包中添加更多的类。
  • 可以通过包密封(package sealing)机制将一个包密封起来,就不能向这个包添加类了。
  • 制作包含密封包的JAR文件的方法。
 
4.8 类路径--目的是让Java程序在运行时,JVM能够顺利找到各个.class类文件
  • 类存储在文件系统的子目录中,类的路径必须和包名匹配
  • 类文件也可以存储在JAR(Java归档)文件中,在一个JAR文件中,可以包含多个压缩形式的类文件和子目录。在程序中用到第三方(third-party)库文件时,通常给出一个或多个需要包含的JAR文件。
  • JDK也提供了许多JAR文件。在jre/lib/rt.jar中包含数千个类库文件。
  • 为了使类能被多个程序共享,需要做到下面几点:
          1)把类放到一个目录中,如home/user/classdir,这个目录是包树状结构的基目录
          2)将JAR文件放在一个目录中,例如:/home/user/archives
          3)设置类路径,类路径是所有包含类文件的路径的集合。
             UNIX环境(用冒号分割):
             /home/user/classdir:.:/home/user/archives/archive.jar
             Windows环境(用分号分割):
             c:\classdir;.;c:\archives\archive.jar
             上述,句点(.)表示当前目录
  • 可以在JAR文件目录中,指定通配符:表示在归档目录中的所有JAR文件(不包含.class文件)都包含在类路径中。
           /home/user/classdir:.:/home/user/archives/'*'
           c:\classdir;.;c:\archives\*
  • 由于运行时库文件(rt.jar和在jre/lib与jre/lib/ext目录下的一些其他的JAR文件)会被自动搜索,所以不必将他们显示的列在类路径中。
  • javac编译器总是在当前目录中查找文件,但Java虚拟机仅在类路径中有“.”时才查看当前目录。如果没有设置类路径,那也不会产生什么问题,默认的类路径包含“.”目录。但如果设置了类路径,却忘记包含“.”目录,则程序任然可以编译通过,但不能运行。
  • 类路径所列出的目录和归档文件是搜索类的起点。假设虚拟机要寻找某个类com.hostmann.corejava.Employee类文件:
          1)首先查看存储在jre/lib和jre/lib/ext目录下的归档文件中所存放的系统类文件。若没找到,则再依次查看类路径:
          2)/home/user/classdir/com/hostmann/corejava/Employee.class
          3)com/hostmann/corejava/Employee.class 从当前目录开始
          4)com/hostmann/corejava/Employee.class inside /home/user/archives/archive.jar
  • 设置类路径
          1)最好采用-classpath(或-cp)选项指定类路径:
              java -classpath /home/user/classdir:.:/home/user/archives/archive.jar MyProg
              java -classpath c:\classidr;.;c:\archives\archive.jar MyProg
          2)也可以通过设置classpath环境变量完成这个操作
             export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
             set CLASSPATH=c:\classidr;.;c:\archives\archive.jar
             知道shell退出,类路径都有效。
 
4.9 文档注释
  • JDK提供了javadoc工具,用于有源文件生成一个HTML文档。
  • 如果在源文件中添加了以专用的定界符/**开始的注释,就可以很容易的生产一个专业的文档。
4.9.1 注释的插入
  • javadoc工具从下面几个特性中抽取信息
          1)包
          2)公有类和接口
          3)公有的和受保护的构造器和方法
          4)公有的和受保护的域
  • 注释以/**开始,以*/结束
  • 每个/**.....*/文档注释在标记之后紧跟着自由格式文本,标记由@开始,如@author 或 @param
4.9.2 类注释
  • 必须放在import语句之后,类定义之前
4.9.3 方法注释
  • 必须放在所描述的方法之前,除了通用的标记外,还可以使用如下标记
           1)@param 变量描述
           2)@return 描述
           3)@throws 类描述
4.9.4 域注释
  • 只需要对公有域(通常指的是静态常量)建立文档
4.9.5 通用注释
  • @author姓名
  • @version文本
  • @since文本
  • @deprecated文本
  • @see 引用
4.9.6 包与概述注释
4.9.7 注释的抽取
 
4.10 类的设计技巧
  • 一定要保证数据私有,绝对不要破坏封装性
  • 一定要对数据初始化
  • 不要在类中使用过多的基本类型
  • 不是所有的域都需要独立的域访问器和域更改器
  • 将职责过多的类进行分解
  • 类名和方法名要能够体现他们的职责
          类名命名的良好习惯:采用一个名词(Order),前面有形容词修饰的名次(RushOrder),或动名词修饰的名次(BuildingAddress)
 
 
 
 
 
 
 
posted @ 2015-06-21 09:54  蜗牛码农  阅读(263)  评论(0编辑  收藏  举报