学Java的第二天,往往比第一天更劝退。第一天装好JDK、跑出Hello World,还能靠新鲜感撑着;第二天开始碰数据类型、运算符、控制流和数组,代码开始“不讲道理”地报错,脑子里的概念也开始打架。如果你正卡在这个阶段,这篇日记就是写给你看的。今天的内容围绕Java最核心的基础语法展开,包括8种基本数据类型、运算符的优先级陷阱、三种循环结构的适用场景、数组的初始化与越界问题、冒泡排序的手写实现,以及面向对象编程里的类、对象、构造方法、封装和static关键字,最后整理了我当天踩过的报错和排查过程。适合刚学完环境配置、准备系统性过一遍Java基础的自学者,也适合正在刷Java面试题或备战蓝桥杯的入门选手——今天讲的很多点,笔试里真的会考,环境变量配置和基础语法也是面试八股里绕不开的环节。
1. 第二天的学习路线:从“能运行”到“能写逻辑”
1.1 为什么第二天不急着写项目
很多自学Java的人第二天就想做点“看得见的东西”——做个计算器、写个贪吃蛇。我的建议是打住。第一天你只是验证了环境通了,Java语法、运行机制其实还没真正入门。这时候强行写界面、做交互,你会同时面对语法、逻辑、工具链三重问题,报错都不知道该查哪个方向。
趁这个机会把基础语法过一遍,性价比高得多。Java的学习路线里,基础语法这关越扎实,后面接触Spring Boot、MyBatis这类工程框架时,就越能看懂“为什么这么写”。我见过不少直接上手框架的学习者,写是能写,但一问到”这段代码在内存里发生了什么“就答不上来——面试时这就成了硬伤。
1.2 今天的学习目标拆解
我给自己定的第二天目标很克制,不多,但要求弄懂、能默写:
- 搞清楚Java的8种基本数据类型是什么,各自的取值范围和默认值,以及和包装类的区别
- 熟练使用运算符,重点是自增自减、短路逻辑、类型转换这几块容易出错的细节
- 掌握三种循环结构在什么场景下选哪个,以及break和continue的区别
- 手写一遍冒泡排序,用Arrays工具类再实现一遍,体会两种写法的差距
- 初步认识类与对象,理解构造方法、封装和static关键字
这个清单看着不多,实际学下来要六到八个小时。学完最大的感受是:Java语法的严谨程度远超想象,很多东西不是“能运行就行”,而是从编译层面就帮你挡住了低级错误。只要你方向对了,速度慢一点没关系。
2. 数据类型与变量:Java代码的“地基”
2.1 8种基本数据类型速览
Java是静态类型语言,这意味着每个变量在声明时就必须明确类型。这既是Java和Python这类动态语言最直观的区别,也是很多Python转Java的人最初不适应的点。Python里你写x = 1就行,Java里必须写int x = 1;。
8种基本数据类型我整理成了表格,方便贴在电脑前面:
| 类型 | 占用空间 | 取值范围 | 默认值 | 典型用途 |
|---|---|---|---|---|
| byte | 1字节 | -128~127 | 0 | 文件流读取、节省内存的大数组 |
| short | 2字节 | -32768~32767 | 0 | 极少直接用,知道就行 |
| int | 4字节 | 约正负21亿 | 0 | 最常用的整数类型 |
| long | 8字节 | 极大范围 | 0L | 时间戳、大数值计算 |
| float | 4字节 | 约正负3.4E38 | 0.0f | 科学计算,但精度有限 |
| double | 8字节 | 约正负1.7E308 | 0.0d | 默认小数类型 |
| char | 2字节 | 0~65535 | '\u0000' | 单个字符 |
| boolean | 不确定 | true/false | false | 判断条件,只存这两个值 |
有个细节特别容易踩坑:整数字面量默认是int类型,小数字面量默认是double类型。所以给short赋值时,如果数字超出short的范围,直接编译报错;给float赋值时,数字后面必须加f或F,不然编译不了。我当时就在float price = 12.5;这行报了类型转换错误,查了半分钟才反应过来,得写成12.5f。
2.2 包装类与自动拆装箱
和基本数据类型配套的,是它们各自的包装类:Integer、Long、Double、Boolean、Character等。你需要理解的关键逻辑是:基本类型存在于栈内存,高效但“功能不全”;包装类是对象,能调用方法,能放进集合框架,但占用的内存和开销更大。日常开发里,两者会自动转换,这就是自动拆装箱。比如:
Integer count = 100; // 自动装箱:int -> Integer int value = count; // 自动拆箱:Integer -> int这里藏着一个面试常考的知识点,Integer的缓存机制。对于 -128~127 范围内的整数,Java会复用同一个对象,所以Integer a = 127; Integer b = 127; a == b的结果是true;但到了128,结果就是false了。我当天没学到这里,后来刷题遇到才明白。现在提前告诉你,免得以后面试被问倒:整数比较,要用equals(),别用==。
2.3 字符串不是基本类型
初学者最容易搞混的就是String。它看着用起来简单,但本质上是一个类。官方推荐用String的地方非常多,但你要记住一个原则:判断字符串相等,必须用equals()方法,不能用==。因为==比较的是引用地址,而不是内容。这是我当天实际翻车的地方,后面第6章我会专门展开讲。
字符串还有一个不可变性:String被创建后内容就不能变了。你看到的那种拼接操作,实际上是创建了新的字符串对象。所以频繁拼接字符串的代码,性能会很差——这时就该用StringBuilder或StringBuffer。
3. 运算符与控制流:让程序“长脑子”
3.1 运算符优先级:一个隐蔽的大坑
运算符大概是第二天最容易出bug的地方之一。Java的运算符优先级从高到低有一长串规则,但真正需要记住的痛点并不多。我整理几个实际编码时最常用的:
- 算术运算符:
*/%优先于+- - 赋值运算符:
=优先级最低 - 逻辑运算符:
!高于&&,&&高于|| - 比较运算符:
><==!=在逻辑运算符之前
真正让我栽跟头的是自增自减。i++是先取值后加,++i是先加后取值。单独写没毛病,但放进表达式里就乱了。我当时的代码:
int i = 3; int result = i++ + ++i;这行代码最终结果是8。过程是:先取i的当前值3参与运算,然后i变成4;接着++i先把i变成5,再取5参与运算。3加5等于8,运算结束后i是5。这种代码在实际开发里能不用就不用,可读性极差。但面试和笔试里它出现的频率超高,所以你必须搞懂。
还有一个细节是短路逻辑。a && b如果a为false,b根本不会执行;a || b如果a为true,b也不会执行。这个特性用好了能避免空指针,比如:if (str != null && str.length() > 0)这段代码,一旦str是null,因为短路机制,后一个判断不会执行,所以不会抛空指针异常。要是写成&和|,那两边都会执行,null调用length()直接崩。
3.2 if/else与switch怎么选
条件控制里,日常用量最大的就是if/else。但这里有个代码质量的问题,多层嵌套的if/else会有很多层,我第二天就写过六层嵌套的代码,把自己都看晕了。后来学到的处理方式是:能提前return就提前return,能用switch就用switch,能用枚举映射就用枚举映射。
switch在Java 8时只能匹配整数和枚举,Java 7开始支持String,Java 14之后有了更简洁的箭头写法。我当时学的还是传统的case写法,每个case里记得加break,不然会出现“穿透”——后面的case会继续执行。这个特性偶尔会被故意用来做技巧,但新手阶段老老实实加break,别玩花活。
一个实用的判断原则:判断条件是范围型(比如大于、小于、区间)用if/else;判断条件是确定的多值(比如星期几、订单状态)用switch或枚举。把这句话记下来,你以后的代码会清爽很多。
3.3 三种循环的适用场景
Java里有for、while、do-while三种循环,很多新手分不清区别,其实逻辑很简单:
- for循环:知道循环次数时首选。
for (int i = 0; i < 10; i++) - while循环:不知道次数,只知道循环条件时用。比如读文件,读到结尾为止
- do-while:不管条件是否成立,先执行一次再判断。这个用得最少,但有些场景(比如菜单展示至少要出现一次)必须用它
循环有个非常重要但容易出错的点:break退出整个循环,continue跳过本轮循环。两者作用范围都只限于当前一层循环。我当时写双重循环的时候,就想用break退出外层,结果只是退出了内层,效果完全不对。解决办法是给外层循环起标签名:
outer: for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { if (j == 2) { break outer; } } }这种带标签的break用得不多,但确实是Java合法的语法。网上不少Java笔试题会考这种冷门知识点,知道有这么回事就好。
4. 数组与经典排序实战:备战蓝桥杯的第一课
4.1 数组的声明、初始化和遍历
第二天下午我学的是数组。Java里数组是一个容器,可以存放固定数量的同一类型数据。数组的声明和初始化有三种写法:
int[] arr1 = new int[5]; // 声明长度,默认值填充,int类型默认0 int[] arr2 = new int[]{1, 2, 3}; // 声明并赋值 int[] arr3 = {4, 5, 6}; // 简化写法,只能同时声明和赋值这里有个值得注意的坑:int[] arr1 = new int[5]创建数组后,里面的默认值是0,而不是“空”。如果你拿这个数组去参与运算,结果是0,很容易被当成逻辑bug。引用类型数组(比如String[])默认值才是null。
遍历数组最推荐增强for循环,也就是foreach写法:
for (int num : arr1) { System.out.println(num); }这种写法简单、不易越界,适合只读场景。但如果要修改数组里的值,或者需要知道当前是第几个元素,就得用传统for配合索引。
4.2 手写冒泡排序:必会的基本功
排序算法是java面试和蓝桥杯的高频基础,而冒泡排序基本是必修第一课。我当天手写了一遍,代码不多,但第一次写错了两处:内层循环的边界条件写错,导致数组越界;还有一个是提前结束的优化标志没加,白跑了很多趟。
标准的冒泡排序逻辑:每一轮把相邻的两个元素比较,如果顺序不对就交换,这样每一轮结束后,数组中最大的数会“冒”到末尾。重复这个操作n-1轮,数组就排好了。代码长这样:
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,因为每完成一轮,末尾就有i个元素已经就位,不需要再比较。如果不减i,比较次数就白白多了很多。加swapped标志是我后来优化的,如果某一轮完全没有交换,说明已经有序,可以直接退出循环——在数据基本有序的情况下,这个优化能把时间复杂度从O(n²)降到接近O(n)。入门阶段我建议你先手写、别急着用现成工具,手写能帮你建立对循环和交换操作的直观理解。
4.3 Arrays工具类:排序的开挂方式
手写完冒泡排序之后,Java还自带了一个工具类Arrays,里面封装了排序、查找、填充、比较等常用操作。实际开发里你不会手写冒泡,而是直接用Arrays.sort():
int[] arr = {5, 2, 8, 1, 9}; Arrays.sort(arr); // 从小到大排序 System.out.println(Arrays.toString(arr)); // 输出 [1, 2, 5, 8, 9]Arrays.sort()底层用的是双轴快速排序,对基本类型数组性能很好,比自己写的冒泡快得多。所以我建议的学习策略是:先手写一遍冒泡理解原理,再学Arrays工具类了解工程上怎么用。两样都掌握,笔试让你手写你写得出,实际开发你也知道该调哪个接口。
Arrays里还有几个我很常用的方法:
int[] copy = Arrays.copyOf(arr, arr.length); // 复制数组 int index = Arrays.binarySearch(arr, 8); // 二分查找,数组必须有序 Arrays.fill(arr, 0); // 把数组全部填充为04.4 数组越界的调试经验
当天我遇到了一个特别典型的报错:ArrayIndexOutOfBoundsException,数组越界异常。原因是我拿遍历索引的时候写了i <= arr.length,多算了一次,最后一个元素访问越界。
这个异常是所有Java新手都会遇到的第一个运行时异常,知道怎么排查很重要:
- 看报错堆栈里的行号,精确告诉你出错在哪一行
- 检查循环条件里是
<=还是< - 检查是否有从别的地方传入的数组长度和实际不匹配
我可以跟你直说,排查这类问题,最快的方法不是瞪眼读代码,而是像我在实际操作里做的那样,先在关键行打印一下索引值和数组长度看一眼。打日志是Java调试的万能起手式,尤其在你还不会用断点调试之前。
5. 面向对象初步:从“写代码”到“建模”
5.1 类与对象:图纸和实物的关系
学完基础语法,下午我开始碰面向对象编程。这是Java的核心思维,也是从“写脚本”转变到“写工程”的分水岭。这里可以用一个生活类比帮助理解:类是图纸,对象是按图纸造出来的实物。一张图纸可以造出无数个实物,每个实物独享自己的数据,但它们的方法逻辑来自同一份图纸。
定义一个类,实际上是在定义一种新数据类型:
public class Student { String name; int age; void introduce() { System.out.println("我叫" + name + ",今年" + age + "岁"); } }再通过new关键字创建对象:
Student s = new Student(); s.name = "张三"; s.age = 18; s.introduce();这里要理解的核心是:Java对象在内存中默认分配在堆上,变量s只是指向这个对象的一个引用。这也是为什么对象比较要用equals而不是==——因为==比较的是引用地址,两个内容完全相同但地址不同的对象,用==判断是false。
5.2 构造方法与封装:让对象更安全
创建对象时,new Student()后面的括号其实是在调用一个特殊的方法——构造方法。构造方法的名字必须和类名完全一样,没有返回值也不能写void。如果不写构造方法,Java会提供默认的无参构造。但如果你自己写了有参构造,默认的无参构造就没了,需要手动补上,不然new Student()会编译报错。这个细节我在网上看过很多次,也真的踩到过:
public class Student { private String name; private int age; // 无参构造 public Student() { } // 有参构造 public Student(String name, int age) { this.name = name; this.age = age; } }构造方法之外还有一个重要概念是封装。封装的核心做法是把类的属性设为private,对外只提供public的getter和setter方法。这么做的好处是,给属性赋值时可以在setter里做校验,避免非法数据进入对象。比如age设置成负数就抛异常:
public void setAge(int age) { if (age < 0 || age > 150) { throw new IllegalArgumentException("年龄不合法"); } this.age = age; }很多Java新手觉得封装麻烦,多写一堆getter和setter,但这是Java开发规范里约定俗成的习惯,面试八股里会问,实际项目管理里也会要求。IDE可以直接生成这些方法,别嫌麻烦。
5.3 static关键字:属于类的,不属于对象
static是Java里很特别的一个关键字。被static修饰的成员属于类本身,所有对象共享一份,而不是每个对象各有一份。我当天用代码验证了一下:
public class Counter { static int count = 0; int instanceNum = 0; public Counter() { count++; instanceNum++; } } Counter c1 = new Counter(); Counter c2 = new Counter(); // count == 2, c1.instanceNum == 1, c2.instanceNum == 1这个例子能直观看出区别:static的count是全局共享的,创建两个对象后它变成2;instanceNum是每个对象自己的,各是1。
static修饰的方法也不需要通过对象调用,直接用类名调用就行。这就是为什么Main方法被定义为public static void main(String[] args)——它是入口方法,必须在创建任何对象之前就能被JVM调用。
static还有一个实用方向是常量。比如:
public static final int MAX_SIZE = 100;这种写法在整个项目里是全局共享且不可变的,比到处写裸数字要规范得多。
5.4 枚举类型初探
第二天傍晚我提前看了一眼枚举,算是给自己预习。枚举用enum关键字定义,是一组固定常量集,比如订单状态:
public enum OrderStatus { PENDING, PAID, SHIPPED, COMPLETED }枚举在Java里不是简单的一组常量,它是一个完整的类,可以加构造方法、属性和方法,而且还自带values()方法可以遍历所有枚举值。蓝桥杯和公司笔试题里都爱考枚举在switch里的用法。我当时只是了解到这个程度,已经很受震撼了——一个看起来简单的关键字,背后居然藏着这么多面向对象的特性。
6. 今天踩过的坑与排查记录
6.1 环境变量配置与启动失败
第二天早上,我同学的电脑上出现了第一天没暴露的问题:JDK装好了,但命令行输入java -version提示找不到命令,启动直接失败。我帮他排查后确认是环境变量没配置好,这里我把配置过程的关键点也记录一下,墙裂建议直接用系统自带的“环境变量”编辑界面操作,不要自己手动拼路径,否则容易漏掉分号和后缀名导致失败:
需要配置三个位置:
JAVA_HOME:指向JDK安装目录,比如D:\Java\jdk-17Path:加上%JAVA_HOME%\binCLASSPATH:设置为.;%JAVA_HOME%\lib或者.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar,前面的点是当前目录,表示允许从当前目录加载类文件
配置完成后,一定要重新打开命令行窗口再测试,因为旧窗口读不到新配置。如果你还用的是 Windows 11 系统,在“编辑系统环境变量”里注意 Path 要拆成一条一条加,不要连在一起拼一个长字符串,这样既规范也方便后续维护。如果你是macOS或Linux,JAVA_HOME通常用/usr/libexec/java_home命令定位,导出的写法也比较固定。总之版本要跟实际安装的JDK严格对应,JDK 17 和 JDK 8 的路径是不一样的。
6.2 中文乱码问题
我在控制台打印中文的时候出现了一堆乱码。原因很简单:代码文件是UTF-8编码,但Windows命令行默认用GBK编码输出,两边对不上。解决办法是在启动参数里加上-Dfile.encoding=UTF-8,或者把源码文件编码改成GBK(不推荐,治标不治本)。IDE(比如IntelliJ IDEA)里可以通过File Encoding设置全局编码为UTF-8,新版本的IDE一般默认就是UTF-8,但老项目经常是各种编码混用,遇到乱码先统一编码再排查其他的。
6.3 String用==比较翻了车
我之前提到过字符串比较的问题,当天实际写的代码是这样的:
String a = "hello"; String b = new String("hello"); System.out.println(a == b); // false System.out.println(a.equals(b)); // true因为Java对不通过new创建的字符串字面量会被放入字符串常量池,所以a == b在某些情况下(两个直接赋字面量)是true;但new出来的对象在堆上,地址不同,所以==就是false。这个判断规则不统一的情况让很多新手抓狂,最后记住一条就行:字符串比较永远用equals。在开发中,规范上用"hello".equals(b)这种写法也能避免b为null时抛空指针。
6.4 常见异常速查表
第二天我攒了好几个异常,整理成表方便你对照参考:
| 常见异常 | 触发场景 | 排查思路 |
|---|---|---|
| ArrayIndexOutOfBoundsException | 数组越界 | 检查索引范围,尤其是循环条件里的边界 |
| NullPointerException | 对象为null时调用方法 | 确认对象是否初始化,先判空再调用 |
| ClassNotFoundException | 类找不到 | 检查classpath和jar包依赖 |
| NumberFormatException | 字符串转数字失败 | 确认字符串是纯数字,别混入空格或字母 |
| Compilation error(类型不匹配) | 类型用错 | 检查基本类型和包装类,检查类型转换 |
| ConcurrentModificationException | 迭代集合时修改集合 | 改用迭代器的remove或收集后再统一处理 |
遇到异常,先看编译器的提示,再看运行时的堆栈信息,绝大多数情况下堆栈信息已经把行号说清楚了,照着改就行。
7. 给同样在自学Java的人几句实在话
7.1 代码量是唯一硬指标
第二天结束,我统计了一下,今天大概写了200行代码(包括练习和改错反复写的部分)。对于入门阶段来说,每天有效代码量在150到300行左右就足够踏实了。重要的是每行代码都要懂,不懂就查、就问、就改。建议你准备一本实验记录,把每次改bug的过程记录清楚——我自己的体会是,用编号记录问题、描述现象、尝试过哪些方案、最后怎么解决的,这套习惯能显著加快你的排错速度,因为在Java起步阶段,你遇到的90%错误都是重复发生的,有个记录本比每次重新排查高效得多。
7.2 手写代码作业不可替代
今天最大的收获,其实是手写冒泡排序那一遍。后来大厂面试和蓝桥杯的笔试里,手写排序题还没少出。有些人平时背答案,没手写过,到了笔试就卡壳——手写是肌肉记忆加逻辑记忆的双重保险,很难出错。我自己就建议所有自学者,即使有了Arrays.sort这种工具,也给经典算法留出每周手写一遍的时间。
7.3 别忘了去了解“为什么”
学习Java的最大误区之一是能跑就行。能跑和会了是两回事。今天内容里,前前后后能看到很多“为什么”:为什么short赋值要转类型?为什么Integer比较要用equals?为什么数组默认值是0?为什么static成员是共享的?当你能把这些为什么解释清楚,才真正算吃透了这一块。后面的学习里,不管是数据一致性、定时任务、并发、Spring Boot框架,还是JVM调优,底层逻辑都离不开这些基础概念。地基打得牢,上层建筑才盖得高,这句老话在Java学习路线上绝对算是一条铁律。
最后再分享一个小技巧:第二天结束的时候,把今天写的代码里最容易出错的几段删掉,合上资料重新再打一遍。这比看十遍教程都有用。这一遍打完,你不需要有什么特别的感觉,就把代码备份好,第三天可以安心过渡到字符串处理、集合和常用库函数的学习了。Java学习是一场持久战,第二天只是热身,但热身热到位了,后面跑起来会轻松得多。