☰
Java学习第二天:JDK配置、基础语法与冒泡排序实战指南
2026/10/8 3:39:58 网站建设 项目流程

Java学习第二天,今天不打算碰那些听起来很唬人的框架。昨天装完JDK,又听人提什么Spring Boot、MyBatis-Plus,差点直接拉项目源码去跑,还好被一个老同事拦住——他说第一天连基本语法都没过就上手框架,后面报错分不清是JDK问题、依赖问题还是代码问题,排查难度直接翻倍。仔细想了一下确实是这样,第二天最该做的事情就是把基础夯实:从环境配置、第一个程序,到数据类型、再到面向对象和手写冒泡排序。这篇文章就是完整记录,包含我踩过的坑和验证过的方法,适合正在自学Java、尤其是刚装好环境的初学者参考。

1. 环境配置与JDK版本:第二天先别急着写代码

1.1 JDK版本怎么选:JDK 8和JDK 17的真实差异

很多人学Java第一关就卡在"下载哪个JDK"。网上说法乱七八糟,有的说JDK 8经典稳定,有的说JDK 17是新时代,还有人说学JDK 21最时髦。第二天我直接把几个版本都试了试,最后留在本机的是Adoptium Temurin(也就是Eclipse Temurin)的JDK 17。

先解释一下为什么这么选。Oracle JDK从Java 17开始走LTS长期支持路线,17是继8和11之后最受欢迎的LTS版本之一,社区活跃,主流IDE和框架全部支持。JDK 8虽然老项目多,但官方免费更新支持已经越来越紧,新特性(比如switch表达式、文本块text block、record类)在8里根本不能用,而且很多公司已经往17甚至21上迁移。对第二天学习来说,17是非常稳妥的选择,语法上又与新版兼容性好,之后升级21也不需要重学。

不过要注意一点,如果准备面试岗位是老项目维护方向,JDK 8的语法差异点也要心里有数。比如JDK 8里String常量池在Heap里、接口只能有抽象方法,而JDK 17里接口可以写private方法、switch表达式返回值更简洁。这些内容后面学到深入了自然就明白,第二天不用焦虑版本选择,装一个LTS版本先跑起来才是正事。

1.2 JAVA_HOME、Path、CLASS_PATH到底都是干什么的

环境变量这一关,几乎每台Windows电脑都有个惨痛教训。我配置完成后整理了一张流程表,实测下来最稳的是按下面顺序操作:

步骤操作变量/命令
1下载并安装JDK 17解压或安装到D:jdk17
2新建系统变量JAVA_HOME指向JDK安装根目录D:jdk17
3编辑Path变量新增%JAVA_HOME%bin
4验证cmd执行java -version 和 javac -version

很多人只配Path不配JAVA_HOME,短时间能用,但后面装Maven、Tomcat、IDEA时就会遇到"找不到JDK"的报错。这里打个比方,JAVA_HOME相当于在系统里登记了"我的JDK住在哪",IDE和构建工具按门牌号去找;Path相当于给命令行加了快捷通道,在cmd输入java时系统能顺着Path指向的bin目录找到java.exe可执行文件。两个变量各有分工,一个给工具链用,一个给命令行用。

CLASS_PATH这个变量是个历史遗留。JDK 8之前需要手动设置,JDK 9起类路径默认从classpath和当前目录推导,不需要显式配置也可以正常编译运行。第二天的新手反而容易画蛇添足,我建议先不配CLASS_PATH,等学到类库管理(比如Maven)时再理解它也不迟。

1.3 多个JDK共存怎么切换:javac和java版本不一致的坑

我电脑上同时装了JDK 8和JDK 17,为了测试两边不互相干扰踩了不少坑。先把最重要的结论放前面:不要指望只靠修改Path切换JDK版本,因为Path里一旦残留另一个JDK的路径,cmd执行java时只会按Path顺序找到第一个匹配的目录,版本切换就完全失灵。

正确的做法是让JAVA_HOME作为唯一入口。Path里只保留一条%JAVA_HOME%bin,切换版本时只改JAVA_HOME指向的目录,其他什么都不动。还需要注意旧版本残留的问题,比如我之前安装时勾选了"添加至用户Path",系统会把具体路径D:jdk8bin写进Path,这时候你改了JAVA_HOME也没用,命令行依然优先用D:jdk8bin下的java,因为手输的绝对路径匹配顺序靠前。遇到这种情况需要手动把Path里旧的JDK路径删掉,只保留%JAVA_HOME%bin。

