Java 变量与类型

要点

基本类型保存数值等基础值,包装类型是对象并可为 null。var 是局部类型推断,不是动态类型。

旧回顾:类型声明的变化

本页短代码块是方法内或 JShell 中的局部语法演示,省略的 map、path 等需由使用场景提供;标为错误的语句用于解释失败条件。后面的两个完整实验包含入口和 import,可按给定命令单独执行。

旧写法:一切类型显式声明,泛型信息重复冗长。

ArrayList<String> list = new ArrayList<String>();
Map<String, List<Integer>> index = new HashMap<String, List<Integer>>();

现代写法(Java 10+):

var list = new ArrayList<String>();
var index = new HashMap<String, List<Integer>>();

Java 仍是静态类型语言。var 根据初始化表达式推断一个编译期类型;它与显式写出该推断类型的含义相同,但不保证与任意原声明等价。例如 List<String> x = new ArrayList<>(); 改成 var x = new ArrayList<String>(); 后,变量类型从接口变为 ArrayList<String>,会影响后续赋值与重载选择。

8 种基本类型速览

类型字节范围默认值典型用途
byte1-128 ~ 1270二进制流处理
short2±3.2万0少见,历史遗留
int4±21亿0默认整型
long8±9.2×10¹⁸0L时间戳、大计数(字面量加 L)
float4IEEE 7540.0f少用,字面量加 f
double8IEEE 7540.0d默认浮点
char20 ~ 65535'u0000'一个 UTF-16 代码单元,不一定是完整字符
boolean–true/falsefalse逻辑判断

表中的默认值适用于字段和数组元素,局部变量必须先明确赋值才能读取。char 的默认值是零代码单元,不是空格;许多 emoji 需要两个代码单元表示。整数字面量可用下划线增强可读性,如 1_000_000。金额可以用约定最小单位的 long,或带精度与舍入规则的 BigDecimal,避免用二进制浮点表达必须精确的十进制账务。

var:局部变量类型推断(Java 10+)

定义: 编译器根据右侧初始化表达式推断类型,仅作用于局部变量。

能用与不能用

var list = new ArrayList<String>();        // ✅ 推断为 ArrayList<String>
var stream = list.stream();                // ✅
for (var entry : map.entrySet()) {}        // ✅ 增强 for
try (var reader = Files.newBufferedReader(path)) {} // ✅ try-with-resources

// ❌ 以下全部编译错误
// var x;                      // 缺少初始化器,无法推断
// var y = null;               // null 无类型信息
// public var name = "tom";    // 不能用于字段
// var f = (String s) -> s.length(); // 无目标类型的 lambda

规则清单: 1. 仅限局部变量(含 for 循环变量、try-with-resources) 2. 普通局部变量声明须有初始化器;后续赋值须兼容推断类型(允许适用的转换),类型不会随赋值改变 3. 字段、方法参数、返回类型仍需显式声明 4. 可在 lambda 参数上标注 var(Java 11+),用于加注解等场景

什么时候用 var

  • ✅ 右侧类型一眼可见:var map = new HashMap<String, List<Integer>>();
  • ✅ 中间变量:var nameUpper = name.strip().toUpperCase();
  • ❌ 返回类型不直观的链式调用:var result = service.process(input);——读代码的人猜不出类型,可读性反而下降

包装类型与三大陷阱

每种基本类型都有对应包装类:Integer、Long、Double、Boolean、Character……编译器通过 valueOf(装箱)与 intValue 等(拆箱)在两者间自动转换。

陷阱一:== 比较的是引用

Integer a = 127, b = 127;
System.out.println(a == b);      // true  —— 缓存池 [-128, 127] 内是同一对象

Integer c = 128, d = 128;
System.out.println(c == d);      // 不作保证:实现可以缓存更大范围
System.out.println(c.equals(d)); // true;c 非 null 时用 equals 比较值

Integer.valueOf 保证缓存 -128..127,允许缓存更多值;不能依靠 128 推断必然不同。两侧都是引用时 == 比较对象身份;一侧是基本类型时可能先拆箱。变量可能为 null 时可用 Objects.equals(a, b)。参见 Integer.valueOf 的契约。

陷阱二:拆箱 NPE

Map<String, Integer> counter = new HashMap<>();
int total = counter.get("missing");          // get 返回 null,拆箱 → NullPointerException

int safe = counter.getOrDefault("missing", 0); // ✅ 现代写法

陷阱三:三元表达式隐式拆箱

