
最近好几个准备转行的朋友问我同一个问题“Java 到底该怎么入门网上资料一抓一大把反而不知道从哪下手。”这让我想起自己当年啃《Java 核心技术》啃到怀疑人生的日子。其实学 Java 没那么玄乎关键是别一口气吃成胖子。我写这个系列笔记就是把“踩过的坑、绕过的弯、最后沉淀下来的东西”按主题拆开讲第一篇先从环境和基础语法说起把地基打牢。后面的笔记再慢慢展开面向对象、集合、异常、IO 这些硬骨头。这篇笔记适合完全零基础的小白也适合那些“会抄代码但说不清原理”的自学者。我会尽量用大白话讲清楚每个概念到底在干嘛配上可以直接照着敲的代码和验证方法你不需要提前懂什么计算机原理打开电脑跟着做就行。学完这篇你应该能自己写出一个带判断逻辑的简单 Java 程序并且知道程序跑起来每一步背后发生了什么。1. 先把“Java 能跑起来”这件事搞定环境搭建与验证1.1 JDK 不是 JRE先搞清楚你下载的是什么很多新手第一次装 Java 就被各种名词搞晕JDK、JRE、JVM还有一个叫 OpenJDK 的东西。简单说JVMJava Virtual Machine是 Java 程序真正运行的地方它负责把 Java 字节码翻译成当前系统能理解的机器指令JRE 是 JVM 加上一组基础类库能让已经编译好的 Java 程序跑起来JDK 则在 JRE 基础上再塞进编译器javac、调试工具、打包工具等一堆开发用的东西。所以你写代码必须装 JDK只装 JRE 是没法编译的。那选哪个版本我的建议很直接如果你是跟着教程学教程用什么版本你就用什么版本。目前绝大多数公司和教程都停留在 Java 8 或 Java 11Java 8 的稳定性和社区资料丰富程度暂时没有对手。如果你是新项目可以直接上 Java 17 这个 LTS长期支持版本语法上更现代学了也不亏。选 OpenJDK 还是 Oracle JDK个人学习完全没区别OpenJDK 免费、够用就直接选它。Windows 用户下载安装包的时候注意选对应系统的 x64 Installer 版本macOS 用户选 .dmg 或 .pkg。安装过程有个小细节安装目录里最好别带中文和空格比如直接装在C:\Java\jdk-17或者/usr/local/java否则后续配置环境变量和一些老工具会让你烦躁。1.2 环境变量配置到底在配什么装完 JDK 后我遇到最多的求助就是“环境变量配了半天还是提示java不是内部或外部命令”。环境变量的作用说白了就是告诉操作系统“java”和“javac”这两个命令藏在哪个文件夹里。你在命令行敲的任何命令系统都会去 PATH 环境变量记录的路径里挨个找找不到就报错。Windows 上的配置步骤分两步。第一步新建一个JAVA_HOME系统变量值填你的 JDK 安装路径比如C:\Java\jdk-17。第二步在Path变量里新增一行%JAVA_HOME%\bin。这个%JAVA_HOME%\bin的意思是“以 JAVA_HOME 为基准进入它的 bin 子目录”bin 目录里就有java.exe和javac.exe。这么绕一圈的好处是以后你升级 JDK 只需要改 JAVA_HOME 一个地方Path 不用动。macOS 和 Linux 稍微简单点因为安装包或包管理器一般会自动生成路径你只需要在 shell 配置文件比如~/.zshrc或~/.bashrc里添加export JAVA_HOME$(/usr/libexec/java_home -v 17) export PATH$JAVA_HOME/bin:$PATH配完之后别急着高兴先做验证。新开一个命令行窗口依次输入java -version javac -version如果能看到版本信息而不是“command not found”说明环境通了。这里有个我经常提醒新手的点配置完环境变量后必须新开一个终端窗口因为旧窗口里加载的还是修改前的环境变量这是新手最容易忽略的一步。注意如果看到Error: could not open ...jvm.cfg或类似报错八成是机器上装了多个 JDK环境变量指向了不完整的安装目录。把 JAVA_HOME 指到当前存在且完整的 JDK 根目录别指到 bin 层。1.3 编译运行一个最朴素的 Hello World环境配好后用最朴素的方式确认整条链路是通的。新建一个文本文档命名为HelloWorld.java注意在主类声明的类名和文件名必须完全一致这里类名是HelloWorld文件名就必须是HelloWorld.java大小写也不能差。写入下面的代码public class HelloWorld { public static void main(String[] args) { System.out.println(Hello Java!); } }用命令行进入该文件所在目录执行javac HelloWorld.java java HelloWorld第一条命令javac会把 .java 源文件编译成 JVM 能识别的 .class 字节码文件第二条命令java才会启动 JVM 解释执行。这个“先编译后解释”的过程是 Java 静态语言特性的核心体现——它不像 Python 那样在运行时直接解释源码而是先把源码翻译成统一的中间格式再交给不同平台的 JVM 执行。这就是热搜词里“java是静态链接的”这个说法的一种通俗理解。很多 IDE 帮你屏蔽了这两条命令但你在命令行亲手敲一遍对理解“编译-运行”两个阶段非常有帮助。2. Java 的“零件”数据类型、变量与命名规则2.1 八种基本类型一张表看懂Java 是强类型语言意思是每个变量在声明时就必须明确它装的是什么类型的数据后面想塞别的类型就得做转换。Java 的基本类型一共 8 种全是数值型或字符型没有字符串基本类型。我用一张表给你梳理清楚从低到高的取值范围关系类型占用空间取值范围默认值byte1 字节-128 ~ 1270short2 字节-32768 ~ 327670int4 字节约 ±21 亿0long8 字节约 ±9.2×10^180Lfloat4 字节约 ±3.4×10^380.0fdouble8 字节约 ±1.8×10^3080.0dchar2 字节0 ~ 65535\u0000boolean不一定true / falsefalse这里面有几个容易踩的坑。第一个是long类型的字面量必须加L后缀否则即使值看起来不大编译器也会按 int 处理超出范围直接报错。第二个是float必须加f后缀因为小数默认是 double。第三个是我见过最多的int做除法比如5 / 2结果是 2 而不是 2.5因为两个整数相除结果还是整数小数部分直接被截断。这是无数新手写计算程序算错答案的经典原因。2.2 字符串不是基本类型是“常客”热搜词里有“java数据类型”我顺便把字符串这个特殊存在讲透。String在 Java 里是一个类属于引用类型它并不在基本类型表里。但 Java 对 String 做了很多“特殊照顾”比如你可以直接用双引号赋值String s hello而不像其他对象那样必须new用号可以直接拼接字符串。这种用起来像基本类型、本质是对象的设计让 String 成了 Java 里戏最多的类型。新手要特别注意String的比较方式。用比较两个 String 很多时候结果是对的因为 JVM 会在字符串常量池里复用字面量对象这反而会掩盖问题。比如String a hello; String b hello; System.out.println(a b); // 结果是 true因为指向同一个常量池对象 String c new String(hello); System.out.println(a c); // 结果是 false因为 c 是 new 出来的新对象真正稳妥的做法是用a.equals(c)判断内容相等。凡是写过一两年 Java 的人几乎都在和equals上栽过跟头。你只需要记住比较对象内容用equals只用来比较基本类型数值或确认是否同一引用。2.3 标识符命名第一课就教最容易忽略“java 标识符命名规则”上热搜说明这基础到很多人懒得记但面试题和在线判题系统真的会抠。规则其实只有四条只能由字母、数字、下划线_、美元符号$组成不能有空格和特殊符号不能以数字开头不能是 Java 关键字比如class、public、static这些词你不能拿来当名字严格区分大小写Name和name是两个完全不同的变量。在这四条红线之上行业惯例是类名用大驼峰HelloWorld变量名和方法名用小驼峰userName、getUserName()常量全大写加下划线MAX_VALUE。为什么强调这些因为别人读你代码时第一眼就能从命名看出你这是类的定义还是普通变量这直接决定协作效率。我在公司 review 代码时看到int a 1;这种命名多半会打回去让开发者改。2.4 类型转换自动提升再向下强转不同类型的数据参与运算时Java 会先把它们自动提升到表达式中最大的类型。比如int和double运算int 会先变成 double结果也是 double。相安无事但反过来double 转 int 就必须强转而且强转是直接截断小数部分不是四舍五入double d 3.99; int i (int) d; // i 是 3不是 4还有一种隐蔽的坑叫“整数溢出”。比如一个int变量不断自增超过 21 亿多之后会突然变成负数这在处理累加、计数器时极容易发生。反复累加的场景建议提前判断数据规模或者直接用long甚至BigInteger。3. 流程控制与数组写出能“做决定”的程序3.1 if、switch 与循环的实战选择一个程序如果没有判断和循环就是一条笔直的流水线毫无实用价值。Java 里的判断有if-else和switch两种循环有for、while、do-while三种。选择的原则其实很朴素多个固定值判等用switch其他情况一律用if。为什么因为switch在 JDK 7 之后支持整数、字符串和枚举但本质还是“拿一个值和多个值逐一比对”如果写区间判断比如成绩大于 90 是优秀switch就费劲了。讲一个高频考点switch的 fall-through 行为。如果 case 后面没写break执行完这个分支会继续往下执行下一个 case 的代码直到遇到break或整个 switch 结束。很多新手忘记写 break结果输出了一长串不该输出的内容。写循环时注意break是跳出当前整个循环continue是跳过本次迭代进入下一次这两个用混会让程序逻辑变得很诡异。3.2 数组定长的容器索引从 0 开始数组是 Java 最基础的容器类型。声明方式有两种int[] arr new int[5];或int[] arr2 {1, 2, 3};。new int[5]表示创建一个可以装 5 个 int 的数组初始值全部是 0随后通过索引写入索引范围是 0 到 4。这里必须记住一句话索引从 0 开始长度是 5 的时候最后一个有效索引是 4。热搜词“java中数组越界异常”指的就是你访问了不该访问的索引。比如长度为 5 的数组你取arr[5]运行时会抛出ArrayIndexOutOfBoundsException。要避免越界最稳妥的办法是用arr.length这个属性控制循环边界而不是拍脑袋写死数字for (int i 0; i arr.length; i) { System.out.println(arr[i]); }有个配套的增强型 for 循环for-each用于只读遍历for (int value : arr) { System.out.println(value); }这个写法不会越界但它拿不到当前索引如果你同时需要下标就用普通 for 循环。3.3 蓝桥杯风格的数字题目热身热搜词里有“蓝桥杯数字题目”这类竞赛入门题往往不考偏门 API就考你能不能把基础语法用灵活。我拿一个非常经典的“回文数”练手判断一个整数从左到右和从右到左读是否完全一样比如 121 和 12321。先不依赖现成函数用取余和除法一步步拆解public class PalindromeNumber { public static void main(String[] args) { int num 12321; int original num; int reversed 0; while (num 0) { int digit num % 10; // 取最后一位 reversed reversed * 10 digit; // 拼接到结果的末尾 num num / 10; // 去掉最后一位 } if (original reversed) { System.out.println(是回文数); } else { System.out.println(不是回文数); } } }这里的关键操作是% 10拿到末位数/ 10去掉末位数。这两个操作是很多数字类题目的基础比如“各位数字之和”“逆序输出数字”“统计位数”都能用这套循环模板解决。我建议你把这段代码亲手敲一遍敲完改成统计各位数字之和再改成输出每一位数字一鱼多吃很快你就能形成数字拆解的条件反射。4. 面向对象编程从“工具人代码”到“构造世界”4.1 类与对象图纸和实例的关系面向对象编程OOP是 Java 的根热搜词里反复出现“面向对象编程java”说明这是大家默认的重点。最简单的理解方式类就是一张设计图纸对象就是按图纸造出来的实际物品。图纸定义了物品有哪些属性字段、成员变量和行为方法函数图纸本身不能住人但按图纸造出来的每个房子都有自己的房间号。比如说你要表达“学生”这个概念。先定义一个Student类public class Student { String name; int age; void introduce() { System.out.println(我叫 name ,今年 age 岁); } }然后在main方法里创建对象Student s new Student(); s.name 小明; s.age 18; s.introduce();new Student()就是在根据图纸造出一个实实在在的对象s是这个对象的引用也就是你拿着它去操作那个对象。每个new出来的对象是相互独立的换句话说你造出 100 个学生对象改其中一个的名字不会影响另外 99 个。类中还可以定义构造方法专门在new时初始化对象属性public class Student { String name; int age; Student(String name, int age) { this.name name; this.age age; } }调用时直接new Student(小红, 20)。这个this关键字指代“当前正在创建的这个对象”用来区分参数name和成员变量name否则name name等于自己给自己赋值什么都没写进去。这是新手写构造方法时最容易出的逻辑错误。4.2 封装、继承、多态面试必问的三大特性封装把内部字段藏起来不让人直接改而是通过getter和setter方法控制读写。比如学生年龄不能为负你就可以在setAge方法里加个判断不符合要求就拒绝赋值。对外暴露的是方法对内保护的是数据代码维护起来会更安全。继承子类用extends关键字继承父类的非私有成员实现“复用”。比如再定义一个GraduateStudent继承Student它自动就有name、age、introduce()还能额外加自己的thesisTitle。多态同一个方法在不同子类里表现不同。父类引用可以指向子类对象调用方法时按实际对象的类型执行。比如你有一个“给所有学生发通知”的函数参数是Student类型传本科生、研究生进去都行函数内部调用同一个introduce实际执行的是各自的版本。我见过太多人背概念头头是道一让写代码就懵。我的忠告是先亲手敲一个最简单的继承例子比如父类Animal、子类Dog和Cat重写父类的sound()方法然后分别用父类引用指向不同子类对象观察输出。自己把代码跑通了概念才是你的。4.3 枚举类型约定常量更优雅枚举enum在项目里非常常用。直接定义一个表示星期几的枚举public enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }使用的时候Day today Day.MONDAY; if (today Day.MONDAY) { System.out.println(新的一周开始了); }相比用int常量比如1表示周一枚举把可取值限制在一组固定选项里传参传错值编译器就能提醒你。枚举还可以带属性和构造方法比如给每种状态配上描述和颜色值这在做订单状态流转时特别好用。新手可以先从无属性的简单枚举用起慢慢体会到它的好处。5. 把常用工具库用起来字符串处理、容器与排序5.1 String 常用方法盘点Java 的 String 方法多到记不过来但初学者先掌握这几个就够用length()获取字符串长度charAt(i)获取指定位置字符substring(begin, end)截取子串含头不含尾indexOf(s)查找子串首次出现位置找不到返回 -1split(regex)按正则拆分字符串返回 String[]trim()去除首尾空白toUpperCase()/toLowerCase()转换大小写equals()/equalsIgnoreCase()比较内容举一个使用场景从alice,25,engineer中提取关键信息String line alice,25,engineer; String[] parts line.split(,); String name parts[0]; int age Integer.parseInt(parts[1]); System.out.println(name 的年龄是 age);注意split的入参是正则表达式逗号这种普通字符没关系但如果想按点.拆必须写成\\.因为正则里点号代表任意字符。这个坑找人 debug 半小时的案例不在少数。5.2 判断字符串里有没有字母和数字热搜词里有一条很具体的需求“java 判断字符串中是否不是字母和数字”。这个在密码强度校验、输入合法性检查中很常见。Java 提供了一个语法里最值得学的工具——正则表达式。判断一个字符串是否由字母或数字组成一行代码String s hello123; boolean isLegal s.matches([a-zA-Z0-9]); System.out.println(isLegal); // true如果要求不能全是字母和数字也就是字符串里含有其他字符正则反过来写boolean hasOtherChar !s.matches([a-zA-Z0-9]);也可以逐字符判断更适合做计数for (char c : s.toCharArray()) { if (!Character.isLetterOrDigit(c)) { System.out.println(发现非法字符 c); } }两种方法各有优点。正则表达能力强、代码短但报错信息晦涩逐字符判断直观、便于统计。初学阶段建议两种都写一遍对理解“字符串就是字符的数组”这一点会很有帮助。5.3 排序手写冒泡再拥抱工具类搜索词里有“冒泡排序java”和“java排序”确实排序是绕不开的。冒泡排序的思路就像水里的气泡往上浮每次比较相邻两个元素前一个比后一个大就交换位置一趟下来最大的会挪到末尾。重复 n-1 趟全部有序public static void bubbleSort(int[] arr) { for (int i 0; i arr.length - 1; i) { for (int j 0; j arr.length - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } } }内层循环写成j arr.length - 1 - i是关键每结束一趟末尾已经有 i 个元素排好了不用再比较它们。第 5 行到第 7 行是经典的“借助临时变量交换两个数”你也可以用arr[j] ^ arr[j1]; arr[j1] ^ arr[j]; arr[j] ^ arr[j1];这种位运算写法但可读性差不推荐新手这么写。面试官问冒泡排序喜欢追问的扩展点是如果某一趟没有任何交换说明数组已经有序可以提前终止排序。也就是说在上述代码外加一个boolean swapped标记每趟开始时置 false发生交换就置 true一趟结束后如果还是 false 直接 break。这个小优化能让面试官觉得你真的理解了这个算法而不是死记硬背。实际项目中没人手写排序直接Arrays.sort(arr)或对集合用Collections.sort(list)底层是 Dual-Pivot Quicksort对基本类型和 TimSort对对象类型性能非常好。但你依然要会手写常见排序因为面试考的是逻辑思维而且很多在线笔试平台并不允许你调用现成的排序。6. 常见问题排查与避坑技巧6.1 中文乱码问题Java 程序输出中文变成??或乱码几乎每个新手都遇到过。原因通常是源文件编码和控制台编码不一致。Windows 命令行默认编码可能是 GBK而你的源文件是 UTF-8。简单粗暴的解决办法用javac -encoding UTF-8 HelloWorld.java编译然后设置控制台代码页为 UTF-8chcp 65001。更省心的方案是直接用 IDEA 或 VS Code 这类现代 IDE它们会统一管理文件编码很少出乱码。不得不承认工具用对了有些坑根本不用踩。6.2 数组越界异常怎么快速定位ArrayIndexOutOfBoundsException会在异常信息里明确告诉你下标是多少以及合法的范围是多少。比如Exception in thread main java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5读到这句话就知道访问了索引 5而数组长度是 5合法索引是 0 到 4。排查思路也很固定先看是不是循环边界写错了再看是不是读入的数据长度和你预期的不同。我在实际开发中遇到最多的是读文件或数据库查询结果后直接用固定数字取下标结果数据变少了就立刻越界。所以数组操作一定要绑定length或size()别写死。6.3 环境变量配置后 java 命令不生效前面提到要新开终端窗口这是第一条。第二条是确认Path变量中旧版本的 JDK 路径是否还在。Windows 的 java 命令可能被装在C:\Windows\System32下的可执行文件干扰它们在 Path 里的顺序可能更靠前。解决办法是检查 Path 顺序让%JAVA_HOME%\bin排在前面或者去C:\Windows\System32删掉java.exe注意别误删系统文件一般建议先调顺序。macOS 上则要检查/usr/libexec/java_home是否指向你安装的版本。6.4 启动失败常见原因速查“java启动失败怎么解决”也是热搜词我总结一张速查表遇到问题对号入座报错现象常见原因解决思路找不到或无法加载主类类名与文件名不一致或 main 方法签名写错检查类名大小写和 public static void main(String[] args)Error: Could not find or load main class编译了但 class 文件路径不对确认 java 命令后跟的类名不带 .class 后缀端口被占用服务类程序启动时端口已被别的进程占用netstat -ano查端口杀掉占用进程或改配置UnsupportedClassVersionError编译版本高于运行版本一般是你用 JDK 17 编译却用 JRE 8 运行统一 JDK 版本或加--release 8编译OutOfMemoryError堆内存不够调大-Xmx参数或检查代码是否有死循环创建对象这些表里的问题我都亲手踩过。特别提醒第三行那个“找不到或无法加载主类”原因可能是你运行java HelloWorld时不在 class 文件所在目录或者类名敲错了大小写。Java 的命令行工具报错比较直白冷静读一下英文提示大多数问题能自己解决。最后分享一点我在反复入门过程中的体会这篇笔记写到这里已经很长了我不打算套路化地复述“Java 知识体系多么庞大”之类的话。说实话第一遍学 Java你不需要记住所有细节你需要的是建立“环境能跑、语法能懂、小功能能写出来”的正反馈循环。我在带新人时最推崇的路径是安装完环境先写 20 个小程序覆盖数字处理、判断、循环、数组、字符串、简单的类设计然后把每个小程序刻意改错几处观察报错信息是什么样的。对故意制造错误这件事特别重要因为你能熟练地从报错信息里定位问题才算真正开始有“程序员的手感”。另外一个小技巧给所有打算刷蓝桥杯或面试题的人把常用代码片段存到一个自己的笔记里别嫌简单比如“整数逐位拆解”“字符串按分隔符拆分”“数组逆序输出”“统计字符串中字母数字个数”。这些就是各种包装题目的底层零件。零件用熟了看见题目就能条件反射地组装思路那些“一看就会、一写就废”的尴尬会少很多。下一篇笔记我会重点展开集合框架List、Map、Set 的不同场景和异常处理机制那是从“会用 Java 写小程序”跨到“能写业务代码”的分水岭。先把手上的代码敲起来我们下篇见。