Skip to content

第2章 变量与类型


元数据卡

  • 前置知识:第1章——能写、编译、运行 Java 程序
  • 预计时间:60 分钟
  • 核心难度:
  • 完成标志:能够声明各种类型的变量、赋值、在终端打印变量的值,理解类型转换的基本规则

本章分层

  • 必读:Java 基本数据类型(int/double/boolean/char/String);变量声明与初始化;类型转换规则
  • 选读:final 常量;var 关键字;表达式的自动类型提升
  • 深水区:JVM 内存中不同数据类型的存储布局;浮点数 IEEE 754 精确表示

本章不会要求你掌握

  • 所有基本类型的二进制表示细节
  • 引用类型与基本类型的底层差异
  • 自动装箱/拆箱的编译器行为

你在哪

变量村的清晨,老陈师傅的工坊里飘着一股铁锈和旧木头的味道。他把 HelloWorld 那张纸钉在了墙上。"第一句话你已经会说。"他转身从抽屉里翻出一张发黄的旅行地图——画的是一个村庄,每栋房子外面挂着一个牌子:整数仓库、小数银行、字符驿站、真假哨站。

"变量村不是白叫的。"他把地图摊在桌上,"计算机里面,每个变量都是一栋小房子——有个门牌号(叫名字),有个房间(占内存),里面住着东西(叫值)。今天的任务:认识这里所有类型的房子。"

你的任务

"Hello, World!" 是一个写死的句子。如果把程序比作菜谱,那它只写了"放盐"——但盐放多少、什么时候放、放在哪个碗里,全都没写。

真实程序里,数据是变化的:今天用户叫张三,明天可能叫李四;今天是 18 度,明天可能是 25 度。你不能每次数据变了就改源代码重新编译——那太傻了。

你需要一个办法:在内存里划出一块区域,给这块区域起个名字,往里面放东西,随时能拿出来用或者换成别的——这就是 变量


遭遇战 → 获得技能

① 第一个问题:存一个数字

我凭直觉写:

java
x = 5

编译——报错。老陈师傅并不意外。

"Java 不让你这么干。它要你提前说清楚:你要存的东西是什么种类、占多大空间。"

在 Java 里,声明一个变量需要两样东西:类型名字

java
// VariablesDemo.java
public class VariablesDemo {
    public static void main(String[] args) {
        int score = 95;
        System.out.println(score);
    }
}

如何运行: javac VariablesDemo.javajava VariablesDemo预期输出: 95

"int 是类型名,意思是整数。score 是变量名,你用这个名字来引用这块内存。"老陈师傅在纸上画了个小方框。

java
int score = 95;   // 声明 + 赋值,一步到位

他接着写:

java
int score;        // 只声明:告诉 Java 我要一个叫 score 的整数容器
score = 95;       // 再赋值:往容器里放数据

"声明和赋值可以分开做。但你不能拿了空容器就乱用——Java 编译器会检查你有没有给变量初始化。"

java
int score;
System.out.println(score);  // ❌ 编译错误:variable score might not have been initialized

② 八种基本类型

"整数有四种。"老陈师傅用粉笔敲了敲黑板:

类型大小取值范围默认值
byte1 字节-128 ~ 1270
short2 字节-32,768 ~ 32,7670
int4 字节-2^31 ~ 2^31-10
long8 字节-2^63 ~ 2^63-10L

"绝大多数情况下你只用 intbyteshort 用在内存紧张的地方(比如嵌入式设备、网络协议解析)。long 只在数字可能超过 20 亿的时候用——long 的字面量末尾要加大写 L。"

java
// PrimitiveTypes.java
public class PrimitiveTypes {
    public static void main(String[] args) {
        byte b = 100;
        short s = 30000;
        int i = 1_000_000;           // Java 7 起可以用下划线分隔数字——纯语法糖
        long l = 3_000_000_000L;     // 超过 int 范围时必须加 L

        System.out.println("byte: " + b);
        System.out.println("short: " + s);
        System.out.println("int: " + i);
        System.out.println("long: " + l);
    }
}

