数据类型与变量
学习目标
- 能区分八种基本类型、引用类型、包装类型和
null。 - 能根据数据含义选择合适的变量类型,读懂常见字面量后缀和默认值。
- 理解字段、数组元素和局部变量在初始化方面的不同,并知道自动装箱的风险。
核心知识点
专业术语
- 基本类型(primitive type):
byte、short、int、long、float、double、char、boolean,变量直接保存值。 - 引用类型(reference type):类、接口、数组、枚举等类型,变量保存对象引用,也可以保存
null。 - 包装类型(wrapper type):
Integer、Long、Double等对象类型,为基本值提供对象语义和泛型兼容能力。 - 自动装箱/拆箱(boxing/unboxing):编译器在基本类型与对应包装类型之间插入转换代码。
- 字面量(literal):直接写在源码中的值,如
42、3.14、'A'、true和"Java"。 - 默认值(default value):字段和数组元素在未显式赋值时由 Java 提供的初始值;局部变量没有自动默认值。
| 类型 | 常用字面量 | 字段/数组默认值 | 适合场景 |
|---|---|---|---|
byte | 12(赋值目标足够窄时) | 0 | 原始字节、协议数据 |
short | 1200 | 0 | 少量明确的 16 位整数 |
int | 42、0x2A、0b101010 | 0 | 普通计数、下标和整数计算 |
long | 42L | 0L | 时间戳、较大的整数标识 |
float | 3.14F | 0.0F | 需要较低精度的近似计算 |
double | 3.14 | 0.0D | 一般科学和测量计算 |
char | 'A'、'中' | \u0000 | 一个 UTF-16 码元 |
boolean | true、false | false | 开关和条件状态 |
白话解释与边界
八种基本类型
基本类型像直接写在变量格子里的值;引用类型像指向对象的地址标签。声明 String name = null 时没有创建字符串对象,调用 name.length() 会抛出 NullPointerException。数组也是引用类型,int[] 的元素默认是 0,但数组变量本身仍可能为 null。
引用类型与 null
引用类型的变量保存对象引用而不是对象内容;null 代表当前没有对象可访问,使用前应先定义是否允许空值。
包装类型与装箱拆箱
包装类型用于集合和泛型,例如 List<Integer> 不能写成 List<int>。自动装箱让 int 可以放进 Integer,自动拆箱让 Integer 参与整数运算,但拆箱 null 会抛出 NullPointerException。需要允许“没有值”时,优先明确使用包装类型并在拆箱前校验。
字面量与默认值
字面量的类型由写法决定:普通整数是 int,加 L 才是 long;普通小数是 double,加 F 才是 float;字符用单引号,字符串用双引号。下划线、二进制和十六进制前缀只改善书写,不改变数值含义。
char 与 Unicode 边界
默认值只适用于字段和数组元素,方法里的局部变量必须显式赋值后才能读取。final 变量一旦完成赋值就不能再赋值,但它并不会把引用对象自动变成不可变对象。char 是 UTF-16 码元,不一定等于一个完整 Unicode 字符;转换和精度的详细边界见下一节。
常用用法
基本类型:保存简单数值
基本类型适合表达确定的数值、字符和真假值;选择类型时同时考虑取值范围和精度。
// 语义:基本类型适合表达确定的数值、字符和真假值。
// 初始状态:count 初始为 3;price 初始为 2.5。
public class PrimitiveDemo {
public static void main(String[] args) {
int count = 3;
// 关键变化:count 接收表达式 3 的计算结果。
// 初始状态:count 的初始值为 3。
double price = 2.5;
System.out.println(count * price);
// 输出:7.5
}
}包装类型:在泛型中表达可空值
包装类型可以用于集合和泛型,但拆箱 null 会抛出异常,使用前要明确空值策略。
// 语义:包装类型可以用于集合和泛型,但拆箱 null 会抛出异常,使用前要明确空值策略。
// 初始状态:count 初始为 3。
public class WrapperDemo {
public static void main(String[] args) {
Integer count = 3;
System.out.println(count + 1);
// 输出:4
}
}不常用但需要知道
null:表示没有可用对象
null 不是空字符串;调用它的方法前必须先判断或使用明确的空值处理方式。
public class NullDemo {
// 作用:null 不是空字符串;调用它的方法前必须先判断或使用明确的空值处理方式。
// 初始状态:value 被赋为 null,所以 value == null 的判断结果为 true。
public static void main(String[] args) {
String value = null;
// 关键变化:value 接收表达式 null 的计算结果。
// 初始状态:value 的初始值为 null。
System.out.println(value == null);
// 输出:true
}
}char 与码点:处理 Unicode 边界
一个 Unicode 码点可能占两个 UTF-16 char;处理用户可见字符时不要只用 length() 推断字符数。
// 输入:text 只含表情 "🙂",它在 UTF-16 中由两个 char 组成。
public class CodePointDemo {
public static void main(String[] args) {
// 作用:一个 Unicode 码点可能占两个 UTF-16 char;处理用户可见字符时不要只用 length() 推断字符数。
String text = "🙂";
// 关键变化:text 接收表达式 "🙂" 的计算结果。
// 初始状态:text 的初始值为 "🙂"。
System.out.println(text.codePointCount(0, text.length()));
// 输出:1
}
}简单案例
import java.util.ArrayList;
import java.util.List;
public class TypeBasicsDemo {
static int fieldCount;
static int[] fieldArray = new int[2];
public static void main(String[] args) {
int count = 3;
long id = 9_000_000_000L;
double ratio = 0.75;
char first = 'A';
boolean enabled = true;
Integer boxed = count; // 自动装箱
int unboxed = boxed; // 自动拆箱
Integer missing = null;
List<Integer> scores = new ArrayList<>();
// 初始状态:scores 是空的 ArrayList<Integer>。
scores.add(100); // 集合需要包装类型
// 关键变化:scores.add(100) 追加整数 100,scores 变为 [100]。
System.out.println(count + ", " + id + ", " + ratio);
// 输出:3, 9000000000, 0.75
System.out.println(first + ", " + enabled + ", " + scores);
// 输出:A, true, [100]
System.out.println("defaults=" + fieldCount + ", " + fieldArray[0]);
// 输出:defaults=0, 0
System.out.println("same value=" + (count == unboxed) + ", missing=" + (missing == null));
// 输出:same value=true, missing=true
}
}count 和 unboxed 都保存整数值;boxed 是对象引用,missing 明确表示没有对象。fieldCount 和 fieldArray[0] 是字段与数组元素,因此拥有默认值;如果把相同声明放到方法内部而不初始化,编译器会拒绝读取。
易混点
null不是0、false或空字符串,只能赋给引用类型。Integer和int的身份不同;包装对象可能为null,自动拆箱并不免费也不总是安全。- 字段和数组元素有默认值,局部变量没有默认值;两者的区别是“声明过”不等于“可以读取”。
42L、42.0F、'4'、"4"是四种不同类型的字面量。char是一个 UTF-16 码元;完整 Unicode 码点、用户可见字符和String.length()不是同一个概念。
课后小问
为什么
List<int>不能通过编译,而List<Integer>可以? 答案:Java 泛型的类型参数必须是引用类型,基本类型int需要使用对应的包装类型Integer。 解析:集合内部依赖对象语义,编译器会在scores.add(100)处自动装箱;读取元素时又可能自动拆箱,所以必须留意元素是否可能为null。为什么方法里的
int count; System.out.println(count);会报错,而类字段int count;可以打印 0? 答案:字段和数组元素有语言规定的默认值,局部变量必须由程序员明确赋值。 解析:局部变量未赋值通常意味着控制流可能没有给它可靠的值,编译器会在读取处阻止这种不确定状态;字段则在对象或类初始化阶段得到0。Integer value = null; int n = value;的问题是什么? 答案:赋值时发生自动拆箱,null没有可拆出的整数值,会抛出NullPointerException。 解析:可以先用value == null判断,或者使用明确的默认值策略,例如int n = value == null ? 0 : value;不要让隐式拆箱掩盖空值含义。
本节小结
- 八种基本类型直接保存值,引用类型保存对象引用,
null只表示没有引用对象。 - 包装类型让基本值能够进入泛型和集合,但自动拆箱可能触发空值异常。
- 字面量后缀决定初始类型,字段和数组元素有默认值,局部变量必须明确初始化。
final约束重新赋值,不能单独推出对象内容不可变;char表示 UTF-16 码元。
快速回顾
- 能列出八种基本类型及其常见使用场景和字面量写法。
- 能判断一个变量是否允许为
null,以及何时会发生自动拆箱。 - 能区分字段、数组元素和局部变量的默认值规则。
- 能解释
Integer、int、char和String之间的基本边界。
阿源的知识库