# 变量与四种基本类型：Java 的 "盒子" 与类型契约

变量是什么、为什么声明时必须写明类型，int/double/boolean/char 各适合装什么，以及强类型意味着什么

> 变量与类型 · 基本数据类型 · 强类型语言 · 约 7 分钟 · 09 月 07 日

## 本篇要点

1. 变量是内存里一块带名字的空间：声明就是向编译器申请一个位置并贴上标签，类型决定这块空间能装什么值、能做哪些运算；编译成字节码后，变量名消失，指令只认位置。
2. 声明句式固定为“类型 名字 = 初始值;”，读作“声明一个 int 类型的变量 age，初始装入 18”；类型必须写在最前面，因为它是编译器检查后面每一行代码的依据。
3. int 装整数（32 位，约 -21 亿到 21 亿），double 装小数（64 位，约 15 位有效数字），boolean 只装 true/false，char 用单引号装单个字符、可存汉字；双引号的“李”是 String，属引用类型，跟 char 是两回事。
4. “=”是“装入”不是“等于”：先算右边、再把结果写进左边变量，所以 age = age + 1 合法且常写；左边必须是变量。
5. 强类型语言的意义在于编译期把关：把字符串塞给 int、把 3.5 塞给 int、读取未赋值的局部变量，都会在 javac 阶段被拦下，程序根本不会运行——IDE 里的红波浪线来自同一套规则。

---

上一篇我们走完了“代码怎么跑起来”的全程，并且知道了一个关键事实：`javac` 会在程序运行之前，把源码从头到尾检查一遍，检查合格才产出 `.class` 文件。它检查的远不只是拼写。从这一篇开始，我们要逐步认识它重点把关的第一大类东西——**数据**。

真实程序很少像 HelloWorld 那样只打印一句固定的话。你的年龄、这门课的得分、账户余额，都是会变的数据。程序要处理它们，第一步是把它们存进内存，随取随改。这个存放处叫**变量**（variable）。先看一段完整的小程序：

```java
public class MyInfo {
    public static void main(String[] args) {
        int age = 18;              // 整数：年龄
        double height = 1.75;      // 小数：身高（米）
        boolean isStudent = true;  // 是或否：是不是学生
        char initial = '李';        // 单个字符：姓氏的第一个字

        System.out.println(age);
        System.out.println(height);
        System.out.println(isStudent);
        System.out.println(initial);
    }
}
```

每一行输出一个变量里装的值。下面把这几行拆开看：变量到底是什么，Java 又为什么要求每声明一个变量，都得先报出它的类型。

## 变量：内存里一只带标签的盒子

程序运行时的所有数据都存在内存（RAM）里。内存可以想象成一长排带门牌号的小房间，每个房间能装一定量的数据。问题是门牌号是一串给机器看的数字，又长又难记，于是 Java 允许你给某个房间贴一张自己认识的标签——变量名。以后你说“把 19 放进 age”，编译器就知道该写哪间房。

这只“盒子”有两个特点。第一，**标签只在源码世界里存在**：`javac` 编译成字节码后，指令里不再出现 age、height 这些名字，而是直接按房间编号读写。这正是上一篇说的“翻译”的一部分——编译器把你的命名翻译成了位置。第二，**盒子能换内容，不能换规格**：age 一旦声明成 int，就永远是 int，能变的只是里面装的那个数。

声明一个变量，本质上就是告诉编译器：请为我在内存安排一个位置，并允许我用这个名字称呼它。

## 为什么声明时必须先写出类型

任何变量的声明都遵循同一个句式：先写类型，再写名字，可以顺手给初始值，最后以分号结束：

```java
int age = 18;   // 声明 int 变量 age，初始装入 18
int score;      // 只声明，先不装值
```

读法是固定的：“声明一个 int 类型的变量，名字叫 age，初始值 18。”注意顺序：类型在最前面。为什么 Java 不允许更省事的写法 `age = 18;`，靠猜也知道它是整数？因为编译器需要一张“规格单”。Java 官方教程里有一句很本质的话：变量的数据类型，决定了它能容纳哪些值，以及能对它执行哪些运算 [1]。类型就是那张规格单——知道 age 只能装整数，编译器才能判断后面每一行对 age 的操作是否合法。声明 `int score;` 之后写 `score = 90;` 合法，写 `score = "优秀";` 就会被拦下（原因见最后一节）。规格单先立好，检查才有依据。

## 四种最常用的基本类型

Java 里的类型分两大类：**基本类型**（primitive type，语言内置、变量直接装值）和**引用类型**（reference type，变量装的是指向某个对象的“门牌号”）。String 就属于引用类型，今天先不碰它，只认识基本类型中最常用的四个 [2]：