如何运行: javac PrimitiveTypes.javajava PrimitiveTypes预期输出:

byte: 100
short: 30000
int: 1000000
long: 3000000000

他看了我一眼:"小数有两种。"

类型大小精度默认值
float4 字节~7 位有效数字0.0f
double8 字节~15 位有效数字0.0d

"float 字面量末尾必须加 fFdouble 加不加 d 都行——Java 默认小数就是 double。"

java
float pi = 3.1415f;       // 不加 f 会编译错误——3.1415 默认是 double
double e = 2.71828;       // 不加 d 没问题,double 是默认的

"然后是另外两种——跟数字无关。"

java
char grade = 'A';         // 单个字符,用单引号包住
boolean isReady = true;   // 真或假,只有两个值

🐍 Python 差异

Python 不需要你声明类型——你直接写 score = 95 就好,Python 自动推断类型并且允许随时改变。这看起来方便,但缺点是你可能不小心把 score 赋值为字符串 "hello",后面用到 score + 5 就抛运行时异常了。Java 是静态类型——变量类型一旦声明,终生不变。

Python 也没有 byteshortlong 的区别——它只有一个 int,大小只受内存限制。float 就是双精度(约等于 Java 的 double)。char 也不存在——Python 里字符就是长度为 1 的字符串。

** C++ 差异**

C++ 的基本类型看起来和 Java 很像,但有一个关键区别:C++ 的 int 大小不固定——16 位系统上是 2 字节,32/64 位上是 4 字节。Java 的 int 在任何平台上都是固定的 4 字节,跨平台一致性是 Java 的设计目标之一。

C++ 还有 unsigned 修饰符——unsigned int 只能存非负数,但上限翻倍。Java 没有无符号类型(直到 Java 8 才在一些 API 上做了补救)。


var——偷懒的正确方式

"Java 10 之后,你有一种偷懒的办法。"老陈师傅又写:

java
var name = "Alice";     // 编译器自己推断:name 是 String 类型
var count = 42;          // 编译器自己推断:count 是 int 类型
var pi = 3.14;           // 编译器自己推断:pi 是 double 类型

"这叫类型推断。你写 var,编译器看等号右边的值,自己判断类型。但有个重要的限制:var 必须在声明时就赋值,而且赋值之后类型就锁死了——你不能声明 var x; 然后再赋值。"

java
var x;      // ❌ 编译错误:cannot infer type for local variable x
x = 10;

"还有,var 只能用在方法内部的局部变量——不能用它做类成员变量、方法参数、或者方法返回值。"

java
var n = 100;
n = "hello";  // ❌ n 已经被推断为 int,不能再赋字符串

"你看,Java 帮你做了声明的工作,但没妥协类型安全。这是 Python 和 Java 的一个中间地带。"


④ 类型转换——从小盒子换到大盒子

"有时候你需要把一种类型的值放到另一种类型的变量里。"老陈师傅把几个大小不一的木盒子排开。

自动转换(隐式):小→大

java
int i = 100;
long l = i;           // ✅ int 自动变成 long——小盒子装进大盒子,安全
double d = i;         // ✅ int 自动变成 double——整数变小数,不丢精度

规则很简单:byte → short → int → long → float → double——箭头方向上的转换自动发生,因为目标类型的范围更大。

强制转换(显式):大→小

"但反过来呢?"

java
double pi = 3.14159;
int i = pi;           // ❌ 编译错误:incompatible types

"能编过才怪。你把一个可能装了 3.14 的大箱子,强行塞进只能装整数的小箱子——数字丢了小数部分。Java 不会替你做这个决定。你得明确说:我知道我会丢精度,我同意。"

