【JavaCore · I】第四章_对象与类

311 阅读7分钟

主要内容

  • 面向对象设计
  • 如果创建标准Java类库中的类对象
  • 如果编写自己的类

4.1_面向对象程序设计概述

4.1.1_类

  • 类是构造对象的模板。
  • 封装(encapsulation,数据隐藏):将数据和行为组合在一个包中,对对象的使用者隐藏了数据的实现方式。
  • 实例域(instance field):对象中的数据。
  • 方法:操纵数据的过程。
  • 对象的状态:实例(对象)的实例域值。
  • 超类 Object:所有类的父类。
  • 继承:扩展一个类建立另一个类的过程。

4.1.2_对象

对象的三大特征

  • 行为:对象可被施加的方法。
  • 状态:对象如何响应被施加的方法。
  • 标识:识别具有相同行为和状态的对象。

4.1.4_类之间的关系

  • 依赖(uses-a):
  • 聚合(has-a):类A对象包含一些类B对象。
  • 继承(is-a):

4.2_使用预定义类

4.2.1_对象与对象变量

  • 一个对象变量并没有实际包含一个对象,而是引用一个对象;
  • 任何对象变量的值都是对存储在另一个地方的一个对象的引用,实际的对象在堆内存中;
  • 当一个对象包含另一个对象变量时,这个变量依然包含着指向另一个堆对象的指针。

4.3_用户自定义类

4.3.3_剖析Employee类

public修饰实例域允许程序中的任何方法对其进行修改和读取,破坏了类的封装。

4.3.4_从构造器开始

  • 所有的Java对象都是在堆中构造的,构造器伴随着new操作符一起使用。
  • 在构造器中定义与实例域重名的局部变量会屏蔽同名的实例域。
public Employee(String n, double s, ...) { 
    String name = n;   // Error 
    double salary = s; // Error
}

4.3.5_隐式参数与显示参数

4.3.6_封装的优点

警告:不要编写返回引用可变对象的访问器方法,比如

class Employee {
  private Date hireDay;
  public Date getHireDay() {
    return hireDay; // Bad
  }
}

Date对象是可变的,如果需要返回一个可变对象的引用,首先应对它进行clone。为什么?

  • 因为当外部调用了getHireDay()这个方法后,生成了另一个对于hireDay所引用对象的引用,在新的引用中调用Date类中的方法,同样会改变当前实例中hireDay的值。
  • clone:存放在另一个位置的对象副本。
class Employee {
  private Date hireDay;
  public Date getHireDay() {
    return (Date)hireDay.clone; // Ok
  }
}

4.3.7_基于类的访问权限

方法可访问所属类的所有对象的私有数据。

  • 某个方法出现这样的语句if(harry.equals(boss)):这是正确的,因为 boss 是 Employee 类对象,Employee 类的方法可访问 Employee 类的任何一个对象的私有域。

4.3.8_私有方法

4.3.9_final 实例域

class Employee {
  private final StringBuilder sb;
  public void f() {
    sb.append("modifid");  // Ok
  }
}

final关键字只是表示存储在sb变量中的对象引用不会再指向其他StringBuilder对象,不过这个对象还可以修改。

4.4_静态与与静态方法

4.4.1_静态域

class Employee {
  private static int nextId = 1;
  private int id;
}
  • 类的所有势力共享nextId
  • 每个类的对象独有自己的id域

4.4.2_静态常量

本地方法(`native`)可绕过Java语言的存取控制机制修改final域,比如`System.out`就是`final`变量

System.setOut(...)

4.3.3_静态方法

静态方法是一种不能向对象实施操作的方法!!!

class Employee {
  private static int nextId = 1;
  private int id;
  public static int getNextId() {
    return nextId;  // Ok
  }
  public static int getId() {
    return id;  // Bad
  }
}
  • 可以认为静态方法是没有隐含参数this的方法。

4.4.4_工厂方法

class App {
  public static void main(String[] args) {
    // construct objects here
  }
}

main 方法不对任何对象进行操作。

4.5_方法参数

Java采用按值调用。当参数有2种类型:

  • 基本数据类型:值传递不可改变原值,因为先拷贝再处理。
  • 对象引用:引用拷贝使用的是浅拷贝,即两个引用指向同一个内存地址,因此修改引用状态影响到双方。
public static void tripleSalary(Employee e) {
  x.raiseSalary(200);    
}
当调用
harry = new Employee(...)
tripleSalary(harry);
  • e 被初始化为 harry 值的引用。
  • raiseSalary 方法作用于该对象。
  • harry 继续引用该被操作的对象。

