JavaSE学习笔记——对象与类

面向对象程序设计的基本概念

  • 面向过程与面向对象的区分
    • 传统的程序设计使用的是,结构化程序设计方法,通过设计一系列的过程(算法)来求解问题。也即,在面向过程的程序设计中,首先确定如何操作数据(设计算法),然后确定如何组织数据以便于数据操作(设计数据结构)。
    • 面向对象的程序设计中,首先确定如何组织数据(设计数据结构),而后再决定如何操作数据(设计各种方法)
  • 面向对象和面向过程的不同适用范围
    • 对于一些小规模的问题,将其分解为过程的开发方式比较理想;
    • 面向对象的方式更适合解决规模较大的问题。
    • 类可以看作构造对象的模板。按照类的定义,由类构造对象的过程称为创建类的实例。
    • 需要注意的是,Java编写的所有代码都位于某个类的内部
  • 封装
    • 将数据和行为组合在一个包中,并向对象的使用者隐藏数据的实现方式。
    • 对象(类的实例)中的数据叫做实例域(instance field),操纵数据的过程叫方法(method)。每个特定的类实例(对象)都有一组特定的实例域值,这些值的集合就是这个对象的当前状态。
    • 实现封装的关键在于,绝对不能让类中的方法直接地访问其他类地实例域,程序仅通过对象的方法与对象数据进行交互。
  • 对象
    • 对象的行为:可以对对象施加哪些操作。
    • 对象的状态:当施加了特定的方法时,对象如何响应。
    • 对象标识:如何辨别具有相同行为与状态的不同对象。
  • 类之间的关系
    • 依赖(uses-a):A类的方法操纵B类的对象,则A类依赖于B类。
    • 聚合(has-a):类A的对象中包含了类B的对象,这种关系也叫关联关系。
    • 继承(is-a):类A是在类B的基础上,扩展得来的,类A不仅包含从类B继承而来的方法,还会扩展一些新的内容。

预定义类的使用

  • 对象与对象变量的区分
    • 一个对象变量并没有实际包含一个对象,而是引用了一个对象而已!此处的对象变量更类似于C++中指向对象的指针。所以java中的null引用对应着C++中的null指针。
    • Date deadline;
      /*只是定义了一个对象变量deadline,可以引用Date类对象
      但它本质上还不是一个对象!不能由deadline调用任何Date类的方法。*/
      deadline = new Date();
      /*使用new构造一个对象,并用这个对象初始化deadline对象变量。*/
      
    • 与C++不同,所有的Java对象都存储在堆中,当一个对象包含另一个对象变量的时候,这个变量包含的是指向另一个堆对象的指针。
  • GregorianCalendar类的使用
    • Date类的实例有一个状态,也即每个Date类的实例对应着一个特定的时间点。这个时间是用距离一个固定时间点的毫秒数来表示的(可正可负),这个固定时间点也就是纪元1970年1月1日00:00:00。但是Date类对时间的所有操作都是基于公历的,并不适用于各种其他的历法。
    • GregorianCalendar类的一些方法
      /*通过提供年、月、日等时间信息构造一个表示特定日期的日历对象,
      需要注意的是,月份从0开始计数,0表示一月份,因此使常量初始化月份更好。
      */
      new GregorianCalendar(2020, 1, 21);
      new GregorianCalendar(2020,Calendar.January, 21); 
      new GregorianCalendar(2020, Calendar.January,21, 23, 59, 59);
      
      /*通过calendar类中的一些常量,从GregorianCalendar类中获取信息,
      或者通过calendar类中的一些常量修改GregorianCalendar类对象的一些信息*/
      GregorianCalendar now = new GregorianCalendar();
      int month = now.get(Calendar.MONTH);
      int day = now.get(Calendar.DAY_OF_WEEK);
      now.set(Calendar.YEAR, 2020);
      now.set(Calendar.MONTH, Calendar.APRIL);
      now.set(Calendar.DAY_OF_MONTH, 21);
      int firstDayOfWeek = now.getFirstDayOfWeek();
      
      /*Date类对象与GregorianCalendar类对象的转换*/
      GregorianCalendar calendar = new GregorianCalendar();
      Date someday = calendar.getTime();
      

