ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

零基础学Java:从环境配置到小项目实战的完整路径

零基础学Java:从环境配置到小项目实战的完整路径

简介:这份资源是韩顺平「循序渐进学Java零基础」课程的完整笔记PDF,面向零基础或刚入门Java编程的学习者,帮助其系统建立Java知识体系并了解实际就业方向。笔记按三个阶段组织:阶段一讲解Java语法、数据类型、控制结构与类对象等基础;阶段二深入异常处理、多线程、I/O流与集合框架;阶段三延伸至数据库操作、网络编程与面向对象设计原则,并配有SSM、Android、Hadoop等开发场景说明。资源包共1个PDF文件,大小约59.36MB,内容以章节笔记形式呈现,目录结构清晰,便于按模块查阅与复习。目前已有2715人学习下载,适合希望从零起步、循序渐进掌握Java核心概念并了解其应用领域的读者作为配套学习资料使用。

1. 零基础学 Java 的第一道坎:为什么你照着视频敲完,还是写不出一个能跑的小工具

很多人第一次接触 Java,是从一套叫“循序渐进学 Java 零基础”的完整笔记开始的。视频里老师敲一行,你跟着敲一行,编译通过,控制台打印出结果,那一刻觉得自己会了。可关掉视频,让你独立写一个“从键盘读三个数,输出最大值”的小程序,手就停在Scanner那一行,不知道下一步该写什么。这不是你笨,而是“跟着敲”和“自己写”之间,隔着一条叫“知识结构化”的沟。这套笔记的价值,不在于它讲了多少语法,而在于它把 Java 零基础到能写小项目的路径拆成了可复现的台阶。这篇文章要做的,就是把这些台阶重新铺一遍,告诉你每一步踩在哪里、参数怎么设、哪里最容易翻车,让你从“看懂了”走到“写得出”。

2. 把 JDK、IDE 和第一个类跑通:环境配置的四个硬指标

2.1 JDK 版本怎么选:LTS 是底线,不是建议

零基础最容易在第一步就卡住:JDK 到底装哪个版本。网上搜一圈,有人说 8,有人说 17,有人说 21。我的血泪经验是:只选 LTS 版本,且优先选当前主流的 LTS。LTS 是长期支持版,意味着语法稳定、教程兼容、企业里跑得最多。截至我写这篇笔记时,主流选择是 JDK 17 或 JDK 21。如果你跟的笔记里用的是 JDK 8 的语法,那用 17 跑也完全没问题,因为 Java 向下兼容做得很好。但反过来,用 8 去跑 17 的新特性(比如record、switch表达式)就会直接编译失败。

安装时有一个参数必须确认:JAVA_HOME。Windows 上很多人装完 JDK,java -version能跑,但javac报“不是内部或外部命令”,就是因为只把bin目录加进了Path,没设JAVA_HOME。正确做法是:

# 以 JDK 17 为例,安装路径假设为 C:\Java\jdk-17 # 设置 JAVA_HOME 指向 JDK 根目录,不是 bin JAVA_HOME=C:\Java\jdk-17 # Path 里追加两个条目,顺序不能反 Path=%JAVA_HOME%\bin;%Path%

逻辑说明:JAVA_HOME是给其他工具(Maven、Gradle、Tomcat)定位 JDK 用的,Path是给命令行定位java和javac用的。只配Path不配JAVA_HOME,后面学构建工具时必翻车。验证命令:

java -version javac -version

两条命令输出的版本号必须一致。如果java是 17 而javac是 8,说明系统里存在多个 JDK,Path顺序错了。

2.2 IDE 选哪个:新手别碰 Eclipse,直接上 IntelliJ IDEA 社区版

零基础阶段,工具越少越好。Eclipse 需要手动配构建路径、装插件、调编码格式,每一步都是坑。IntelliJ IDEA 社区版免费,开箱即用,新建项目时自动帮你生成src目录和模块配置。安装时只有一个注意点:不要勾选“Add launchers dir to the PATH”,除非你确定自己需要命令行启动 IDE。这个选项会把 IDEA 的启动脚本塞进系统路径,后面如果卸载不干净,命令行里会残留一堆无效命令。

新建第一个项目时,IDEA 会让你选 JDK。这里直接指向你刚才设的JAVA_HOME路径。项目结构长这样:

my-first-java/ ├── src/ │ └── Main.java └── my-first-java.iml

src是源码根目录,所有.java文件放这里。.iml是 IDEA 的模块配置文件,不用手动改。

2.3 第一个类:从main方法到控制台输出