验证环境配置是否正确的标准动作是打开新的cmd窗口(记住必须重新打开,旧窗口读到的环境变量不会刷新),依次执行java -version、javac -version。输出结果里java和javac版本应当完全一致,如果一个是1.8一个是17,说明Path里有残留路径。另外可以用where java命令查看命令实际命中的exe位置,这是排查优先级问题最直接的命令。

2. 从Hello World到IDEA:第一个程序的完整链路

2.1 记事本写Hello World:理解编译和运行的边界

第二天学Java,我强烈建议你至少在记事本里手写一次Hello World。这一步不是为了装,而是为了理解Java程序从源码到运行到底发生了什么。在记事本新建一个文本文件,把文件名改成Hello.java,注意.java后缀必须显示出来,内容如下:

public class Hello { public static void main(String[] args) { System.out.println("Hello, Java Day 2!"); } }

保存后打开cmd,先cd到文件所在目录,然后执行javac Hello.java。你会发现目录下多了一个Hello.class文件,这就是编译产物——字节码。然后再执行java Hello(注意不带.class后缀),控制台才输出结果。

这个过程中有一个比代码本身更重要的知识点:Java是编译加解释混合执行的语言。javac把源码编译成平台无关的字节码,字节码不针对任何具体操作系统;运行期JVM再把字节码翻译成当前系统能执行的机器码。所以同一个Hello.class文件,放在Windows、Linux、Mac上都能跑,前提是每个平台都装了对应该平台的JVM。这就是"一次编译,到处运行"的真正含义。有同学问"Java是静态链接的",严格说这里和C语言的静态链接不是一回事,Java类文件在运行时才被类加载器加载到JVM,类之间的关联是运行时动态解析的,这也是JVM能实现自动内存管理和类隔离的基础。

2.2 IntelliJ IDEA安装与新建工程:社区版足够用

跟记事本较完劲,接下来就用正经工具提高效率。IntelliJ IDEA是目前Java开发最主流的IDE,学习阶段下载Community社区版就完全够用,Boot Camp那种Ultimate版的功能(比如Spring框架支持)后续做项目再考虑也不迟。下载安装包一路Next即可,启动后选择Open或者New Project。

新建工程时注意几个设置:Project SDK选我们刚才配好的JDK 17,Language level跟随SDK选择17,模板可以不用。工程结构上,默认会初始化一个src源码目录,我们主要在src下建包和类。记住IDEA里的"包"本质就是文件夹,通常命名用反域名如com.demo,包下面再建类。

写代码时有一些高频快捷键值得记下来:输入psvm回车自动生成main方法,在main方法里输入sout回车生成System.out.println,这俩是使用频率最高的。写完代码点右上角绿色三角运行,IDEA会自动执行编译和启动,控制台输出结果。IDEA本质上还是调用javac在编译,只是把这些命令封装成可视化的按钮。明白这一点,后面遇到"编译报错"时才不会对着IDE抓瞎。

2.3 运行期和编译期内存设置:OOM不只会出现在运行

"java启动失败怎么解决"是搜得特别多的一个问题,我第二天就遇到了,而且是很诡异的情况。在IDEA里运行一个小程序,控制台直接报OutOfMemoryError: Java heap space,一看就是堆内存溢出。一开始我以为是代码里有死循环或者数据量太大,检查半天没发现问题,最后才意识到是IDEA编译进程的内存不够。

这里要区分两个堆:运行Java程序的进程有自己的堆,这个堆由运行时的启动参数控制,比如-Xmx8000m就是指定最大堆为8000MB;但IDEA编译源码时,编译过程同样运行在一个独立的JVM进程里,这个进程的堆由IDEA自带的Build process heap size控制。两码事不能混在一起调。我一开始以为在Run Configuration的VM options里写上-Xmx8000m就能解决一切,结果编译还是报OOM,就是因为编译进程的堆设置藏在IDEA的Settings里,路径是Settings → Build, Execution, Deployment → Compiler → Build process heap size (Mbytes)。

把这个值从默认的700调整到1500之后,编译报错就消失了。如果你确定运行期需要大堆,也要注意-Xmx参数的单位和总内存的关系,盲目把堆调到8000却不看机器物理内存,系统反而会频繁触发垃圾回收甚至卡顿。正确的排查顺序永远是先看代码,再看配置,内存参数是最后手段而不是解决问题的主路径。