Integer nullable = null;
boolean useDefault = false;
Integer result = useDefault ? 1 : nullable; // 选择 nullable 分支时拆箱为 int,抛 NPE
// useDefault 为 true 时只计算字面量分支,不会因未选中的 null 抛异常
Integer safe = useDefault ? Integer.valueOf(1) : nullable; // 两分支同为 Integer,保留 null

💡 相关错误与排查详见常见错误排查。

文本块(Java 15+)

// 旧写法:拼接 + 转义
String jsonOld = "{n" +
        "  "name": "Java",n" +
        "  "version": 25n" +
        "}";

// 现代写法:文本块
String json = """
        {
          "name": "Java",
          "version": 25
        }
        """;

文本块会规整换行、去掉公共附带缩进,再处理转义。公共缩进取决于非空内容行以及结尾定界符所在空行等因素,不能只看定界符一处。上面的 json 最后含换行,jsonOld 没有,因此二者并非逐字相同。文本块也不会自动进行 JSON 或 SQL 转义;实际 SQL 参数应使用预处理绑定。String 的相关方法见标准库核心。

最佳实践

数值类型由范围和精度需求决定:计数可能超出 int,金额通常需要约定十进制精度与舍入。var 保留静态类型推断,右侧无法一眼看出含义时显式类型可能更易读。

包装类型可能为 null,自动拆箱会因此失败;Objects.equals 等空值安全比较与对象自身 equals 的前提不同。Map.getOrDefault 也不把所有显式 null 值变成默认值。用缺键、存在且为 null、正常值三个输入检查边界。

完整实验:拆箱发生在什么时候

以下完整程序使用 Java 21,保存为 Main.java,执行 javac --release 21 Main.java,再执行 java Main。预期输出 boxing boundaries: ok。程序主动捕获需要观察的 NPE,其余错误会令进程失败;运行记录见 基础类型验证记录。

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class Main {
    private char initial;
    static void check(boolean value) {
        if (!value) throw new AssertionError("检查失败");
    }
    static void expectNpe(Runnable action) {
        try { action.run(); } catch (NullPointerException expected) { return; }
        throw new AssertionError("应发生 NPE");
    }
    public static void main(String[] args) {
        check(new Main().initial == 'u0000');
        check("😀".length() == 2);
        Integer a = 127, b = 127;
        check(a == b); // 保证范围内。
        Integer c = 128, d = 128;
        check(Objects.equals(c, d)); // 不对范围外的引用身份作假设。
        Map<String, Integer> counts = new HashMap<>();
        check(counts.getOrDefault("missing", 0) == 0);
        counts.put("present", null);
        expectNpe(() -> { int value = counts.getOrDefault("present", 0); });
        Integer nullable = null;
        boolean useDefault = false;
        expectNpe(() -> { Integer value = useDefault ? 1 : nullable; });
        Integer safe = useDefault ? Integer.valueOf(1) : nullable;
        check(safe == null);
        Integer selected = !useDefault ? 1 : nullable;
        check(selected == 1); // 未选中 nullable 分支,不会拆箱它。
        var number = 1L;
        number = 2; // int 可以拓宽为已推断的 long。
        check(number == 2L);
        System.out.println("boxing boundaries: ok");
    }
}

完整实验:文本块的空白就是数据

此程序另存成 Main.java,用同样命令执行,预期输出 text block: ok。不要与上面的同名类拼到一个文件。

public class Main {
    public static void main(String[] args) {
        String block = """
                alpha
                  beta
                """;
        if (!block.equals("alphan  betan")) {
            throw new AssertionError("公共缩进或结尾换行不符预期");
        }
        String noFinalNewline = """
                alpha""";
        if (!noFinalNewline.equals("alpha")) throw new AssertionError();
        System.out.println("text block: ok");
    }
}

练习时先在纸上标出每行的空格和换行,再调整结尾定界符位置。规则来源:JLS 字面量与文本块、装箱转换 与 条件表达式。

练习与实践

练习一:验证缓存边界

  1. 比较 127、128、1000 的装箱身份和值;记录运行环境,但不要把观察到的缓存上限当作语言承诺。用 Objects.equals 重写业务比较。

练习二:var 重构

  1. 找一段旧代码,为合适的局部变量加上 var,为不合适的位置写下理由

练习三:文本块

  1. 用文本块输出一段 SQL 与一段 JSON,并用 .formatted() 填充变量

相关文档

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