| 类型 | 装什么 | 例子 | 内存占用 |
|---|---|---|---|
| int | 整数：年龄、数量、序号 | `int age = 18;` | 4 字节（32 位） |
| double | 带小数的数：身高、成绩 | `double height = 1.75;` | 8 字节（64 位） |
| boolean | 只有 true（真）或 false（假） | `boolean isStudent = true;` | 规范未精确定义 |
| char | 单个字符 | `char initial = '李';` | 2 字节（16 位） |

几处细节值得记牢。`int` 只有 32 位，能装的整数范围是 -2147483648 到 2147483647，约负 21 亿到正 21 亿，装不下就得换 64 位的 `long`（以后再讲）。`double` 是双精度浮点数，有大约 15～16 位有效数字；官方教程特别提醒，金额这类需要精确计算的场合不要用 double [2]，因为浮点数在二进制里常常只是近似值。`boolean` 只接受两个词：`true` 和 `false`，用来记录“是/否、开/关”这类开关状态。`char` 用**单引号**且只能装一个字符，因为它是 16 位 Unicode 编码，所以汉字也装得下；双引号写出的 `"李"` 是字符串 String，能装一串字符，跟 `char` 完全是两回事——单引号一个，双引号一串。

顺带认识一个术语：`18`、`1.75`、`true`、`'李'` 这些直接写进源码的裸值叫**字面量**（literal）。像 `1.75` 这样的小数字面量，Java 默认按 double 处理——这一点下一节马上要用到。

## “=”是装进去，不是“等于”

初学者最容易在 `=` 上栽跟头。看这行代码：

```java
age = age + 1;
```

按数学的读法，这是句矛盾话：age 怎么可能等于 age 加一？但在程序里它完全合法，正确的读法是：“把 age 盒子里的当前值取出来，加上 1，再把结果装回 age 这个盒子。”执行顺序是**先算右边，再写左边**：如果 age 里现在是 18，那么右边先算出 19，然后 19 被装进盒子，age 变成 19。

所以 `=` 在 Java 里是“赋值”，是“装入”，不是“相等”。它要求左边必须是一个变量（得有个盒子让你放东西），右边可以是任何能算出值的表达式。这也解释了上一节说的“盒子能换内容”：`age = age + 1` 干的正是换内容这件事。

## 类型写错，javac 当场拦住你

现在可以回答本篇最重要的问题了：强类型语言到底意味着什么？看三行都过不了编译的代码：

```java
int age = "十八";              // 编译错误：String 不能转成 int
int score = 3.5;               // 编译错误：double 转 int 可能丢失精度
int x;
System.out.println(x);         // 编译错误：x 可能尚未初始化
```

第一行：`"十八"` 是 String（引用类型），往只装整数的 int 盒子里塞字符串，编译器报“类型不兼容”，直接拒绝生成 `.class`。第二行：`3.5` 是 double 字面量，硬塞进 int 会丢掉小数点后的 0.5，Java 不允许这种“有损”的转换悄悄发生。第三行最有教育意义：方法里声明的局部变量**不赋值就不许读**——Java 不给局部变量默认值 [1]，防止你读出一个来历不明的数。

注意这三处错误发生在哪一步：**javac 阶段，程序根本没运行**。这是强类型语言给初学者的第一重礼物——错误在“写的时候”就被抓住，而不是“跑起来之后”才爆出来。你在 IDEA、VS Code 里写代码时看到红波浪线，也是同一套类型规则在背后工作：编辑器一边打字一边用编译器的规则检查，类型对不上立刻标红。如果类型检查被推迟到运行时，程序得先跑起来、一路执行到出错那一行才会报错，而在此之前可能已经打印了一半结果、甚至拿着坏数据继续算，让错误变得更难追查。

把这几节串起来看：声明变量时写类型，不是形式主义——那是你与编译器签订的一份契约。你承诺“这个盒子只装 int”，编译器回报以全程的编译期把关。代价是多打几个字，换来的是一大批错误永远没有机会进入运行阶段。

认识完盒子和四种基本类型，下一步自然是让这些数字参与运算和比较——那是运算符的地盘，留到下一篇。

## 来源

1. [Oracle Java 官方教程"变量"页：数据类型决定变量能容纳的值与可执行的运算，局部变量不自动获得默认值](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html)
2. [Oracle Java 官方教程"基本数据类型"页：八种基本类型总览，int/double/boolean/char 各自的位数与范围，以及浮点数不适用于精确金额的提醒](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)

---

原文：https://pangzhengboyin.com/articles/java-variables-primitive-types-f403909a

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具。说出想学什么，它会先了解你的基础，再把主题写成一篇篇 5–10 分钟能读完的文章；边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
