
新手学Java最容易被灌进的鸡汤就是基础很重要但基础到底是什么、怎么学很少有人讲透。变量、数据类型、表达式这三块就是Java一切语法能力的底座。你要是能把它们背后的内存机制、类型转换规则、运算求值顺序搞明白后面看集合、泛型、并发都会顺畅很多应付面试八股文也不会心里发虚。这篇文章就是一次完完整整的梳理从变量在内存里怎么待着到8种基本数据类型怎么选再到表达式里那些容易怀疑人生的坑全部用大白话加实际案例讲清楚。1. 变量从内存布局到命名规范1.1 变量到底是什么名字、类型和值的三元组刚开始学Java很多人把变量理解成一个装数据的盒子这个类比没毛病但不够。变量本质上是一块内存区域它有名字、有类型、有值。名字是给你编程时用的类型决定了这块内存有多大、能存什么格式的数据值就是里面真正存放的内容。int count 10; String name 张三;上面两行代码第一行在栈上分配了4个字节空间存整数10第二行栈上存的是一个引用地址真正的字符串对象在堆上。这就是基本类型变量和引用类型变量最核心的区别也是后面理解值传递还是引用传递的基础。// count变量栈上的4字节直接存10 // name变量栈上的4字节32位系统/8字节64位存的是堆对象的地址所以面试题里经常问Java到底是值传递还是引用传递如果你能先说出基本类型存值、引用类型存地址再往下聊就会顺很多。变量在Java里必须先声明后使用这一点跟Python、JS这种动态语言完全不同。动态语言你可以随手写x 10Java里不声明直接编译报错这也是入门时最容易骂娘的地方。1.2 成员变量、局部变量、静态变量的差异Java里的变量按归属位置分成三类成员变量也叫实例变量、静态变量也叫类变量、局部变量。很多人上班写业务代码天天用这仨但被问到区别时只能答出static修饰的是静态变量有static没static的区别这就太浅了。表格是最直观的对比方式变量类型声明位置默认值生命周期归属成员变量类内、方法外有默认值如int是0对象是null随对象创建而分配随对象被回收而消亡每个对象一份静态变量类内、方法外带static有默认值类加载时初始化直到类卸载所有对象共享一份局部变量方法体、代码块、参数列表必须显式初始化无默认值方法调用结束或代码块执行完就销毁当前作用域有一个特别容易踩的坑局部变量不初始化直接用会编译报错而成员变量不初始化只是拿着默认值。很多新手在方法里写int sum; sum 1;就会报错原因是局部变量sum没有初始值编译器不让你拿一个不确定的值参与运算这是Java强调安全性的体现。作用域的规则也值得单独拎出来说。局部变量只在它声明的那层花括号内有效出了这个作用域就访问不到。比如for (int i 0; i 10; i) { // i在循环体内有效 } // 这里再用i直接编译报错这个规则看着简单但它直接影响你是否能写出低耦合的代码。我见过有人图省事把很多中间计算结果声明成成员变量结果对象状态被到处改bug查得怀疑人生。正确做法是能用局部变量就用局部变量作用域越小代码越不容易被意外污染。1.3 命名规范与隐藏坑Java标识符的硬性规则是以字母、下划线_、美元符$开头后面可以跟数字但不能用数字开头不能用关键字和保留字。理论上是这样但实际工程里命名规范比合法性更重要。小驼峰命名法是事实标准方法名、变量名首字母小写后面每个单词首字母大写比如userName、orderTotalAmount。常量用全大写加下划线比如MAX_SIZE。类名用大驼峰比如UserService。这些都是写代码的基本素养ide插件如阿里规约能帮你自动检查。这里我要重点讲一个特别坑的JavaBean命名问题。JavaBean规范有个反直觉的属性推断规则Introspector.decapitalize在去掉getter/setter前缀后如果剩余的前两个字符都是大写就保持原样否则首字母转小写。举个例子public class User { private String URL; public String getURL() { return URL; } }因为getter是getURL()去掉get后剩下URL前两个字符U和R都是大写所以框架认为属性名是URL而不是uRL。看起来没问题但如果你用的是某些版本不讲究的JSON库或者你的字段名是uRL这种不伦不类的写法序列化之后就很容易出现字段名对不上的情况。网上那个Java bean 大写字母开头的变量json时就变成小写了的说法根源就是字段命名不规范 框架推断规则叠加的结果。解决办法很简单字段一律用小驼峰命名不要用大写字母开头更不要用uRL这种奇形怪状的缩写。另外我强烈建议变量命名别用单字母l、O、I太容易和数字1、0混淆了debug的时候非常痛苦我一个同事就因为局部变量叫l排查了半天才发现把字母l看成了数字1。2. 数据类型8种基本类型和引用类型的选型逻辑2.1 基本类型清单与内存占用Java有四类8种基本数据类型它们是语言内置的、不是对象、直接存值。很多新手背过这张表但背下来和真正理解是两回事。面试官喜欢问byte和short为什么不常用float和double怎么选boolean到底占几个字节这些都是考察记忆之外的理解。类型占用字节取值范围默认值byte1-128 ~ 1270short2-32768 ~ 327670int4-2147483648 ~ 21474836470long8-9223372036854775808 ~ 92233720368547758070Lfloat4约 ±3.4E38精度约6~7位有效数字0.0fdouble8约 ±1.8E308精度约15~16位有效数字0.0dchar20 ~ 65535无符号\u0000boolean取决于JVM实现true / falsefalsebyte、short、char这三个类型在算术运算中会先被提升为int这句话每次写代码都可能遇到。short a 1; short b 2; short c a b;这行代码是编译报错的因为a b的结果是int你拿int直接赋给short等于把一个大面包塞进小口袋编译器不允许必须显式强转short c (short) (a b);。boolean的大小没有严格规定单说在数组里HotSpot JVM里boolean数组一般一个元素占1字节单独的boolean变量在栈上通常占4字节一个寄存器槽位。面试如果问这个回答规范未强制规定由JVM实现决定HotSpot中单独boolean占4字节、boolean数组每个元素占1字节就是加分答案。int和long的选择还有隐含的大坑。比如long l 2200000000;这行代码直接编译报错因为字面量2200000000默认是int已经超过int最大值。必须写成long l 2200000000L;后缀L必须加上。同理float字面量要加f或Ffloat f 3.14; 也会报错因为3.14默认是double。这些就是我说的基础中的基础写错一行编译就教你做人。2.2 包装类、自动装箱和缓存池为什么Java有了int还要有Integer原因很现实泛型不能用基本类型集合只能存对象。所以每个基本类型都有对应的包装类Integer、Long、Byte、Short、Character、Float、Double、Boolean。包装类是引用类型它们之间有自动装箱和自动拆箱机制写起来很爽但坑也不少。先看一个老生常谈的面试题Integer a 127; Integer b 127; System.out.println(a b); // true Integer c 128; Integer d 128; System.out.println(c d); // false第一次写出这段代码的人基本都会懵。原因在Integer的缓存池直接赋值时Java会调用Integer.valueOf(int)来装箱而valueOf对 -128到127之间的整数会从缓存里取同一个对象。所以 a 和 b 指向同一个缓存对象是true128超出了缓存范围每次都new新对象比较的是地址所以是false。// valueOf方法的简化逻辑大致是这样 public static Integer valueOf(int i) { if (i IntegerCache.low i IntegerCache.high) { return IntegerCache.cache[i (-IntegerCache.low)]; } return new Integer(i); }字节码层面更扎心它还会缓存状态限制但住这些够了。这个缓存池机制带来了第二个坑NPE。包装类可以为null基本类型不可以。当你把一个null的Integer赋给int时自动拆箱就会触发空指针异常。我见过线上项目报NPE排查半天发现是一个Integer参数为null往下传的时候被直接拆箱成了int。所以现在的工程实践里有个共识POJO的属性用包装类因为数据库字段可能为null你没法保证每次都有值方法内部的局部变量用基本类型因为局部变量大多数情况下不应该为null用基本类型可以从语法层面避免NPE。这个选型原则在我面人的时候几乎是必考题答得出来说明是真有经验。2.3 强制转换和数据精度自动类型转换的方向是小转大顺着这条线走没问题byte → short → int → long → float → doublechar也能转int。但float和double的方向要格外注意long是8字节float是4字节却可以自动从long转成float因为float表示范围比long大代价是会丢失精度。这又是Java里一个反直觉的细节。强制转换的风险就更多了。(int) 3.99得到3纯粹截断小数位不是四舍五入。把大整数强制转成小整数会直接截断高位可能变成负数int big 300; byte b (byte) big; // 300转byte溢出结果是44为什么是44300对应的二进制是 1 0010 1100只留低8位 0010 1100就是44。这个数值计算过程值得自己推一遍推完你对强转是丢高位保留低位这句话就有肌肉记忆了。还有字符串和数字的转换也是高频使用点。Integer.parseInt(123)返回int如果字符串里有非数字字符就会抛NumberFormatException很多新手没做try-catch程序一崩就慌。Double.parseDouble(3.14)同理。反过来数字转字符串有几种写法String.valueOf(123)、123 、Integer.toString(123)我推荐String.valueOf因为 虽然短但每次拼接都会创建字符串对象在循环里用容易产生垃圾对象。浮点精度问题放到后面表达式部分细说这里先记住一个结论double和float是二进制浮点数无法精确表示所有十进制小数金额计算不要直接拿double做要用BigDecimal。3. 表达式运算符、优先级和求值顺序3.1 运算符全景与优先级速记表达式是由变量、字面量、运算符、方法调用组合而成的一段有值的代码。Java运算符可以分为六大类算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、条件运算符。很多人题目写得少一遇到混合表达式就靠IDE给括括号但面试和排查bug的时候优先级你必须心里有数。常用优先级从高到低可以这么记括号()单目运算符!、、--算术乘除*、/、%算术加减、-移位、关系、、、相等、!逻辑与逻辑异或^逻辑或|短路与短路或||三元? :赋值、、-等注意、^、|比、||优先级高所以true || false false实际计算的是true || (false false)结果是true。很多人会误以为||和优先级一样从左到右那就错了。这种细节不止面试考生产代码里只要出现if (a || b c)这种写法读代码的人就得停下来想优先级所以最好的习惯是加括号别考验队友的脑子。3.2 短路求值与三目运算符的隐藏行为短路的意思是左边一旦是false右边根本不会执行。||短路是左边一旦是true右边也不执行。这个特性看着简单但经常被拿来做防御性编程也经常被写错。if (list ! null list.size() 0) { // 这里可以安全地访问list } // 如果 不短路list为null时list.size()直接NPE // 正因为短路左边false时右边不执行才能安全短路也会带来隐蔽的bug比如在条件里调用带副作用的函数int a 0; if (a ! 0 10 / a 2) { } // a为0时右边不会执行不会抛出ArithmeticException这看起来是好特性但如果你依赖方法的副作用比如if (check() init())这个init()不一定执行代码逻辑就会出问题。三目运算符? :也有个经典坑就是类型不一致时的自动类型转换。看这段Object obj true ? a : 1; System.out.println(obj.getClass()); // class java.lang.Integer按理说一个分支是char一个分支是intchar可以提升为int所以结果是Integer值97。如果你期望得到Character那就大错特错了。再看Object obj2 true ? 1 : 2.0; System.out.println(obj2.getClass()); // class java.lang.Doubleint和double混合整型自动提升为浮点结果是Double 1.0。三目运算符的类型转换规则是两个操作数类型不一致时能自动提升就提升提升不了就做拆箱/装箱规则比最复杂的方法重载还难记。所以写三目表达式时最好让两个分支类型一致否则就显式强转别让编译器替你决定。3.3 类型提升和字符串拼接的规则表达式求值时的类型提升规则算是Java最容易考到又是最容易被忽略的。核心就几句byte、short、char参与算术运算时先提升为int表达式中有一个操作数是long整个结果是long有一个操作数是float结果是float有一个操作数是double结果是double所以short short结果是intint long结果是longlong float结果是floatfloat double结果是double。这个规则的逻辑是把精度小的自动向精度大的靠拢避免丢失精度。字符串拼接是Java里的万能胶水。只要表达式里有一个操作数是String另一个操作数就会被转成字符串拼接起来。于是有了两个经典坑题System.out.println(a 1 2); // a12 System.out.println(1 2 a); // 3a运算符是左结合的从左到右算。第一行先算a 1结果是a1再和2拼接得到a12。第二行先算1 2是整数3再和a拼接得到3a。这个题我在面试里问过很多人看着简单答错率超高。我在实际开发中见过最离谱的坑是有人写日志时把数字拼接顺序搞反明明要的是orderId 1001 结果写成1001 orderId 出来的日志变成1001orderId排查问题的时候对不上数据白忙活半天。所以日志、提示信息里做字符串拼接一定注意拼接顺序。3.4 那些让你怀疑人生的求值细节除了优先级和类型提升表达式求值还涉及先算哪一边的问题。Java保证操作数是从左到右求值的但赋值是右结合也就是先把右边的值算出来再赋给左边。看这个经典int i 0; int result i i; System.out.println(result); // 1分析一下i是好用又难懂的操作符。它在表达式里先返回i的旧值0再把i变成1然后右边那个i取的是当前值1。所以0 1 1。如果把i i换成i i左边先变成1再返回1右边i也是1结果是2。这种前缀和后缀的区别不仅面试爱考自己写循环时如果搞混数组越界或者死循环都能给你整出来。还有几个常见的边界情况容易踩int a 7 / 2; // 结果是3整数除法直接舍去小数不是四舍五入 int b 7 % 2; // 结果是1取余数 double c 7 / 2.0; // 结果是3.5只要有一个浮点数参与就是浮点除法 int d 1 / 0; // 编译不报错运行抛ArithmeticException double e 1.0 / 0; // 结果是Infinity浮点除0不抛异常浮点数比较是另一个重灾区。0.1 0.2 0.3在Java里是false因为0.1和0.2在二进制里都是无限循环小数double只能存放近似值。正确比较两个浮点数要用差值绝对值小于一个阈值double x 0.1 0.2; double y 0.3; System.out.println(Math.abs(x - y) 1e-9); // true如果你在做金额相关的系统直接上BigDecimal构造参数用字符串比如new BigDecimal(0.1)不要用new BigDecimal(0.1)后者拿进去的就已经是double的近似值了。还有编译报错表达式必须包含类类型我印象中这个报错经常出现在试图在静态上下文里使用this或super的场景。比如在main方法里写this.someMethod()Java找不到一个隐式的类类型引用就会报这个错。解决思路也很简单静态方法里没有this你需要先new一个对象再用对象调用实例方法。4. 环境变量配置先把Hello World跑起来4.1 JDK下载与安装细节说完了语法层面的变量、数据类型、表达式很多新手还有一个实际问题卡在那——连环境都没跑起来。Java环境变量配置是搜热词榜常客说明这个问题确实困扰了不少人。第一步是下载JDK。现在主流的发行版有Oracle JDK和OpenJDK从Java 11开始两者功能基本对齐个人学习用OpenJDK完全够。版本选择上学习期间用8也不是不行但我建议直接用17或者更新的LTS版本因为以后工作项目大概率往新版本迁移老八股文里的某些语法细节在新版本虽然兼容但新特性不看也不行。安装JDK时注意目录不要有中文、不要有空格否则后面一些老工具解析路径会出问题。我见过有人装在C:\Program Files\Java\jdk-17这个路径带空格以前某些脚本处理不好就会翻车虽然现在的IDE基本都能处理但能免则免。4.2 环境变量到底配哪些JAVA_HOME、PATH、classpath环境变量配置的核心是三个JAVA_HOME、PATH、classpath。JAVA_HOME 指向JDK安装根目录比如C:\Java\jdk-17。很多框架和工具比如Maven、Tomcat、Gradle启动时需要读JAVA_HOME来决定用哪个Java它相当于给全系统指了个路。PATH 里要加入%JAVA_HOME%\bin这样你在命令行随便哪个目录输入java或javac都能被找到。注意是完整的%JAVA_HOME%\bin不是只配JAVA_HOME就完事了。classpath 这个变量现在的开发基本不用手动配置了。Java 9之后模块化体系引入classpath的作用被大大削弱老教程让你必须配.当前目录的做法在新版本里既不必要还可能引入安全风险。如果你在网上看到老教程让你配置 classpath直接跳过就好。Windows下的配置步骤我写一遍纯小白照着做就行右键此电脑→属性→高级系统设置点环境变量在系统变量区域点新建变量名填JAVA_HOME变量值填JDK安装路径找到Path变量双击编辑点新建填入%JAVA_HOME%\bin一路点确定然后关闭所有命令行窗口重新打开一个macOS或Linux下配置就写在shell配置文件里比如export JAVA_HOME/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home export PATH$JAVA_HOME/bin:$PATH然后把配置写进~/.bashrc或~/.zshrc执行source ~/.bashrc生效。4.3 验证和常见配置问题配置完别急着写代码先在命令行验证java -version javac -version如果能看到版本信息说明环境OK。如果提示java 不是内部或外部命令常见原因按概率从高到低排一下没有重新打开命令行窗口PATH是启动终端时加载的改动环境变量后要新开一个窗口Path里没有%JAVA_HOME%\bin或者JAVA_HOME路径写错比如多写了一层bin安装路径里有非英文字符导致解析失败装了多个JDKPath里有其他版本的java路径抢在前面我教你一个最稳的排查方法在命令行输入where javaWindows或which javaLinux/macOS系统会告诉你它实际找到的java在你的哪个路径下然后你就能判断是不是路径顺序被别的版本占了。这种问题我们开发群里几乎每周都有人问基本都是上面四个原因之一。到了IDE时代真正用命令行编译运行的机会不多了但命令行能力还是要会比如看CI脚本、排查服务器上运行的Java版本都离不开。你可以在系统里多装一个JDK再用JAVA_HOME切换版本这些后面用到再细说。5. 基础知识点检查清单与实战避坑5.1 高频面试/笔试题速览基础不牢地动山摇。整理了几个最常考的点每个都建议手敲一遍、打断点看变量变化比死记答案强得多。String比较String s1 ab; String s2 new String(ab);s1 s2是falses1.equals(s2)是true。原因比较引用地址equals比较内容。Integer缓存前面说过的127和128的经典对比理解valueOf缓存机制就不难。i和i的区别表达式返回值不同变量自增结果相同。三目运算类型提升两个分支类型不一致时结果是提升后的类型。char和int的运算char c a; c 1结果是int吗是的值是98。整数除法5 / 2结果是2不是2.5。短路特性false something()中something()不会执行。这些东西看着零散其实是同一批底层规则的多个面向类型提升、自动装箱缓存、引用比较语义、运算符结合性和优先级。只要把底层规则记住了题目怎么变都拆得开。5.2 常见问题排查表自己在网络和团队里收集的一些问题直接整理成速查表放到收藏夹里问题现象可能原因解决方案IDE能运行命令行提示java 不是内部或外部命令PATH没配好或未重开终端检查JAVA_HOME和PATH重新打开命令行Integer用比较值结果一会儿true一会儿false用了引用比较缓存池外地址不同比较包装类值用equals0.1 0.2 0.3结果是false浮点无法精确表示十进制小数用Math.abs(x - y) 1e-9金额用BigDecimal大long强转int得到负数强制转换截断高位导致溢出强转前先判断范围是否在int范围内JSON序列化后Java Bean字段名变小写或对不上字段不是规范小驼峰Introspector推断异常字段统一小驼峰命名重新生成getter/setterInteger参数为null赋给int抛NPE自动拆箱遇nullPOJO字段用包装类判空后再使用int result 10 / 0;抛ArithmeticException整数除零Java直接判死入参校验分母不为0或改用浮点并接受Infinity5.3 一个综合练习冒泡排序里的变量、类型和表达式最后放一个小练习把今天讲的都串起来。冒泡排序几乎每个人都写过但你要能从变量、数据类型、表达式三个角度去分析它才算真正用上了这篇文章的知识。public static void bubbleSort(int[] arr) { int n arr.length; for (int i 0; i n - 1; i) { 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; } } } }这里面n是局部变量声明时必须初始化arr.length是数组的引用属性i、j、temp都是循环体或方法体内的局部变量循环每次迭代重新分配、结束后失效arr[j] arr[j 1]是关系表达式结果是booleanarr[j 1]是数组索引表达式temp arr[j]是赋值表达式。如果你还能说清楚为什么arr.length在一开始就取出来存到n里而不是每次循环都访问一次说明你已经具备基本的性能意识了。写代码这事最怕眼高手低。变量、数据类型、表达式是Java里的螺丝钉看起来不起眼但整个大厦都是它们撑起来的。你能不能在面试中脱口而出byte short的结果类型能不能一眼看出三目运算的类型提升结果能不能在NPE出现时三秒钟定位到是自动拆箱的问题——这些判断力全靠平时积累。我面试时特别喜欢问Integer缓存和浮点比较不是为了刁难人而是这两个坑是真实生产环境里最常见的事故源头能答上来的人说明写代码时是真的踩过坑或者认真研究过不是只在看视频背八股。最后再给你一个建议今天这篇文章里的每一个示例代码块都自己动手敲一遍开一个调试器在表达式求值的关键行打断点观察变量值的变化。等你看清楚i在那一瞬间到底先返回了什么、Integer缓存对象的地址到底是不是同一个、(short)强转之后那个数是怎么从300变成44的你对Java基础的三块底板就真正焊死了。后面再学集合、泛型、并发、JVM你会发现今天这些小知识点随时都在用而且每次返回来看都还能看出点新东西。