1.1 基本数据类型
Java 是一门强类型语言,共有 8 种基本数据类型(Primitive Type)分为三大类:数值型(整型,浮点型)、字符型和布尔型
类型总览
| 分类 | 类型 | 字节数 | 取值范围 | 默认值 | 备注 |
|---|
| 整型 | byte | 1 | -128 ~ 127 | 0 | |
| 整型 | short | 2 | -32768 ~ 32767 | 0 | |
| 整型 | int | 4 | -2^31 ~ 2^31-1 | 0 | 默认整数类型 |
| 整型 | long | 8 | -2^63 ~ 2^63-1 | 0L | 字面量需加 L 后缀 |
| 浮点型 | float | 4 | ±3.4E-38 ~ ±3.4E+38 | 0.0f | 字面量需加 f 后缀 |
| 浮点型 | double | 8 | ±1.7E-308 ~ ±1.7E+308 | 0.0d | 默认浮点类型 |
| 字符型 | char | 2 | 0 ~ 65535 (Unicode) | '\u0000' | 表示单个 Unicode 字符 |
| 布尔型 | boolean | 1位 | true / false | false | JVM 中通常用 int 表示 |
代码示例
public class PrimitiveTypes {
public static void main(String[] args) {
// 整型
byte b = 100; // 1字节
short s = 10000; // 2字节
int i = 100000; // 4字节,默认
long l = 10000000000L; // 8字节,必须加 L 后缀
// 浮点型
float f = 3.14f; // 4字节,必须加 f 后缀
double d = 3.1415926535; // 8字节,默认
// 字符型
char c1 = 'A'; // 2字节
char c2 = '\u0041'; // Unicode 编码表示 'A'
char c3 = 65; // ASCII 码表示 'A'
// 布尔型
boolean flag = true;
}
}
注意事项
- long 和 float 的字面量后缀不可省略:不加
L 的整数字面量默认是 int,不加 f 的浮点字面量默认是 double。 - char 可以存中文:因为 char 是 2 字节 Unicode,一个中文字符正好是 2 字节。
- boolean 占用空间:JVM 规范中 boolean 被当作 int 处理(4字节),但在 boolean 数组中每个元素占用 1 字节。
- 默认值仅对成员变量有效:局部变量必须显式初始化,否则编译不通过。
public class DefaultValues {
// 实例变量:自动初始化为默认值
byte b; // 0
short s; // 0
int i; // 0
long l; // 0L
float f; // 0.0f
double d; // 0.0d
char c; // '\u0000'
boolean flag; // false
public void test() {
int local; // 局部变量
// System.out.println(local); // 编译错误!未初始化
}
}
1.2 变量类型
Java 中有三种变量,按声明位置和修饰符区分。
对比总览
| 变量类型 | 声明位置 | 生命周期 | 默认值 | 存储位置 | 修饰符 |
|---|
| 局部变量 | 方法、构造方法或代码块中 | 方法执行期间 | 无,必须显式初始化 | 栈内存(方法栈帧) | 只能用 final |
| 实例变量 | 类中、方法外 | 与对象共存亡 | 有默认值 | 堆内存(对象中) | 访问控制修饰符 |
| 类变量(静态变量) | 类中、方法外,static 修饰 | 类加载时创建,类卸载时销毁 | 有默认值 | 方法区/元空间 | static + 访问控制修饰符 |
内存模型 JDK8+
┌─────────────────────────────────────────────┐
│ JVM 内存结构 │
├───────────────┬───────────────┬─────────────┤
│ 虚拟机栈 │ 堆内存 │ 方法区 │
│ (Stack) │ (Heap) │ Metaspace(元空间) │
├───────────────┼───────────────┼─────────────┤
│ 方法栈帧 │ 对象实例 │ 类元信息(类结构、字段、方法字节码) │
│ 局部变量表 │ 数组 │ 运行时常量池、符号表、Vtable虚方法表 │
│ 操作数栈 │ 实例变量 │ 即时编译代码、类加载信息 │
│ 方法返回地址 │ Class对象、静态变量、字符串常量池 │
│ 动态链接 │ │
│ 线程私有 │ 线程共享 │ 线程共享 │
└───────────────┴───────────────┴─────────────┘
运行时常量池 → 永远在元空间(方法区)
方法栈帧:每调用一个方法,就会在虚拟机栈创建一块独立内存 = 栈帧
如调用 sayHello() → 生成 sayHello() 栈帧 调用 main() → 生成 main() 栈帧 方法执行完,栈帧直接弹出销毁。
局部变量表存放:局部变量、方法参数、对象引用
操作数栈:JVM 运算专用临时空间,做加减、赋值、打印时中转数据
方法返回地址: 方法执行完后,要回到调用处继续执行,记录跳转位置。
动态链接:栈帧保存符号引用,运行时把符号引用解析成真实内存地址
比如代码里 System.out.println,编译时只是符号,运行时动态找到对应方法地址。
对象实例:new User("张三") 在堆中创建一块内存,就是 User 对象实例
数组:所有数组无论基本类型还是引用类型,本体都在堆。
实例变量:属于单个对象,存在对应对象实例内部
Class 对象、静态变量,每个类在堆里唯一存在一个 Class 对象 User.class静态变量 school 不归属任何实例,存在堆的 User.class 对象中:
字符串常量池
"北大" "张三" "李四" 这类双引号字面量,存放在堆的 StringTable 字符串常量池
元空间(方法区)只存类模板,不存对象数据
类元信息 (类结构、字段、方法字节码)编译后 User.class 文件里的固定模板,加载后放入元空间
运行时常量池、符号表:运行时常量池:class 文件自带常量池加载进来,存数字、符号引用
符号表:记录类名、方法名、字段名等符号,供动态链接使用
Vtable 虚方法表:存放当前类所有重写 / 继承的虚方法地址,多态调用时快速查找方法。
比如如果 User 有父类,重写 toString (),虚表会记录 toString 的内存地址
即时编译代码(JIT)
热点方法被多次调用,JIT 把字节码编译成本地机器码,缓存到元空间,提升执行速度
类加载信息
记录当前类由哪个类加载器加载、父类、接口、访问权限等元数据。
栈:方法执行现场,无对象本体
堆:所有对象、数组、静态变量、字符串池
元空间:类的模板定义(只存描述,不存数据值)
通俗总览
栈管运行(方法调用),堆管存储(对象、静态数据),元空间管类模板(类定义信息)
栈内存 (Stack) —— 线程的 “执行剧本”
核心职责:存储每个方法调用对应的栈帧,描述方法执行过程
存储内容:局部变量表(基本类型、对象引用)、操作数栈、方法返回地址、动态链接
生命周期:方法调用创建栈帧,方法执行完毕立刻销毁,随线程生随灭
内存分配:连续内存,栈大小固定(可通过 -Xss 设置)
线程模型:线程私有,天然线程安全,不存在并发竞争
补充:不存储方法代码,方法字节码存在元空间
堆内存 (Heap) —— GC 的 “主战场”
核心职责:存储运行时动态创建的数据,所有 new 出来的对象、数组
存储内容:实例对象、数组、实例变量、Class 对象、静态变量、字符串常量池
生命周期:对象无有效引用后等待 GC 回收,存活时间不确定
内存分配:非连续内存,动态扩展;-Xms 初始堆,-Xmx 最大堆,是 JVM 最大内存区域
线程模型:线程共享,存在线程安全问题;通过 TLAB 线程本地分配缓冲区优化并发分配
方法区 / 元空间 (Method Area / Metaspace) —— 类的 “档案馆”
核心职责:存储类元信息(类结构、字段、方法字节码)、运行时常量池、符号引用
版本重大变化(Java 8):
彻底移除永久代(PermGen),改用元空间 Metaspace
元空间使用本地内存(Native Memory),不再占用 JVM 堆内存
JDK7 起就将静态变量、字符串常量池移出永久代,放入堆内存
生命周期:类加载时创建类元数据,类完全卸载(ClassLoader 被回收)才释放
线程模型:线程共享,类加载、反射访问需要保证线程安全
典型异常:OutOfMemoryError: Metaspace,加载类过多耗尽本地内存
GC 特点:回收频率极低,仅 Full GC 时尝试卸载无用类、回收废弃常量
混淆
局部变量 → 虚拟机栈
实例变量 → 堆(对应实例对象内部)
静态变量 → JDK7+ 堆(Class 对象末尾)
方法代码、类结构 → 元空间
运行时常量池 → 元空间
字符串常量池 (StringTable) → JDK7+ 堆
Class反射对象 → 堆;描述这个类的元数据 → 元空间
快速记忆:
栈:线程私有,存局部变量、方法执行现场,用完即销毁,无 GC
堆:GC 主战场,所有对象、数组、静态变量、字符串池,线程共享
元空间:方法区新实现,本地内存,只存类的模板信息,不存对象数据
代码示例
public class VariableTypes {
// 类变量(静态变量)
static int staticVar = 100;
static final String CONSTANT = "常量";
// 实例变量
int instanceVar = 200;
String name;
public void method(int param) { // param 也是局部变量
// 局部变量
int localVar = 300;
String localStr;
// System.out.println(localStr); // 编译错误!未初始化
System.out.println(localVar); // 300
System.out.println(instanceVar); // 200
System.out.println(staticVar); // 100
{
// 代码块中的局部变量
int blockVar = 400;
System.out.println(blockVar);
}
// System.out.println(blockVar); // 编译错误!超出作用域
}
}
注意事项
- 局部变量必须显式初始化,否则编译报错。
- 实例变量的默认值可以依赖,但不推荐 —— 显式初始化是良好习惯。
- 静态变量在类加载时初始化,只会初始化一次,所有实例共享。
- JDK 8 之后,方法区被元空间(Metaspace)取代,使用本地内存,不再受 JVM 堆大小限制。
1.3 运算符
运算符分类
算术运算符
| 运算符 | 含义 | 示例 |
|---|
+ | 加 / 字符串拼接 | a + b |
- | 减 | a - b |
* | 乘 | a * b |
/ | 除 | a / b |
% | 取模(取余) | a % b |
++ | 自增 | a++ 或 ++a |
-- | 自减 | a-- 或 --a |
关系运算符(比较运算符)
| 运算符 | 含义 | 结果类型 |
|---|
== | 等于 | boolean |
!= | 不等于 | boolean |
> | 大于 | boolean |
< | 小于 | boolean |
>= | 大于等于 | boolean |
<= | 小于等于 | boolean |
逻辑运算符
| 运算符 | 含义 | 短路特性 |
|---|
&& | 逻辑与 | 是(左边为 false 时不执行右边) |
| ` | | ` | 逻辑或 | 是(左边为 true 时不执行右边) |
! | 逻辑非 | — |
& | 按位与 / 非短路逻辑与 | 否 |
| ` | ` | 按位或 / 非短路逻辑或 | 否 |
位运算符
| 运算符 | 含义 |
|---|
& | 按位与 |
| ` | ` | 按位或 |
^ | 按位异或 |
~ | 按位取反 |
<< | 左移 |
>> | 右移(带符号) |
>>> | 无符号右移 |
赋值运算符
=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=、>>>=
运算符优先级
| 优先级 | 运算符 | 结合性 |
|---|
| 高 | () [] . | 左 |
| | ! ~ ++ -- | 右 |
| | * / % | 左 |
| | + - | 左 |
| | << >> >>> | 左 |
| | < <= > >= instanceof | 左 |
| | == != | 左 |
| | & | 左 |
| | ^ | 左 |
| | ` | ` | 左 |
| | && | 左 |
| | ` | | ` | 左 |
| | ?:(三元) | 右 |
| 低 | = += 等赋值运算符 | 右 |
记忆口诀:乘除 > 加减 > 比较 > 逻辑 > 赋值
字符串比较:== vs equals()
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true (同一字符串池对象)
System.out.println(s1 == s3); // false (不同对象)
System.out.println(s1.equals(s3)); // true (内容相同)
最佳实践
// 1. 使用括号明确优先级
int result = (a + b) * c / (d - e); // 清晰
// 2. 浮点数比较使用误差范围
double x = 0.1 + 0.2;
double y = 0.3;
if (Math.abs(x - y) < 1e-10) { // 正确做法
System.out.println("相等");
}
// if (x == y) { ... } // 错误!可能不相等
// 3. 避免在复杂表达式中使用自增自减
int i = 5;
int j = i++ + ++i; // 难以理解,避免使用
// 建议分开写:
i++;
int k = i;
i++;
j = k + i;
// 4. 短路求值的妙用
if (obj != null && obj.getValue() > 0) { // 安全:先判空
// ...
}
1.4 类型转换
自动类型转换(隐式转换)
规则:小范围类型自动转换为大范围类型,不会丢失精度。
byte → short → int → long → float → double
char ↗
byte b = 10;
short s = b; // byte → short,自动
int i = s; // short → int,自动
long l = i; // int → long,自动
float f = l; // long → float,自动(但可能损失精度!)
double d = f; // float → double,自动
char c = 'A';
int ci = c; // char → int,自动(ci = 65)
注意:long(8字节)转 float(4字节)虽然范围更大,但可能损失精度,因为 float 的有效位数(约7位)不如 long(19位)。
强制类型转换(显式转换)
规则:大范围类型转为小范围类型,需要使用强制转换符,可能丢失数据。
double d = 3.99;
int i = (int) d; // i = 3,小数部分被截断(不是四舍五入)
long l = 300L;
byte b = (byte) l; // b = 44,高位被截断,结果不可预测
// 溢出示例
int big = 130;
byte small = (byte) big; // small = -126(byte 范围 -128~127,溢出循环)
包装类与自动装箱/拆箱
// 自动装箱:基本类型 → 包装类
Integer a = 10; // 等价于 Integer.valueOf(10)
Double d = 3.14; // 等价于 Double.valueOf(3.14)
// 自动拆箱:包装类 → 基本类型
int b = a; // 等价于 a.intValue()
double e = d; // 等价于 d.doubleValue()
// 混合运算时自动拆箱
Integer x = 100;
Integer y = 200;
int sum = x + y; // 自动拆箱后计算
Integer 缓存机制
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // true(缓存范围内,同一对象)
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4); // false(超出缓存范围,不同对象)
// 正确的比较方式
System.out.println(i3.equals(i4)); // true
Integer 默认缓存 -128 到 127 之间的值(上限可通过 -XX:AutoBoxCacheMax 调整)。
字符串与基本类型的转换
// 字符串 → 基本类型
int i = Integer.parseInt("123");
double d = Double.parseDouble("3.14");
boolean b = Boolean.parseBoolean("true");
// 基本类型 → 字符串
String s1 = String.valueOf(123);
String s2 = Integer.toString(456);
String s3 = 789 + ""; // 简单但不推荐
String s4 = String.format("%d", 100);
1.5 数组
数组特点
- 类型统一:所有元素必须是同一类型
- 长度固定:创建后不可改变长度
- 索引访问:从 0 开始,最大为
length - 1 - 连续存储:内存中连续分配,支持高效随机访问
声明与创建
// 声明(不能指定长度!)
int[] arr1; // 推荐写法 类型[] 变量名
int arr2[]; // C 风格,不推荐
// 创建(必须指定长度)
arr1 = new int[5]; // 所有元素初始化为 0
String[] strs = new String[3]; // 所有元素初始化为 null
// 声明+创建一步完成
double[] nums = new double[10];
静态初始化
// 方式一:简写形式
int[] arr1 = {1, 2, 3, 4, 5};
// 方式二:完整形式
int[] arr2 = new int[]{1, 2, 3, 4, 5};
// 注意:简写形式只能用于声明时。不能单独用在赋值语句里。
// int[] arr3;
// arr3 = {1, 2, 3}; // 编译错误!
// int[] arr3 = {1,2,3}
//不然就老老实实 new int[]
int[] arr3;
arr3 = new int[]{1, 2, 3}; // 正确
多维数组
// 规则二维数组(3行4列)
int[][] matrix = new int[3][4];
matrix[0][0] = 1;
matrix[2][3] = 12;
// 静态初始化二维数组
int[][] grid = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 不规则数组(每行长度不同)
int[][] jagged = new int[3][];
jagged[0] = new int[2];
jagged[1] = new int[4];
jagged[2] = new int[3];
数组操作
遍历
int[] arr = {1, 2, 3, 4, 5};
// 方式一:for 循环(有索引)
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// 方式二:for-each(简洁,无索引)
for (int value : arr) {
System.out.println(value);
// 注意:修改 value 不会影响原数组!
}
复制(重点)
//这三种方法对数组本身执行的都是浅拷贝
int[] src = {1, 2, 3, 4, 5};
// 方式一:手动循环
int[] dest1 = new int[src.length];
for (int i = 0; i < src.length; i++) {
dest1[i] = src[i];
}
// 方式二:System.arraycopy()(最高效) (速度最快,native方法,直接操作内存)
int[] dest2 = new int[src.length];
System.arraycopy(src, 0, dest2, 0, src.length);
// 方式三:Arrays.copyOf()(最简洁) 利用 System.arraycopy(),可指定新数组长度。
int[] dest3 = Arrays.copyOf(src, src.length);
// 方式四:数组.clone()
int[] dest4 = src.clone();
排序
int[] arr = {5, 2, 8, 1, 9};
// Arrays.sort()(双轴快速排序,O(n log n))
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // [1, 2, 5, 8, 9]
// 部分排序
Arrays.sort(arr, 1, 4); // 只排序索引 [1, 4) 的元素
// 冒泡排序(手写版,O(n²))
void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// 选择排序(手写版,O(n²))
void selectionSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIdx]) {
minIdx = j;
}
}
int temp = arr[i];
arr[i] = arr[minIdx];
arr[minIdx] = temp;
}
}
搜索
int[] arr = {1, 3, 5, 7, 9};
// 线性搜索 O(n)
int linearSearch(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) return i;
}
return -1;
}
// 二分搜索 O(log n)(前提:数组必须有序)
int index = Arrays.binarySearch(arr, 5); // 返回 2
int notFound = Arrays.binarySearch(arr, 4); // 返回 -3(-(插入点) - 1)
注意事项
arr.length 获取长度,是属性不是方法(区别于 String.length())。- for-each 中修改临时变量不影响原数组,需要修改元素请使用普通 for 循环。
- 遍历时不能修改数组结构(增删元素),但普通 for 循环中修改已有元素是可以的。
- 数组越界:访问
arr[arr.length] 会抛出 ArrayIndexOutOfBoundsException。
1.6 String 字符串
核心特点
- 不可变(Immutable):
String 是 final 类,一旦创建内容不可改变。 - 线程安全:不可变性天然保证线程安全。
- 字符串池(String Pool):JVM 维护一个字符串常量池,相同字面量复用同一对象。
创建方式
// 方式一:字面量(进入字符串池)
String s1 = "Hello";
// 方式二:new(不进入字符串池,在堆中创建新对象)
String s2 = new String("Hello");
// 方式三:字符数组
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String s3 = new String(chars);
// 验证
System.out.println(s1 == s2); // false
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s2)); // true
常用方法
String str = " Hello World ";
// 长度和判空
str.length(); // 13(包含两边空格)
str.isEmpty(); // false
str.isBlank(); // false(JDK 11+,只判断是否空白字符)
// 查找
str.indexOf('o'); // 5(首次出现位置)
str.indexOf("World"); // 7
str.lastIndexOf('o'); // 8
// 比较
str.equals("Hello"); // false
str.equalsIgnoreCase(" hello world "); // true
str.compareTo("ABC"); // 字典序比较
// 判断前缀后缀
str.startsWith(" He"); // true
str.endsWith("d "); // true
str.contains("Wo"); // true
// 截取
str.substring(1, 6); // "Hello"([1, 6) 左闭右开)
// 替换
str.replace('l', 'L'); // " HeLLo WorLd "
str.replace("Hello", "Hi"); // " Hi World "
str.replaceAll("\\s", "_"); // "_Hello_World_"(正则替换)
// 去空格
str.trim(); // "Hello World"(去掉首尾半角空格)
str.strip(); // "Hello World"(JDK 11+,支持全角空格)
// 大小写
str.toUpperCase(); // " HELLO WORLD "
str.toLowerCase(); // " hello world "
// 分割与拼接
String[] parts = "a,b,c".split(","); // ["a", "b", "c"]
String joined = String.join("-", "A", "B", "C"); // "A-B-C"
intern() 方法
String s1 = new String("hello");
String s2 = "hello";
System.out.println(s1 == s2); // false
String s3 = s1.intern(); // 将 s1 的内容加入字符串池
System.out.println(s3 == s2); // true
StringBuilder vs StringBuffer vs String
| 特性 | String | StringBuilder | StringBuffer |
|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变) | 不安全 | 安全(synchronized) |
| 性能 | 拼接时最慢(频繁创建新对象) | 最快 | 较快 |
| 适用场景 | 少量字符串操作 | 单线程频繁修改 | 多线程频繁修改 |
// StringBuilder 示例(推荐单线程下使用)
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
sb.insert(5, ","); // "Hello, World"
sb.replace(0, 5, "Hi"); // "Hi, World"
sb.delete(4, 5); // "Hi, World"
sb.reverse(); // "dlroW ,iH"
String result = sb.toString();
// StringBuffer 示例(多线程安全)
StringBuffer sbf = new StringBuffer();
sbf.append("Thread-safe");
频繁拼接的性能对比
// 低效方式:每次 + 都创建新的 String 对象
String s = "";
for (int i = 0; i < 10000; i++) {
s += i; // 创建 10000 个临时对象
}
// 高效方式
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i); // 只在内部数组扩容
}
String result = sb.toString();
1.7 输入输出(Scanner)
基本使用
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取字符串
System.out.print("请输入姓名:");
String name = scanner.next(); // 读到空格/换行停止
System.out.print("请输入年龄:");
int age = scanner.nextInt(); // 读取整数
System.out.print("请输入身高:");
double height = scanner.nextDouble(); // 读取浮点数
System.out.print("是否成年:");
boolean adult = scanner.nextBoolean(); // 读取布尔值
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
常用方法
| 方法 | 说明 |
|---|
next() | 读取下一个单词(空格/换行分隔) |
nextLine() | 读取整行(到换行为止) |
nextInt() | 读取 int |
nextDouble() | 读取 double |
nextBoolean() | 读取 boolean |
hasNext() | 是否有下一个标记 |
hasNextInt() | 下一个标记是否能转为 int |
close() | 关闭 Scanner |
缓冲区问题(重点)
Scanner scanner = new Scanner(System.in);
System.out.print("请输入年龄:");
int age = scanner.nextInt(); // 读取 25,但换行符 \n 留在缓冲区
// System.out.print("请输入姓名:");
// String name = scanner.nextLine(); // 直接读取了残留的 \n,返回空字符串!
// 解决方案:消耗掉残留的换行符
scanner.nextLine(); // 清除缓冲区的 \n
System.out.print("请输入姓名:");
String name = scanner.nextLine(); // 正常读取
System.out.println("年龄:" + age + ",姓名:" + name);
输入校验
import java.util.Scanner;
import java.util.InputMismatchException;
Scanner scanner = new Scanner(System.in);
int number = 0;
boolean valid = false;
while (!valid) {
try {
System.out.print("请输入一个整数:");
number = scanner.nextInt();
valid = true;
} catch (InputMismatchException e) {
System.out.println("输入无效,请重新输入!");
scanner.next(); // 清除无效输入
}
}
System.out.println("你输入的是:" + number);
最佳实践
// 1. 使用完毕后关闭(System.in 一般不关,但在文件/流场景必须关)
Scanner scanner = new Scanner(System.in);
// ... 操作 ...
scanner.close();
// 2. 格式化输出
String name = "张三";
int score = 95;
System.out.printf("姓名:%s,成绩:%d%n", name, score);
System.out.format("姓名:%-10s 成绩:%03d%n", name, score);
// 3. 提供清晰输入提示
System.out.println("=== 学生信息录入 ===");
System.out.print("姓名(字符串):");
格式化输出常用占位符
| 占位符 | 说明 | 示例 |
|---|
%s | 字符串 | printf("%s", "Hello") |
%d | 十进制整数 | printf("%d", 100) |
%f | 浮点数 | printf("%.2f", 3.1415) → 3.14 |
%c | 字符 | printf("%c", 'A') |
%b | 布尔值 | printf("%b", true) |
%n | 换行符(跨平台) | |
%x | 十六进制 | printf("%x", 255) → ff |
1.8 包管理
包(Package)概述
包用于组织 Java 类,防止命名冲突,提供访问保护。Java 使用包来模拟文件系统的目录结构。
package 语句
// 必须是 Java 文件的第一行非注释代码
package com.example.util;
// 一个 Java 文件只能有一个 package 语句
public class StringUtils {
public static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}
}
import 语句
// 位于 package 之后,类定义之前
package com.example.app;
import java.util.List; // 导入单个类
import java.util.ArrayList; // 导入单个类
import java.util.*; // 导入整个包(不导入子包)
import static java.lang.Math.*; // 静态导入
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 静态导入后可直接使用方法
double result = sqrt(pow(2, 3) + abs(-4)); // 不需要 Math. 前缀
double pi = PI;
}
}
静态导入详解
// 导入特定静态方法
import static java.lang.Math.max;
import static java.lang.Math.min;
// 导入所有静态成员
import static java.lang.Math.*;
int bigger = max(10, 20); // 无需 Math.max()
int smaller = min(10, 20); // 无需 Math.min()
double pi = PI; // 无需 Math.PI
注意:过多使用静态导入会降低可读性,建议仅在频繁使用(如数学计算、测试断言)时使用。
目录结构对应关系
包名和目录结构必须严格对应:
项目根目录/
└── src/
└── com/
└── example/
├── util/
│ ├── StringUtils.java → package com.example.util;
│ └── DateUtils.java → package com.example.util;
├── model/
│ └── User.java → package com.example.model;
└── app/
└── Main.java → package com.example.app;
包命名规范
| 规范 | 示例 |
|---|
| 全小写字母 | com.example.myapp |
| 域名倒序 | com.google.gson |
| 避免关键字 | 不要用 java、javax 开头 |
| 层级用点分隔 | com.company.project.module |
Java 常用包一览(重点)
| 包名 | 主要功能 |
|---|
java.lang | 核心类(自动导入):String、Math、System、Thread 等 |
java.util | 工具类:集合框架、日期、Scanner、Random 等 |
java.io | 输入输出:File、InputStream、Reader 等 |
java.net | 网络编程:Socket、URL、HttpURLConnection 等 |
java.sql | 数据库操作:Connection、ResultSet、Statement 等 |
java.math | 高精度数学:BigInteger、BigDecimal 等 |
附录:速查表
基本类型默认值与范围
| 类型 | 默认值 | 最小值 | 最大值 | 包装类 |
|---|
byte | 0 | -128 | 127 | Byte |
short | 0 | -32768 | 32767 | Short |
int | 0 | -2^31 | 2^31-1 | Integer |
long | 0L | -2^63 | 2^63-1 | Long |
float | 0.0f | — | — | Float |
double | 0.0d | — | — | Double |
char | '\u0000' | 0 | 65535 | Character |
boolean | false | — | — | Boolean |
数组 vs 集合
| 特性 | 数组 | 集合(ArrayList等) |
|---|
| 长度 | 固定 | 动态可变 |
| 类型 | 可存基本类型和对象 | 只能存对象(自动装箱解决) |
| 性能 | 更高 | 有扩容开销 |
| API | 较少 | 丰富(增删改查排序等) |
本文档涵盖了 Java 基础语法的核心知识点:基本数据类型、变量与内存模型、运算符、类型转换、数组操作、字符串处理、Scanner 输入输出以及包管理机制。
评论