今天是我自学Java的第五天。前四天把环境变量、JDK安装、基本语法和数组过了一遍,今天开始进入面向对象,同时结合数组把排序算法狠狠练了一遍。说实话,Day5这个节点最容易卡人——前面刚觉得自己会写循环了,一到类、对象、方法这些抽象概念就开始发懵。我把自己这一天的学习笔记整理出来,给同样在学Java基础阶段的同学参考。这篇笔记不打算面面俱到,只讲今天真正动手验证过、且以后写代码一定用得到的部分。
1. 今日内容概览与路线调整
1.1 Day5到底该学什么
今天学的是Java基础里的重头戏:面向对象编程的基本套路、封装与this,以及数组和常用工具类的排序操作。很多人以为第五天就可以碰框架,实际不是。Java基础阶段的核心顺序应该是语法、面向对象、集合框架、异常、IO,框架是后面的事。我给自己定的Day5目标是三块:手写冒泡排序、搞懂Arrays.sort和Comparator、理解POJO封装思想。这三块单独看没什么关系,但组合起来就是一次完整的“语法→数据结构→面向对象”串联。
1.2 为什么把排序放在面向对象前面
很多学习路线会一上来就讲面向对象,把排序算法扔到后面。我的体会是反过来更顺。排序算法用到的数组、循环、条件判断、临时变量交换,恰好把前四天学过的语法点全部串起来。一边写冒泡排序一边复习数组下标和边界控制,这个手感建立起来之后,再接触ArrayList和对象排序才不会慌。而且冒泡排序是Java面试题里出现频率极高的基础题,早一点吃透,后面刷题会轻松很多。
1.3 笔记怎么记才不变成抄书
今天记笔记我换了个方式:每块知识点都从四个问题出发——“是什么”“解决什么问题”“怎么用”“坑在哪”。不追求把官方文档抄一遍,只记录自己写代码时真正踩过的逻辑和值得长期使用的写法。下面每个章节就是按这个思路展开的。
2. 排序算法:从冒泡到sort函数
2.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 tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } }这里有几个细节容易忽略。外层循环控制“需要比较几轮”,n个元素最多n-1轮;内层循环里arr.length - 1 - i的-i是核心优化——每一轮结束后,最大的数已经沉到最后,下一轮不需要再和它比较。tmp在Java里就是临时变量的约定命名,表示交换时多出来的那只手。很多初学者问tmp在Java中的意思,其实它只是变量名,换成temp也一样,真正重要的是交换逻辑:先把arr[j]存进tmp,再把arr[j+1]赋给arr[j],最后把tmp赋给arr[j+1]。
不加优化的冒泡排序时间复杂度恒为O(n^2),即使数组本来就有序,也要比较n(n-1)/2次。实际面试里可以加一个标志位提前退出:
public static void bubbleSortOptimized(int[] arr) { boolean swapped; for (int i = 0; i < arr.length - 1; i++) { swapped = false; for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; swapped = true; } } if (!swapped) { break; } } }加上这个优化,最好情况下的时间复杂度就变成O(n)。同时要记住冒泡排序是稳定排序,相同元素的相对位置不会改变,这也是面试常考点。
2.2 Arrays.sort:默认排序和Comparator的正确用法
实际开发中,手写冒泡排序主要是为了面试和教学,日常工作直接用Arrays.sort。这个静态方法对基本类型数组使用的是快速排序算法,对对象数组使用的是归并排序算法。它的常用方式有这些:
int[] nums = {5, 2, 9, 1}; Arrays.sort(nums); // 升序排列 String[] names = {"Java", "Spring", "MySQL"}; Arrays.sort(names); // 按字典序排列 Integer[] scores = {89, 45, 78, 95}; Arrays.sort(scores, Collections.reverseOrder()); // 降序,注意要用包装类型这里最坑的一点是:基本类型数组想降序排序,不能直接传Collections.reverseOrder(),需要把int[]改成Integer[]。因为Comparator只能作用于对象类型,int不是Object的子类。如果想给对象排序,比如按学生成绩排序,需要自定义比较规则:
students.sort((s1, s2) -> Integer.compare(s1.score, s2.score));这行代码用lambda表达式替代了匿名Comparator类。Comparator的compare方法返回负数表示s1在前,返回正数表示s2在前,返回0表示相等。这个“返回值的符号决定顺序”是理解sort函数用法的关键。很多人把compare和compareTo搞混,后面第3章会结合Comparable接口再讲一次。
2.3 排序实操中最容易踩的三个坑
第一个坑是“数组降序排序报错”,上面提到了,解决方法是换成包装类型,或者自己写循环把结果倒过来。第二个坑是“Comparator返回值写反”,想降序却写成return s1.score - s2.score,结果还是升序;正确的降序是return s2.score - s1.score,或者用Integer.compare(s2.score, s1.score),避免差值溢出。第三个坑是“字符串排序不是按长度,而是按字典序”,比如"Apple"排在"banana"前面,因为大写字母的ASCII码比小写字母小。这个和自然语言习惯不太一样,写代码时一定要注意。
3. 面向对象核心:类、对象、封装和this
3.1 用POJO理解封装到底封装了什么
面向对象编程Java部分的第一步,不是一上来写继承,而是先理解一个最简单的类。我Day5用Student类开刀:
public class Student { private String name; private int score; public Student(String name, int score) { this.name = name; this.score = score; } public String getName() { return name; } public int getScore() { return score; } }这种只有私有字段、构造方法和getter/setter的类,业界叫POJO,就是Plain Old Java Object。很多人不理解:既然name、score都可以直接访问,为什么非得用private再写一堆getter?直接把字段public不是更省事吗?
封装的核心不是“不让看”,而是“控制变化”。举个例子,假设成绩满分从100改成150,如果字段是public,所有外部代码用student.score都得改;如果走getScore(),你只需要在方法里做转换,外部调用点不用动。更重要的是一致性保护,比如评分不能超过100,可以在setter里做校验,避免外部把score设成-80。这就是面试回答“什么是封装”时一定要提到的点:隐藏内部实现细节,对外提供稳定访问接口。
3.2 this关键字到底在指谁
上面的Student构造方法里出现了两次name:
public Student(String name, int score) { this.name = name; this.score = score; }参数name把成员变量name遮住了,如果不加this,直接写name = name,实际上是在自己给自己赋值,成员变量仍然是null。this代表“当前对象”,this.name是当前对象的成员变量,右边name是形参。理解this的关键是把创建对象的过程想明白:执行new Student("张三", 90)时,JVM在堆里分配一块区域,构造方法内部的this就是那块区域本身。
除了解决命名冲突,this还有一个常见用法是构造器重载时的this()调用,比如无参构造里调用有参构造。但注意this()必须放在方法体的第一行,否则编译报错。Day5基础阶段可能用不到,但看别人的代码时不会懵。
3.3 静态方法和实例方法怎么选
初学Java时经常遇到“静态方法里不能调用非静态方法”的报错。根本原因是:静态方法属于类本身,不依赖具体对象;实例方法必须要有对象才能调用。比如main方法是静态的,它要调用普通方法,要么把方法也定义成static,要么先创建对象再用对象调用。
public class Demo { public static void main(String[] args) { Demo demo = new Demo(); demo.printInfo(); Demo.staticInfo(); } public void printInfo() { System.out.println("instance method"); } public static void staticInfo() { System.out.println("static method"); } }这个机制背后是为了内存和工具类能力:Math.max、Arrays.sort都是静态方法,我们不需要先new一个Math对象再算最大值。但在面向对象的建模里,大多数方法应该是实例方法,因为行为往往是针对具体对象的。Day5这个阶段我会刻意练习:先问这个方法需不需要访问对象的字段,需要就是实例方法,不需要才考虑static。
4. 常用库函数与API:少重复造轮子
4.1 Math和Arrays,零基础也能立刻上手的工具
Java提供了大量现成的库函数,我花了一个小时整理自己最常用的。Math类里用最多的有Math.max(a, b)、Math.min、Math.abs、Math.pow、Math.sqrt、Math.random。Math.random()返回0.0到1.0之间的double,想生成[0, n)的整数可以用(int)(Math.random() * n),想生成[1, 100]的整数就用(int)(Math.random() * 100) + 1。这个写法在刷算法题时特别常用,蓝桥杯这种比赛里没少考。
Arrays类是数组的最佳搭档。Arrays.toString(arr)可以把数组内容转成可读字符串,不然直接打印int[]会得到[I@1b6d3586这种地址;Arrays.equals(a, b)比较两个数组的内容是否相等;Arrays.fill(arr, 0)可以快速把数组所有元素设为0;Arrays.copyOf(arr, newLength)用于扩容。这些都是Java基础里的老演员,面试笔试都爱考。
4.2 String拼接为什么推荐StringBuilder
初学Java时最爽的事是字符串用+连接,比如"姓名:" + name + ",成绩:" + score。但如果在循环里大量拼接字符串,这个写法会触发严重的性能问题。Java的String是不可变对象,每次+都会创建一个新的字符串对象。举个例子,循环10000次拼接,就创建了10000个中间字符串,这些对象很快变成垃圾,增加GC压力。改进方式是:
StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { sb.append("第").append(i).append("行\n"); } String result = sb.toString();StringBuilder底层是可变字符数组,容量不足时自动扩容,append在绝大多数情况下都不会产生新的对象。日常写日志、拼SQL、拼JSON,都用StringBuilder。这背后是“减少对象创建”的思维,在Java内存管理里很底层。
4.3 集合框架提前接触一点点
Day5还不需要把所有集合都看一遍,但ArrayList值得提前认识。数组长度固定,一旦创建就无法改变;ArrayList底层也是数组,但可以自动扩容。基础用法:
ArrayList<String> list = new ArrayList<>(); list.add("Java"); list.get(0); list.set(0, "jdk"); list.remove(0); list.size();注意ArrayList不能存基本类型,需要存Integer、Double这些包装类型。看别人代码时遇到Integer不要慌,它就是把int包了一层。Java为什么这样设计?因为集合框架设计时要求元素是Object,基本类型不属于类体系,包装类补齐了这个缺口。Day5先会用ArrayList,后续再深入源码。
5. 从笔记到动手:写一个成绩排序小工具
5.1 需求设计:怎么把今天学的知识串起来
只看笔记不写代码等于白学。我给自己安排了一个小目标:做一个学生成绩排序工具。需求很简单:输入几个学生的姓名和成绩,按成绩从高到低打印。这个需求覆盖了POJO、ArrayList、Comparator、遍历输出,正好是Day5知识点的综合应用。
5.2 完整代码实现
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class ScoreSorter { public static void main(String[] args) { List<Student> students = new ArrayList<>(); students.add(new Student("张三", 88)); students.add(new Student("李四", 97)); students.add(new Student("王五", 76)); Collections.sort(students, new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { return Integer.compare(s2.getScore(), s1.getScore()); } }); for (Student s : students) { System.out.println(s.getName() + " - " + s.getScore()); } } } class Student { private String name; private int score; public Student(String name, int score) { this.name = name; this.score = score; } public String getName() { return name; } public int getScore() { return score; } }运行结果:
李四 - 97 张三 - 88 王五 - 76这里用Integer.compare(s2.getScore(), s1.getScore())而不是s2.getScore() - s1.getScore(),是为了防止成绩极端情况下整数溢出造成排序错乱。另外Student类没有public修饰,和ScoreSorter放在同一个文件里,Java允许这样,但一个文件里只能有一个public类,且文件名要匹配那个public类。
5.3 过程中遇到的环境和启动问题
代码逻辑没问题,但运行阶段我连续踩了两个环境坑。第一个是“启动失败”,直接报“错误: 找不到或无法加载主类”。排查思路有几步:先检查类名和文件名是否完全一致,包括大小写;再检查代码中有没有多个类同时使用了public关键字;然后手动执行javac ScoreSorter.java看看有没有编译错误;最后确认当前目录是否在CLASSPATH里。实际我那次是把一个辅助类写成了public,导致与文件名不匹配,自然找不到主类。启动失败怎么解决,最关键的是看编译器和JVM给出的原始报错,别急着重装JDK。
第二个坑是多JDK环境变量冲突。电脑上同时装了JDK8和JDK17,因为不同项目要求不同环境。Java环境变量使用多个JDK时,如果系统PATH里同时存在两个JDK的bin目录,命令行执行java -version会时对时错。解决办法是只配一个JAVA_HOME,然后在PATH里引用%JAVA_HOME%\bin;切换版本时,只改JAVA_HOME的值,重启终端,再执行java -version确认版本。要养成只维护一个JAVA_HOME的习惯,其他JDK放在固定目录备用,而不是一起塞进PATH。
5.4 顺手整理一张环境问题排查清单
代码跑通后,我把这次的经验列成了一张检查单:启动失败先查类名和main方法签名,再查JDK环境;内存报错优先调JVM启动参数而不是先怀疑代码;环境变量改了之后一定要重开终端。把笔记变成可复用的检查单,这个习惯帮助我后面少踩不少坑。
6. 面试向总结与后续规划
6.1 今天的内容在面试里会怎么问
学完Day5,我试着站在面试官角度把今天内容改成问题。第一类是“手写冒泡排序并优化”,考察基础语法和算法复杂度;第二类是“Comparable和Comparator的区别”,考察对象排序能力;第三类是“private字段为什么要有getter/setter”,考察封装思想;第四类是“String和StringBuilder的区别”,考察对不可变对象的理解;第五类是“ArrayList和数组的区别”,考察集合基础。这些几乎都是Java面试题里的常客。如果今天的学习能让你轻松回答其中两三个,说明进度是健康的。
6.2 后面几天怎么衔接
按我的节奏,Day6会开始学继承、多态、抽象类和接口,这是面向对象编程Java的第二大块;Day7进入异常处理和泛型;再往后就是集合框架的深入、HashMap源码阅读;最后才轮到Spring Boot这类框架。很多初学者一上来就想学mybatisplus怎么根据实体类生成建表SQL,结果连POJO是什么都说不清楚,这是步子太大了。基础打牢固之后,这些框架层的东西理解起来会非常快。
6.3 今天最想分享的学习心得
Day5最大的收获不是记住了冒泡排序怎么写,而是建立了“学完就立刻做一个小项目验证”的正反馈循环。这个成绩排序工具体积很小,但把一整个阶段的知识点全部用了一遍。Java学习笔记写到现在,我的经验是:不要追求一天学很多新概念,而是把新知识和旧知识耦合起来,哪怕只是把一个数组排序改成用对象排序,也比单纯抄十页代码有价值。如果你也刚好学到第5天,建议你把我这份笔记里的代码亲手敲一遍,然后试着加一个“语文、数学、英语三科平均分排序”的需求,你一定会有不一样的体会。