3. 数据类型、变量与命名:最容易埋坑的细节

3.1 八种基本数据类型:从位数到范围一次理清

Java的基本数据类型一共有八种,没有字符串类型。String是引用类型,很多人一开始搞混,这里要重点记住。八个基本类型分别是byte、short、int、long、float、double、char、boolean,它们各自的位数和默认值:

类型占用位数默认值取值范围简要说明
byte80-128 ~ 127
short160-32768 ~ 32767
int320约±21亿
long640L比int大得多,赋值要加L后缀
float320.0f小数,赋值要加f后缀
double640.0d小数,默认浮点类型
char16'u0000'单个字符,用单引号
boolean不严格定义false仅true/false

long类型赋值时如果不加L后缀,编译器会认为整数字面量是int类型,超出int范围直接编译失败。float同理,不加f后缀小数默认被当成double。这两个坑面试常问、平时写代码也常犯。

浮点数还有一个更隐蔽的精度问题:0.1这个数字在二进制里无法精确表示,所以浮点数运算会出现0.1 + 0.2不等于0.3的现象。金融、计费相关场景一定不要用float和double直接做运算,后面学到BigDecimal就知道替换方案了。第二天先把"为什么会这样"的疑问留在心里,原理在IEEE 754标准里,等学到二进制换算再回头验证。

3.2 变量、常量和编码:局部变量必须先初始化

Java里变量必须先声明后使用。成员变量(定义在类里、方法外)有默认值,比如int默认0。但方法内的局部变量没有默认值,不赋值直接使用会报"variable might not have been initialized"的编译错误。这个规则比较反直觉,因为很多人写C语言时会有"局部变量是垃圾值"的认知,Java为了安全直接禁止这种用法。我第二天就吃过这个亏,定义了一个int count然后println(count),编译直接失败。

常量用final关键字修饰,命名上要求全大写加下划线,比如MAX_SIZE。final修饰的变量一旦赋值不能修改。另外Java源码的编码问题也很重要:从JDK 11开始IDE新建项目默认UTF-8,但如果拷贝别人代码文件出现中文乱码,大概率是文件Encoding没统一。IDEA里打开Settings → Editor → File Encodings,把Global和Project都设成UTF-8。为避免乱码,建议所有代码文件统一UTF-8编码,再配合控制台输出编码设置,基本上能避免一半中文乱码的困扰。

3.3 标识符命名规则:这些坑二十分钟就能踩完

Java标识符就是给类、方法、变量起的名字,规则不多但很重要。先说硬性规则:可以由字母、数字、下划线、美元符号组成,但不能以数字开头;不能使用关键字;不能使用true、false、null三个字面量作为标识符。下面的代码我有意写错,可以自己感受一下:

int 1abc = 10; // 编译错误:不能以数字开头 int class = 10; // 编译错误:class是关键字 int _ = 10; // 编译错误:JDK 9之后_不允许单独作为变量名 String 名字 = "中文变量"; // 语法上允许,但强烈不建议

最后一行中文变量名在语法层面是合法的,因为Unicode字符集覆盖了中文,实际开发中却会让团队其他人崩溃,堆内存排查、日志检索都受影响,所以业内约定俗成用英文命名。命名风格上,类名用大驼峰(StudentInfo),方法名和变量名用小驼峰(getStudentName、studentName),常量用全大写下划线(MAX_STUDENT_COUNT)。另外缩写尽量少用,我见过一个变量叫tempMap,第一次看还好,三个月后再维护根本想不起来它临时存放的是什么,起名多花十秒,能省下排查的一小时。

4. 面向对象编程入门:从类和对象理解Java的设计

4.1 类与对象:图纸和房子的关系

面向对象编程是Java的核心思想,但很多新手被抽象概念吓退。第二天我会用一个特别朴素的比喻帮助理解:类就是图纸,对象就是按图纸造出来的房子。图纸上规定了房子有哪些房间(属性)、能住几个人(方法),但图纸本身不是房子;只有执行new这个动作,才真正造出了一间可以住人的房子。

看代码更直观。定义一个学生类:

public class Student { String name; int age; public void study() { System.out.println(name + " is studying"); } public static void main(String[] args) { Student s = new Student(); s.name = "小明"; s.age = 18; s.study(); } }

