第2章:变量与类型
本章只讲 Java。Python 和 C++ 的变量会在第5章末尾做统一对比。
一个故事开头
假设你是奶茶店老板,要记每天的销售情况。
"今天小明买了一杯波波奶茶,小红买了一杯杨枝甘露,小张买了两杯..."
第三天你就记不住了。你拿出一个本子,在本子上写下:
波波奶茶库存: 50杯
杨枝甘露库存: 30杯
今日销售额: 0元然后每卖出一杯,你就划掉旧数字,写上新的。
变量就是程序里的"本子"——用来记数据的东西。不同的是,电子本子比纸质本子聪明得多:它知道自己只能记什么类型的数据,而且你报错的时候它会直接告诉你。
1. 从一道麻烦题开始
第1章我们写的程序是死的——输出什么内容,代码里就写了什么内容。
System.out.println("你好, 小明!");
System.out.println("小明, 你的分数是 95");现在我想改成"小红"怎么办?把 "小明" 一个个找出来替换成 "小红"。
烦不烦?如果有 100 处地方要改呢?
变量解决的就是这个问题——别把数据写死,把它存在一个有名字的"盒子"里,用到的地方都引用这个盒子的名字。数据变,只改一处。
String name = "小明";
System.out.println("你好, " + name + "!");
System.out.println(name + ", 你的分数是 95");换人?只改第一行 name = "小红",全自动更新。
2. 变量是什么——一个带标签的盒子
把变量想成一个纸箱:
┌─────────────────────────┐
│ 标签: name │ ← 变量的名字
│ 类别说明: 只能装文字 │ ← 变量的类型
│ ┌───────────────────┐ │
│ │ "小明" │ │ ← 里面装的值
│ └───────────────────┘ │
└─────────────────────────┘每个变量都有三个要素:名字、类型、值。一个不能少。
在 Java 里,声明一个变量就是做这样一个盒子:
// 声明 + 初始化一步到位
int age = 25; // 一个叫 age 的盒子,只能装整数,里面装了 25
double price = 19.99; // 一个叫 price 的盒子,只能装小数
boolean ready = true; // 一个叫 ready 的盒子,只能装 true/false💥 拆了它:试试往错误类型的盒子里塞东西
int age = 25;
age = "二十五"; // ← 试试这个编译结果:incompatible types: String cannot be converted to int
Java 说:"你这个盒子标明了只能放整数,你非要塞文字进去,我搞不了。"
这就是静态类型语言的意思——在程序跑起来之前,编译器就帮你检查了类型是否匹配。看着烦人,但省下的调试时间远远更多。
3. 声明、初始化、赋值——三件事
很多人搞混这三个,其实区别特别简单:
public class BoxDemo {
public static void main(String[] args) {
// 声明:做一个空盒子,贴上标签
int age;
// 此时盒子里啥也没有——实际上 Java 不让你拿空盒子的东西
// System.out.println(age); // ❌ 编译错误:没初始化
// 赋值(第一次 = 初始化):往盒子里放东西
age = 25;
// 可以用它了
System.out.println(age); // 25
// 重新赋值:倒掉旧的,放新的
age = 30;
System.out.println(age); // 30
// 声明+初始化一步到位(最常用的写法)
int quantity = 3;
}
}💥 拆了它:试试不初始化直接用
把上面的 int age; 后面的两行全删掉,直接 System.out.println(age);。看编译器怎么说。
error: variable age might not have been initializedJava 的规矩是:变量在初始化之前不能拿来用。这不是故意刁难你——未初始化的变量里面的值是未定义的垃圾数据,拿来用会生产莫名其妙的 bug。Java 干脆直接拦住你,不让你犯错的机会。
4. 八种基本类型
Java 有 8 种内置的基本类型(Primitive Type)。它们不是类,就是纯粹的"值"——直接存在内存里的数据。
你不需要全部记住,但需要知道都有什么以及什么时候用。
整数家族:4 种
| 类型 | 大小 | 能存的范围 | 什么时候用 |
|---|---|---|---|
byte | 1 字节 | -128 ~ 127 | 存大量小数字(图片像素、文件格式) |
short | 2 字节 | ±3.2 万 | 很少用 |
int | 4 字节 | ±21 亿 | 95% 的情况用这个 |
long | 8 字节 | ±9.2 × 10¹⁸ | 数字超过 21 亿(时间戳、天文数字) |
byte b = 100; // 小数字
short s = 30000; // 不大不小
int i = 2000000000; // 日常够用
long l = 9000000000000L; // 超大的数,后面要加 L注意 long 的字面量要加 L(或小写 l),不然 Java 以为你在写 int。
小数家族:2 种
| 类型 | 大小 | 精度 | 什么时候用 |
|---|---|---|---|
float | 4 字节 | 约 7 位有效数字 | 极度省内存时(图形学) |
double | 8 字节 | 约 15 位有效数字 | 99% 的情况用这个 |
float f = 3.14f; // 后面要加 f,不然 Java 以为你是 double
double d = 3.14; // 默认小数就是 double其他:2 种
boolean isReady = true; // 只有 true 和 false
char letter = 'A'; // 一个 Unicode 字符,用单引号
char chinese = '中'; // char 支持中文一个小结论
实际开发中,你 90% 的时间只用三种:
int count = 42; // 整数
double total = 19.99; // 小数
boolean paid = false; // 是/否另外五种有它们的用途,但初学者不必刻意去记。
💥 拆了它:试试超过 int 上限
int x = 3000000000; // 30亿,int 上限约21亿编译结果:integer number too large
改成 long x = 3000000000L; 就好了。
5. 类型转换——小盒子和大盒子
不同类型的变量之间可以互相转。规则只有两条:
小范围 → 大范围:自动转,安全。大范围 → 小范围:手动转,可能丢数据。
自动转换(从小的往大的装)
int i = 100;
long l = i; // int → long:自动的(long 比 int 大)
double d = i; // int → double:自动的,100 → 100.0
// 运算时也会自动提升
int a = 5;
double b = 2.0;
double result = a + b; // int + double → int 先转 double 再算 → 7.0💥 拆了它:整数除法的陷阱
double result = 5 / 2;
System.out.println(result); // 猜猜是多少?你可能以为是 2.5,但结果是 2.0。
为什么?因为 5 和 2 都是 int,Java 先做整数除法 5/2 = 2(整数除法丢弃小数部分),再把结果 2 转成 double 的 2.0。
double correct = 5.0 / 2; // 2.5 ✅
double correct2 = 5 / 2.0; // 2.5 ✅
double correct3 = (double) 5 / 2; // 2.5 ✅记好:整数除法只留整数部分。 只要运算中有一个数是 double,结果就是 double。
强制转换(从大的往小的装)
从大范围往小范围转是不安全的——可能会溢出或丢精度。所以 Java 不让你自动转,你必须手动写 (类型):
double pi = 3.14159;
int intPi = (int) pi; // 3(小数直接砍掉,不是四舍五入!)int big = 300;
byte small = (byte) big; // 300 超过了 byte 的范围(-128~127)
System.out.println(small); // 44!溢出了,数据静默丢失强制转换的风险:Java 让你编译通过,但不保证结果有意义。
🧪 动手试试
// 猜猜每行的输出,再跑一遍验证
double price = 19.99;
int dollars = (int) price;
System.out.println(dollars);
int x = 10;
int y = 3;
System.out.println(x / y);
System.out.println((double) x / y);6. 常量:final——做了就不能改
如果你有一个值,定义了就不想让人改——用 final。
final double PI = 3.141592653589793;
final int MAX_USERS = 1000;
PI = 3.14; // ❌ 编译错误!final 变量不能重新赋值
MAX_USERS = 500; // ❌ 编译错误!常量的命名惯例:全大写,单词之间用下划线。
final double TAX_RATE = 0.08;
final int MAX_RETRY = 3;
final String APP_NAME = "团子奶茶店";这样一眼就能看出 "哦,这个值不会变"。
7. 变量名——好的名字就是最好的注释
// ❌ 烂命名——三天后你自己都看不懂
int a = 10;
int b = 5;
int c = a * b + a / b;
// ✅ 好命名——看到名字就知道要干嘛
int studentCount = 30;
int groupSize = 5;
int totalGroups = studentCount / groupSize;Java 命名规矩:
| 种类 | 写法 | 例子 |
|---|---|---|
| 变量名 | 小驼峰 | studentCount |
| 常量名 | 全大写下划线 | MAX_SIZE |
| 类名 | 大驼峰 | GradeCalculator |
| 包名 | 全小写 | com.example.shop |
💥 拆了它:试试中文变量名
Java 其实支持中文变量名——Unicode 字符都行:
int 年龄 = 25;
String 名字 = "小明";
System.out.println(名字 + "今年" + 年龄 + "岁");能跑。但不要这么写——中文编码在不同环境下可能出问题,而且英文变量名是行业通用标准。
8. 完整例子:奶茶店的点单计算
public class MilkTeaOrder {
public static void main(String[] args) {
// 常量
final double TAX_RATE = 0.08;
final String SHOP_NAME = "团子奶茶店";
// 变量
String drink = "波波奶茶";
int quantity = 2;
double unitPrice = 15.0;
// 计算
double subtotal = quantity * unitPrice;
double tax = subtotal * TAX_RATE;
double total = subtotal + tax;
// 输出
System.out.println("===== " + SHOP_NAME + " =====");
System.out.println(drink + " × " + quantity);
System.out.println("小计: " + subtotal + "元");
System.out.println("税: " + tax + "元");
System.out.println("总计: " + total + "元");
}
}输出:
===== 团子奶茶店 =====
波波奶茶 × 2
小计: 30.0元
税: 2.4元
总计: 32.4元⚠️ 需要知道:这个例子用
double算金额只是为了演示变量类型。在真实的收银系统里,千万别用double或float存钱——浮点数精度不够,0.1 + 0.2 可能不等于 0.3。正确的做法是用BigDecimal或把金额按"分"存成整数。你现在刚起步,知道有这个问题就行,后面的章节会细讲。
9. 关于 String
这一章主要讲 8 种基本类型。但还有一个东西你第一章就用上了——String(字符串)。
String name = "小明";String 不是基本类型,它是一个类。但 Java 给了它特殊待遇,让你可以像基本类型一样直接用字面量(双引号字符串),不用 new。
关于 String 的更多内容,第9章会整章讲。现在你就知道它是"装一段文字用的就行了"。
我猜你会问
Q: 为什么要有 8 种基本类型,3 种不够吗? A: 历史原因。1980 年代内存很贵,省一个字节都很重要。现在你只管用 int、double、boolean 就够了。但 byte 在文件解析里还在用,long 在时间戳里避不开,char 在处理文本时需要。
Q: float f = 3.14; 为什么报错? A: 因为 3.14 默认是 double 类型。double → float 是缩小转换,需要你手动强制。写 float f = 3.14f; 就好了——f 后缀告诉 Java "这是个 float 字面量"。
Q: int 为什么最大值是 21 亿? A: int 占 4 字节 = 32 位,能表示 2³² = 约 43 亿个不同的值。一半给负数,一半给非负数。具体范围:-2,147,483,648 到 2,147,483,647。
Q: 什么时候需要关心变量占多少内存? A: 刚开始完全不需要。等你写百万级数据的程序时才有意义——比如处理一张 4K 图片,选对类型能省几百 MB。
你现在学会的东西
- 变量 = 带标签的盒子,有名字、类型、值三个属性
- Java 有 8 种基本类型,平时 90% 用
int、double、boolean - 声明 ≠ 初始化,没初始化的变量不能读
- 小范围 → 大范围自动转,反过来要手动强制且可能丢数据
- 整数除法只留整数部分,想要精确除法让至少一个操作数是
double final变量不能改,常量名全大写- 变量名要让人能读懂
✅ 验收标准
完成本章后,你应该能:
- [ ] 声明
int、double、boolean、String变量并初始化 - [ ] 解释编译器为什么拒绝把文本赋给整数变量
- [ ] 区分自动类型转换和强制类型转换,至少各举一个例子
- [ ] 写出整数除法的正确写法(
5.0 / 2而不是5 / 2) - [ ] 用
final声明常量并说明为什么它有用
📌 常见卡点
- 忘记初始化就读变量——编译报错,先赋值再用
- 整数除法丢掉小数——至少一个操作数改成 double
- `float` 后面忘加 `f`——默认小数是 `double`,`double → float` 需要强转
- 中文变量名——JVM 支持但行业不用,坚持英文
🔜 现在不需要理解
- 引用类型和基本类型的本质区别——第7章面向对象会讲
- `String` 为什么不是基本类型——第9章字符串专题
- 栈和堆上的内存分配——第三卷深入
🧪 练习
1. 声明变量:创建一个 Student 类(不能运行的——写一个片段就行),包含 name(String)、age(int)、score(double)三个字段,并初始化它们。
2. 类型转换:写一段代码,用 int 和 double 演示
- 自动类型转换
- 强制类型转换
- 整数除法陷阱(
5 / 2)及正确写法
3. 常量:声明一个 final double TAX_RATE = 0.08,然后试着给它重新赋值——看看编译器报什么错。