Java 入门实战:从零搭建环境到写出第一个可用的命令行工具

Java 入门实战:从零搭建环境到写出第一个可用的命令行工具

Java 这门语言已经存在了近三十年,但它在企业级后端、金融系统、Android 开发中的地位至今没有被撼动。学习 Java 的意义不在于追逐热点,而在于它能帮你建立扎实的编程基本功:强类型系统、面向对象思想、JVM 运行机制,这些概念在后续学习任何语言或框架时都会反复用到。

这篇文章不走"罗列语法"的老路,而是按照真实开发的流程展开:装环境、写代码、踩坑、排错、完成一个能跑的小工具。跟着做一遍,你对 Java 的理解会超过看十篇语法教程。

一、环境搭建:JDK 不是 JRE

很多人入门第一步就栽在环境上。先把概念理清楚:

  • JDK(Java Development Kit) :开发工具包,包含编译器 javac、运行时 JRE 和一堆工具(javadocjstack 等)。写代码必须装 JDK。
  • JRE(Java Runtime Environment):只负责运行,不含编译器。
  • JVM(Java Virtual Machine):字节码的运行载体,是"一次编译,到处运行"的核心。

三个是包含关系:JDK ⊃ JRE ⊃ JVM。

安装步骤(Windows)

  1. Adoptium 或 Oracle 官网下载 JDK 17(LTS 长期支持版,企业主流选择)。
  2. 安装后配置环境变量 JAVA_HOME 指向安装目录,如 C:\Program Files\Java\jdk-17
  3. Path 中添加 %JAVA_HOME%\bin

验证安装:

powershell 复制代码
java -version
javac -version

两个命令都输出版本号才算成功。常见错误是只配了 java 没配 javac,或者机器上装了多个 JDK 导致版本打架------用 where java 查一下实际生效的是哪个。

IDE 选择

  • IntelliJ IDEA(社区版免费):智能提示和重构能力业界第一,推荐。
  • VS Code + Java 插件:轻量,适合多语言混编的开发者。

入门阶段建议先用记事本 + 命令行编译跑通一个程序,理解 javac 编译、java 运行的过程,再切换到 IDE。否则容易形成"点一下绿色三角就能跑"的黑盒认知。

二、第一个程序:看懂每一行

新建 Hello.java

java 复制代码
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, Java");
    }
}

编译运行:

powershell 复制代码
javac Hello.java   # 生成 Hello.class 字节码文件
java Hello         # JVM 加载字节码并执行,注意这里不带 .class 后缀

逐行拆解:

  1. public class Hello:Java 是彻底的面向对象语言,所有代码必须写在类里。文件名必须与 public 类名一致,这是很多新手编译报错的第一个坑。
  2. public static void main(String[] args):程序入口方法,签名一个字母都不能改。static 意味着 JVM 调用它时不需要先创建对象。
  3. System.out.printlnSystem 类的静态字段 out 是一个 PrintStream 对象,println 是它的方法。

对比 Python 的 print("hello"),Java 确实啰嗦。但这种啰嗦换来的是明确的类型约束和结构边界------项目变大后你会感激这一点。

三、语法核心:别死记硬背,理解设计意图

3.1 基本类型与引用类型

Java 有 8 种基本类型:byteshortintlongfloatdoublecharboolean。其余一切都是引用类型(对象)。

需要记住的实际只有几个要点:

  • int 占 4 字节,范围约 ±21 亿。金额计算别用 double(浮点精度问题),用 long 存最小单位(分)或 BigDecimal
  • 字符串是 String 类,不是基本类型。比较内容必须用 equals,不能用 ==
java 复制代码
String a = new String("abc");
String b = new String("abc");
System.out.println(a == b);        // false,比较的是内存地址
System.out.println(a.equals(b));   // true,比较的是内容

== 对引用类型比较的是"是不是同一个对象",这是面试和实际 Bug 的双重重灾区。

3.2 集合:工作中 80% 的数据结构操作都靠它

数组长度固定、API 贫瘠,实际开发中用得最多的是集合框架。入门掌握三个就够:

java 复制代码
import java.util.*;

// List:有序、可重复,按索引访问
List<String> names = new ArrayList<>();
names.add("张三");
names.add("李四");
System.out.println(names.get(0));   // 张三

// Map:键值对,按 key 快速查找
Map<String, Integer> scores = new HashMap<>();
scores.put("张三", 90);
scores.put("李四", 85);
System.out.println(scores.get("李四"));  // 85

// Set:去重
Set<String> tags = new HashSet<>();
tags.add("java");
tags.add("java");   // 重复元素不会加入
System.out.println(tags.size());    // 1

选型原则:

需求 选择
按顺序存取、按下标访问 ArrayList
频繁增删中间元素 LinkedList(实际很少用)
键值对查找 HashMap
键值对 + 有序 TreeMap / LinkedHashMap
去重 HashSet

HashMap 是 Java 面试中出现频率最高的知识点,入门阶段先会用,进阶再去研究它的哈希冲突、扩容机制、JDK 8 的红黑树优化。

3.3 异常处理:程序健壮的底线

异常不是错误,是"可预期的意外"。文件不存在、网络超时、用户输入格式错误,这些都该用异常机制处理,而不是让程序直接崩溃。

java 复制代码
import java.io.*;

public static String readFirstLine(String path) {
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader(path));
        return reader.readLine();
    } catch (FileNotFoundException e) {
        return "文件不存在: " + path;
    } catch (IOException e) {
        return "读取失败: " + e.getMessage();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException ignored) {}
        }
    }
}

上面的 finally 关流写法很丑,Java 7 引入的 try-with-resources 可以自动关闭资源:

java 复制代码
public static String readFirstLine(String path) {
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        return reader.readLine();
    } catch (IOException e) {
        return "读取失败: " + e.getMessage();
    }
}

规则:实现了 AutoCloseable 接口的对象,放在 try 的小括号里,用完自动关闭。 这是现代 Java 的标准写法。

检查异常(checked exception)必须捕获或声明抛出,运行时异常(NullPointerExceptionArrayIndexOutOfBoundsException 等)属于代码 Bug,应该通过修复逻辑而不是 try-catch 来掩盖。

四、面向对象:从"把代码装进类"到"用类设计程序"

OOP 三大特性:封装、继承、多态。概念背下来没用,关键是理解它们解决什么问题。

用一个简单的银行账户例子说明:

java 复制代码
public class BankAccount {
    // 封装:字段私有,外部不能直接改余额
    private String owner;
    private double balance;

    public BankAccount(String owner, double initialBalance) {
        this.owner = owner;
        this.balance = Math.max(initialBalance, 0);
    }

    // 通过方法控制对数据的合法操作
    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("存款金额必须大于0");
        }
        this.balance += amount;
    }

    public boolean withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            this.balance -= amount;
            return true;
        }
        return false;
    }

    public double getBalance() {
        return this.balance;
    }
}

封装的本质是保护数据不被随意破坏 。如果 balance 是 public 的,任何代码都能 account.balance = -9999,业务逻辑就形同虚设。

继承和多态的典型场景是"同一操作,不同实现":

java 复制代码
abstract class Shape {
    abstract double area();
}

class Circle extends Shape {
    private double radius;
    Circle(double r) { this.radius = r; }

    @Override
    double area() { return Math.PI * radius * radius; }
}

class Rectangle extends Shape {
    private double width, height;
    Rectangle(double w, double h) { this.width = w; this.height = h; }

    @Override
    double area() { return width * height; }
}

// 调用方不需要关心具体是哪种形状
public static void printArea(Shape s) {
    System.out.println("面积: " + s.area());
}

printArea 接受 Shape 类型,传入圆就按圆算,传入矩形就按矩形算------这就是多态。以后新增一个三角形,printArea 一行都不用改。这就是"对扩展开放,对修改关闭"的开闭原则。

五、实战:写一个命令行记账工具

学了语法不练等于没学。下面这个不到一百行的程序综合了上面讲的所有知识点:类设计、集合、异常处理、文件读写。

功能:记录收支,程序退出时保存到文件,下次启动自动读取。

java 复制代码
import java.io.*;
import java.util.*;

public class ExpenseTracker {
    // 记录类:一笔账
    static class Record {
        String item;
        double amount;   // 正数收入,负数支出

        Record(String item, double amount) {
            this.item = item;
            this.amount = amount;
        }

        // 存成 "餐饮,-35.5" 这种格式
        String toLine() {
            return item + "," + amount;
        }

        static Record fromLine(String line) {
            String[] parts = line.split(",", 2);
            return new Record(parts[0], Double.parseDouble(parts[1]));
        }
    }

    private static final String DATA_FILE = "records.txt";
    private final List<Record> records = new ArrayList<>();

    public void add(String item, double amount) {
        records.add(new Record(item, amount));
    }

    public void printSummary() {
        double total = 0;
        System.out.println("===== 账单明细 =====");
        for (Record r : records) {
            System.out.printf("%-12s %+.2f 元%n", r.item, r.amount);
            total += r.amount;
        }
        System.out.printf("结余: %.2f 元%n", total);
    }