自定义类的使用

  • 一个完整的Java程序中,由若干个类组合在一起,但是只有一个类有main方法。每一个java源文件的名称必须和文件中public类的名字相匹配,并且一个.java文件中,只能有一个public类。
  • 构造器与类同名,在构造类对象的时候,构造器会运行,以将实例域初始化为所希望的状态。
  • 构造器总是伴随着new操作符的执行被调用,不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的。
  • 在构造器方法中,不能定义与实例域重名的局部变量。
  • 在访问器方法中,不要返回对可变对象的引用!
    class Employee{
       private Date hireDay;
       ...
       public Date getHireDay(){
          return this.hireDay;
       }
    }
    
    public static void main(Strings []){
       Employee harry = new Employee(...);
       Date d = harry.getHireDay();
       d.setTime(d.getTime() - (long)(10*365*24*60*60*1000));
    }
    /*d和harry.getHireDay()引用的是同一个Date类对象,
    并且这个对象是可变的(有更改器方法)。
    因此对d调用更改器方法,就会修改Date类对象的实例域。
    相当于在Employee类的外部,存在一个途径,
    可以在不调用Employee类更改器的情况下,修改其实例域!
    严重破坏封装性!*/
    
  • 在访问器方法中,如果需要返回对可变对象的引用,应该首先对可变对象进行clone,而后将clone的副本的引用作为返回值,写入访问器方法。
    class Employee{
       ...
       public Date getHireDay(){
          return this.hireDay.clone();
       }
    }
    /*任何对getHireDay()返回值的操作,实际上都是在操作clone出来的副本,
    实例域中原本的可变对象就被很好地封装起来,不受影响。*/
    
  • 方法的访问权限是基于类的,而不是基于具体对象的。方法可以访问所属类的私有特性,而不仅限于访问隐式参数的私有特性。
    class Employee{
       ...
       public boolean equals(Employee other){
          return this.name.equals(other.name);
          /*此处直接从other对象读取其实例域的值,
          而不是通过other对象的访问器方法!
          如果类A的某个方法中,隐式参数和显示参数都是类A的对象,
          那么在该方法中就可以直接访问对象的私有实例域*/
       }
    }
    

静态域与静态方法

  • 静态域可以理解为类域,即便某个类没有一个实例对象存在,这个类的静态域也存在。每个类中的静态域是唯一的,每个对象对于除静态域以外的实例域保存一份自己的副本。
    class Employee{
       private static int nextId = 1;
       private int id;
    }
    Employee[] stuff = new Employee[1000];
    /*在stuff对象数组中,有1000个实例域id,但只有一个静态域nextId*/
    
  • 静态方法,静态方法本身是不能向对象实施操作的,在这种方法中不存在this参数。静态方法不能操作对象,所以不能在静态方法中访问实例域,它只能访问所属类的静态域。
    //使用静态方法的两种情况
    //一个方法不需要访问对象状态,所需参数全部由显示参数提供
    import Math;
    Math.pow(x,a);
    //一个方法只需要访问类的静态域
    class Employee{
       private static int nextId = 1;
       private int id;
       public static int getNextId(){
          return nextId;//返回静态域中的一个值
        }
    }
    
  • 工厂方法
  • main方法,本质上它是一个静态方法。main方法不操作任何一个对象。在启动程序的时候还没有任何一个对象存在,静态的main方法会执行并创建程序所需要的对象。在同一个源文件中可能存在多个类,每个类中都可以添加main方法,但当出现多个main方法的时候,只有与源文件同名的public类的main方法会被执行

方法参数(值传递?or 引用传递?)

  • 按值调用表示方法接收到得是调用者提供的值。
  • 按引用调用表示方法接收的是调用者提供的变量地址。
  • 对于基本数据类型的方法参数,Java总是采用按值调用。
  • 对于对象类型的参数,Java本质上仍然使用按值调用!
    public static void swap(Employee x, Employee y){
       Employee temp = x;
       x = y;
       y = tmp;
    }
    Employee a = new Employee("Alice"...);
    Employee b = new Employee("Bob"...);
    swap(a, b);
    //如果Java是按引用调用,a指向Bob,b指向Alice
    //但事实上,a仍然指向Alice,b仍然指向Bob!
    //这是因为swap方法的参数x y被初始化为两个对象引用的拷贝,swap方法中交换的实际上是这两个拷贝的值! 
    
  • 总而言之,无论方法中的参数是什么类型,Java方法中使用的都只是该类型的一个拷贝值!(方法参数是一个对象类型的时候,拷贝的是指向这个对象的一个引用的值)

