---
url: /doc/Java/nvrumdvk/index.md
---
## 7.2 包

> 包的作用：1. 区分相同名字的类 2. 当类很多时，便于管理 3. 控制访问范围
>
> 语法：`package com.name` 其中 `com` `name` 分别是 一级 和 二级目录，用 `.` 分隔
>
> 包的本质：就是创建不同 文件夹/目录 来保存 类 文件

**命名规则：**

* 只能包含 数字 `1 2 3`、字母 `a b A b`、下划线 `_`、小圆点 `.`
* 不能用 数字 开头。每级目录都不能。

**命名规范：**

* 全小写字母 + 小圆点
* `com.公司名.项目名.业务模块名`

**常用的包：**

`java.lang`：基本包，默认引入，不需要再引入

`java.util`：系统提供的工具包。工具类。

`java.net`：网络包，网络开发。

`java.awt`：Java 的界面开发，GUI。

**引入包：**

* 只引入该包下的一个类：`import java.util.Scanner`
* 引入该包的所有内容（不建议）：`import java.util.*`

**使用细节：**

1. `package` 的作用是声明当前类所在的包，要放在 类 的 最上面。一个 类 中最多有一句 `package`

2. `import` 放在 `package` 下面，类定义 前面。可以有多条语句，且没有顺序要求

3. \==编译器编译时 **不会** 检查目录结构。==

   \==即使一个包处于错误的目录下（只要其不依赖其他包）也可能通过编译。==

   \==但是，虚拟机会找不到该包，最终程序无法运行。==

4. \==从 1.2 版本开始，用户不能再把包放在 java. 开头的目录下了。若如此做，这些包会被禁止加载==

## 7.3 访问修饰符

### 7.3.1 访问权限特点

Java 提供 4 种 访问控制修饰符号，用于控制方法和属性（成员变量）的访问权限（范围）

* 公开级别：`public`，对外公开。