错误警告:认为Java对对象采用的是引用调用是错误的。反例如下:

public static void swap(Employee x, Employee y) { 
  Employee t = x;
  x = y;
  y = t;
}
...
Employee a = new Employee("A");
Employee b = new Employee("B");
swap(a,b);
// x引用A,y引用B
Employee t = x;
x = y;
y = t;
// 现x引用B,y引用A

方法结束时,x、y都丢弃了,原来的a、b还是各自引用A、B。

Java 中方法参数的使用总结

  • 方法不能修改一个基本数据类型的参数。
  • 方法可改变一个对象参数的状态。
  • 方法不能让对象参数引用一个新的对象。

4.6_对象构造

4.6.1_重载

方法重载的条件:

  • 形参数量不同。
  • 形参顺序不同。
  • 形参类型不同

4.6.2_默认域初始化

如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值

  • 数值为 0
  • 布尔值为 false
  • 对象引用为 null

4.6.3_无参数构造器

如果类中至少有一个构造器,但是没有提供无参数构造器,在构造对象时,如果不提供参数,则报错!!! - 请记住:仅当类没有提供任何构造器时,系统才会提供一个默认的无参构造器。

4.6.5_参数名

4.6.6_调用另一个构造器

下面是调 用构造器的具体处理步骤:

  • 所有数据域被初始化为默认值(0、false 或 null)。
  • 按照在类声明中出现的次序, 依次执行所有域初始化语句和初始化块。
  • 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体.
  • 执行这个构造器的主体.
class Employee { 
    private static int nextld;
    private int id; 
    private String name; 
    private double salary;
    // object initialization block 
    { 
        id = nextld; 
        nextld++;
    }
    public Employee(String n, double s) {
        name = n; salary = s;
    }
    public Employee() { name = ""; salary = 0; }
}
  • Random():构造一个新的随机数生成器。
  • int nextlnt(int n):返回一个 0 ~ n-1 之间的随机数。

4.6.8_对象析构与 finalize 方法

  • 可以为任何一个类添加finalize方法。finalize 方法将在垃圾回收器清除对象之前调用。
  • 在实际应用中,不要依赖于使用 finalize 方法回收任何短缺的资源, 这是因为很难知道这个 方法什么时候才能够调用。

4.7_包

Java 允许使用包 package 将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。

4.7.1_类的导入

方式一

在每个类名之前添加完整的包名。 例如:

  • java.tiie.LocalDate today = java.tine.LocalDate.now();

方式二

使用 import 语句导人一个特定的类或者整个包。

  • import java.util.*;

4.7.2_静态导入

import 语句不仅可以导人类,还增加了导人静态方法和静态域

import static java.lang.System.*;:就可以使用 System 类的静态方法和静态域,而不必加类名前缀:

  • out.println("Goodbye, World!");
  • exit(0);
  • 导入Math包:sqrt(pow(x, 2) + pow(y, 2))

4.7.3_将类放入包中

package com.horstiann.corejava;
public class Employee {} 

4.7.4_包作用域

  • 标记为 public 的部分可以被任意的类使用;
  • 标记为 private 的部分只能被定义它们的类使用。
  • 如果没有指定 public 或 private, 这个部分(类、方法或变量)可以被同一个包中的所有方法访问。

4.10_类设计技巧

  • 保证数据私有。
  • 一定要对数据初始化。
    • Java 不对局部变量进行初始化,但是会对对象的实例域进行初始化
  • 不要在类中使用过多的基本类型。
    • 例如, 用一个称为Address的新的类替换一个Customer类中以下的实例域,这样,可以很容易处理地址的变化,例如,需要增加对国际地址的处理。
      private String street; 
      private String city; 
      private String state; 
      private int zip; 
      
  • 将职责过多的类进行分解。
    public class CardDeck  { // bad design
       private int口 value; 
       private int[] suit; 
       public CardDeck() { . . .} 
       public void shuffle {...} 
       public int getTopValue { . . .} 
       public int getTopSuit { . . . } 
       public void drawO {...} 
    } 
    
    public class CardDeck { 
       private Card[] cards; 
       public CardDeckO {...} 
       public void shuffle() { . . . } 
       public Card getTop { . . . } 
       public void draw() { . . . }
    }
    
    public class Card { 
       private int value; 
       private int suit;
       public Card(int aValue, int aSuit) { . . . } 
       public int getValue { . . . } 
       public int getSuit { . . . }
    }
    
  • 类名和方法名要能够体现它们的职责.
  • 优先使用不可变的类.