java
double pi = 3.14159;
int i = (int) pi;     // ✅ (int) 是强制类型转换
System.out.println(i); // 输出:3(小数部分被截断,不是四舍五入!)

"括号里写目标类型,这叫强制类型转换(cast)。"

java
// TypeConversion.java
public class TypeConversion {
    public static void main(String[] args) {
        // 隐式转换——自动
        int apples = 10;
        long total = apples;              // int → long
        double price = apples;            // int → double

        // 显式转换——手动
        double weight = 85.7;
        int kg = (int) weight;            // double → int,丢掉 0.7

        // 还有可能丢数据的
        int big = 1000;
        byte small = (byte) big;          // 数据溢出!1000 远远超过 byte 的范围

        System.out.println("long: " + total);
        System.out.println("double: " + price);
        System.out.println("int (from double): " + kg);
        System.out.println("byte (from int): " + small);
    }
}

如何运行: javac TypeConversion.javajava TypeConversion预期输出:

long: 10
double: 10.0
int (from double): 85
byte (from int): -24

"看到了吧?(byte) big 输出 -24——这不是 Java 的 bug,而是数据溢出时发生溢出回绕。1000 的二进制超过了 byte 能表示的范围(-128~127),剩余部分从最左边被"截掉"了,结果变成了 -24。"

"所以——强制类型转换是你在跟编译器说:别管我,我知道我在干什么。 大多数时候你知道,但你也可能犯错。"

🐍 Python 差异

Python 没有"类型转换"的说法——你在 Python 里用 int(3.14) 会得到 3,这不叫 cast,叫构造函数调用。Java 的类型转换发生在编译期(编译器知道类型的情况下),而 Python 的 int(3.14) 发生在运行时。

Python 不存在数据溢出——它的整数可以任意大。1000 变成 byte?Python 没有 byte 类型。

** C++ 差异**

C++ 的强制转换和 Java 类似:int i = (int)3.14;。但 C++ 有四种不同的 cast 操作符(static_castdynamic_castconst_castreinterpret_cast)——Java 只有一种 (type) 括号语法。

C++ 也会发生溢出,并且行为由 CPU 决定(在 C++ 里对有符号整数的溢出是未定义行为——编译器可能做任何事),但 Java 严格规定了溢出回绕的行为(像二进制补码那样)。


常见陷阱

事故一:浮点数运算不是你想的那样

java
double a = 0.1;
double b = 0.2;
System.out.println(a + b);   // 你以为是 0.3

输出:

0.30000000000000004

"不是 0.3?"

"不是 0.3。原因和 Java 无关——这是二进制浮点数的固有限制。0.1 在二进制里是一个无限循环小数,跟 1/3 在十进制里是 0.3333... 一样。你用 double 只能存一个近似值,多个近似值加起来误差就冒出来了。"

事故二:char 不是字符串

java
char ch = "A";   // ❌ 编译错误——char 用单引号,双引号是字符串
java
char ch = 'AB';  // ❌ 编译错误——char 只能装一个字符

事故三:整数除法

java
int a = 5;
int b = 2;
System.out.println(a / b);   // 输出:2,不是 2.5

"两个整数相除,Java 做整数除法——直接舍去小数部分。如果你要小数结果,至少一个操作数要用小数类型。"

java
System.out.println(5 / 2.0);   // 输出:2.5
System.out.println(5.0 / 2);   // 输出:2.5
System.out.println((double) 5 / 2);  // 输出:2.5

通关挑战

