
1. 这块内容到底在学什么1.1 一个Java程序的最小骨架如果你刚开始学Java第一次接触“面向对象”之前最先要啃下来的就是这一块标识符、常量与变量、数据类型、运算符与表达式。这是Java基础语法里的第一道硬门槛也是后面学面向对象、集合、IO、多线程时绕不开的底层零件。很多刚接触编程的人容易被“类”和“对象”四个字吓住但真正拖慢进度的往往不是对象和类而是这一章里没有打牢的基本功名字怎么起数据怎么存类型怎么转表达式怎么算。先看一段最基础的代码public class Hello { public static void main(String[] args) { String name 小明; int age 18; System.out.println(姓名 name 年龄 age); } }这段代码里几乎包含了这一章的全部知识点。Hello、main、String、args、name、age这些是标识符public、class、static、void这些是关键字小明、18是字面量name、age是变量String、int是数据类型是赋值运算符是字符串拼接运算符。你看一行代码表面上是输出一句话实际上背后已经调动了标识符规则、变量声明、类型系统、运算符求值好几层东西。所以我把这章看作Java语法的最小闭环学懂了它后面再看类、对象、方法你会觉得“哦原来只是把这段代码里的一部分抽出来重复用而已”。1.2 为什么这部分永远是面试和笔试的高频区很多人觉得这些基础语法太简单了不值得花时间。但你去翻任何一套Java面试题尤其是网上流传的各种“Java基础八股文”前几个问题大概率跑不出这几个方向byte和int转换会不会丢精度和equals有什么区别自增运算符i和i到底怎么算运算符优先级出个表达式问结果float和double为什么不能直接比较相等。这些题目全都在这一章的范围里。基础不是难而是碎。碎知识点最适合做面试筛子因为你背得再熟只要一个细节没注意到就会答错。比如很多人知道int默认值是0却不知道局部变量根本没有默认值不初始化直接使用会编译报错很多人知道long比int大却不知道整数字面量默认是int过大数字不加L后缀直接编译失败。这类细节就是这一章的价值所在。所以这篇东西适合谁看三类人一是正在自学Java、刚开始写代码的在校学生二是准备Java实习或校招、需要快速复习基础语法的人三是带新课的老师或助教想找一份能直接讲给学生听的现场教学笔记。我会按照“规则—原理—实操—踩坑”的顺序把这四个主题全部拆开带上代码和常见报错争取让你看完能自己敲一遍、能应付面试题也能在实际项目里少踩几个坑。2. 标识符命名规则和规范不能踩的坑2.1 硬性规则什么能写什么不能写标识符就是你在代码里起的名字类名、方法名、变量名、常量名、包名统统都算。Java编译器的规则特别死违反任何一条直接编译失败。我把它整理成四句话只能由字母、数字、下划线_、美元符号$组成。不能以数字开头。不能是Java关键字或保留字。不能用true、false、null这三个字面量值作为名字。这里的“字母”要尤其注意Java是支持Unicode字符的所以理论上中文也能做变量名比如int 年龄 18;编译完全没问题。但实际开发里几乎没人这么干原因后面说。再看几个例子int age 20; // 正确最常见的写法 int _age 20; // 正确但不推荐 int $age 20; // 正确但基本没人用 int 1age 20; // 编译错误数字不能开头 int class 20; // 编译错误class是关键字 int true 1; // 编译错误true是字面量这里有一个容易忽略的细节true、false、null虽然在关键字列表里不一定出现但它们和关键字一样不能作为标识符使用。另一个知识点是从Java 9开始单独的一个下划线_已经被列为关键字你不能再用_当变量名写了就报错。以前有人偷懒用_表示临时变量现在这个口子彻底堵死了。2.2 命名规范从可编译到可读规则保证的是“能跑”规范保证的是“能读”。你写的代码首先是给人看的其次才是给机器执行。Java社区的命名规范非常统一面试官看到你变量名写得乱七八糟第一印象就不会好。常见的规范就这几条类名用大驼峰也就是每个单词首字母大写UserInfo、OrderService。方法名和变量名用小驼峰首字母小写后面每个单词首字母大写userName、getOrderById。常量名全部大写单词之间用下划线分隔MAX_LOGIN_COUNT、DEFAULT_PAGE_SIZE。包名全部小写域名倒写com.example.demo。名字本身要有含义别用a、b、c、data1这种毫无信息量的命名。我见过不少同学喜欢用拼音缩写比如yonghuming代表用户名或者mc代表名称。这种代码在作业里还能忍到了团队协作里就是灾难。你一个月后回头看自己的代码看到String yhm 张三;可能要想半天更别说别人来接你的锅了。好名字不一定要长但一定要准确表达用途userName就比name更精确unpaidOrderList就比list1清晰得多。2.3 实际开发中的标识符避坑清单标识符踩坑非常隐蔽因为很多坑不是编译报错而是代码风格和协作问题。我总结几条实际经验第一不要用中文标识符。虽然能编译但Windows系统经常因为文件编码不同导致乱码而且中英文混写会让代码显得很不专业。更麻烦的是有些IDE配置不当中文标识符在某些环境下会变成乱码到时候排查问题都不知道从哪开始。第二不要用$开头。$虽然在语法上是合法字符但在一些代码生成器、内部类、嵌套类机制里$有特殊用途。JVM规范里甚至明确说明$用于某些编译器生成的内部类命名。你日常写代码碰到的概率不大但何必给自己埋雷呢。第三大小写必须敏感。Java里UserId和userId是两个完全不同的标识符JVM去找符号的时候区分大小写。所以当你看到Cannot find symbol这种报错时第一反应就是检查变量名拼写尤其是字母大小写是否一致。第四不要和JDK核心类同名。给类起名叫String、Integer、System虽然不违法但会在你自己的类里把原生的java.lang.String遮蔽掉后面代码一复杂你连字符串类型都不敢写了。起名之前先想想这个名字是不是太“大众”了换一个更具体的。3. 常量与变量数据怎么在内存中安家3.1 字面量与常量的区别常量这个词有两个意思一个是代码里写死的“字面量”另一个是用final修饰的“常量变量”。很多初学者会搞混。字面量就是直接写出来的数据比如100、3.14、A、hello它们在代码里长什么样就是什么值没有任何可变性。常量则是一个变量只不过你用final告诉编译器这个变量一旦赋值之后再也不能改。例如final int MAX_LOGIN_COUNT 5; MAX_LOGIN_COUNT 6; // 编译报错无法为final变量分配值如果这个常量在类里定义通常会写成public static final这样它属于类本身所有对象共用一份且对外不可修改。比如Integer.MAX_VALUE就是一个典型的常量它被定义成public static final int MAX_VALUE 2147483647;。为什么要用常量最直接的好处是消灭“魔法数字”。假如你在代码里写了if (score 60)60这个数字本身看不出业务含义如果你定义final int PASS_SCORE 60;然后写if (score PASS_SCORE)别人一看就知道这是及格线。将来及格线改成70你也只需要改一个地方而不是满项目去搜索60。3.2 变量的三种身份局部变量、成员变量、静态变量按照定义位置和归属变量可以分成三类。很多新手分不清成员变量和静态变量我直接用一张表给你讲清楚变量类型定义位置默认值生命周期局部变量方法内或代码块内没有默认值必须显式初始化从声明位置到所在代码块结束成员变量实例变量类体内、方法外有默认值如0、false、null伴随对象的创建与销毁静态变量类变量类体内带static修饰有默认值同成员变量随类加载而存在全局共享举个例子public class VarDemo { int instanceValue; // 成员变量默认值是0 static int staticValue; // 静态变量默认值是0 public void test() { int localValue; // 局部变量没有默认值 localValue 10; // 必须先赋值再使用 System.out.println(localValue); } }这三类变量最大的区别不是语法位置而是生存周期和默认值。成员变量属于对象每个对象都有一份独立拷贝静态变量属于类所有对象共享同一份。局部变量则活在方法栈帧里方法一执行完就销毁了。3.3 局部变量使用前必须初始化这条规则很重要因为它是个经典面试题为什么局部变量没有默认值而成员变量有答案是成员变量存储在堆内存中系统会在对象创建时自动给它一个安全的初始值比如数值类型是0、布尔是false、引用是null这样即使你忘记赋值也不会读到一块随机的脏数据。局部变量存储在栈帧里编译器为了保证安全干脆强制要求你在使用前必须显式赋值否则直接编译报错。你可以亲自试一下public class InitDemo { public static void main(String[] args) { int x; System.out.println(x); // 编译报错variable x might not have been initialized } }编译器提示的英文很直接这个变量可能没有初始化。你别想着“我运行时一定能走进某个分支然后赋值”编译器只知道你可能没赋值就会拦你。所以写局部变量最省事的习惯就是声明时直接初始化比如int count 0;。3.4 从变量到内存的直观理解这一章不用把JVM内存模型全部铺开但有一个概念必须现在建立起来基本类型变量存的是值引用类型变量存的是地址。比如int age 18;在栈帧里会有一块空间直接放着数字18。而String name 小明;name这个变量本身放的是一个引用也就是一个“指向堆内存的地址编号”真正放着字符数据的是堆里的对象。这种“变量名指向对象”的模式是后面理解面向对象中“对象引用”的基础。我现在教学生的时候喜欢用一个快递柜的类比基本类型变量相当于你直接站在柜子前柜子里放着你的快递引用类型变量相当于你手里拿着一张取件码取件码指向某个柜子。你复制一个引用变量相当于多复制了一张取件码但柜子还是同一个。这个概念现在不用完全吃透但脑子里要有印象。4. 数据类型8种基本类型和引用类型4.1 基本类型一览表Java的数据类型分成两大类基本类型和引用类型。基本类型一共有8种我来给你列一个完整表格面试考“取值范围”的时候你照着背就行类型字节数默认值取值范围或说明byte1字节0-128 ~ 127short2字节0-32768 ~ 32767int4字节0-2147483648 ~ 2147483647long8字节0L-9223372036854775808 ~ 9223372036854775807float4字节0.0f约 ±3.4E38有效位数约7位double8字节0.0d约 ±1.7E308有效位数约15位char2字节\u00000 ~ 65535表示Unicode字符boolean未明确规定false只有true和false两个容易混淆的点char不是字符串它只能表示单个字符用单引号boolean并不是1个bit或1个byteJava规范没有规定它的大小JVM在运行时会根据上下文自行处理所以你在面试题里看到“boolean占1字节”这种说法严格来说是不准确的最安全的回答是“规范未明确规定”。4.2 自动类型提升与强制转换数据类型转换是这一章的硬骨头也是面试红人。先说自动类型转换当一个小的类型往大的类型赋值时编译器会自动完成转换不会丢精度。规则大致是byte→short→int→long→float→doublechar→int→long→float→double注意float的精度范围超过long所以long自动转float是允许的但可能丢失尾数精度。这里有个高频考点byte、short、char参与算术运算时会自动提升为int。看代码byte a 10; byte b 20; // byte c a b; // 编译错误不兼容的类型从int转换到byte可能会有损失 int c a b; // 正确为什么Java要这么设计因为JVM底层的算术运算最小单位基本就是int让byte和short直接参与会引入频繁的字节处理效率不高。所以编译器干脆全部提升成int再算也算是一种“统一口径”。强制转换则是反向的大类型往小类型转必须显式加上(类型)而且要自己承担精度损失的风险。看例子int i 300; byte b (byte) i; // b 实际结果是44因为发生了溢出截断 double d 3.99; int n (int) d; // n 是3不是4强制转换直接丢掉小数部分强制转换浮点数成整数时是向零截断不是四舍五入。这个点特别容易在笔试里出你要记住(int) 3.99等于3而不是4。4.3 字面量陷阱L、F后缀和整数默认int写代码的时候字面量的默认类型会坑掉不少人。整数字面量默认是int浮点字面量默认是double。这句话你需要刻在脑子里因为很多编译错误都源于此。long num1 2147483648L; // 必须加L因为不加L会超出int范围直接编译报错 float num2 3.14F; // 必须加F因为3.14默认是doubledouble不能自动转float double num3 3.14; // 正确默认就是double如果你写long num 2147483648;编译器会提示“过大的整数”。因为右边的字面量被当成int处理而2147483648已经超过int的最大值还没等你赋值就已经出错了。加了L之后这个数才被当成long。同理float f 3.14;也会报“不兼容的类型”因为3.14是double从double到float是缩窄转换必须加F后缀或者写(float) 3.14。这类错误非常经典几乎每个初学者都会遇到至少一次。4.4 String为什么单独说String虽然长得像基本类型但它其实是引用类型而且是一个不可变类。什么叫不可变就是你创建了一个字符串对象之后它的内容不能改变任何看起来像“修改”的操作实际上都是创建了一个新的字符串对象。这里最容易踩的坑就是比较。很多新手写if (str abc)然后用去判断字符串内容是否相等。对于基本类型比较值没问题对于引用类型比较的是引用地址不是内容。正确的做法是用str.equals(abc)。关于String的深浅原理比如常量池、不可变设计的好处后面讲面向对象和JVM的时候会展开。这一章你只要记住两点String是引用类型判断内容相等用equals而不是。5. 运算符与表达式计算规则和优先级5.1 算术运算符和自增自减的“先”与“后”算术运算符就是、-、*、/、%。这里面有几个细节笔试和开发都很常用。第一个细节整数除法自动取整。7 / 2的结果不是3.5而是3因为两边都是整数结果也会是整数小数部分直接丢掉。如果其中一个数是浮点数比如7.0 / 2结果才是3.5。第二个细节取余运算结果的符号跟被除数一致。7 % 3是1-7 % 3是-17 % -3是1。你只要记住先按正数算然后看被除数是负号还是正号结果就给对应的符号。第三个细节自增和自减--的位置。i是“先使用再加一”i是“先加一再使用”。看代码int a 5; int b a; // b 5a变成6 int c a; // a先变成7c 7这个知识点不难但很多人会在复杂表达式里翻车。比如i i i;这种写法不同编译器版本给出的结果可能都不一样属于典型的“写出这种行为的人应该被打手”的代码。我强烈建议你平时写代码时把自增操作单独放一行不要嵌进其他计算里既清晰又安全。5.2 关系、逻辑、三元条件判断的好帮手关系运算符有、、、、、!它们的返回值永远是boolean要么true要么false。逻辑运算符有、||、!以及、|。这里最值得讲的就是短路逻辑。左边为false时右边的表达式根本不会执行||左边为true时右边也不会执行。短路是语文上的“而且”和“或者”如果前面已经决定了结果后面就不用看了。短路有什么用它可以帮你避免很多运行时异常。比如int i 0; boolean result (i ! 0) (10 / i 1); // 因为 i ! 0 是 false右边的 10 / i 不会执行所以不会除零如果你把换成右边照样会执行直接抛ArithmeticException。所以看到代码里有人用单个做逻辑判断一定要警惕它通常不是个好习惯。三元运算符? :可以看作一个精简版if-else。格式是条件 ? 值1 : 值2条件成立取值1否则取值2。例如int max a b ? a : b;三元运算适合简单场景嵌套不要超过一层。你要是写出a ? b ? c : d : e这种代码那代码评审会上大概率是要被按着头改的。5.3 赋值运算符和位运算符面试里的隐藏考点赋值运算符不只有还有、-、*、/、%以及位运算的复合赋值。最容易被问到的坑是复合赋值会偷偷做强制转换。看这个例子byte b 10; b 1; // 编译通过等价于 b (byte)(b 1); // b b 1; // 编译报错因为 b 1 是 int不能直接赋给 byte为什么b 1能编译b b 1不行因为复合赋值运算符内部会自动插入一个到左边类型的强转相当于把int截断成byte。这个规则在很多教材里不细讲但面试官非常爱问。位运算符包括、|、^、~、、、。其中是无符号右移是Java特有的右移时高位补0是有符号右移正数补0负数补1。举几个常见位运算数据int a 5; // 二进制 0101 int b 3; // 二进制 0011 a b 1; // 0001 a | b 7; // 0111 a ^ b 6; // 0110 ~a -6; // 按位取反后符号位也取反 1 3 8; // 左移3位相当于乘以2的3次方位运算在日常业务代码里不算常用但在网络协议、加密、权限系统、底层框架里仍然是高频工具。应付面试时至少要知道这些运算符的含义和一些典型计算比如用int flag的某一位做开关就是位运算的经典场景。5.4 运算符优先级别把表达式写成一锅粥Java里运算符有几十个优先级从高到低排下来很长完整表格可以去查官方文档。我给你一个简化但够用的清单按优先级从高到低排列后缀自增自减expr、expr--一元运算expr、--expr、、-、!、~乘除取余*、/、%加减、-移位、、关系比较、、、、instanceof相等判断、!按位与按位异或^按位或|逻辑与逻辑或||三元?:赋值、、-、*、/等这里有一个非常经典的坑的优先级高于、|、^。所以当你写if (a b 0)时Java会把它解析成a (b 0)左边是数字右边是布尔值编译直接报错。正确写法是必须加括号if ((a b) 0)。我在实际开发里的建议非常简单粗暴遇到不确定优先级的地方不要纠结直接加括号。括号不是给编译器看的是给后来维护代码的人看的。你少背一条优先级规则不会死但多写一个括号能救命。6. 常见报错与排查技巧实录6.1 编译期错误速查表我自己讲课的时候最喜欢让学生先犯错再纠正因为编译器的报错信息就是最好的学习材料。下面这张表是这一章里出现频率最高的编译错误你对照着查就行报错信息关键片段可能原因解决办法cannot find symbol标识符拼写错误、大小写不一致、变量未声明核对名称检查作用域variable might not have been initialized局部变量未赋值就直接使用声明时初始化或先赋值再使用incompatible types: possible lossy conversion from int to byte大类型赋值给小类型没有强转加上强转(byte)integer number too large整数字面量超过int范围且没加L加L后缀或改成long类型reached end of file while parsing大括号不匹配、类或方法没写完整检查花括号配对unclosed string literal字符串少写了双引号或用了中文引号检查引号是否半角not a statement运算符位置错误表达式语法不对检查表达式结构例如ab;单独放一行没有意义这张表不用背你平时遇到报错时回来翻一下然后自己亲手改一次记忆会比看十遍都牢。6.2 逻辑错误的经典现场编译错误还不可怕可怕的是程序能跑但结果不对。逻辑错误不会让编译器报警只能靠你睁大眼睛慢慢找。这一章我能列出的经典现场至少有四个第一整数除法丢精度。你写double avg total / count;如果total和count都是int那么即使avg是double右边的total / count也会先按整数除法算出整数再转成double。解决方法是把其中一个数写成浮点数比如total * 1.0 / count或者直接给其中一个变量加double。第二浮点数比较永远false。0.1 0.2 0.3这个表达式的结果是false因为二进制无法精确表示0.1和0.2计算结果是0.30000000000000004。如果你需要比较浮点数应该用Math.abs(a - b) 0.000001或者干脆用BigDecimal处理金额计算。第三短路导致后续逻辑没执行。比如代码写if (condition1 || logToFile())当condition1为true时logToFile()永远不会执行。你以为是日志写入偶发失败其实是短路机制把你的调用吞掉了。解决方法是把有副作用的调用从逻辑表达式里拆出来单独一行执行。第四字符串用比较。两个内容完全相同的字符串用得出的结果可能是true也可能是false取决于JVM是否复用了常量池里的对象。新手用比较字符串十次有五次会踩坑所以最开始就养成用equals的习惯能省很多事。6.3 我的几条教学和自检经验把这章讲一遍不难但要让学生真正不踩坑我的经验是让他们主动“造坑”。在上练习课的时候我会有意安排几个错误版本的程序让他们先改错再运行。比如故意少写一个L后缀故意用比较字符串故意把改成让程序崩溃或输出异常。经历过一次“哦原来是这里错了”的瞬间比听我讲十遍都要管用。个人在做代码审查时也有一套自检顺序先看常量是否该提出来再看变量类型是不是最小可用类型然后看类型转换有没有可能丢精度最后看表达式里有没有混乱的优先级。这四个问题过一遍大多数基础语法层面的隐患都能被拦下来。最后一句话送给刚开始学Java的朋友很多人在这一章觉得自己会了结果一写代码就各种红叉。其实很正常我带过的学生里几乎有一半都在类型转换和运算符优先级上翻过车。我的建议很笨但很有效每学一个规则立刻敲一段小代码故意写成错的看编译器怎么提示你然后再改正。这样踩过几个坑之后你会比那些只看不写的人扎实得多。