Files
java/Class/readme.md
2025-12-29 23:41:10 +08:00

9.3 KiB
Raw Blame History

这两个问题问得非常精准,直接触及了 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(包)。这就像是用“行政区划”代替“坐标”。

第一步:给文件定包名(文件开头)

假设你有一个项目,结构如下:

src/
 ├── com.myapp.model/
 │    └── User.java
 └── com.myapp.main/
      └── Main.java

User.java 的第一行要写:

package com.myapp.model;

public class User { ... }

第二步引入Import

Main.java 中,如果你想用那个 User 类,你需要在文件开头使用 import 关键字:

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.javaMain.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

  • 特点:查找快,增删慢(尾部除外)。
  • 用法
ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.get(0); // 访问

**B. 集合:HashSetTreeSet**

对应 C++ 的 std::unordered_setstd::set

  • HashSet:无序,速度极快(基于哈希表)。
  • TreeSet:自动排序(基于红黑树)。
  • 用法
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(1); // 重复元素会被忽略
System.out.println(set.contains(1)); // true

**C. 映射:HashMapTreeMap**

对应 C++ 的 std::unordered_mapstd::map。这是服务器开发中最常用的数据结构,用于存储配置、用户信息等。

  • HashMap键值对Key 不能重复。
  • 用法
HashMap<String, Double> userAssets = new HashMap<>();
userAssets.put("Wan", -114514.0); // 存入
double asset = userAssets.get("Wan"); // 取出


3. 一个实战小例子

既然你之前写了 User 类,我们看看如何用 Map 把用户 ID 和对象关联起来,这样查询时就不需要循环遍历整个数组了:

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 的操作?

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比如链表反转来验证一下你的理解