    public void save() {
        try (PrintWriter writer = new PrintWriter(new FileWriter(DATA_FILE))) {
            for (Record r : records) {
                writer.println(r.toLine());
            }
            System.out.println("已保存到 " + DATA_FILE);
        } catch (IOException e) {
            System.err.println("保存失败: " + e.getMessage());
        }
    }

    public void load() {
        File file = new File(DATA_FILE);
        if (!file.exists()) return;   // 第一次运行没有数据文件,正常
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String line;
            while ((line = reader.readLine()) != null) {
                records.add(Record.fromLine(line));
            }
        } catch (IOException | NumberFormatException e) {
            System.err.println("读取历史数据失败: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        ExpenseTracker tracker = new ExpenseTracker();
        tracker.load();

        Scanner scanner = new Scanner(System.in);
        System.out.println("记账工具 | 输入格式: 项目 金额 (负数为支出),输入 q 退出");
        while (true) {
            System.out.print("> ");
            String input = scanner.nextLine().trim();
            if (input.equalsIgnoreCase("q")) break;

            String[] parts = input.split("\\s+", 2);
            if (parts.length != 2) {
                System.out.println("格式错误,示例: 午餐 -35.5");
                continue;
            }
            try {
                tracker.add(parts[0], Double.parseDouble(parts[1]));
            } catch (NumberFormatException e) {
                System.out.println("金额必须是数字");
            }
        }

        tracker.printSummary();
        tracker.save();
    }
}

编译运行:

powershell 复制代码
javac ExpenseTracker.java
java ExpenseTracker

这个程序故意用了最简单的 CSV 格式存数据。你可以继续迭代它:给账目加日期、按类别统计、改用 JSON 存储(引入 Jackson 或 Gson 库,顺便学会用 Maven 管理依赖)。每一步迭代都是真实开发中会遇到的问题。

六、避坑清单:新手最常犯的几个错误

  1. 文件名和 public 类名不一致 → 编译错误。
  2. 字符串用 == 比较 → 逻辑错误,用 equals
  3. 空指针(NPE) :调用方法前没判断对象是否为 null。养成习惯:方法入参先校验,返回值可能为 null 时考虑用 Optional
  4. switch 忘记 break → 穿透到下一个 case。JDK 14+ 的新语法 switch -> 天然避免了这个问题。
  5. 改了代码忘记重新编译 → 跑的还是旧的 .class 文件。用 IDE 后基本不会遇到,但命令行阶段容易中招。
  6. 包名乱写 → 类找不到。包名全小写,通常是域名倒写,如 com.example.app

七、后续路线:入门之后往哪走

Java 的知识体系很大,建议按这个顺序推进,每一步都配一个小项目:

阶段 内容 练手项目
1. 核心语法 本文全部内容 命令行记账工具
2. 进阶特性 泛型、Lambda、Stream API、多线程基础 多线程文件下载器
3. 工程化 Maven/Gradle、单元测试 JUnit、日志 SLF4J 给记账工具加测试和依赖管理
4. Web 开发 Servlet → Spring Boot、MySQL、REST API 个人博客后端
5. 深入 JVM 类加载、内存模型、GC、JVM 调优 分析一次 OOM 事故

几个值得收藏的资源:

结语

学 Java 最大的误区是把时间花在"看"上:看视频、看教程、看别人的笔记。真正有效的路径只有一个------写代码、报错、查文档、改代码、再跑。语法三天能过完,但把 ExpenseTracker 这样的小工具从零写到能用的过程,才是真正在脑子里建立编程直觉的过程。

把上面的代码敲一遍,别复制粘贴。敲错的地方,就是你没学会的地方。

相关推荐
ch.ju1 小时前
Java—idea知识:如何创建新项目
java·开发语言·intellij-idea
土司大王1 小时前
LeetCode hot100——74.搜索二维矩阵:Java 二分模板
java·算法·leetcode
2302_1112 小时前
java依赖小结
java·运维·数据库
sunshine22 girl2 小时前
Java学习三 高级语法3, 方法
java·学习
captain3762 小时前
网络原理(3)-TCP核心机制连接管理▲▲▲
java·服务器·网络·ide·网络协议·tcp/ip·java-ee
企业数字化笔记2 小时前
固定资产历史数据怎么导入系统?Excel模板、字段映射和数据校验
android·java·数据库·后端
troy1282 小时前
Python 基础语法(一):变量、数据类型与运算符
java·服务器·python
还是奇怪3 小时前
OpenAI GPT-5.6 构建指南拆解:创业公司如何用模型选择与 Responses API 降低 Agent 成本
java·数据库·人工智能·gpt
可涵不会debug3 小时前
LangChain 文本分割器详解:字符分割、Token 分割、硬约束递归分割实战
java·前端·数据库