* 受保护级别：`protected`，对 子类 和 同一个包中的类 公开。

  \*\*\*——什么是 子类？详见 \[\[#7.5 继承]]

* 默认级别：没有修饰符号，向 同一个包的类 公开。

* 私有级别：`private`，只有 同类 可以访问，不对外公开。

| (⌐■\_■)         | 默认（无修饰符） | private | protected | public |
| -------------- | :--------------: | :-----: | :-------: | :----: |
| 本类           |        可        |   可    |    可     |   可   |
| 同包中的子类   |        可        | 不可以  |    可     |   可   |
| 同包的非子类   |        可        | 不可以  |    可     |   可   |
| 其他包的子类   |      不可以      | 不可以  |    可     |   可   |
| 其他包的非子类 |      不可以      | 不可以  |  不可以   |   可   |

### 7.3.2 使用说明

1. 修饰符可以修饰类中的 属性、成员方法 及 类
2. \==只有 默认 和 `public` 才能修饰 类，并遵循上述访问权限特点==
3. 成员方法 的访问规则和 属性 相同
4. private 修饰的变量可以被 任意本对象同类的对象访问

## 7.4 封装

> 封装（encapsulation）就是把抽象出的 数据\[属性] 和对数据的 操作\[方法] 封装在一起。数据 被保护在内部，程序的其他部分只有通过被授权的 操作\[方法]，才能对数据进行操作。

封装的好处：

* 隐藏实现细节
* 可以对数据进行验证，保证安全合理

**实现步骤：**

1. 将属性私有化 `private`
2. 提供一个公共的 `set` 方法，用于对属性判断并赋值
3. 提供一个公共的 `get` 方法，用于获取属性的值

**编译多个源文件：**

```cmd
javac MyClass.java
```

该文件中使用了其他类时，Java 编译器会查找对应名称的 .class 文件。没有找到的场合，转而寻找 .java 文件，并对其编译。倘若 .java 文件相较原有 .class 文件更新，编译器也会自动重新编译该文件。

### ==7.4.1 静态导入==

> 有一种 import 语句允许导入静态方法和字段，而不只是类

比如：

```
import static java.lang.Math.*;
```

这个场合，使用 Math 包内的静态方法、字段时，不需要再添加类名前缀。

```
double n = pow(10, 5);					// <———— 本来是 double n = Math.pow(10, 5);
double pi = PI;							// <———— 本来是 double pi = Math.PI;JAVA
```

***—— 上述方法、字段见 \[12.5 Math 类]***

### ==7.4.2 JAR 文件==

> 为了避免向用户提供包含大量类文件的复杂目录结构，可以将 Java 程序打包成 JAR （Java 归档）文件。
>
> 一个 JAR 文件既可以包含类文件，也可以包含诸如图像和声音等其他类型的文件。
>
> JAR 文件是压缩的。其使用了 ZIP 压缩格式。

**创建 JAR：**

使用 jar 工具以制作 JAR 文件。该工具在 jdk/bin 目录下

```java
jar cvf 包名 文件名1 文件名2 ...
```

关于 jar 工具的各种指令，还是自己去百度一下吧

## 7.5 继承

> 继承：能解决代码复用，让我们的编程更接近人类思维。当多个类存在相同的 属性（变量）和 方法 时，可以从这些类中抽象出 父类（基类/超类）。在 父类 中定义这些属性·方法，所有的子类不需要重新定义这些属性和方法，只需要通过 `extends` 来声明继承父类即可。
>
> \==通过继承的方法，代码的复用性提高了，代码的维护性和拓展性也提高了。==

```java
public class Son extends Father {};				// Son 类继承了 Father 类
```

定义类时可以指明其父类，也能不指明。不指明的场合，默认继承 Object 类。

所有类有且只有一个父类。Object 是所有类的直接或间接父类。只有 Object 本身没有父类。

### 7.5.1 使用细节

1. 子类 继承了所有属性和方法，但私有（`private`）的 属性·方法 不能在 子类 直接访问。要调用父类提供的 公共（`public`）等方法 访问。
2. \==子类 必须调用 父类 的 构造器，完成 父类 的 初始化。==
3. \==当创建 子类对象 时，不管使用 子类的哪个构造器，默认情况下总会调用 父类的无参构造器。如果 父类 没有提供 无参构造器，则必须在 子类的构造器 中用 `super` 去指定使用 父类的哪个构造器 完成 对父类的初始化。否则编译不能通过。==
4. 如果希望指定调用 父类的某构造器，则显式地调用一下：`super(形参列表);`
5. `super` 在使用时，必须放在构造器第一行。
6. 由于 `super` 与 `this` 都要求放在第一行，所以此两个方法不能同时存在于同一构造器。
7. Java 所有的类都是 `Object` 的子类。换言之，`Object` 是所有类的父类。
8. \==父类构造器的调用不限于直接父类，将持续向上直至追溯到顶级父类 `Object`==
9. 子类 最多只能直接继承 一个 父类。即，Java 中是 单继承机制。
10. \==不能滥用继承。子类 和 父类 之间必须满足 is - a 的逻辑关系。==

> is-A 继承关系：“表示类与类之间的继承关系、接口与接口之间的继承的关系以及类对接口实现的关系”。
> has-A 合成关系：“是关联关系的一种，是整体和部分（通常为一个私有的变量）之间的关系，并且代表的整体对象负责构建和销毁代表部分对象，代表部分的对象不能共享”
>
> is\_a 是继承关系.
> has\_a 是组合关系（描述一个类中有另一个类型的实例）

### ==7.5.2 继承的本质==

* 内存布局：
  1. 在 **方法区**，自顶级父类起，依次加载 类信息。
  2. 在 堆 中开辟一个空间，自顶级父类起，依次创建并初始化各个类包含的所有属性信息。
  3. 在 栈 中存放该空间的 地址。
* 如何查找信息？
  1. 查看该子类是否有该属性。如果该子类有这个属性且可以访问，则返回信息。
  2. 子类没有该属性的场合，查看父类是否有该属性。如有且可访问，则返回信息。如不可访问，则报错。
  3. 父类也没有该属性的场合，继续查找上级父类，直到顶级父类（Object）。
  4. 如需调用某个特定类包含的特定信息，可以调用该类提供的方法。

### ==7.5.3 `super` 关键字==

> `super` 代表父类的引用。用于访问父类的 属性、方法、构造器。

**super 的使用：**

* `super.属性名`：访问父类的属性。不能访问父类的私有（private）属性。
* `super.方法名(形参列表)`：访问父类的方法。不能访问父类的私有（private）方法。
* `super(参数列表);`：访问父类的构造器。此时，super 语句必须放在第一句。

**使用细节：**

1. \==调用父类构造器，好处是分工明确。父类属性由父类初始化，子类由子类初始化。==
2. \==子类中由和父类中成员（属性和方法）重名时，要调用父类成员必须用 `super`。没有重名的场合，`super`、`this` 及直接调用的效果相同。==
3. \==`super` 的访问不限于直接父类。==如果爷爷类和本类中都有同名成员也能使用。如果多个基类中都有同名成员，则遵循就近原则。

### 7.5.4 方法重写 / 覆盖

> 方法重写/覆盖（Override）：如若子类有一个方法，和父类的某方法的 名称、返回类型、参数 一样，那么我们就说该子类方法 覆盖 了那个父类方法。

**使用细节：**

1. 子类方法的参数，方法名称，要和父类方法完全一致。
2. \==子类方法的返回类型需和父类方法 一致，或者是父类返回类型的子类。==，向下兼容。
3. 子类方法 **不能缩小** 父类方法的访问范围（访问修饰符）

### 阻止继承：final 类、方法和域

经过 final 修饰的类不可拓展，除了域，它的方法也为 final 的。

## ==7.6 多态==

> 多态：方法 或 对象 有多种形态。多态 是面向对象的第三大特征，是建立在 封装 和 继承 的基础之上的

### ==7.6.1 多态的体现==

1. 方法的多态：重写 和 重载 体现了 方法的多态。

2. **对象的多态：**

   * 一个对象的 编译类型 和 运行类型 可以不一致。

     `Animal animal = new Dog();`

     上例，编译类型是 `Animal`，运行类型是子类 `Dog`。要理解这句话，请回想 \[\[6 面向对象编程(基础)]]的**继承**部分：`animal` 是对象的**引用**。

   * 编译类型在定义对象时就确定了，不能改变。

   * 运行类型是可以变化的。

     上例中，再让 `animal = new Cat();`，这样，运行类型变为了 `Cat`

   * 编译类型看定义时 `=` 的左边，运行类型看 `=` 的右边。

### ==7.6.2 使用细节==

1. \==多态的前提：两个对象 / 类存在继承关系。==

2. 多态的向上转型：

* 本质：父类的引用指向了子类的对象。（如 \[ 7.6.1.2 ]）
* 语法：`父类类型 引用名 = new 子类类型(参数列表);`
* 编译类型看左边，运行类型看右边。
* \==可以调用父类中的所有成员，但不能调用子类特有的成员，而且需要遵守访问权限。因为在编译阶段，能调用哪些成员是由编译类型决定的。==
* 最终的运行结果要看子类的具体实现。即从子类起向上查找方法调用（与 \[ 7.5.2 ] 规则相同）。

> 向上转型的好处：
>
> 1. 提高代码的灵活性：通过向上转型，可以将不同的子类对象转换为其父类对象，从而可以使用统一的接口来操作这些对象，减少了代码的复杂性和冗余性。
>
> 2. 提高代码的扩展性：当需要增加新的子类对象时，只需要在父类对象的基础上进行扩展即可，而不需要对原有的代码进行重构，从而降低了代码的维护成本。
>
> 3. 提高代码的可维护性：在向上转型的情况下\*\*，只需要关注父类对象的行为，而不需要关注子类对象的具体实现细节\*\*，从而提高了代码的可维护性。
>
>    例如:
>
>    ```java
>    class A{
>    	public void play(){...}
>    }
>    class B extend A{
>    	@Override
>    	public void play(){...}
>    }
>    class C extend A{
>    	@Override
>    	public void play(){...}
>    }
>    //如果我们想要调用某个类的paly方法，无需像这样实现：
>    public void show(B b){
>    	b.paly();
>    }
>    public void show(C c){
>    	c.paly();
>    }
>    //而是这样：
>    public void show(A a){
>    	a.paly();
>    }
>    ```
>
>    [聊聊 java 的向上转型和向下转型](https://juejin.cn/post/6993341672755036173)
>
>    \==如果是访问成员变量，编译的话就是看父类，运行同样是看父类。==
>    如果访问的方法，编译就看父类，运行则看子类。
>    如果是静态方法，编译和运行都是看父类。
>    总结：只有方法才有多态的特性

3. 多态的向下转型：

* 语法：`子类类型 引用名 = (子类类型)父类引用;`

  \[7.6.2.2] 的例子里，向下转型。这个语法其实和 \[2.8.2 强制类型转换] 很像。

  `Dog dog = (Dog)animal;`

* 只能强转父类的引用，不能强转父类的对象。只有引用子类对象的父类引用才能被向下转型为子类对象。

  也就是说，==向下转型之前，必须先向上转型。==

* 要求父类的引用必须指向的是当前目标类型的对象。即上例中的 `animal` 运行类型需是 `Dog`

* \==向下转型后，可以调用子类类型中的所有成员。==

4. \==属性没有重写一说。**和 方法 不同，属性的值 看编译类型。**==

5. \==`instanceof` 比较操作符。用于判断对象类型是否是某类型或其子类型。此时判断的是 **运行类型**。==

   ```java
   //向下转型的时候比较常见,可以防止意外的类型转换异常。
   A a = new B();
   if(B instanceof A){
   	B b = (B)a;
   }
   ```

### ==7.6.3 理解方法调用==

在对象上调用方法的过程如下：

1. 编译器查看对象的声明类型和方法名。该类和其父类中，所有同名方法（包括参数不同的方法）都被列举。

   至此，编译器已经知道所有可能被调用的方法。

2. 编译器确认方法调用中提供的参数类型。

   那些列举方法中存在参数类型完全匹配的方法时，即调用该方法。

   \==没有发现匹配方法，抑或是发现经过类型转换产生了多个匹配方法时，就会报错==

   至此，编译器已经知道要调用方法的名字和参数类型

3. 如若是 private 方法、static 方法、final 方法、构造器，那么编译器将能准确知道要调用哪个方法。这称为 **静态绑定**

   与之相对的，==如果调用方法依赖于隐式参数类型，那么必须在运行时 **动态绑定**==

4. 程序运行并采取动态绑定方法时，JVM 将调用那个 **实际类型** 对应的方法。

倘若每次调用方法都进行以上搜索，会造成庞大的时间开销。为此，JVM 预先为每个类计算了 **方法表**。

方法表中列举了所有方法的签名与实际调用的方法。如此，每次调用方法时，只需查找该表即可。

特别地，使用 super 关键字时，JVM 会查找其父类的方法表。

**==动态绑定机制==：**

> 在运行时能够自动选择调用的方法的现象称为动态绑定

* 当调用对象方法的时候，该方法和该对象（隐式参数）的内存地址/运行类型绑定。
* 当调用对象属性时，没有动态绑定机制。于是哪里声明，哪里调用。

## 7.7 Object 类

> Object 类是所有类的超类。Java 中所有类默认继承该类。

### ==equals 方法==

```java
boolean equals(Object obj)
```

用于检测一个对象是否等于另一对象。

在 Object 中，该方法的实现是比较 形参 与 隐式参数 的对象引用是否一致。

**与 `==` 的区别：**

* `==`：既可以判断基本类型，也可以判断引用类型。如果判断基本类型，判断的是值是否相等。如果判断引用类型，判断的是地址是否相等。

* equals 方法：==是 Object 中的方法，只能判断引用类型。这个场合下，和`==`一样==，默认判断地址是否相等，但子类中往往重写该代码，以判断内容是否相等。

  \==在子类中定义 equals 方法时，首先调用超类的 equals 方法。那个一致时，再比较子类中的字段。==

**Java 语言规范要求 equals 方法具有如下特性：**

* *自反性*：对于任何非空引用 x，`x.equals(x)` 应返回 true

* *对称性*：对于任何引用 x 和 y，当且仅当 `x.equals(y)` 为 true 时，`y.equals(x)` 为 true

  \==如果所有的子类具有相同的相等性语义（判断相等的条件具有泛用性），可以使用 `instanceof` 检测其类型。否则，最好使用 `getClass` 方法比较类型。==

* *传递性*：对于任何引用 x、y、z，如果 `x.equals(y)` 为 true ，`y.equals(z)` 为 true，那么 `x.equals(z)` 也应该为 true

* *一致性*：如果 x 和 y 的引用没有发生变化，反复调用 `x.equals(y)` 应该返回相同的结果

* 对于任何非空引用 x，`x.equals(null)` 应该返回 false

### ==hashCode 方法==

```java
int hashCode()
```

返回对象的 散列码值。

散列码值是由对象导出的一个整型值。散列码是无规律的。如果 x 与 y 是不同对象，两者的散列码基本上不会相同。

**字符串**的散列码是由其内容导出的，而**其他引用对象**的散列码是根据存储地址得出的。

**散列码的作用：**

1. 提高哈希结构的容器的效率。
2. \==两个引用，若是指向同一对象，则哈希值一般不同。==
3. 哈希值是根据地址生成的，因而，哈希值不能等同于地址

**相关方法：**

* `Objects.hashCode(Object obj)`

  这是一个 null 安全的返回散列值的方法。传入 null 时会返回 0

* \==`Objects.hash(Object... values)`==

  组合所有传入参数的散列值

* `Integer.hashCode(int value)`

  返回给定基本数据类型的散列值。所有包装类都有该静态方法

* `Arrays.hashCode(xxx[] a)`

  计算数组的散列码。数组类型可以是 Object 或基本数据类型

空对象调用 hashCode 方法会抛出异常。

\==hashCode 与 equals 的定义必须相符。如果 `x.equals(y)` 返回 true，那么 `x.hashCode()` 与 `y.hashCode()` 应该返回相同的值。==

### ==toString 方法==

```java
String toString()
```

返回表示对象的一个字符串。Object 的默认实现如下

```java
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
```

* `Class getClass()`

  返回包含对象信息的 Class 对象。

* `String getName()`

  由 Class 类实例调用。返回这个类的全类名

  全类名：即包名 + 类名。比如 `com.prictice.codes.Person`

* `Class getSuperClass()`

  由 Class 类实例调用。以 Class 形式返回其父类

  Object 使用时返回 null

* `Integer.toHexString(int val)`

  返回一个数字的十六进制表示的字符串

toString 方法非常实用。Java 标准类库中的很多类重写了该方法，以便用户能获得一些有关对象状态的信息。

\==打印对象 或 使用 `+`操作符拼接对象 时，都会自动调用该对象的 toString 方法。==

\==当直接调用对象时，也会默认调用该方法。==

### finalize 方法

1. 当对象被回收时，系统会自动调用该对象的 `finalize` 方法。子类可以重写该方法，做一些释放资源的操作。
2. 何时被回收：当某对象没有任何引用时，JVM 就认为该对象是一个垃圾对象，就会（在算法决定的某个时刻）使用垃圾回收机制来销毁该对象。在销毁该对象前，会调用 `finalize` 方法。
3. 垃圾回收机制的调用，是由系统决定。也可以通过 `System.gc();` 主动触发垃圾回收机制。这个方法一经调用就会继续执行余下代码，而不会等待回收完毕。
4. 实际开发中，几乎不会运用该方法。

## 7.8 断点调试（Debug）

> 断点调试：在程序某一行设置一个断点，调试时，代码运行至此就会停住，然后可以一步一步往下调试。调试过程中可以看各个变量当前的值。如若出错，则测试到该出错代码行即显示错误并停下。进行分析从而找到这个 Bug。
>
> 调试过程中是运行状态，所以，是以对象的 运行类型 执行。
>
> 断点调试是程序员必须掌握的技能，能帮助我们查看 Java 底层源代码的执行过程，提高程序员 Java 水平。

快捷键如下

* 跳入：`F7`
* 跳过：`F8`
* 跳出：`shift + F8`
* resume，执行到下一个断点：`F9`

## 附录

### 零钱通程序

* **Wallet.java**

  ```java
  package com.the_wallet;

  public class Wallet {
      public static void main(String[] args) {
          Data p1 = new Data("Melody");
          p1.menu();
          System.out.println("再见~");
      }
  }
  ```

* Data.java

```java
package com.the_wallet;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class Data {
    private String name = "user";
    private double balance = 0;
    private String[][] detail = new String[1][5];

    private Data() {
        detail[0][0] = "项目\t";
        detail[0][1] = "\t\t";
        detail[0][2] = "时间";
        detail[0][3] = " ";
        detail[0][4] = " ";
    }

    public Data(String name) {
        this();
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void menu() {
        char inp = 'a';
        double inpD;
        Scanner scanner = new Scanner(System.in);
        while (inp != 'y' && inp != 'Y') {
            System.out.print("\n===============零钱通菜单==============="
                    + "\n\t\t\t1.零钱通明细"
                    + "\n\t\t\t2.收益入帐"
                    + "\n\t\t\t3.消费入账"
                    + "\n\t\t\t4.退   出"
                    + "\n请选择（1-4）：");
            inp = scanner.next().charAt(0);
            System.out.println("======================================");
            switch (inp) {
                case '4':
                    System.out.println("确定要退出吗？（y/n）：");
                    inp = scanner.next().charAt(0);
                    while (inp != 'y' && inp != 'n' && inp != 'Y' && inp != 'N') {
                        System.out.println("请输入“y”或者“n”！听话！");
                        inp = scanner.next().charAt(0);
                    }
                    break;
                case '1':
                    showDetail();
                    break;
                case '2':
                    System.out.println("请输入收益数额：");
                    inpD = scanner.nextDouble();
                    if (inpD <= 0) {
                        System.out.print("收益需要为正，记录消费请选择“消费入账”");
                        break;
                    }
                    earning(inpD);
                    break;
                case '3':
                    System.out.println("请输入支出数额：");
                    inpD = scanner.nextDouble();
                    if (inpD < 0) {
                        inpD = -inpD;
                    }
                    if (balance < inpD) {
                        System.out.println("您的余额不足！");
                        break;
                    }
                    System.out.println("请输入支出项目：");
                    spending(inpD, scanner.next());
                    break;
                case 'g':
                    break;
                default:
                    System.out.print("错误。请输入数字（1-4）");
            }
        }
    }

    private void earning(double earn) {
        String[][] temp = new String[this.detail.length + 1][5];
        record(detail, temp);
        this.balance += earn;
        tidy("收益入账", earn, true, temp);
        showDetail();
        System.out.println("\n收益记录完成");
    }


    private void spending(double spend, String title) {
        String[][] temp = new String[this.detail.length + 1][5];
        record(detail, temp);
        this.balance -= spend;
        tidy(title, spend, false, temp);
        showDetail();
        System.out.println("\n消费记录完成");

    }

    private void record(String[][] detail, String[][] temp) {
        for (int i = 0; i < detail.length; i++) {
            for (int j = 0; j < 5; j++) {
                temp[i][j] = detail[i][j];
            }
        }
    }

    private void tidy(String title, double num, boolean isPos, String[][] temp) {
        Date date = new Date();
        SimpleDateFormat sDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        if (title.length() <= 2) {
            temp[temp.length - 1][0] = title + "\t\t";
        } else {
            temp[temp.length - 1][0] = title + "\t";
        }
        String sign = isPos ? "+" : "-";
        temp[temp.length - 1][1] = sign + num + "";
        temp[temp.length - 1][2] = sDate.format(date);
        temp[temp.length - 1][3] = "余额：";
        temp[temp.length - 1][4] = balance + "";
        detail = temp;
    }

    private void showDetail() {
        System.out.println("--------------------------------------");
        for (int i = 0; i < detail.length; i++) {
            System.out.println(detail[i][0] + detail[i][1] + "\t" + detail[i][2] + "\t\t" + detail[i][3] + detail[i][4]);
        }
        System.out.println("--------------------------------------");
    }
}
```
