zoukankan      html  css  js  c++  java
  • 1-04 对象与类

    1-04 对象与类

    4.1 面向对象程序设计概述

    • OOP(面向对象程序设计):数据放在第一位,算法放在第二位。
    4.1.1 类
    • (class)是构造对象的模板或蓝图。
    • 由类构造(construct) 对象的过程称为创建类的实例( instance)。
    • 封装( encapsulation,有时称为数据隐藏)是与对象有关的一个重要概念。从形式上看,封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。
    • 对象中的数据称为实例域( instance field),操纵数据的过程称为方法(method)。对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态( state)
    • 继承:通过扩展一个类来建立另外一个类的过程。
    4.1.2 对象
    • 对象的三个主要特性:
      • 对象的行为(behavior)一可以对对象施加哪些操作,或可以对对象施加哪些方法?
      • 对象的状态(state) 一当施加那些方法时, 对象如何响应?
      • 对象标识(identity) 一如何辨别具有相同行为与状态的不同对象?
    4.1.3 识别类
    • 识别类的简单规则是在分析问题的过程中寻找名词,而方法对应着动词。
    4.1.4 类之间的关系

    在类之间,最常见的关系有

    • 依赖(“uses-a”):一个类的方法操纵另一个类的对象
      • 应该尽可能地将相互依赖的类减至最少。
    • 聚合(“has-a”),即关联:类A的对象包含类B的对象;
    • 继承(“is-a”):是一种用于表示特殊与一般关系的。
      Java核心技术读书笔记_04 对象与类_1
    • UML ( Unified Modeling Language,统一建模语言) 绘制类图,用来描述类之间的关系。

    4.2 使用预定义类

    4.2.1 对象与对象变量
    • 要想使用对象,就必须首先构造对象,并指定其初始状态。然后,对对象应用方法。
    • 在Java程序设计语言中,使用构造器(constructor)构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。
    • 构造器的名字应该与类名相同。因此Date类的构造器名为Date。要想构造一个Date对象,需要在构造器前面加上new操作符。
    • 对象变量不是一个对象,一个对象变量并没有实际包含一个对象,而仅仅引用一个对象
    Date deadline; //对象变量需要被初始化才能使用,否则会出现编译错误
    
    • 在Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。
    • 可以显式地将对象变量设置为null,表明这个对象变量目前没有引用任何对象。
    • 如果将一个方法应用于一个值为null的对象上,那么就会产生运行时错误。
    • 所有的Java对象都存储在堆中。当一个对象包含另一个对象变量时,这个变量依然包含着指向另一个堆对象的指针。
    4.2.2 Java类库中的LocalDate类
    • LocalDate类封装了实例域来维护所设置的日期。

    • 类库设计者决定将保存时间与给时间点命名分开。所以标准Java类库分别包含了两个类:

      • 一个是用来表示时间点的Date类;
      • 另一个是用来表示大家熟悉的日历表示法的LocalDate类。
    • 不要使用构造器来构造LocalDate类的对象。实际上,应当使用静态工厂方法(factory method)代表你调用构造器。

      LocalDate.now(); //构造一个新对象,表示构造这个对象时的日期;
      localDate.plusDays(int);//得到一个新的LocalDate
      
    4.2.3 更改器方法与访问器方法
    • 访问对象的同时修改对象的方法称为更改器方法(mutator method)
    • 只访问对象而不修改对象的方法有时称为访问器方法(accessor method)

    4.3用户自定义类(workhorse class)

    4.3.1 Employee类
    • 在一个源文件中,只能有一个公有类,但可以有任意数目的非公有类。
    4.3.2 多个源文件的使用
    • 即两个类文件是如何调用的
    4.3.3 剖析Employee类
    • 关键字public意味着任何类的任何方法都可以调用这些方法。
    • 关键字private确保只有Employee类自身的方法能够访问这些实例域,而其他类的方法不能够读写这些域。
    • 类通常包括类型属于某个类类型的实例域。如String类、LocalDate类。
    4.3.4 从构造器开始
    • 构造器与类同名
    • 每个类可以有一个以上的构造器
    • 构造器可以有0个、1个或多个参数
    • 构造器没有返回值
    • 构造器总是伴随着new操作一起调用
    • 所有Java对象都是在堆中构造的,构造器总是伴随着new操作符一起使用
    • 不要在构造器中定义与实例域重名的局部变量
    4.3.5 隐式参数与显式参数
    • 隐式( implicit)参数,是出现在方法名前的类对象。第二个参数位于方法名后面括号中的数值,这是一个显式(explicit)参数。(有些人把隐式参数称为方法调用的目标或接收者。)
    • 在每一个方法中,关键字this表示隐式参数。
    public void raiseSalary(double byPercent)
        double raise = this.salary * byPercent 1 100;
        this.salary += raise;//将实例域与局部变量明显地区分开来。
    }
    
    4.3.6 封装的优点
    • 在有些时候,需要获取或设置实例域的值。因此,应该提供下面三项内容:

      • 一个私有的数据域
      • 一个公有的域访问器方法
      • 一个公有的域更改器方法
    • 上述做法的好处

      • 可以改变内部实现,除了该类的方法之外,不会影响其他代码
      • 更改其可以执行错误检查,然而直接对域进行赋值将不会进行这些处理
    • 注意不要返回引用可变对象的访问器方法。如:

      class Employee
      {
      	private Date hireDay;
      	. . .
      	public Date getHireDay(){
         		 return hireDay;// Bad
      	}
      	...
      }
      
      • LocalDate类没有更改器方法,与之不同,Date类有一个更改器方法setTime,可以
        在这里设置毫秒数。
        Date对象是可变的,这一点就破坏了封装性!请看下面这段代码:
      Employee harry = .. .;
      Date d = harry.getHireDay();
      double tenYearsInMil1iSeconds = 10*365.25+ 24 6060 *1000;
      d.setTime(d.getTime(- (long)tenYearsInMi11iSeconds);
      // let's give Harry ten years of added seniority
      
      • 出错的原因很微妙。d和harry.hireDay引用同一个对象。对d调用更改器方法就可以自动地改变这个雇员对象的私有状态。

      • 如果需要返回一个可变对象的引用,应该首先对它进行克隆(clone)。对象clone时指存放在另一个位置上的对象副本。修改后的代码如下:

      class Employee
      {
          public Date getHireDay(){
              return (Date) hireDay.clone();// ok
          }
      }
      //如果需要返回一个可变数据域的拷贝,就应该使用clone。
      
    4.3.7 基于类的访问权限
    • 方法可以访问所调用对象的私有数据。一个方法可以访问所属类的所有对象的私有数据。
    4.3.8 私有方法
    • 场景:可能希望将一个计算代码划分成若干个独立的辅助方法。通常,这些辅助方法不应该成为公有接口的一部分,这是由于它们往往与当前的实现机制非常紧密,或者需要一个特别的协议以及一个特别的调用次序。最好将这样的方法设计为private的。
    4.3.9 final实例域
    • 可以将实例域定义为final。构建对象时必须初始化这样的域。也就是说,必须确保在每个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够再对它进行修改。
    • final修饰符大都应用于基本(primitive)类型域,或不可变(immutable)类的域(如果类中的每个方法都不会改变其对象,这种类就是不可变的类。例如,String 类就是一个不可变的类)。

    4.4 静态域与静态方法

    4.4.1 静态域(static)
    • 静态域属于类,而不属于任何独立的对象。而每一个对象对于所有的实例域(不加static)却都有自己的一份拷贝。
    • 在绝大多数的面向对象程序设计语言中,静态域被称为类域
    • static:属于类且不属于类对象的变量和函数。
    4.4.2 静态常量
    • 静态变量使用得比较少,但静态常量却使用得比较多。如Math.PISystem.out
    • 前面曾经提到过,由于每个类对象都可以对公有域进行修改,所以,最好不要将域设计为public。然而,公有常量(即final域)却没问题。
    4.4.3 静态方法
    • 静态方法是一种不能向对象实施操作的方法,即没有隐式的参数。例如Math类的pow方法。
    • 可以认为静态方法是没有this参数的方法。
    • 静态方法不能访问实例域,因为它不能操作对象。但是,静态方法可以访问自身类的静态域
    • 可以使用对象调用静态方法,但是不建议这么做。应该直接使用类名来调用静态方法。
    • 在下面两种情况下使用静态方法:
      • 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(如:Math.pow)。
      • 一个方法只需要访问类的静态域(例如:Employee.getNextld)。
    4.4.4 工厂方法
    • 类似LocalDate和NumberFormat的类使用静态工厂方法(factory method)来构造对象。你已经见过工厂方法LocalDate.nowLocalDate.of

    • 为什么NumberFormat类不利用构造器完成这些操作呢?这主要有两个原因:

      • 无法命名构造器。构造器的名字必须与类名相同。但是,这里希望将得到的货币实例和百分比实例采用不用的名字。

      • 当使用构造器时,无法改变所构造的对象类型。而Factory方法将返回一个DecimalFormat类对象,这是NumberFormat的子类。

        NumberFormat currencyFormatter = NumberFormat
            .getCurrencyInstance();
        NumberFormat percentFormatter = NumberFormat
            .getPercentInstance();
        double x = 0.1;
        System.out.println(currencyFormatter.format(x));// prints $0.10
        System.out.println(percentFormatter.format(x);   // prints 10%
        
    4.4.5 main方法
    • main方法是一个静态方法,不对任何对象进行操作。
    • 事实上,在启动程序时还没有任何一个对象。静态的Main方法将执行并创建程序所需要的对象。

    4.5 方法参数

    • 按值调用(call by value)表示方法接收的是调用者提供的

    • 按引用调用(call by reference)表示方法接收的是调用者提供的变量地址

    • 一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值

    • Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,特别的是,方法不能修改传递给它的任何参数变量的内容。

    • 方法参数共有两种类型:

      • 基本数据类型:一个方法不能够修改一个基本数据类型的参数
      • 对象引用:而对象引用是可以修改的,因为方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用了同一个对象。
    • 有些时候会认为Java程序设计语言对对象采用的时引用调用,实际上,这种理解是不对的。

      • 首先,编写一个交换两个雇员对象的方法:
      public static void swap(Employee x,Employee y)// doesn't work
      {
          Employee temp = x;
      	x = y;
      	y = temp;
      }
      
      • 如果Java对对象采用的是按引用调用,那么这个方法就应该能够实现交换数据的效果:
      Employee a = new Employee("Alice",. ..);
      Employee b = new Employee("Bob",. ..);
      swap(a,b);
      //does a now refer to Bob,b to Alice?
      
      • 但是,方法并没有改变存储在变量a和 b中的对象引用。swap方法的参数x和y被初始
        化为两个对象引用的拷贝,这个方法交换的是这两个拷贝。
      ! x refers to Alice, y to Bob
      Employee temp = x;
      x = y;
      y = temp;
      //now x refers to Bob,y to Alice
      
      • 最终,白费力气。在方法结束时参数变量x和y被丢弃了。原来的变量a和 b仍然引用
        这个方法调用之前所引用的对象。
      • 这个过程说明:Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的
    • 下面总结一下Java中方法参数的使用情况:

      • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
      • 一个方法可以改变一个对象参数的状态。
      • 一个方法不能让对象参数引用一个新的对象

    4.6 对象构造

    4.6.1 重载
    • 一个类可以有多个构造器,这种特征叫重载(overloading)
    • 重载解析:通过各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。
    • Java允许重载任何方法,而不只是构造器方法。
    • 要完整地描述一个方法,需要指出方法名以及参数类型。这叫做方法的签名。(返回类型不是方法签名的一部分,也就是说,不能有两个名字相同、参数类型也相同却返回不同类型值的方法)
    4.6.2 默认域初始化
    • 如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值:数值为0、布尔值为false、对象引用为null。
    • 这是域与局部变量的主要不同点。必须明确地初始化方法中的局部变量。但是,如果没有初始化类中的域,将会被自动初始化为默认值(0、false 或null)。
    4.6.3 无参数的构造器
    • 如果在编写一个类时没有编写构造器,那么系统就会提供一个无参数构造器。这个构造器将所有的实例域设置为默认值。于是,实例域中的数值型数据设置为0、布尔型数据设置为false、所有对象变量将设置为null。
    • 如果类中提供了至少一个构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法。
    • 仅当类没有提供任何构造器的时候,系统才会提供一个默认的构造器。
    • 如果希望所有域被赋予默认值,可以采用下列格式:
    public ClassName()
    {
    }
    
    4.6.4 显式域初始化
    • 确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值,这是一种很好的设计习惯。

    • 可以在类定义中,直接将一个值赋给任何域。

      class xxx
      {
          private String name = ""; 
          ...
      }
      
      • 在执行构造器之前,先执行赋值操作。

      • 当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时,这种方式特别有用。

      • 初始值不一定是常量值。可以调用方法对域进行初始化。

        class Employee{
        	private static int nextId;
        	private int id = assignId(); //调用方法初始化
        
        	private static int assignId(){
        		int r = nextId;
        		nextId++;
        		return r;
        	}
        }
        
    4.6.5 参数名
    • 命名技巧基于:参数变量用同样的名字将实例域屏蔽起来。例如,如果将参数命名为salary,salary将引用这个参数,而不是实例域。但是,可以采用this.salary的形式访问实例域。回想一下, this指示隐式参数, 也就是所构造的对象。 下面是一个示例:
    public Emp(String name, double salary){
        this.name = name;
        this.salary = salary;
    }
    
    4.6.6 调用另一个构造器
    • 关键字this引用方法的隐式参数。然而,这个关键字还有另外一个含义。如果构造器的第一个语句形如this(...),这个构造器将调用同一个类的另一个构造器
      下面是一个典型的例子:
    public Employee(double s)
    {   calls Employee(String, double)
        this("Employee #" + nextId, s);
        nextId++; 
    }
    
    • 当调用new Employee(60000)时,Employee(double)构造器将调用Employee(String, double)构造器。采用这种方式使用this关键字非常有用,这样对公共的构造器代码部分只编写一次即可
    4.6.7 初始化块
    • 前面已经讲过两种初始化数据域的方法:
      • 在构造器中设置值
      • 在声明中赋值
      • 实际上,Java还有第三种机制,称为初始化块( initialization block)。在一个类的声明中,可以包含多个代码块。只要构造类的对象,这些块就会被执行。例如,
    class Employee{
        private static int nextId; .
        private int id;
        private String name;
        private double salary;
        
        // object initialization block
        {
            id = nextId;
            nextId++;
        }
        
        public Employee(String n, double s)
        {
            name = n;
            salary = s;
        
        public Employee()
        {
            name =" ;
            salary = 0;
        }
        
        ...
    }
    
    • 在这个示例中,无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。首先运行初始化块,然后才运行构造器的主体部分
    • 由于初始化数据域有多种途径,所以列出构造过程的所有路径可能相当混乱。下面是调用构造器的具体处理步骤:
      • 所有数据域被初始化为默认值(0、false或null)。
      • 按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块。
      • 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体。
      • 执行这个构造器的主体。
    • 静态初始化块
      • 如果对类的静态域进行初始化的代码比较复杂,那么可以使用静态的初始化块。
      • 将代码放在一个块中,并标记关键字static。
      • 在第一次类加载的时候,将会进行静态域的初始化。
      • 所有的静态域初始化语句以及静态初始化块都将依照类定义的顺序执行。
    // static initialization block
    static
    {
        Random generator = new Random();
        nextId = generator .nextInt(0000);
    }
    
    4.6.8 对象析构与finalize方法
    • 在析构器中,最常见的操作是回收分配给对象的存储空间。
    • 由于Java有自动的垃圾回收器,不需要人工回收内存,所以Java不支持析构器
    • 可以为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象之前调用。
    • 如果某个资源需要在使用完毕后立刻被关闭,那么就需要由人工来管理。

    4.7 包

    • 所有标准的Java包都处于java和javax包层次中。
    • 使用包的主要原因是确保类名的唯一性
    • Sun公司建议将公司的因特网域名(这显然是独一无二的)以逆序的形式作为包名,并且对于不同的项目使用不同的子包。
    • 从编译器的角度来看,嵌套的包之间没有任何关系。例如,java.util 包与java.util.jar包毫无关系。每一个都拥有独立的类集合。
    4.7.1 类的导入(import)
    • 一个类可以使用所属包中的所有类,以及其他包中的公有类(public class)。
    • 只能使用星号*导入一个包,而不能使用import java.*或import java.*.*导入以java为前缀的所有包。
    4.7.2 静态导入
    • import语句不仅可以导入类,还增加了导入静态方法和静态域的功能。

      • //例如,如果在源文件的顶部,添加一条指令:
        import static java.lang.System.*;
        //就可以使用System类的静态方法和静态域,而不必加类名前缀:
        out.println("Coodbye,Wor1d!");// i.e.,System.out
        exit(O);// i.e.,System.exit
        //另外,还可以导入特定的方法或域:
        import static java.lang.System.out;
        
      • 实际上,上面这种编写形式不利于代码的清晰度。

    4.7.3 将类放入包中
    • 要想将一个类放入包中,就必须将包的名字放在源文件的开头。
    • 如果没有在源文件中放置package语句,这个源文件中的类就被放置在一个默认包(defaulf package)中。默认包是一个没有名字的包。
    • 编译器在编译源文件的时候不检查目录结构。
    4.7.4 包作用域
    • 如果没有指定public或private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问。

    4.8类路径

    • 类的路径必须与包名匹配
    • 类文件也可以存储在JAR(Java归档)文件中。在一个jar文件中,可以包含多个压缩形式的类文件和子目录,这样既可以节省有可以改善性能。
    • JAR文件使用ZIP格式组织文件和子目录。可以使用所有ZIP实用程序查看内部的rt.jar以及其他的JAR文件
    4.8.1 设置类路径

    4.9文档注释

    • javadoc,它可以由源文件生成一个HTML文档。
    4.9.1 注释的插入
    • javadoc实用程序(utility) 从下面几个特性中抽取信息:
      • 公有类与接口
      • 公有的和受保护的构造器及方法
      • 公有的和受保护的域
    • 每个/** ... */文档注释在标记之后紧跟着自由格式文本(free-form text)。标记由@开始,如@author或@param。
    • 自由格式文本的第一句应该是一个概要性的句子。javadoc实用程序自动地将这些句子抽象出来形成概要页。
    4.9.2 类注释
    4.9.3 方法注释
    • @param 变量描述
    • @return 描述
    • @throw 类描述
    4.9.4 域注释
    • 只需要对公有域(通常是指静态常量)建立文档。
    4.9.5 通用注释
    • @author
    • @version
    • since:对引入特性的版本描述
    • deprecated:将对类、方法或变量添加一个不再使用的注释
    • @see 将在“see also”部分添加一个超级链接
    4.9.6 包与概述注释
    • 要想产生包注释,就需要在每-一个包目录中添加一个单独的文件。
    4.9.7注释的抽取

    4.10类设计技巧

    • 一定要保证数据私有
    • 一定要对数据初始化
      • Java不对局部变量进行初始化,但是会对对象的实例域进行初始化。
      • 最好不要依赖于系统的默认值,而是应该显式地初始化所有的数据。
    • 不要在类中使用过多的基本类型
    • 不是所有的域都需要独立的域访问器和域更改器
    • 将职责过多的类进行分解
    • 类名和方法名要能够体现他们的职责
    • 优先使用不可变的类
      • 更改对象的问题在于,如果多个线程试图同时更新个对象,就会发生并发更改。其结果是不可预料的。如果类是不可变的,就可以安全地在多个线程间共享其对象。
  • 相关阅读:
    1069. Prufer Code 夜
    CROCMBTU 2012, Elimination Round (ACMICPC) D. Restoring Table 夜
    CROCMBTU 2012, Elimination Round (ACMICPC) H. Queries for Number of Palindromes 夜
    1145. Rope in the Labyrinth 夜
    1721. Two Sides of the Same Coin 夜
    1182. Team Them Up! 夜
    1162. Currency Exchange 夜
    1056. Computer Net 夜
    FOJ 2013 A short problem
    Codeforces 11.23
  • 原文地址:https://www.cnblogs.com/nojacky/p/13906441.html
Copyright © 2011-2022 走看看