零基础笔记里第一个程序通常是HelloWorld。但我要你多写一步:把HelloWorld改成一个能接收输入并做判断的小程序。因为只输出字符串,你练不到Scanner、变量、条件判断这三个核心点。

import java.util.Scanner; public class Main { public static void main(String[] args) { // 创建 Scanner 对象,绑定标准输入流 Scanner scanner = new Scanner(System.in); System.out.print("请输入第一个整数:"); int a = scanner.nextInt(); // 阻塞等待用户输入一个整数 System.out.print("请输入第二个整数:"); int b = scanner.nextInt(); // 三元运算符判断最大值 int max = (a > b) ? a : b; System.out.println("最大值是:" + max); scanner.close(); // 关闭输入流,释放资源 } }

逻辑说明:Scanner是 Java 标准库里的输入工具类,nextInt()方法会阻塞程序,直到用户在控制台输入一个整数并回车。如果输入的不是整数(比如字母),程序会抛InputMismatchException,这是新手最常见的运行时错误之一。参数说明:System.in是标准输入流,对应键盘;System.out是标准输出流,对应控制台。scanner.close()不是必须的,但养成习惯,后面学文件 IO 时不会忘。

运行方式:在 IDEA 里右键Main.java,选Run 'Main.main()'。控制台会依次提示输入,输入两个整数后输出最大值。如果你在命令行里跑,先javac Main.java编译,再java Main运行。

注意:类名必须和文件名完全一致,包括大小写。Main.java里只能有一个public class Main。这是 Java 的硬性规定,不是风格建议。

3. 变量、循环、数组:把语法练成肌肉记忆的三个训练法

3.1 变量作用域:为什么你的for循环里定义的变量在外面用不了

零基础笔记讲到变量时,通常会列一堆数据类型:int、double、boolean、char、String。但真正让新手翻车的不是类型本身,而是作用域。看这段代码:

public class ScopeDemo { public static void main(String[] args) { for (int i = 0; i < 3; i++) { int sum = i * 2; System.out.println("循环内 sum = " + sum); } // 下面这行会编译报错:找不到符号 sum // System.out.println("循环外 sum = " + sum); } }

逻辑说明:sum定义在for循环的花括号{}内部,它的生命周期只在这个块里。出了这个块,变量就被销毁了。这是 Java 块级作用域的基本规则。新手常犯的错是:在循环里累加一个变量,却把变量定义在循环内部,导致每次循环都重新创建,累加结果永远是最后一次的值。正确做法是把累加变量定义在循环外面:

int total = 0; // 定义在循环外 for (int i = 1; i <= 100; i++) { total += i; // 累加 } System.out.println("1 到 100 的和是:" + total);

参数说明:total的作用域是整个main方法,循环内外的代码都能访问。i的作用域仅限于for循环,循环结束后i不可见。这个区别在写嵌套循环时尤其重要。

3.2 循环的三个必调参数:初始值、终止条件、步长

for循环的语法是for (初始值; 终止条件; 步长)。零基础阶段,90% 的循环 bug 都出在这三个参数上。我一般会让学生做这个练习:打印 1 到 100 之间所有能被 3 整除但不能被 5 整除的数。

public class LoopPractice { public static void main(String[] args) { int count = 0; // 计数器,统计符合条件的个数 for (int i = 1; i <= 100; i++) { // 初始值 1,终止条件 <=100,步长 +1 if (i % 3 == 0 && i % 5 != 0) { // 能被 3 整除 且 不能被 5 整除 System.out.print(i + " "); count++; if (count % 5 == 0) { // 每输出 5 个数换一行 System.out.println(); } } } System.out.println("\n符合条件的数共有:" + count + " 个"); } }

逻辑说明:i % 3 == 0判断整除,i % 5 != 0判断不整除,&&是逻辑与,两个条件同时成立才执行输出。count % 5 == 0用来控制换行,每输出 5 个数字换一行,避免所有数字挤在一行看不清。参数说明:初始值i = 1不能写成i = 0,因为 0 能被任何非零整数整除,会多出一个不符合预期的结果。终止条件i <= 100不能写成i < 100,否则会漏掉 100。步长i++不能写成i += 2,否则会跳过所有偶数,漏掉像 6、12 这样能被 3 整除的偶数。

3.3 数组:从声明到遍历,三个必须记住的边界

数组是零基础阶段第一个“批量处理数据”的工具。声明数组有三种写法:

int[] arr1 = new int[5]; // 声明并分配空间,默认值全为 0 int[] arr2 = {1, 2, 3, 4, 5}; // 声明并初始化,长度由元素个数决定 int[] arr3 = new int[]{1, 2, 3}; // 完整写法,不常用但要知道

逻辑说明:new int[5]在堆内存里分配了 5 个连续的int空间,每个元素默认值是 0。{1, 2, 3, 4, 5}是简化写法,只能在声明时使用,不能先声明再赋值。参数说明:数组的长度用arr.length获取,注意没有括号,不是arr.length()。这是新手最容易写错的地方之一。

遍历数组有两种方式:

int[] scores = {85, 92, 78, 96, 88}; // 方式一:普通 for 循环,能拿到索引 for (int i = 0; i < scores.length; i++) { System.out.println("第 " + (i + 1) + " 个成绩:" + scores[i]); } // 方式二:增强 for 循环,只能拿值,拿不到索引 for (int score : scores) { System.out.println("成绩:" + score); }

逻辑说明:普通for循环的终止条件是i < scores.length,不是i <= scores.length。写成<=会抛ArrayIndexOutOfBoundsException,因为数组索引从 0 开始,最大索引是length - 1。增强for循环底层也是普通for,但语法更简洁,适合只读遍历。如果你需要在遍历时修改数组元素,必须用普通for,因为增强for里的变量是值的拷贝。

注意:数组一旦创建,长度不可变。arr.length是属性,不是方法。想动态扩容,需要用ArrayList,那是后面集合框架的内容。

4. 避坑:零基础学 Java 最容易翻车的五个场景

4.1 现象:javac报“编码 GBK 的不可映射字符” → 原因:源文件编码和编译编码不一致 → 解决:统一用 UTF-8

Windows 中文环境下,记事本保存的.java文件默认是 GBK 编码,而javac默认用平台编码编译。如果文件里有中文注释或中文字符串,就会报这个错。解决方式有两种:一是把文件编码改成 UTF-8(IDEA 默认就是 UTF-8),二是编译时指定编码:

javac -encoding UTF-8 Main.java

我一般会直接在 IDEA 里把File -> Settings -> Editor -> File Encodings全部设为 UTF-8,包括 Global Encoding、Project Encoding、Default encoding for properties files。这样新建的文件不会再有编码问题。

4.2 现象:java Main报“找不到或无法加载主类” → 原因:类名和文件名不一致,或者包路径不对 → 解决:检查public class后面的名字

Java 要求public类的类名必须和文件名完全一致。Main.java里写public class main会编译失败。另外,如果类在包里(比如package com.example;),运行时必须用完整类名:

java com.example.Main

而且要在包结构的根目录下运行,不是进到com/example/里面运行。这是新手用命令行跑 Java 时最常见的错误。

4.3 现象:Scanner输入字符串时跳过,直接执行下一行 → 原因:nextInt()后残留了换行符 → 解决:用nextLine()吃掉换行

看这段代码:

Scanner scanner = new Scanner(System.in); System.out.print("请输入年龄:"); int age = scanner.nextInt(); // 用户输入 25 后按回车 System.out.print("请输入姓名:"); String name = scanner.nextLine(); // 这里会直接跳过,name 为空字符串

原因:nextInt()只读取整数部分,回车符\n还留在输入缓冲区里。nextLine()一执行,立刻读到这个残留的换行符,返回空字符串。解决方式是在nextInt()后面加一行scanner.nextLine()把换行符吃掉:

int age = scanner.nextInt(); scanner.nextLine(); // 吃掉残留的换行符 String name = scanner.nextLine(); // 这次能正常读取

4.4 现象:整数除法结果永远是整数,小数部分被丢弃 → 原因:两个int相除,结果自动向下取整 → 解决:把其中一个操作数转成double

int a = 7; int b = 2; System.out.println(a / b); // 输出 3,不是 3.5 System.out.println((double) a / b); // 输出 3.5

逻辑说明:Java 的整数除法规则是:两个整数相除,结果还是整数,小数部分直接截断(不是四舍五入)。想得到小数结果,必须把至少一个操作数转成浮点类型。(double) a / b先把a转成double,然后double / int自动把b也提升为double,结果就是double。

4.5 现象:==比较字符串时,两个内容相同的字符串返回false→ 原因:==比较的是引用地址,不是内容 → 解决:用.equals()方法

String s1 = "hello"; String s2 = new String("hello"); System.out.println(s1 == s2); // false,地址不同 System.out.println(s1.equals(s2)); // true,内容相同

逻辑说明:s1指向字符串常量池里的对象,s2指向堆内存里新创建的对象,地址不同,所以==返回false。.equals()是String类重写的方法,比较的是字符序列的内容。零基础阶段记住一条:字符串比较一律用.equals(),不要用==。除非你明确知道自己在比较引用地址。

5. 从能跑到能改:用一个小项目把零散语法串起来

5.1 项目目标:控制台版学生成绩管理器

这个项目不涉及数据库、不涉及界面,只用你前面学的变量、循环、数组、Scanner和条件判断。功能需求:

功能编号功能描述涉及知识点
F1录入 5 个学生成绩数组、Scanner、for循环
F2打印所有成绩数组遍历
F3计算平均分累加、除法、类型转换
F4找出最高分和最低分条件判断、循环
F5统计及格人数(>=60)条件判断、计数器

5.2 代码实现:一个类,五个方法

import java.util.Scanner; public class ScoreManager { // 常量:学生人数,方便统一修改 static final int STUDENT_COUNT = 5; public static void main(String[] args) { double[] scores = new double[STUDENT_COUNT]; Scanner scanner = new Scanner(System.in); // F1:录入成绩 for (int i = 0; i < STUDENT_COUNT; i++) { System.out.print("请输入第 " + (i + 1) + " 个学生的成绩:"); scores[i] = scanner.nextDouble(); } // F2:打印所有成绩 System.out.print("所有成绩:"); for (double score : scores) { System.out.print(score + " "); } System.out.println(); // F3:计算平均分 double sum = 0; for (double score : scores) { sum += score; } double average = sum / STUDENT_COUNT; System.out.printf("平均分:%.2f%n", average); // F4:找最高分和最低分 double max = scores[0]; double min = scores[0]; for (int i = 1; i < scores.length; i++) { if (scores[i] > max) max = scores[i]; if (scores[i] < min) min = scores[i]; } System.out.println("最高分:" + max); System.out.println("最低分:" + min); // F5:统计及格人数 int passCount = 0; for (double score : scores) { if (score >= 60) passCount++; } System.out.println("及格人数:" + passCount); scanner.close(); } }

逻辑说明:STUDENT_COUNT用static final修饰,是常量,全大写命名。这样改学生人数时只需要改一个地方。scores数组用double类型,因为成绩可能有小数。printf的%.2f表示保留两位小数,%n是跨平台换行符。找最高分和最低分时,初始值设为scores[0],循环从索引 1 开始,避免自己和自己比较。参数说明:scanner.nextDouble()读取浮点数,如果输入非数字会抛异常。实际项目中需要加try-catch做输入校验,但零基础阶段先跑通主流程。

5.3 验证方法:三组测试数据覆盖边界

跑通代码只是第一步,验证才是关键。用这三组数据测试:

测试组输入数据预期结果验证点
T160, 60, 60, 60, 60平均 60,最高 60,最低 60,及格 5边界值全部及格
T259, 59, 59, 59, 59平均 59,最高 59,最低 59,及格 0边界值全部不及格
T30, 100, 50, 75, 88平均 62.6,最高 100,最低 0,及格 3混合数据

如果 T1 的及格人数输出 5,T2 输出 0,说明>= 60的判断正确。如果 T3 的平均分输出 62.6 而不是 62,说明double类型和printf格式化都正确。任何一组不符合预期,就回到对应的方法里检查循环边界和条件判断。

注意:scanner.nextDouble()在输入整数时也能正常读取,比如输入60会当成60.0。但输入abc会直接抛InputMismatchException,程序崩溃。这是零基础阶段可以接受的,但你要知道这个边界在哪里。

5.4 一个具体技巧:用printf对齐输出,让控制台结果像表格

零基础阶段很多人输出成绩时用字符串拼接,结果长短不一,控制台看起来乱七八糟。printf可以指定宽度和对齐方式:

System.out.printf("%-10s%-10s%n", "姓名", "成绩"); System.out.printf("%-10s%-10.2f%n", "A同学", 85.5); System.out.printf("%-10s%-10.2f%n", "B同学", 92.0);

逻辑说明:%-10s表示左对齐,占 10 个字符宽度,不足补空格。%-10.2f表示左对齐,占 10 个字符宽度,保留两位小数。%n是换行。这样输出的表格列是对齐的,看起来舒服很多。参数说明:-表示左对齐,不加-默认右对齐。10是总宽度,包括小数点和小数部分。如果实际内容超过 10 个字符,宽度会自动扩展,不会截断。

我自己的习惯是:只要输出涉及多列数据,一律用printf,不用字符串拼接。这个习惯从零基础阶段就养成,后面写日志、输出报表时能省很多对齐的功夫。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进