对象构造

  • 重载:多个方法有相同的名字,不同的参数。编译器会将各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配,如果找不到匹配或者找到了多个匹配,则报出编译时错误。
  • 方法的签名:方法名 + 参数类型。签名是对一个方法的完整描述。
  • 对于方法中的局部变量而言,Java不为其提供默认初始化。
  • 对于类中的域来说,如果在构造器里面没有显示地为其进行初始化,那么就会被自动地赋为默认值。
  • 如果类中不包含任何的构造器,那么系统会提供一个无参构造器,系统提供的这个构造器中会将所有的实例域赋为默认值。如果在类中编写了无参构造器,最好在该无参构造器中对实例域手动进行初始化。
  • 在一个构造器中调用另一个构造器
    public Employee(double s){
       //调用构造器Employee(String, double)
       this("Employee #" + nextId, s);
       nextId++;
    }
    
  • 初始化块:在类的声明中,可以包含多个代码块,只要构造类的对象,这些代码块就会被执行。在构造对象的时候,首先执行类声明中的代码块,而后再运行构造器的主体部分!
    class Employee{
       private static int nextId;
    
       private int id;
       private String name;
       private double salary;
    
       //初始化块
       {
          id = nextId;
          nextId++;
       }
    
       public Employee(String aName, double aSalary){
          name = aName;
          salary = aSalary;
       }
       public Employee(){
          name = "";
          salary = 0;
       }
       ...
    }
    //无论使用哪个构造器构造对象,
    //id域都会在初始化块中先被初始化!而后再运行特定构造器!
    

包的使用

  • 标准的Java类库分布在多个不同的包中,同名的包只要放置在不同的包中就不会发生冲突。嵌套的包之间没有任何的关系,比如java.util与java.util.jar之间。
  • 类的导入,一个类可以使用所属包中的所有类,以及其他包中的公有类。
  • 在C++中必须使用include语句将外部特性的声明加载进来,因为C++编译器无法查看任何文件的内部,除了正在编译的文件以及在头文件中明确包含的文件。Java编译器可以查看文件的内部,只要告诉它到哪里去查看就可以了。
  • 导入静态方法和静态域
    import static java.lang.System.*;
    //添加这样一条语句,就可以使用system类的静态域和静态方法。
    out.println("Goodbye~");
    exit(0);
    
  • 将一个类放入一个包中,只需要将包的名字放在源文件的开头即可,类似于package com.horstman.corejava
  • 变量一般都需要声明为private,不然的话就会默认包可见。标记为public的方法可以被任意类使用,标记为private的方法只能被定义它们的类使用,若没有指定public或private则可以被同一个包中的所有方法访问。
  • 标记为public的类可以被其他包中的类访问,没有标记为public的类只能被所属包中的其他类访问。
  • 一个jar包中可以包含多个压缩形式的类文件和子目录,它使用zip格式组织文件,可以使用zip解压方法解压查看jar包

注释的基本要点

  • 方法注释
    @param,注释当前方法的参数。
    @return,对当前方法的返回值进行注释。
    @throws,描述当前方法可能抛出的异常
    
  • 域注释(通常只用于公有域、静态常量)
    /**
    * ...
    */
    
  • 通用注释
    @author,描述当前类编写者
    

基本类设计技巧

  • 一定要保证数据私有,绝对不要破坏封装性!
  • 一定要对数据进行初始化,Java不对局部变量进行初始化,但是会对类对象的实例域进行初始化。
  • 不要在类中使用过多基本类型。
  • 不是所有的域都需要设置访问器和更改器,因为有些实例域是不希望别人获取或者设置的。
  • 将职责过多的类进行分解。
  • 类名和方法名要能够体现他们的职责。
    • 类命名:一个名词(order)、有形容词修饰的名词、有ing修饰的动词+名词。
    • 方法命名:驼峰法

你可能感兴趣的:(JavaSE学习笔记——对象与类)