Java里有个概念特别有意思人人都在用、天天都离不开但冷不丁问一句“字面量Literal是什么意思”很多写过一两年代码的人都能当场愣住。我这些年带人和面试问“String s abc;这行代码里哪些部分是字面量”能一次答对的人少得可怜。说白了字面量就是你直接写在源代码里的“原样数据”数字、文字、真假值比如100、3.14、hello、A、true、null这些统统都叫字面量。很多人觉得这有什么好学的不就是几个数值和字符串吗实际上字面量牵着 Java 类型系统、编译机制、字符串常量池、自动装箱这些底层概念是面试八股和日常排错的高频交集。热搜里“字面量什么意思”“java面试题”“字符串比较是否相等”“字符串转数字”常年霸榜恰恰说明新手和求职者普遍在这里栽跟头。这篇文章就把六类字面量一次讲透整数、小数、字符串、字符、布尔、空值顺带把高频考点和实战坑位都整理出来。无论你是刚入门的小白还是准备面试的求职者都能按图索骥。这个内容能解决什么问题一句话让你看到一段 Java 代码时脑子里对它的底层判断是清晰的——这值是什么类型、占多大空间、存在哪里、能不能和另一个值直接比较。很多人学 Spring、背集合源码都挺溜最后反而被最基础的东西绊倒真的不值得。1. 字面量是什么先搞懂它在代码里的身份1.1 从一行最常见的代码说起拿下面这段代码当例子int age 30; String name Alice; boolean isStudent true; char grade A; double pi 3.14159;这里30、Alice、true、A、3.14159全都是字面量。它的定义非常朴素在源代码中直接写出来的、不需要经过任何计算就能确定的值。与之对应的是变量——变量是“容器”字面量是“装进容器的内容本身”。为什么 Java 要单独发明一个术语因为在很多编程语境里我们需要区分“值是什么”和“值怎么写”。比如同一个数字十你可以写十进制10也可以写十六进制0xA还可以写二进制0b1010。编译器看到的字面量形式上不同但最终认定的类型和值是一样的。理解这一点你就理解了字面量的第一层意义它是类型系统在代码里留下的“第一现场”。1.2 编译器先看字面量再看其他东西Java 编译的过程可以粗略理解为词法分析 - 语法分析 - 语义分析 - 生成字节码。词法分析干的事情就是把源代码切分成一个个 token而字面量就是最基础的那类 token。编译器在非常靠前的阶段就要判断这个字面量是整数、小数、还是字符串写错了编译直接报错根本轮不到运行期。我经常跟新人打比方字面量好比你去餐厅点菜写在菜单上的菜名厨师编译器第一眼就要认出这是“宫保鸡丁”而不是“宫保鸡丁??”。如果连菜名都认不出后面炒菜运行当然无从谈起。所以弄清楚每种字面量的“长相”是编译通过的前提这也是为什么面试官特别爱拿“这段代码能不能编译”来考人。1.3 字面量不是变量这个本质区别要分清字面量和变量的核心区别有三点字面量是值本身变量是存储值的内存位置。字面量写在代码里就固定了运行期不会变当然反射改常量那是另一回事。字面量有类型但通常没有“名字”你无法对它寻址。这三个区别看起来简单但实战中到处是它们的影子。比如abc这种字符串字面量你可以在它身上直接调用方法abc.length()因为它本质是一个 String 对象。而true这种布尔字面量就没法调用方法因为 boolean 是基本类型不是对象。如果你不明白“字面量自带类型”这件事看到abc.length()就会觉得莫名其妙。提示字面量是一个“值”的概念它和变量、表达式是并列的。你要判断一个东西是不是字面量就看它是不是直接写在代码里的常量值。2. 整数字面量进制、范围与后缀一个都不能搞错2.1 除了十进制Java 还有三种写法整数字面量默认是十进制但 Java 为了贴近真实场景还支持另外三种进制写法进制前缀示例实际值十进制默认无100100十六进制0x或0X0x64100八进制00144100二进制0b或0B0b1100100100八进制的0前缀是个历史坑。如果你随手写一个012它不是 12而是八进制的 12换算成十进制是 10。这种写法在 Java 7 之后虽然还保留但我在实际开发中极其不建议使用因为它太容易让人误解。十六进制和二进制倒是很有用。比如写颜色值0xFF4081、写权限标志0b111一眼就能看出位与字节的关系。我处理位掩码、协议解析这类代码时几乎全是二进制或十六进制字面量配合注释比任何花哨的算法都直观。2.2 默认类型是 int超范围要加后缀整数字面量在不加后缀的时候默认是int类型。int 的范围是 -2147483648 到 2147483647。如果你直接写long big 2147483648; // 编译报错过大的整数这一行会编译失败因为2147483648超出了 int 能表示的最大值。正确的写法是加后缀L或llong big 2147483648L;这里有个细节值得注意小写l和数字1长得太像我建议一律用大写L。不要觉得这是吹毛求疵团队 Code Review 时因为小写 l 看走眼、把100l看成1001的事情我见过不止一次。反过来还有一种情况字面量明明没超过 byte/short 的范围但直接赋值有时也会报错byte b 127; // 正确 byte b2 128; // 编译报错因为128是 int 字面量默认按 int 处理超出了 byte 范围编译器不允许这种可能丢失精度的隐式转换。Java 有个特例如果整数字面量在目标类型byte、short、char范围内且它是一个编译期常量则可以直接赋值这叫“常量赋值兼容”。所以byte b 127能过byte b2 128不能过。新人经常在这里疑惑“为什么 127 行、128 不行”原因就是这个。2.3 数字下划线从 Java 7 开始的好习惯从 Java 7 起允许在数字字面量里插入下划线提高可读性规则是不能出现在开头、结尾、小数点旁边以及L、F后缀前面int count 1_000_000; long creditCard 5220_1234_5678_9012L; int mask 0b1010_1100;我写金额、ID、长数值时基本都会用下划线分段读起来跟看千分位一样舒服。面试笔试偶尔也会考一条int x 1_000;能不能编译答案是能。但注意int y 1000_;是不能的末尾或连续两个下划线100__0都可以编译允许连续末尾才不行。这类题目考的就是你有没有认真看过 JLSJava 语言规范。2.4 整数范围速查与实操心得类型位数范围byte8-128 到 127short16-32768 到 32767int32-2147483648 到 2147483647long64-9223372036854775808 到 9223372036854775807实操心得上我想分享三条计算时间戳、自增 ID 这类“以后可能变大”的数值一开始就用 long别等溢出了再改全链路改类型的成本高得离谱。不要用整数除法的舍入行为去做业务金额计算整数除法直接丢弃小数部分7 / 2结果是 3 不是 3.5很多新人的报表数据就是这样算错的。常量类里用static final定义命名常量不要把魔法数字magic number散落在代码各处。虽然字面量本身没错但可读性和维护性都是工程问题。3. 小数字面量float 和 double 的精度真相3.1 默认是 double写 float 必须加后缀小数浮点字面量有两种类型float和double。默认情况下带小数点的字面量是 double。如果你想定义一个 float必须加后缀F或fdouble d 3.14; // 正确默认 double float f 3.14; // 编译报错double 不能隐式转 float float f2 3.14F; // 正确为什么float f 3.14;会报错因为3.14是 double 类型double 转 float 可能丢失精度Java 不允许这种自动缩窄转换。这个规则和整数里的byte b2 128报错是同一套逻辑宽转窄必须有显式类型转换除非值是编译期常量且范围正好合适。3.2 科学计数法和三个特殊值浮点字面量还支持科学计数法写法double d1 1.5e10; // 1.5 × 10^10 double d2 5E-3; // 0.005 double d3 1e6; // 1000000.0e或E代表指数前面必须要有数字后面跟指数部分。除了普通数值浮点体系里还有三个“特殊字面量”它们也是 float/double 的合法值NaNNot a Number比如0.0 / 0.0的结果POSITIVE_INFINITY正无穷比如1.0 / 0.0NEGATIVE_INFINITY负无穷关于 NaN 有一个经典的坑NaN ! NaN是成立的。你不能用判断一个值是不是 NaN得用Float.isNaN()或者Double.isNaN()。这个点我在面试里问了五次能答对的大概只有一半。3.3 为什么 0.1 0.2 不等于 0.3这是浮点字面量最著名的“翻车现场”System.out.println(0.1 0.2); // 输出0.30000000000000004原因在于 IEEE 754 标准里浮点数用二进制存储而十进制小数0.1转成二进制是一个无限循环小数存储时就发生舍入误差。说句大实话这不是 Java 的 bugC、Python、JavaScript 全都会这样。我处理浮点数的原则展示、比较、循环步进这类场景优先用整数或 BigDecimal。如果必须用 double比较时设置误差范围Math.abs(a - b) 0.0001。涉及金额、利率、费率一律上BigDecimal用字符串构造不要用new BigDecimal(0.1)。还记得热搜里“mysql将字符串转为日期”“db2 sql判断数字字符串函数”这类问题吗很多数据分析场景最后对不上账根源往往不是 SQL 写错而是数据从 Java 到数据库的途中浮点数精度已经被污染了。所以从写字面量这一刻就要想清楚精度管理。3.4 关于 BigDecimal 的正确姿势我见过很多人只用 BigDecimal但用错了构造方式BigDecimal b1 new BigDecimal(0.1); // 不推荐结果是 0.1000000000000000055511151231257827... BigDecimal b2 new BigDecimal(0.1); // 推荐精确表示 BigDecimal b3 BigDecimal.valueOf(0.1); // 也推荐内部会转字符串0.1这个 double 字面量本身就不精确把它丢进 BigDecimal 只会把误差“固封”下来。正确姿势是用字符串或valueOf。这是我从财务系统项目里实打实踩出来的经验当时一个订单金额反复对不上最后发现就是构造方式错了。4. 字符字面量单引号里的“一个符号”4.1 char 的本质是 16 位无符号整数字符字面量用单引号包裹里面只能有一个字符char c1 A; char c2 中; char c3 \u4e2d; // 中 的 Unicode 转义注意Java 的char本质是一个 16 位无符号整数范围 0 到 65535存储的是 UTF-16 编码下的一个码元code unit。这就是为什么char c 97;能编译最终c的值是字符a。很多人混淆“字符”和“字节”以为一个 char 就是一个字节这是错的。char 是 2 个字节的基础存储单位中文一个汉字在 UTF-16 里正好是一个 char所以热搜里“汉字算一个字符”在 Java 的 char 层面是成立的。但像 emoji 这种补充平面字符UTF-16 需要用两个 char 来存也就是一对代理项surrogate pair处理时得格外小心用String.codePointAt而不是简单的charAt。4.2 转义序列看不见的特殊字符怎么写有些字符没法直接在代码里“打出来”比如换行、制表符、单引号。Java 用反斜杠开头的转义序列表示转义序列含义\n换行\t制表符\r回车\b退格\f换页\单引号\双引号\\反斜杠\uXXXXUnicode 字符比如想表示单引号字符本身必须写\直接写是会编译报错的。这个“转义就是给特殊字符穿了个马甲”的理解帮助我后来在读 JSON 字符串、处理日志分隔符时少踩很多坑。关于\uXXXX有个极少人知道的冷知识Java 编译器在词法分析之前就会处理 Unicode 转义。也就是说\u000d回车符如果出现在字符串字面量里编译器会先把它当成真正的换行符导致字符串被“截断”编译报错。很多讲 Java 的课都不提这个但在分析某些混淆代码时特别有用。如果你看到一段诡异代码String s \u000d;报错原因就在这。4.3 char 可以做算术但别滥用char 参与算术时会自动提升为 intchar ch A; System.out.println(ch 1); // 输出 66 System.out.println((char)(ch 1)); // 输出 BA 1先转成 int 计算得到 66再强转回 char 才是B。如果不强转直接输出就是数字。这个逻辑在写字母加密、字符偏移算法时经常用到但我建议只在明确知道编码范围时使用。比如处理Z之后就不能简单加 1 得到a中间还隔着几个符号。提示单引号包的是 char双引号包的是 String。A和A是完全不同的类型前者是基本类型 char后者是 String 对象。这个区分几乎是每个 Java 面试的送分题可每年还是有人栽。5. 字符串字面量最常用也最容易被问倒5.1 String 不是基本类型但可以有字面量String 是引用类型但它享受“特殊照顾”可以直接用双引号写出字面量形式。这是 Java 语法层面的特权其他类没有。字符串字面量包着什么都可以String s1 hello; String s2 ; String s3 123; String s4 \quoted\;注意字符串里可以包含转义字符。比如路径C:\new\test如果直接写\n会被解释成换行所以必须写成C:\\new\\test。我见过好多次 Windows 路径在 Java 里疯了基本都是这个原因反斜杠没转义。5.2 字符串不可变与常量池字符串对象不可变是 Java 里的基石设计。每次对 String 做、substring、replace等操作都会产生新对象而不是在原对象上修改。这个设计带来了线程安全和缓存便利但也带来一个性能隐患频繁拼接字符串会创建大量垃圾对象。String result ; for (int i 0; i 10000; i) { result result i; // 循环里每次都会 new 对象性能很差 }正确做法是用StringBuilder。这不是“优化洁癖”在高频循环里性能差距能达到几十倍。关于字符串字面量最关键的是常量池机制。代码里所有字符串字面量编译后都会进入 class 文件的常量池运行时会放到 JVM 的字符串常量池里。同一个字符串字面量在程序里出现多次指向的往往是同一个对象。这正是面试经典题的基础String a abc; String b abc; System.out.println(a b); // true都是常量池里的同一个对象而new String(abc)会创建一个新的堆对象String c new String(abc); System.out.println(a c); // falsec 是新建对象 System.out.println(a.equals(c)); // true内容相同5.3 比较字符串永远别用 除非你要比对象引用这大概是中文互联网上被问得最多的 Java 题之一热搜里“字符串比较是否相等”常年都有。答案就是比较内容用equals比较对象引用才用。我还想补充一个进阶点intern()方法。调用c.intern()会去常量池里找内容相同的字符串找到了返回池中的引用找不到就把当前字符串放入池中。所以String d c.intern(); System.out.println(a d); // trueintern 后拿到了池中引用在大量重复字符串比较、去重场景里利用intern和常量池可以省内存。但要小心池本身也有内存压力别盲目 intern 所有字符串。5.4 字符串字面量的编译期拼接这里有一个经常被忽略的规则如果多个字符串字面量用连接编译器会在编译期就完成拼接结果还是一个常量。String s a b c; // 编译期就变成 abc不会运行时拼接所以a b ab的结果是 true因为编译期它俩就是同一个常量。但如果是变量拼接String a a; String b a b; // 运行时拼接新对象ab b就是 false。这个区别用一张表记最清楚代码结果原因ab abtrue常量池相同对象a b abtrue编译期常量折叠new String(ab) abfalse新建对象ab.equals(new String(ab))true内容相等5.5 文本块Java 15 之后的长字符串写法Java 15 正式引入了文本块Text Block用三个双引号包裹可以原样保留换行和缩进String html html bodyHello/body /html ;写 SQL、JSON、HTML 模板时这个特性救了我的命。以前要在 Java 里写一段多行 SQL得拼一堆\n和可读性极差现在直接复制粘贴即可。要注意的是文本块里如果内容包含需要用\转义而且行尾的\可以防止换行这些语法细节用一次就记住了。值得一提的是文本块本身也是字符串字面量一样遵守常量池和不可变规则。6. 布尔与空值字面量两个值和一个“没有”6.1 true 和 falseJava 没有 0 和 1 的约定布尔字面量只有两个true和false。很多从 C/C 转过来的同学会下意识问能不能用1和0代替“if (1)”能编译吗答案是不能。Java 对类型极其严格布尔条件只接受 boolean 类型int 和 boolean 之间没有任何隐式转换。这是刻在语言基因里的设计决策宁可代码啰嗦一点也要避免 C 里“1 是真、0 是假”带来的语义混乱。boolean flag true; if (flag) { ... } // if (1) { ... } 编译报错我这里还推荐一个习惯布尔变量命名用isXXX、hasXXX这类形式比如isSuccess、hasConfig。字面量true/false本身没有歧义但变量名起得不好代码里就会出现if (!notDisabled)这种反人类表达。6.2 布尔运算与短路逻辑布尔字面量经常配合逻辑运算符使用这里最值得记住的是短路运算if (str ! null str.length() 0) { // 只有 str 不为 null 时才会执行 str.length() }左边为 false 时右边根本不会执行。同样||左边为 true 时右边也不会执行。这个机制能保护我们避免空指针异常也常用于判空后执行初始化。我写的每一行判空代码都默认利用短路逻辑。6.3 null引用类型特有的“空值字面量”null 是唯一一个“值类型不确定”的字面量。它可以赋给任何引用类型变量表示“这个引用不指向任何对象”String s null; Object obj null; int[] arr null;但 null 不能赋给基本类型int a null; // 编译报错null 字面量本身没有类型所以你可以把它传给任何引用类型的参数。这也带来一个问题null 到底能不能调用方法显然不能任何对 null 的方法调用都会抛NullPointerExceptionNPE因为压根没有对象。String s null; System.out.println(s.length()); // 运行期抛空指针异常这个“运行期才炸”的特性非常烦人因为你编译期看不出来。Java 8 加了个预防手段Optional但也只是把显式空指针换成了“可能为空”的类型提示底层该判空还得判空。我判断代码安全性有一个简单粗暴的标准凡是外部传入的参数先做 null 检查再做业务逻辑。6.4 避免空指针的实操清单字符串只有 null 和空串两种状态判断时先写str null || str.isEmpty()。从 Map 取值时默认没有 key 就返回 null别假设一定有值。方法返回集合时优先返回空集合不要返回 null调用方省去一层判断。Java 17 的 switch 支持了case null可以在表达式里显式处理 null老版本就别想了。我自己的编码习惯是能用Objects.requireNonNull做快速失败的地方就用它让问题在入口处暴露而不是在十层调用之后才冒出来。7. 面试高频字面量背后的八股考点7.1 包装类缓存与自动装箱基本类型有对应的包装类Integer、Long、Boolean、Character等。字面量在赋值给包装类变量时会自动装箱Integer i 100; // 相当于 Integer.valueOf(100)这里有个著名考点Integer缓存范围是 -128 到 127在这个范围内valueOf返回的是缓存对象所以结果比较会出乎意料Integer a 100; Integer b 100; System.out.println(a b); // true Integer c 128; Integer d 128; System.out.println(c d); // false原因就是 128 不在缓存范围两个堆对象比较对象引用自然不相等。这个知识点直接决定了你写业务代码时会不会莫名其妙踩坑。我用Integer做频繁比较时已经养成了用equals或者intValue()的习惯。7.2 包装类与基本类型混合比较基本类型和包装类在一起比较时会发生自动拆箱Integer i 128; int j 128; System.out.println(i j); // truei 自动拆箱成 int 再比倒是不会触发缓存问题因为拆箱后比的是数值。真正隐蔽的坑在下面这个Integer a null; int b a; // 运行期 NPEa 自动拆箱null 成不了 int所以从数据库、JSON 等外部来源拿到的 Integer做算术之前一定要判空。7.3 字符串与 char 的字面量判断面试里还经常出现一些基础判断题我整理成速查表表达式结果说明a 97truechar 自动提升为 inta a编译错误String 与 char 不可比true 1编译错误boolean 不可与 int 比较long x 10;true 正确int 字面量可宽转为 longfloat f 1.5f;正确加 f 后缀这些题一点都不难但考的就是你有没有把字面量的类型规则记死。别背理解为什么Java 的类型转换只有两种情况显式强转和“不丢精度的自动宽转”其余一律报错。7.4 编译期常量与运行期表达式的区别final int x 10;这种编译期常量用在switch的 case 里没问题但如果int y readFromConfig();这种运行期值即使它最终是 10也不能直接当编译期常量用。很多变更只改常量的场景如果你把常量定义在别的类记得所有引用它的类都会在编译期把值“内联”进去。这就是为什么修改常量后会出现“我改了不生效”的灵异事件——你需要重新编译依赖方。这个细节对做中间件、SDK 的人特别重要。8. 实操复盘我踩过的字面量坑和排查套路8.1 编译期报错对照表写代码时最常见的编译错误基本都是字面量写法不规范造成的报错信息常见原因解决办法Unclosed string literal字符串里使用未转义的换行或引号检查\n、\、\\Unclosed character literal单引号里放了多个字符或没写右引号确认是 char 用的单引号Integer number too large整数超出 int 范围没加 L数字末尾加大写 LPossible lossy conversion from double to float小数默认 double直接赋给 float加 F 后缀或强转incompatible types字面量类型和变量类型不匹配核对类型必要时加后缀排查这类问题我通常先看 IDE 的红线提示然后把目光聚焦在“字面量周围”大多数问题一眼就能找到。8.2 运行期问题的排查思路编译过了不代表完了运行期才是重灾区。我总结了三类高频运行期问题第一类是精度问题。症状是金额算下来差几分、小数累加结果不对。排查时先看参与运算的所有数值来源凡是经过 double 处理过的都考虑改用 BigDecimal。第二类是空指针问题。日志里一行NullPointerException先别急着看后面的业务代码先找最接近 null 源头的那一行。用 IDEA 断点或者日志打印逐层回溯“谁把 null 传进来的”。这些年我养成的习惯是对第三方接口返回值、Map 取值结果、JSON 反序列化对象一律先判空。第三类是字符串比较问题。明明值一样却是 false这就是典型的对象引用比较。排查时全局搜一下代码里有没有对 String 用有就改成equals。顺便说一句JSON 反序列化出来的字符串天然是堆里的新对象用比必挂热搜里“json转字符串”“fastjson序列化”相关的坑很多都是在这里踩的。8.3 给新手的最后几条建议别怕字面量太“基础”所有高级框架最终都要落到最基本的语法上。一句话地基不牢上层白搭。把 Java 语言规范里关于字面量的小节通读一遍花不了多少时间但能帮你建立完整的知识坐标。写代码时多问自己三个问题这个值的类型是什么它能不能直接和另一个值比较放在内存里会不会有精度或性能问题遇到编译报错第一反应不是“我代码没问题”而是“我字面量哪里写错了”。很多时候答案就在眼前只是你没有回头看。说实话字面量这个东西天天都在写但真能把它讲明白的人不多。我见过太多开发者在“字符串比较”“浮点精度”“缓存范围”这些问题上反复踩坑根源就是对最基础的字面量缺少系统理解。如果你能把这篇里的每张表、每个代码示例都亲手敲一遍把背后的为什么想清楚以后再遇到类似问题心里基本就有底了。这年头技术更新快可 Java 底层的这些规矩十年二十年后还是这套早点吃透什么时候都不亏。