This commit is contained in:
e2hang
2025-12-29 23:41:10 +08:00
commit b76abae679
2 changed files with 375 additions and 0 deletions

263
Class/readme.md Normal file
View File

@@ -0,0 +1,263 @@
这两个问题问得非常精准,直接触及了 Java **工程化**的核心。
### 问题 1什么时候用 public、static什么时候单开文件
这是 Java 初学者最容易混淆的地方,我们可以这样理解:
#### **1. 什么时候用 `public`?(决定“谁能访问”)**
* **单开文件时**:如果你希望这个类能被整个项目中的其他类使用,就用 `public`
* **方法/属性**:如果你希望其他类能调用这个方法(比如 `getInfo()`),就用 `public`
* **如果不写**:默认是“包私有”,只有同一个文件夹下的类能访问。
#### **2. 什么时候用 `static`?(决定“是否属于类本身”)**
* **核心逻辑**`static` 意味着这个东西**不需要 `new` 就能用**。
* **工具类/方法**:比如 `Math.sqrt()`,你不需要 `new Math()` 就能算平方根,这就是静态的。
* **内部类**:如果你在一个类里面写另一个类,且不需要外部类的实例就能创建它,就用 `static`
* **注意**:在 `main` 方法里调用的本类方法,必须是 `static` 的。
#### **3. 什么时候单开一个文件?(决定“代码整洁度”)**
* **Java 惯例****绝大多数情况下,一个文件只写一个类**,且文件名就是类名。
* **你的 User 类**:虽然写在 `Main.java` 里能运行,但在做服务器开发时,`User` 是一个“模型Model应该单开一个 `User.java`
* **唯一例外**:如果一个类非常小,且只在某个类内部使用,可以写成“内部类”。
---
### 问题 2如何引入其他路径的文件
在 Java 中,我们不用“文件路径”来找代码,而是用 **`package`(包)**。这就像是用“行政区划”代替“坐标”。
#### **第一步:给文件定包名(文件开头)**
假设你有一个项目,结构如下:
```text
src/
├── com.myapp.model/
│ └── User.java
└── com.myapp.main/
└── Main.java
```
`User.java` 的第一行要写:
```java
package com.myapp.model;
public class User { ... }
```
#### **第二步引入Import**
`Main.java` 中,如果你想用那个 `User` 类,你需要在文件开头使用 `import` 关键字:
```java
package com.myapp.main;
// 引入具体路径下的类
import com.myapp.model.User;
public class Main {
public static void main(String[] args) {
User u = new User(...); // 现在可以用了!
}
}
```
#### **特殊的引入:`java.util.***`
你之前用的 `import java.util.Scanner;` 其实就是引入了 Java 官方提供的“工具包”里的 `Scanner` 类。
---
### 💡 总结一下:
1. **想给别人用**:加 `public`
2. **不想 `new` 就能用**:加 `static`
3. **正式写项目**:一个文件一个类,放在不同的 `package` 里。
**你现在可以尝试一下:新建两个文件 `User.java` 和 `Main.java`,把之前的代码拆开,看看能不能编译成功?这是迈向“真软件开发”的第一步!**
在 C++ 中我们叫 STLStandard Template Library在 Java 中,对应的这套东西叫做 **Java Collections Framework (Java 集合框架)**
它们全部位于 **`java.util`** 这个包下面。所以使用前,你得先 `import java.util.*;`
---
### 1. Java 集合框架大蓝图
Java 的集合主要分为两大派系:
* **Collection 接口**:存的是单个元素(类似 C++ 的 `vector`, `set`, `list`)。
* **Map 接口**存的是键值对Key-Value类似 C++ 的 `map`)。
---
### 2. 常用集合对比与用法
#### **A. 动态数组:`ArrayList**`
最常用的,对应 C++ 的 `std::vector`
* **特点**:查找快,增删慢(尾部除外)。
* **用法**
```java
ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.get(0); // 访问
```
#### **B. 集合:`HashSet` 与 `TreeSet**`
对应 C++ 的 `std::unordered_set``std::set`
* **`HashSet`**:无序,速度极快(基于哈希表)。
* **`TreeSet`**:自动排序(基于红黑树)。
* **用法**
```java
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(1); // 重复元素会被忽略
System.out.println(set.contains(1)); // true
```
#### **C. 映射:`HashMap` 与 `TreeMap**`
对应 C++ 的 `std::unordered_map``std::map`。这是**服务器开发**中最常用的数据结构,用于存储配置、用户信息等。
* **`HashMap`**键值对Key 不能重复。
* **用法**
```java
HashMap<String, Double> userAssets = new HashMap<>();
userAssets.put("Wan", -114514.0); // 存入
double asset = userAssets.get("Wan"); // 取出
```
---
### 3. 一个实战小例子
既然你之前写了 `User` 类,我们看看如何用 `Map` 把用户 ID 和对象关联起来,这样查询时就不需要循环遍历整个数组了:
```java
import java.util.HashMap;
import java.util.Map;
public class TestCollections {
public static void main(String[] args) {
// 创建一个 MapKey 是 ID (String)Value 是 User 对象
Map<String, User> userMap = new HashMap<>();
// 存入数据
userMap.put("0", new User("Wan", "0", 0, -114514));
userMap.put("1", new User("Zhang", "1", 1, 114514));
// 模拟服务器查询:通过 ID 直接秒杀查询,不需要 for 循环
String searchId = "0";
if (userMap.containsKey(searchId)) {
userMap.get(searchId).getInfo();
} else {
System.out.println("用户不存在");
}
}
}
```
---
### 4. 关键区别Java vs C++
* **泛型**Java 使用 `<Integer>`,注意必须是对象类型(包装类),不能直接用 `int`,得写 `Integer`;不能直接用 `double`,得写 `Double`
* **迭代器**:虽然 Java 也有 `Iterator`,但现在更流行用 `forEach` 或者是 `Stream API`
* **内存管理**:你不需要像 C++ 那样担心 `delete` 集合里的对象Java 的 GC垃圾回收会自动处理。
---
### 🛠️ 建议挑战
你可以尝试把之前代码里的 `User[] user = {...}` 换成 `ArrayList<User>`,然后试试看:
1.`list.add()` 添加几个用户。
2.`list.remove(0)` 删除第一个用户。
3. 看看 `list.size()` 的变化。
**你想深入了解一下为什么 Java 集合里不能存 `int` 这种基本类型(装箱/拆箱机制),还是直接动手试试 Map 的操作?**
``` cpp
class Solution {
    void flip(TreeNode* &left, TreeNode* &right){
        if(left == null && right == null) return;
        TreeNode tmp = left;
        left = right;
        right = tmp;
        flip(left->left, left->right);
        flip(right->left, right->right);
    }
    TreeNode* flipTree(TreeNode* root) {
        if(root == null) return root;
        flip(root->left, root->right);
        return root;
    }
}
```
完全正确!你这一句话就点到了 Java 内存管理的死穴。
作为一个 C++ 选手,你可以把 Java 的声明和初始化拆解为这两步:
1. **声明 `TreeNode node;**`:这相当于 C++ 里的 `TreeNode* node = nullptr;`。在栈上只给了你一个 8 字节64位系统的空间里面存的是全零啥也干不了一用就报 `NullPointerException`。
2. **执行 `new TreeNode()**`:这相当于 C++ 里的 `new` 关键字。JVM 跑到堆Heap里划拉出一块内存把对象的成员变量val, left, right填进去然后把这块地盘的**首地址**返回给栈上的 `node`。
---
### 💡 一个让你“豁然开朗”的对比
在 C++ 里,你有两种选法;而在 Java 里,你只有一种选法。
| 特性 | C++ (两种都有) | Java (只有一种) |
| --- | --- | --- |
| **栈对象** | `TreeNode node;` (直接分配内存) | **不存在** (只有基本类型能进栈) |
| **堆对象** | `TreeNode* node = new TreeNode();` | `TreeNode node = new TreeNode();` |
| **指针算术** | `node++` (可以) | **禁止** (安全第一) |
| **内存释放** | `delete node;` (手动) | **自动** (GC 负责) |
---
### 🚀 既然“指针本质”你已经通了,那我们聊聊“服务器”
在 Java 服务器开发中(比如使用 **Spring Boot** 框架这种“new 了才分配地址”的特性衍生出了一个极度重要的概念:**依赖注入 (Dependency Injection, DI)**。
**场景:** 你的 `LoginService` 需要一个 `DatabaseConnector`。
* 在 C++ 里,你可能要在构造函数里手写 `new`。
* 在 Java 里,你只需要声明 `private DatabaseConnector db;`,然后打个注解。
* **本质:** 框架在后台偷偷帮你执行了 `new`,并把那个**地址(引用)**塞给了你的变量。
这就是为什么 Java 适合做大型软件:它通过管理这些“指针”的指向,让你不需要关注对象是怎么创建的,只需要关注怎么用它。
---
### 🧠 最后一个关于指针的“脑筋急转弯”
既然你说类创建出来默认是指针(引用),那 **`String s1 = "Hello";`** 为什么不需要 `new`
这是 Java 为了性能搞的一个“特权”。如果你感兴趣,我们可以聊聊 **字符串常量池 (String Pool)** —— 这也是 Java 节省内存的一大杀手锏。
**或者,你现在想直接上手写个带“指针逻辑”的小 Demo比如链表反转来验证一下你的理解**