这里的Student就是图纸,s是图纸造出来的对象。s.name访问对象的属性,s.study()调用对象的行为。构造函数是用来初始化对象的一种特殊方法,Java里用new关键字触发构造函数。如果没写构造函数,默认会有一个空参构造方法,一旦你自己定义了带参构造方法,默认的空参构造就没了,这时再new Student()会报错,这是新手期很常见的问题。

4.2 封装、继承、多态:三条基本特征一次过

面向对象的三大特征是封装、继承、多态。第二天先建立一个整体印象,不必死磕所有细节。

封装的核心是隐藏内部状态。把属性用private修饰,外部不能直接访问,只能通过public的getter/setter方法间接操作。好处是可以集中校验,比如setAge方法里限制年龄在0到150之间,外部哪怕传了200也会被拦下来。

继承用extends关键字,子类继承父类非私有的属性和方法,并用@Override重写父类方法来实现自己的逻辑。注意Java只支持单继承,一个类只能有一个直接父类。重写方法时必须保持方法签名一致,返回值类型兼容,访问权限不能比父类更严格。

多态是面向对象设计里最灵活的地方。父类类型的变量可以指向子类对象,调用方法时实际执行的是子类重写后的版本。比如:

public class Animal { public void speak() { System.out.println("动物叫"); } } public class Dog extends Animal { @Override public void speak() { System.out.println("汪汪"); } } Animal a = new Dog(); a.speak(); // 输出汪汪

这里变量a是Animal类型,但真正运行的是Dog类的方法,因为JVM在运行期根据实际对象决定调用哪个方法。这个机制叫动态绑定。你不需要在代码里写一堆if判断"到底是不是狗",多态天然地让代码更易扩展。

4.3 包与容器:import和ArrayList、HashMap从哪来

类多了之后需要分门别类,这就是包(package)。包名通常用域名倒置来保证全局唯一,比如com.example.project。不同包里允许有同名类,所以需要用import声明导入,或者写全限定名。比如java.util包下面的ArrayList,需要导包才能直接用:

import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; List<String> names = new ArrayList<>(); names.add("小明"); names.add("小红"); Map<String, Integer> scores = new HashMap<>(); scores.put("小明", 90); scores.put("小红", 95);

看到<>这个菱形语法了吗?这是泛型,表示这个容器只装某一特定类型的元素。ArrayList本质是一个动态数组,适合按顺序访问,添加和查询都很快;HashMap是键值对结构,通过哈希计算快速定位,适合按key查找。Java的集合框架是面试里的常客,很多"八股文"会问到ArrayList和LinkedList的区别、HashMap的底层原理,第二天可以先记住它们最基础的使用方式,后面学到数据结构时再去深入源码,理解会顺畅得多。

5. 手写冒泡排序:基础算法从思路到优化

5.1 冒泡排序的实现思路和第一版代码

第二天练习算法,我选了冒泡排序,因为它是理解排序思想最好的入门算法之一,面试题也高频出现。核心思路一句话:每一轮从头开始,相邻两个元素两两比较,如果顺序不对就交换,一轮下来最大的元素就像气泡一样"冒"到数组末尾。下一轮再从头比较,但不必再碰已经归位的末尾元素。

public class BubbleSort { public static void main(String[] args) { int[] arr = {64, 34, 25, 12, 22, 11, 90}; 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; } } } for (int v : arr) { System.out.print(v + " "); } } }

第一层循环控制轮数,n个元素需要n-1轮。第二层循环每轮比较j和j+1两个位置,注意j的边界是arr.length - 1 - i,随着i增大,每轮需要比较的元素越来越少。交换三个步骤是经典写法:先用临时变量保存前一个值,再赋新值,最后把临时值写回后一个位置。排序后数组变成11 12 22 25 34 64 90。

5.2 冒泡排序优化:标记位让性能提一倍

上面这个写法有一个明显浪费:如果某一轮从头到尾没有任何交换,说明数组已经有序,后面的轮数就白跑了。加一个布尔标记变量,每轮开始置false,发生交换就置true,一轮结束如果仍是false,直接break。对基本有序的数组,这种优化能省掉大量无用循环。

for (int i = 0; i < arr.length - 1; i++) { boolean swapped = false; 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; swapped = true; } } if (!swapped) { break; } }

时间复杂度的计算也要会:比较次数大约是n(n-1)/2,所以时间复杂度为O(n^2)。空间复杂度是O(1),因为只是原数组内交换,没有额外开大块内存。最好情况(已经有序)经过优化后是O(n),最坏情况是O(n^2)。同样的写法,用这个标记优化之后,对已经排序好的数组,第一轮检查完毕立刻退出,实测快很多。这种"能提前结束就提前结束"的思维方式,在后面学快排和归并时也同样用得上。

