今天是我Java学习打卡的第四天。前三天办了环境,写了Hello World,学会了用javac和java两条命令把源代码变成可运行程序,也搞清楚了IDE里那些花花绿绿的按钮后头到底发生了什么。按学习计划,今天该正面硬刚Java程序的基本语法了。这一天的内容不算难,但特别碎:变量、数据类型、运算符、流程控制、方法、数组,每一块单独拿出来都能写很长一篇,放在一起很容易学了后面忘了前面。可偏偏这些碎东西,才是后续所有框架、项目、面试题的底座。我见过不少同学一上来就啃框架,结果连for循环的边界条件都写错,最后又倒回来重新补语法。所以这一篇,我没有用教材式的罗列,而是按我学习时的真实顺序把Java基本语法过一遍,该省的废话全省,该强调的坑一个不落。适合零基础照着练,也适合有基础的人用来查漏补缺。
1. 为什么说“基本语法”是Java入门的分水岭
1.1 语法规则不是约束,是共识
每门语言都有自己的一套规矩。Java的规矩尤其多:类名要大写驼峰,方法名要小写驼峰,每行语句结尾要分号,缩进用几个空格,社区里还能吵上半天。我第一次写的时候也觉得烦,明明Python不写分号也能跑,为什么Java偏要写?后来才明白,这些规则是编译器的约束,更是团队协作的共识。在没有IDE提示的年代,一段没有缩进、乱加分号的Java代码基本没法读;而现在就算IDE能自动格式化,一个团队里如果大家风格不统一,git diff里全是格式变更,真正的逻辑改动反而被淹没。所以第四天学习基本语法,本质上不是背规则,而是学着跟编译器把话说清楚。
1.2 基本语法管到哪一层
很多人把“基本语法”理解成“变量和循环”,其实不止。严格来说,Java程序的基本语法包括标识符与关键字、数据类型、变量、运算符、表达式、语句、流程控制、方法、数组等,连public static void main(String[] args)这种入口写法也算。你可以把它想象成盖房子的砌砖规范:砌砖的手法、砖缝的宽度、转角怎么处理,都写在这套规范里。老实说,Day4不可能把所有细节都吃透,但至少要建立起“哦,原来Java程序是这样组织起来的”这个整体框架。之后你学面向对象、集合、IO,都是在往这个框架里填新的内容。
2. 变量与数据类型:所有计算的基础
2.1 八种基本类型,一张表说清楚
Java的数据类型分两大类:基本类型和引用类型。基本类型共8种,我把它们整理成了下表,方便对照记忆。
| 类型 | 占用字节 | 默认值 | 取值范围 |
|---|---|---|---|
| byte | 1 | 0 | -128 ~ 127 |
| short | 2 | 0 | -32768 ~ 32767 |
| int | 4 | 0 | -2147483648 ~ 2147483647 |
| long | 8 | 0L | -9223372036854775808 ~ 9223372036854775807 |
| float | 4 | 0.0f | 约 ±3.4E38 |
| double | 8 | 0.0 | 约 ±1.8E308 |
| char | 2 | '\u0000' | 0 ~ 65535 |
| boolean | 不固定 | false | true / false |
这里我特别想提醒的一点是取值范围不是背出来的,而是算出来的。比如int占4个字节,共32位,最高位是符号位,所以负数最小值是-2^31,正数最大值是2^31-1,也就是2147483647。我第一次写累加程序时,不小心把变量设成了int,循环加到21亿多直接溢出变成了负数,找了好久才发现。这就是基本类型最典型的坑。
2.2 字面量与变量定义的细节
定义变量时还有一些隐含规则,笔试里容易考,写代码时也容易踩。比如整数默认是int类型,你写long num = 3000000000;这行代码,编译器会直接报错,因为3000000000已经超出int范围,必须写成long num = 3000000000L;。浮点数默认是double,要表示float必须在数字后面加f。字符用单引号,字符串用双引号,'a'是char,"a"却是String。刚入门时我总把这两个搞混,System.out.println('a')和System.out.println("a")输出虽然都是a,但前者本质是数字97,参与加法运算时行为完全不同。记住这一点,后面学运算符能少踩一半的坑。
2.3 运算符:优先级、短路和自增陷阱
运算符也是基本语法里的重头戏。我建议新手不要背优先级表,只要记住几个规律:赋值运算符最低,算术运算符高于关系运算符,关系运算符高于逻辑运算符。实际写代码时,多用括号把优先级写清楚,比背表靠谱。真正容易出问题的是两个地方。一个是短路逻辑:&&和||有短路效果,如果左侧已经能确定结果,右侧就不会执行;而&和|是位运算,左右两边都会算。写if (a != 0 && 10 / a > 1)是安全的,但如果手滑写成&,当a为0时程序会直接抛出除以0的异常。另一个是自增自减的前置后置:int a = 5; int b = a++;得到b=5,因为先赋值再自增;int b = ++a;得到b=6,因为先自增再赋值。我一次面试里就被这个题问住过,当时答反了,印象特别深。
3. 流程控制:分支、循环和跳转
3.1 if和switch怎么选
分支语句最常见的是if-else和switch。我的选择原则很简单:条件涉及范围判断、多个组合条件时用if;条件是比较某个值是否等于一系列固定值(比如枚举、整数、字符串)时用switch。switch从Java 7开始支持String,还能搭配case穿透做简化,但新手经常在switch里忘记写break,导致执行完一个分支后继续往下执行。打个比方,switch就像一个多出口的传送带,break是你走出传送带的门,没写break就会一路滑到底。所以我在Day4的练习里要求自己每个case都写break,等基础熟了再去考虑省略的写法。下面是一个标准例子:
int day = 3; switch (day) { case 1: System.out.println("周一"); break; case 2: System.out.println("周二"); break; case 3: System.out.println("周三"); break; default: System.out.println("其他"); }3.2 for、while、do-while的真实场景
循环里三种写法各有侧重。for适合知道循环次数,比如遍历数组;while适合循环次数不确定、靠条件结束,比如一直读到用户输入“quit”;do-while至少要执行一次,适合“先做一遍检查,再决定要不要继续”的场景。我写了一个1加到100的例子:
int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } System.out.println(sum);这段代码看着简单,但边界条件很容易出错。如果写成i < 100,结果就是4950,和正确答案5050差一个100。所以建议新手每次写循环前,先在纸上把循环变量的初值、终值、变化步长标出来,跑一遍流程再落代码。另外一个高频考点是嵌套循环,冒泡排序就是典型例子。下面是冒泡排序的核心代码:
int[] arr = {5, 1, 4, 2, 8}; 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; } } }这段代码其实就是Day4最值得练习的“Java排序”入门写法。第几轮、内层循环要到哪个位置,都需要仔细琢磨。
3.3 break、continue和return的作用范围
跳转语句里,break用于退出当前循环,continue用于跳过本次循环的剩余语句继续下一次,return则是直接结束整个方法。我现在还记得自己踩过的一个坑:想在双层循环里跳出外层,结果只写了break,程序执行完内层循环后又继续跑外层,数据一直不对。后来才知道Java提供带标签的break,可以在外层循环前加一个标签,然后写break 标签名;直接跳出指定层。但说实话,这种写法会降低可读性,遇到需要跨层跳出的情况,更推荐把嵌套循环抽成独立方法,用return来返回结果。
4. 方法:让代码变得可复用
4.1 方法的结构与“值传递”问题
Java方法的基本结构是修饰符、返回值类型、方法名、参数列表和方法体。类比一下,方法像一个加工车间:参数是送进来的原料,返回值是车间交出去的产品,方法体就是车间里的流水线。比如求两个数的最大值,可以写:
public static int max(int a, int b) { return a > b ? a : b; }这里static关键字和后续面向对象相关,Day4只需要会照葫芦画瓢。真正要理解的是Java参数传递机制。Java官方只说一句话:Java是值传递。但这句话有迷惑性:基本类型传的是值的副本,方法里改了不影响外面;引用类型传的是引用的副本,方法里改引用指向的对象会影响外面,但重新给参数变量赋值不会影响外面的引用。这个知识点在面试里几乎必考,我建议自己动手写两个方法验证一下。
4.2 方法重载:同名不同参
方法重载指的是方法名相同、参数列表不同,与返回值类型无关。编译器会选择最匹配的那个版本调用。举例:
public static void print(int a) { System.out.println("int " + a); } public static void print(double a) { System.out.println("double " + a); }当调用print(1)时,编译器会优先匹配int版本;调用print(1.0)时匹配double版本。如果传入print(1L),两个都不完全匹配,但long可以自动转成double,于是会调用print(double)。重载在Java的System.out.println里体现得淋漓尽致,你传什么类型,它就会自动选对应的方法重载来输出,这也是为什么它能同时支持println(int)、println(String)、println(Object)。理解重载,其实就理解了Java标准库很多“看起来魔法”的地方。
4.3 一个能跑起来的小案例:类型打印器
为了让方法重载不那么抽象,我Day4做的小练习是写一个“类型打印器”:一个showType方法,分别接收int、String和boolean参数,输出不同的信息。代码如下:
public static void showType(int value) { System.out.println("我是整数,值为 " + value); } public static void showType(String value) { System.out.println("我是字符串,值为 " + value); } public static void showType(boolean value) { System.out.println("我是布尔值,值为 " + value); }调用showType(1)会走第一个方法,showType("hello")走第二个,showType(true)走第三个。这个过程让我第一次直观感受到“同名方法可以有不同的行为”,也是后面学习多态的一个很好预热。
5. 数组与字符串:最常用的内建容器
5.1 数组的声明、初始化和边界
数组是同类型元素的固定长度容器。我用三个例子说明声明和初始化:
int[] arr1 = new int[5]; // 默认都是0 int[] arr2 = {1, 2, 3}; // 声明时直接赋值 int[] arr3 = new int[]{4, 5, 6}; // 匿名数组方式注意new int[5]创建后,数组长度就不能变了。很多新手误以为数组可以随意增加元素,实际不行。如果真需要动态扩容,后面学ArrayList就是干这个的。数组下标从0开始,访问arr2[3]会抛出ArrayIndexOutOfBoundsException。这个异常名特别长,我第一次看的时候完全看不懂,后来才明白“IndexOutOfBounds”就是“下标越界”。遍历数组最简单的是增强for循环:
for (int value : arr2) { System.out.println(value); }5.2 String:不变的字符串与变来变去的拼接
String在Java里是不可变对象,一旦创建内容就不能修改。很多人不理解“不可变”到底有多重要。你写String s = "a"; s = s + "b";,看似把s改了,实际是先在常量池里创建了新的字符串"ab",然后把s的引用指向它,原来的"a"并没有变。因为不可变,String才可以在哈希表里安全作为key,线程安全也不用加锁。但也因为不可变,循环里做大量字符串拼接会创建大量中间对象,性能很差。这时候应该用StringBuilder:
StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { sb.append(i); } String result = sb.toString();字符串内容比较必须用equals(),不能用==。==比较的是引用是否相同,只有两个字符串都指向常量池同一个对象时才返回true,这个坑我在写用户登录逻辑时踩过,用户输入的用户名明明和数据库里一样,if (input == storedUser)就是进不去,改成equals就好了。
5.3 字符串常用方法速查
在Day4阶段,没必要把所有String方法都背下来,但下面几个几乎每天都会用到,我整理成了表格。
| 方法 | 作用 | 示例 |
|---|---|---|
length() | 返回字符串长度 | "abc".length()结果是3 |
charAt(int index) | 返回指定位置的字符 | "abc".charAt(1)结果是'b' |
substring(int begin, int end) | 截取子串 | "hello".substring(1, 3)结果是"el" |
indexOf(String s) | 返回子串首次出现位置 | "abcabc".indexOf("bc")结果是1 |
split(String regex) | 按规则拆成数组 | "a,b".split(",")结果是["a","b"] |
equals(Object obj) | 比较内容是否相等 | "abc".equals("abc")结果是true |
toLowerCase() | 转小写 | "AbC".toLowerCase()结果是"abc" |
trim() | 去掉首尾空格 | " a ".trim()结果是"a" |
6. 面向对象铺垫:类、对象与构造方法
6.1 为什么Day4就要开始提类
严格来说,面向对象是后面几天的主线,但Java有一个无法回避的事实:所有代码都必须写在类里。就算你只想写一个简单算法,也得先定义public class Main。所以Day4的语法练习里,我写的每个for循环、每个方法,本质上都是在类内部组织代码。提前建立“类是组织代码的基本单位”这个概念,后面学封装、继承、多态时会轻松很多。可以在Day4就试着定义一个最简单的“用户”类:
public class User { String name; int age; }虽然这个类还只有属性没有方法,但你已经能看出类是一张“图纸”,之后new User()创建出来的对象就是按图纸建出来的具体房子。
6.2 构造方法的几个细节
构造方法是创建对象时调用的特殊方法,名称和类名一样,没有返回值类型。如果你没写任何构造方法,编译器会送一个无参构造;但只要你写了一个有参构造,默认无参构造就没了。这个规则我刚学时总是忘,代码里写了new User()却提示找不到构造器,就是因为自己定义了User(String name)构造方法却忘了补无参构造。我通常会养成显式写无参构造的习惯,或者用Lombok这类工具来生成,不过Day4阶段先自己手写,体会会更深。另外构造方法之间可以用this()互相调用,但必须在第一行。下面是一个补充了构造方法的例子:
public class User { String name; int age; public User() { } public User(String name, int age) { this.name = name; this.age = age; } }6.3 从基本语法到面向对象的过渡
很多教程把“面向对象”放得很靠后,导致初学阶段写任何代码都只想着“方法怎么堆”,完全意识不到类本身才是主体。我的建议是,Day4至少要知道三件事:第一,类是Java源码的顶层容器,所有基本语法最终都落在类里;第二,类里可以定义字段(变量)和方法,字段描述状态,方法描述行为;第三,用new关键字创建对象时,会执行对应的构造方法完成初始化。有了这三个认知,等学到“封装、继承、多态”时,你是在同一个框架上加深理解,而不是推翻重来。
7. 常见问题与调试技巧实录
7.1 编译报错的经典场景
Day4阶段最常见的编译错误,我用几句话总结一下。第一,非法字符:很多同学从文档或聊天软件里复制代码,引号、分号变成中文全角,javac直接报“非法字符”,把标点改成英文即可。第二,找不到符号:变量没有定义、类名写错、方法名大小写不对,都会报这个错,优先检查拼写和作用域。第三,缺少大括号:if后面只跟一行语句时不写花括号虽然能编译通过,但容易埋雷,比如if (a > b) System.out.println(a);这种写法缩进稍微乱一点就看不清范围了。我现在的习惯是,哪怕只有一行也写花括号,这个习惯能减少很多低级bug。
7.2 命令行编译运行和环境变量
本来环境配置是前三天的内容,但不少读者反馈卡在这一步。这里快速带一遍:在Windows上配置JAVA_HOME指向JDK安装目录,再把%JAVA_HOME%\bin加到PATH,之后命令行才能识别javac和java。在命令行里,javac负责编译成.class文件,java负责启动虚拟机运行。如果javac提示“不是内部或外部命令”,基本就是环境变量没配好或配置后没有重开命令行窗口。我见过太多人倒在环境变量上,其实这跟语法没关系,但环境不跑通,后面的代码都白写。所以我建议Day4开始前,先用javac -version和java -version确认环境正常。
7.3 常见问题速查表
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 编译报“非法字符” | 中英文标点混用 | 检查分号、引号、括号是否为英文 |
| 程序编译成功但运行没反应 | 入口方法名写错或没写main | 确认public static void main(String[] args) |
| 数组访问抛异常 | 下标越界 | 检查循环边界,使用arr.length |
| 循环死循环 | 条件永远为真或变量未更新 | 在循环体内打印变量走查 |
| 字符串比较不相等 | 用了==比较内容 | 改为equals |
long赋值报错 | 字面量超出int范围 | 数字后面加L |
switch结果异常 | 缺少break | 每个分支后加上break |
这张表我从Day4一直用到现在,遇到同类问题直接翻一下,效率会高很多。
8. 写在最后:给Day4新手的几个实操建议
8.1 边写边错是最快的记忆方式
学习语法不是背课文,而是边写边验证。每学一个知识点,我都建议开一个小的Main类,只做一件事,比如测试自增运算、测试数组越界、测试字符串拼接。运行通过后,再故意写错,看看编译器会怎么骂你。这样“故意犯错”的记忆比看十遍书都牢。我自己的笔记本上至今还贴着一张便签,写着“==比较的是地址,equals比较的是内容”,这句话是我踩了不少坑才换来。
8.2 别怕英语不好,也别急着背API
Java关键字就那么几十个,见多了自然就认识。一开始看到public static void main觉得像天书,写几次之后就会发现它其实是个固定入口模板。至于API,比如String的几十个方法,不需要第一天全背下来,先记住最常用的几个,用到再查文档。基本语法是根,根扎稳了,后面学集合、泛型、异常、IO才能枝繁叶茂。Day4只是开始,真正熟练至少要连续写一周的小程序,坚持下来,你会在某一天突然发现,自己已经能“不看笔记”写代码了。