• 第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:archivesarchive.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:archivesarchive.jar MyProg
              2)也可以通过设置classpath环境变量完成这个操作
                 export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
                 set CLASSPATH=c:classidr;.;c:archivesarchive.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)
     
     
     
     
     
     
     
  • 相关阅读:
    【转载】STL之priority_queue
    数据结构作业——直通车(并查集)
    Codeforces Round #342 (Div. 2) D. Finals in arithmetic(想法题/构造题)
    Size Balance Tree(SBT模板整理)
    平衡二叉查找树(AVL)的理解与实现
    查找树ADT——二叉搜索树
    (转载)通过金矿模型介绍动态规划
    动态规划(DP)基础
    hdu 1969 Pie(二分查找)
    poj 3104 Drying(二分查找)
  • 原文地址:https://www.cnblogs.com/warrenjiang/p/4591519.html
Copyright © 2020-2023  润新知