5.3 工具类对比:Arrays.sort用什么、什么时候手写

业务代码里几乎不自己写排序,Java提供了现成的排序工具。最常用的是java.util.Arrays类的sort方法,直接对数组排序:

import java.util.Arrays; int[] arr = {4, 2, 9, 1}; Arrays.sort(arr); // 升序排序 System.out.println(Arrays.toString(arr)); // 输出 [1, 2, 4, 9]

Arrays.sort对不同类型有不同策略:基本类型数组用的是双轴快排(Dual-Pivot Quicksort),对象数组用的是TimSort。官方库已经把性能优化到极致,所以工作里直接用工具方法才是正确选择。对List集合也有类似的Collections.sort(List)和list.sort(Comparator),后面学到接口和lambda表达式时会看到更灵活的比较器写法。

那为什么还要手写冒泡?两个原因:第一,面试验收的是算法思维,让你现场写一个知道原理的排序,工具方法背得再熟也没用;第二,某些场景下工具类的稳定性、内存消耗可能不适合,需要自定义排序逻辑,比如按对象某一个字段排序时,还是要理解Comparator背后的比较规则。第二天把冒泡排序彻底整明白,后面学插入排序、选择排序、快速排序,心智负担会轻很多。

6. 第二天报错速查与后续学习路线

6.1 高频报错速查表:从环境到运行一次对照

第二天我收集了从配置环境到写代码遇到的几乎全部报错,整理成速查表,遇到问题先对号入座:

报错信息常见原因解决思路
'javac' 不是内部或外部命令Path没有配置或配置错误检查JAVA_HOME和Path,重新打开cmd
Error: Could not find or load main classjava命令不带.class后缀、类名大小写错误、运行目录不对用java 类全名,不带.class,检查包路径
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException数组越界,比如冒泡排序内层循环边界写错检查循环边界,j+1不能越界
OutOfMemoryError: Java heap space运行堆太小,或代码无限创建大对象先查代码,再考虑-Xmx调堆
UnsupportedClassVersionError编译用的JDK版本高于运行JVM版本统一JDK版本或降级编译目标
中文乱码文件编码与控制台编码不一致统一为UTF-8,IDEA中设置File Encodings

这里面最不好排查的是编译阶段的UnsupportedClassVersionError,有时候你在JDK 17编译的class文件,拿到JDK 8的环境运行,JVM直接拒绝加载,报错说类文件版本号不支持。解决方法是两边JDK版本保持一致,或者在Maven的pom里配置maven.compiler.source和target指定目标版本。

6.2 Java学习路线与第二天的一些体会

第二天的学习告一段落,顺便把整个Java学习路线捋一下,给接下来定个框架:基础语法(数据类型、流程控制、数组)→ 面向对象(类、封装继承多态、接口)→ 常用类库(String、集合、日期时间)→ 异常处理 → IO流 → 多线程 → 网络编程 → JDBC → 数据库与SQL → Maven和Git工具 → Spring Boot框架 → 微服务与中间件。这个路线足够覆盖大部分普通Java开发岗位的需求。像MyBatis-Plus根据实体类生成建表SQL、行级权限这类具体功能,属于框架应用层,到了Spring Boot阶段踩坑时自然会用到,第二天不用着急。

很多人纠结要不要学Python,两者没有绝对优劣。Python语法简洁、上手快,在数据分析和脚本领域有优势;Java强类型、注重工程化规范,在大型企业级应用里生态更成熟。我自己体验是:如果先会Python,学Java时最不适应的是强制类型声明和严格的编译检查,但反过来,正因为编译期能发现更多错误,项目规模扩大后反而更不容易翻车。

第二天最深的体会其实是"搞清楚工具在做什么"比"会点按钮"重要得多。配置环境时如果不了解JAVA_HOME的含义,遇到多个JDK共存的问题就无从下手;写第一个程序时如果不理解javac和java的区别,后面看不懂Maven的编译日志就只能复制粘贴。Java知识体系确实庞大,但只要把基础层的逻辑理顺,后面那些看起来复杂的框架,本质上都是在重复"编译、加载、运行、排查"这几个动作。明天我打算继续啃数组和字符串,把这两个最常用的数据结构彻底弄明白,再回头看今天的冒泡排序,相信会有新的理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询