1. 学习路线与目标规划:第十天的自我审视
坚持到第十天,说实话已经淘汰了一大批人了。我身边同期开始学Java的朋友,有的倒在环境配置,有的倒在面向对象,还有的学着学着就去打游戏了。能点开这篇内容的你,至少说明还在坚持,这就比很多人强了。
第十天是个微妙的节点。按市面上主流的学习路线,前十天基本覆盖了Java基础语法的前半程:环境搭建、数据类型、运算符、流程控制、数组、方法、面向对象入门。如果你是从零开始的,现在应该能写一些简单的控制台小程序了,比如计算器、猜数字、学生成绩管理这类。如果这些还写不顺,建议先别急着往后冲,把前九天的内容复习一遍再来看今天的内容。
我今天想换个思路,不按教科书顺序讲新语法。第十天更应该做三件事:第一,把前九天学的知识串成一条线,搞清楚它们之间的逻辑关系;第二,把环境配置和常见报错彻底踩平,别让工具问题拖后腿;第三,用一个小项目把零散的知识点粘合起来。说白了,第十天是第一个“复盘日”,不是“冲进度日”。
先看看网上那些热搜词,Java面试题、Java数据类型、冒泡排序、环境配置、启动失败、内存溢出,这些恰恰是初学者最容易被卡住的地方。我把它们拆解了一下,发现可以归结为三类问题:知识没串联、工具不会用、代码量不够。今天这篇就围绕这三个痛点展开,每一部分都会给出我实测过的方案和踩坑记录。
另外提一句学习节奏的建议:每天保证两到三小时的有效学习时间,比周末突击八小时强得多。编程这东西讲究手感,手感是靠日积月累练出来的,不是靠某一天的爆发。我见过太多人周一学到凌晨两点,然后歇三天,再学又是从零开始。保持节奏,比追求单日时长重要十倍。
1.1 前九天知识点的串联方式
前九天学的散知识点,其实可以这样串:Java代码从编写到运行,本质上是“源文件 → 字节码 → 机器执行”的过程。围绕这个过程,你写的每一行代码都逃不开三个层面的问题——语法层面(能不能编译通过)、逻辑层面(运行结果对不对)、设计层面(代码结构好不好)。
从语法层面看,你学过的数据类型、运算符、流程控制、数组,这些都是“砖块”。从逻辑层面看,你学过的分支、循环、方法调用,这些是“水泥”。从设计层面看,你刚开始接触的类与对象、封装、继承,这些是“图纸”。第十天的复习,就是把你手里的砖块、水泥和图纸拼成一面墙。
建议你做一个复习动作:拿出一张白纸,不翻书,画出Java基础语法的思维导图,从“数据类型”开始,延伸出基本类型和引用类型,再从引用类型延伸到数组和String,再从String延伸到常用API……画不出来的地方,就是你的薄弱环节,重点补。
1.2 面向对象编程的真正含义
很多初学者学面向对象,死记硬背“封装继承多态”六个字,但根本不知道这东西解决什么问题。我打一个比方:面向过程编程就像你开一家小饭馆,从买菜、洗菜、切菜、炒菜到上菜全是你一个人干,菜量一大必乱。面向对象编程就像你开一家连锁餐厅,每个岗位各司其职,后厨只管做菜,前台只管接单,采购只管进货。
Java里的类就是“岗位说明书”,对象就是“具体干活的员工”,方法就是“工作流程”。你定义一个Student类,里面有姓名、年龄属性,有study方法,这就相当于你给“学生”这个岗位写了份说明书。当你new出一个Student对象时,就是招了一个叫张三的具体学生。张三能学习,李四也能学习,但他们各自的姓名年龄互不干扰。
面试的时候被问到“什么是面向对象”,别背课本上那句“一切皆对象”。你要能说出这样的理解:面向对象的核心是把数据和对数据的操作绑定在一起,通过类来抽象现实事物,通过对象来具体化,通过消息传递(方法调用)来协作完成功能。这个理解水平,已经超过八成应届生了。
2. 环境配置与工具链:把坑填平才能跑得远
环境配置是Java入门第一道坎,也是劝退率最高的一关。我见过太多人在群里问“java环境变量怎么配置”“为什么java命令不识别”,这些问题如果你还在问,说明基础还没打牢。第十天必须把这个环节彻底搞定,不然后面学框架的时候,每装一个新工具就崩一次,心态直接炸。
先梳理一下你需要装的东西:JDK(Java开发工具包)是必须的,IDE(集成开发环境)推荐IntelliJ IDEA,构建工具Maven属于进阶配置但强烈建议提前装,数据库工具(如MySQL、Navicat)后面学JDBC会用到。这一套下来,你的开发环境才算基本齐活。
2.1 JDK多版本共存与切换方案
热搜词里有“java环境变量使用多个jdk”,这个问题很多人都会遇到。比如你一边学Java 8(企业用的最多),一边想体验Java 17的新特性,两个JDK装在同一台机器上,环境变量怎么配?
关键在于PATH和JAVA_HOME的配合。最稳妥的做法是:先装两个版本的JDK,比如安装到C:\Program Files\Java\jdk-8和C:\Program Files\Java\jdk-17。然后设置JAVA_HOME指向你当前需要使用的那个版本,PATH里面加上%JAVA_HOME%\bin。切换的时候,只需要改JAVA_HOME这一个变量值,然后重新打开命令行窗口。
你可以写一个简单的bat脚本放在桌面,切换JDK版本时双击运行即可。脚本内容大致是setx JAVA_HOME "C:\Program Files\Java\jdk-17",然后用setx Path把新的%JAVA_HOME%\bin追加进去。注意setx会覆盖原有Path,操作前一定要先备份原Path。我吃过这个亏,曾经因为setx操作把系统Path搞坏了,连cmd都打不开,只能进安全模式修复,血泪教训。
验证是否切换成功,命令行输入java -version,看输出的版本号是否对应你想要的那个。如果没变,八成是没重开命令行窗口,Windows的环境变量缓存很坑。
2.2 启动失败与内存溢出的排查思路
“java启动失败怎么解决”“OutOfMemoryError”这两个问题在热搜词里出现频率很高,而且初学者一遇到就慌。其实这类问题的排查思路是有固定套路的。
第一步,看报错信息开头的关键词。如果是Unrecognized option,说明JVM参数写错了,比如-XX:MaxPermSize在Java 8以后已经不支持,要换成-XX:MaxMetaspaceSize。如果是Could not reserve enough space for object heap,说明你给定的堆内存超过了机器可用内存——典型场景是32位系统上给JVM分配超过1.5G堆内存。如果是OutOfMemoryError: Java heap space,说明堆内存真的不够用了,需要加-Xmx参数。
第二步,确认你的机器架构和IDE位数。有些老机器装的是32位JDK,而IDE是64位的,那IDE启动时调用的JVM就是32位,堆内存上限很小。这时候你需要把JDK升级为64位版本,或者降低IDE的-Xmx配置。IDEA的配置文件在安装目录bin\idea64.exe.vmoptions,里面-Xmx2048m表示最大堆内存,改完后重启IDEA生效。
注意:修改vmoptions文件时一定要用记事本或VS Code打开,不要用IDEA自带的编辑器。我曾经手滑把文件编码改成了UTF-8带BOM,导致IDEA直接无法启动,排查了半天才发现是编码问题。
至于热搜词里的“进程堆大小调整为8000还是报错”,这个很典型。堆内存调到8G还报OOM,说明程序本身就有内存泄漏或超大对象引用问题,不是堆不够大的事。加内存只是治标不治本,真正要做的是用VisualVM或JProfiler分析堆转储文件,找到是哪个对象占用了大量内存,再定位到代码层面。后续我会专门写一篇OOM分析实战,这里先立个Flag。
2.3 IDEA编译报错的几种典型场景
用IDEA写代码最常见的报错,编译时报java: 程序包xxx不存在,多半是Maven依赖没下载完整。解决办法是右键项目 -> Maven -> Reload Project,让IDEA重新解析依赖。如果还不行,检查本地仓库~/.m2/repository下对应的jar包是否损坏,损坏了直接删掉该目录,Maven会自动重新下载。
另一种是编译时报Error: java: Compilation failed: internal java compiler error,通常是Project SDK和Project language level不匹配。比如你装了JDK 17,但Project language level还在8,某些语法会编译失败。检查路径是File -> Project Structure -> Project,把SDK正确设置为17,language level也同步调整为17。
小结一个经验:环境问题的排查顺序永远是“先确认JDK版本 → 再确认环境变量 → 再确认IDE配置 → 最后才怀疑代码”。不要一上来就怀疑自己代码写错了,环境问题在Java学习初期占比高达70%。
3. 核心基础巩固:数据类型、标识符与排序算法实战
基础不牢,地动山摇。今天既然做复盘,就把几个容易卡壳的硬骨头单独拎出来啃。热搜词里的“java数据类型”“java标识符命名规则”“冒泡排序java”“sort函数用法java”,我串成一块来讲。
3.1 数据类型的内存模型与面试高频题
Java的两大数据类型阵营——基本类型和引用类型,表面上是“存值”和“存地址”的区别,但面试官喜欢往深处挖。比如int和Integer的区别,==比较的是什么,这些几乎是必考题。
基本类型有八种,byte、short、int、long、float、double、char、boolean。它们的核心区别在于占用内存空间不同,byte占1字节,short占2字节,int占4字节,long占8字节。float和double的区别同理,4字节对8字节,精度完全不同。面试中一个高频陷阱题是float a = 1.0;,这行代码其实是错的,因为1.0默认是double类型,直接赋值给float需要强转,正确写法是float a = 1.0f。
引用类型包括类、接口、数组、枚举。它们变量本身存的是堆内存中的地址,也就是引用。用==比较两个引用类型变量,比较的是地址是否相同;用equals比较的才是内容是否相同。但有个细节,String类重写了equals方法,所以"abc".equals("abc")返回true;而Integer缓存了-128到127的值,这个范围内的Integer a = 100; Integer b = 100;用==比较返回true,超过范围就返回false。这些细节不亲手敲一遍,面试被问住再正常不过。
3.2 标识符命名规则与规范
标识符这块内容看似简单,但很多人考试和面试都会在这里翻车。规则层面有四条铁律:不能以数字开头,不能是Java关键字,严格区分大小写,不能包含空格。123abc是非法的,class是非法的,int也是非法的(它是关键字)。
规范层面则完全是另一码事。类名用大驼峰,比如StudentManager;方法名和变量名用小驼峰,比如studentName、getStudentById;常量全大写加下划线,比如MAX_SIZE。这些规范不是Java语法强制要求的,但如果不遵守,团队协作时代码就是一锅粥。我在公司review过新人的代码,变量名从a到z用了二十几个,看完只想让他重新写一版。
还有个容易被忽略的点:$符号和中文都可以作为标识符。$在特殊场景(如内部类、动态代理生成的类名)有用途,日常不建议用。中文可以做变量名,Java语法允许,但代码可读性极差,强烈不建议。
3.3 冒泡排序与sort函数的正确姿势
排序是算法基础中的基础,也是面试手撕代码环节的常客。这里必须分清两个场景:面试考的是你手写算法思路,工作中用的是现成工具方法。
冒泡排序的核心思想是相邻元素两两比较,大的往后冒。但很多人写出来的版本效率很差,问题出在内层循环的范围。标准写法是这样的:
public static void bubbleSort(int[] arr) { int n = arr.length; for (int i = 0; i < n - 1; i++) { boolean swapped = false; for (int j = 0; j < n - 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; } } }这里有个优化点:j < n - 1 - i,每一轮冒泡后,最大的数已经沉到末尾,所以内层循环范围可以逐渐缩小。swapped标志位用于判断某一轮是否发生过交换,如果一整轮都没有交换,说明数组已经有序,直接跳出循环,这是最基础的优化。
工作中,别自己写排序,直接用Arrays工具类。Arrays.sort(int[])底层用的是Dual-Pivot Quicksort,对于引用类型用的是TimSort,性能远比自己写的冒泡好得多。Arrays.sort(arr, 2, 5)可以对数组的指定区间排序。如果要对对象排序,需要对象实现Comparable接口,或者传入Comparator:
List<Student> students = new ArrayList<>(); students.sort(Comparator.comparing(Student::getScore).reversed());一行搞定按成绩降序排序,比手写排序逻辑省太多事。搜索引擎里有“sort函数用法java”,说明很多人不清楚这一点。其实JDK的Arrays和Collections类里藏着大量实用方法,多去看看官方文档,比网上搜零散博客高效得多。
对一个初学者而言,手写冒泡排序主要是为了理解嵌套循环和数组操作,不是为了让你工作中用。明白这一点,你就不会在“要不要背排序算法”这件事上纠结了。
4. 实战项目:用MyBatis-Plus根据实体类生成建表SQL
第十天最好的巩固方式,是做一个能跑通的小项目。我从热搜词里挑一个非常实用的场景来演示——mybatisplus根据java实体类生成创建表的sql语句。这个需求在真实开发中极其常见,尤其是团队里数据库表结构由后端Java工程师维护时,用实体类反推建表SQL能省不少事。
这不是让你现在就精通MyBatis-Plus,而是告诉你一个思路:ORM框架的核心价值,就是让Java对象和数据库表之间建立映射关系。理解了这个思路,后面学Spring Boot时会轻松很多。
4.1 项目搭建与依赖配置
先创建一个Maven工程,在pom.xml中加入MyBatis-Plus和MySQL驱动依赖。Spring Boot项目的话,引入mybatis-plus-boot-starter即可。这里给一个最小依赖示例:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>接着在application.yml中配置数据源信息。如果你本地的MySQL端口、用户名密码与我不同,改成你自己的即可。这一步骤同时也是在复习JDBC的内容,因为MyBatis-Plus底层就是封装了JDBC。
建一个实体类User,加上MyBatis-Plus的注解:
@Data @TableName("user") public class User { @TableId(type = IdType.AUTO) private Long id; private String username; private String email; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; }这里的@TableName("user")指定表名,@TableId指定主键,@TableField标注特殊字段。有了这个实体类,MyBatis-Plus就能自动帮你生成对应的SQL语句。
4.2 自动建表的两种实现路径
第一种路路径是用MyBatis-Plus的扩展模块,在配置类里定义一个MybatisPlusSqlInjector,或者直接用BaseMapper提供的insert方法。第二种路径是写一个工具类,利用Java反射读取实体类的字段和注解,动态拼接CREATE TABLE语句。
第二种路径的理解层面更值得学习,因为它强迫你思考Java反射和注解的本质。核心逻辑三步走:获取实体类的Class对象,遍历声明字段,读取注解信息,把Java类型映射为MySQL类型。Java里String对应MySQL的VARCHAR(255),Integer对应INT,Long对应BIGINT,LocalDateTime对应DATETIME。
类型映射这块有个大坑:如果不指定长度,VARCHAR字段在MySQL里默认是VARCHAR(255),对于用户名可能够用,但如果存文章内容就完全不合适。所以更合理的做法是给实体类加一个自定义注解指定长度,但很多初学者不知道MyBatis-Plus本身不提供长度注解。业界常用的方案是直接引入Flyway或Liquibase这类数据库迁移工具,把建表脚本纳入版本管理。
自动建表工具适合开发环境快速起项目,生产环境千万别让代码自动建表,风险太大。生产环境的表结构变更必须走审核流程,用迁移脚本逐版本升级,这个习惯要一开始就养成。
我自己的体验是,自动建表最舒服的场景是写Demo和学习阶段。想快速起一个功能验证环境,不用手动去Navicat里敲建表语句,代码一跑表就出来了,整个节奏快了很多倍。
4.3 从实体到SQL的完整代码示例
既然提到反射动态生成SQL,我手写过一个极简版本,核心代码贴出来供参考:
public static String generateCreateTableSql(Class<?> clazz) { TableName tableNameAnno = clazz.getAnnotation(TableName.class); String tableName = tableNameAnno.value(); StringBuilder sb = new StringBuilder("CREATE TABLE `") .append(tableName).append("` ("); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { String columnName = camelToUnderscore(field.getName()); Class<?> type = field.getType(); String columnType = mapJavaTypeToMySql(type); sb.append("\n `").append(columnName).append("` ") .append(columnType).append(","); } sb.deleteCharAt(sb.length() - 1); sb.append("\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); return sb.toString(); }其中camelToUnderscore方法实现驼峰转下划线,mapJavaTypeToMySql实现Java类型到MySQL类型的映射。这里用到了反射里的getDeclaredFields()获取所有字段,用getAnnotation()读取注解,用getType()获取字段类型。反射这块知识不是十天能学完的,但你只要记住方向——反射就是Java在运行期“照镜子”看自己结构的能力,后面哪天需要了再深入。
这个例子把面向对象、注解、反射、字符串拼接、命名规则、类型映射全串起来了,正适合第十天的复盘需求。它能做一个独立的小工具类,以后写代码时随时调用。
5. 常见问题排查实录:这几天高频踩坑的复盘
最后一部分,我把这段时间在学习过程中收集到的常见问题整理成一份速查表,从环境到代码再到工程实践,列出问题表现、排查路径、解决方案和我的个人备注。
| 问题 | 典型表现 | 排查路径 | 解决方案 |
|---|---|---|---|
| java命令不可用 | 命令行输入java报“不是内部或外部命令” | 检查JAVA_HOME和PATH | 将JDK的bin目录加入PATH,重开命令行 |
| 编译编码报错 | 含有中文的文件编译后乱码或报错 | 检查源文件编码 | 设为UTF-8,编译时加-encoding UTF-8 |
| Maven依赖下载失败 | 依赖标红,编译报包不存在 | 检查网络与本地仓库 | Maven Reload Project,删除损坏的仓库缓存 |
| 堆内存溢出 | 运行时报OutOfMemoryError | 用jstat/VisualVM观察 | 优化代码,合理设置-Xmx,分析堆转储 |
| 端口被占用 | 启动时报BindException | 查询端口占用 | netstat -ano找到PID,结束进程或换端口 |
| 数据库中文乱码 | 插入中文变为问号 | 检查连接URL和表字符集 | URL加characterEncoding=utf8,建库时指定utf8mb4 |
下面挑几个典型的细说。
5.1 JAVA_HOME设了却仍然不生效
这个问题搜“java环境变量使用多个jdk”的人大概率都遇到过。系统变量里明明配置了JAVA_HOME,PATH里也加了%JAVA_HOME%\bin,但命令行一执行java -version,还是旧版本或者直接报错。
最常见的坑有两处。第一,PATH里可能有其他地方也写了Java的bin路径,且排在%JAVA_HOME%\bin前面,系统是按顺序查找的,前面的优先匹配了。第二,Windows的PATH变量在“系统变量”和“用户变量”里都有,用户变量的优先级高于系统变量,如果你在用户变量里配了一份旧JDK路径,系统变量的配置会被它覆盖。
排查方法是命令行输入where java,Windows会列出所有找到的java.exe的位置和顺序,一眼就能看出问题出在哪。然后逐一清理旧配置,只保留一份%JAVA_HOME%\bin。
5.2 MyBatis-Plus自动建表失败
用MyBatis-Plus测试自动建表时,最常见的报错是“Table 'xxx' doesn't exist”或者“Unknown column”。这通常不是工具的问题,而是你的实体类字段与数据表字段映射不一致。例如实体里有createTime,但表里的列名是create_time,MyBatis-Plus默认开启了驼峰转下划线映射,如果关掉了这个配置就会报错。
另一个容易被忽略的点是配置项logic-delete-value和logic-not-delete-value,当你给实体类加了@TableLogic注解实现逻辑删除时,如果数据库表里没有对应的delete_flag列,所有查询都会报错。解决办法是先手动在表里加上该列,或者把注解暂时去掉。
5.3 排序结果与预期不符
写排序代码时,结果老是不对,绝大多数情况是边界条件没处理好。比如冒泡排序外层循环多循环了一次,或者内层循环的范围写成j < arr.length - 1 - i但数组下标从0开始,导致最后一位没参与比较。
排查这类问题没有捷径,只能靠两种手段:一是写单元测试,给定小规模输入,断言输出结果;二是在循环体里加打印语句,观察每轮排序后数组的变化。IntelliJ IDEA的Debugger也支持条件断点,可以在if (arr[j] > arr[j+1])处打断点,逐步观察变量变化。学会用Debugger比学会任何框架都重要,这个技能能帮你省下大量时间。
5.4 常用库函数与工具类的使用盲区
热搜词里提到“常用库函数algorithm java”,这里要特别纠正一个概念:C++有STL algorithm库,Java里没有一个叫algorithm的库。Java对应的工具类主要分布在java.util和java.lang里面。Arrays类提供了排序、二分查找、复制、填充、转List等方法,Collections类专门操作Collection接口的集合。还有Objects类提供空指针检查和hashCode计算,Math类提供幂运算、绝对值、随机数等。
我见过不少初学者自己写循环找最大值,其实Collections.max(list)一行就搞定了。JDK的官方文档是最好的工具类手册,遇到需求先想想“这个功能JDK是不是已经帮我做好了”,养成查文档的习惯,能少写很多无用代码。
6. 写在第十天的最后:我的学习建议
第十天是一个容易浮躁的时间点。坚持了十天,你可能会觉得自己应该“会点什么了”,但真让你独立写个功能,又觉得无从下手。这种感觉很正常,说明你正在从“听懂”向“会用”过渡,这个过渡期急不来。
我的建议是,从今天开始换一种学习方式:不再按章节顺序往后学,而是给自己定一个小任务,比如“做一个命令行版的学生成绩管理系统”,把前面学的知识全部用上。遇到不会的知识点,查资料现学现用。这种以任务驱动的方式,效率远高于纯啃书本——你在完成任务过程中记住的知识,比你看十遍书都牢固。
之前遇到一个朋友向我请教Java自学路线,我给他推荐“基础语法 → 面向对象 → 集合框架 → IO → 多线程 → 网络编程 → 数据库 → 框架”这条经典路线。但学了半个月后他反馈说太枯燥,坚持不下去。后来我换了个思路,让他直接做一个贪吃蛇小游戏,用Java Swing做界面,一边做一边补知识,他反而学得飞快。所以,当你感到烦躁时,不用怀疑是自己不适合,很可能只是学习方式不匹配。
第十天的复盘,到这里就差不多了。下一篇我打算聊一聊集合框架,这是Java学习中承上启下的关键章节,也是面试必考的重头戏。先把今天的内容消化掉,哪怕只是把文中的代码自己敲一遍,也算没白看。