🗡 热身(5 分钟必做)

  1. 声明以下变量并打印出来:

    • 你的年龄(int
    • 你的身高(double
    • 你的姓的首字母(char
    • 你是否喜欢编程(boolean
  2. 声明一个 long 变量赋值为 10000000000(100 亿),注意加 L 后缀

  3. 声明 byte b = 200;——观察编译错误

  4. 声明 var city = "Beijing";,然后尝试 city = 42;——看编译器说什么

** 挑战(30 分钟选做)**

  1. 写一段代码,演示 int 类型的最大值 Integer.MAX_VALUE 加 1 会发生什么(这叫"整数溢出")
  2. 写一个温度转换器:输入华氏度(double),转换为摄氏度并打印。公式:C = (F - 32) * 5 / 9——注意整数除法陷阱
  3. var 声明一个 float 变量,然后尝试和 double 变量做加法——看编译器如何推断结果的类型

** 排障**

问题最常见原因
variable X might not have been initialized声明了但没赋值就使用
incompatible types: possible lossy conversion大范围类型隐式转小范围——必须加 cast
integer number too largeint 字面量超过了 21 亿,需要加 L 变成 long
cannot find symbol变量名拼写错了,或者还没声明就用
: expected忘了写类型(Java 不是 Python——类型必须声明)

验收标准

  • [ ] 能解释什么是变量(类比贴标签的盒子)
  • [ ] 能写出所有 8 种基本类型的声明和初始化
  • [ ] 知道 var 的用法和限制
  • [ ] 能区分隐式转换和强制转换,知道什么时候该用哪种
  • [ ] 能演示:整数除法丢小数、浮点运算有精度误差
  • [ ] 知道 char 用单引号、String 用双引号

常见卡点

1. long 字面量忘加 L

java
long big = 10000000000;  // ❌ 编译错误:integer number too large

因为 10000000000 被编译器当作 int,但它的值超出了 int 范围。改正:

java
long big = 10000000000L; // ✅ 加 L 告诉编译器这是 long 字面量

2. float 字面量忘加 f

java
float f = 3.14;  // ❌ 编译错误:incompatible types

3.14 默认是 double,不能隐式放进 float。改正:

java
float f = 3.14f; // ✅

3. 把赋值 = 和比较 == 搞混

这个会在后面控制流章节反复出现——赋值 = 是把右边的值放进左边的变量,比较 == 是问"它们相等吗?" 两个是完全不同的操作。

4. 中文标点符号

全角分号 、全角引号 "、全角括号 ()——这些在中文文本里看起来正常,但在 Java 代码里都是语法错误。养成习惯:写代码时输入法切到英文模式。


现在不需要理解

  • 什么是"栈"和"堆"——第5章讲方法时会揭晓
  • 基本类型和引用类型的根本区别——第7章面向对象时会提
  • String 为什么是大写开头(它是一个类,不是基本类型)——第9章字符串专题
  • 浮点数的 IEEE 754 二进制表示——感兴趣可以查,但入门不需要
  • final 关键字——第2章+常量篇会覆盖

旅人笔记

变量就像一个带标签的盒子

你告诉 Java:"我要一个叫 score 的盒子,它只能装 int 类型的东西。" Java 就在内存里给你分配一个 4 字节的"盒子"。然后你把 95 放进去。以后每次用 score 这个名字,Java 就帮你打开那个盒子取出值。

这八个基本类型 (byte, short, int, long, float, double, char, boolean) 是 Java 的"原子"——它们不是由其他类型组合而成的。所有复杂的东西(字符串、对象、数组)最终都是由这些原子拼出来的。

命名规范是个好习惯:变量名用驼峰风格——第一个单词全小写,后面的单词首字母大写:myScoreplayerNameisGameOver

老陈师傅在我写完代码之后,拿了一张纸条贴在墙上:

变量让你能记住东西。
类型让你不会记错。
名字让你能找到它。


下一站预告

有了变量之后,你不再只能打印固定文字——你可以打印不同的数字和字符,可以把一个变量的计算结果存到另一个变量里。

下一步,你会学到表达式和操作符:如何把变量组合起来做计算、比对、判断。当你需要把"温度大于 30 度吗"这样的问题翻译成计算机能理解的语言时,你手上就有了工具。

第3章:表达式与操作符

Built with VitePress | Software Systems Atlas