Java 基础
- **Java 基础(仅基础概括,详细了解请自行学习,有大佬觉得不足可以指点下,后续有空会更新)**
- **1.1 程序结构与运行机制**
- **1.2 数据类型与变量**
- **1.3 运算符与表达式**
- **1.4 控制流语句**
- **1.5 面向对象基础**
- **1.6 数组与字符串**
- **Java 基础面试题解析**
- **总结**
- **最后**
Java 基础(仅基础概括,详细了解请自行学习,有大佬觉得不足可以指点下,后续有空会更新)
1.1 程序结构与运行机制
一、Java 程序的核心结构
Java 程序的基本单元是 类(Class),每个程序由类、对象和方法构成。以下是一个典型 Java 程序的代码结构:
// 1. 类定义(文件名必须与公共类名一致)
public class HelloWorld {
// 2. main 方法(程序入口)
public static void main(String[] args) {
// 3. 代码逻辑
System.out.println("Hello, World!");
}
// 4. 其他方法
public static void printMessage(String message) {
System.out.println(message);
}
}
关键组成部分:
- 类(Class):程序的基本容器,包含属性和方法。
- main 方法:程序执行的唯一入口,格式固定为
public static void main(String[] args)。 - 方法(Method):封装特定功能的代码块,如
printMessage。 - 语句:以分号
;结尾的单行代码,如System.out.println()。
二、Java 程序的完整生命周期
Java 程序的运行涉及 编写、编译、类加载、解释执行、JIT优化、终止 多个阶段:
1. 编写源代码(.java 文件)
- 开发者按照 Java 语法规则编写代码,保存为
.java文件。 - 规范要求:公共类名必须与文件名一致,区分大小写。
2. 编译阶段(.java → .class)
- 编译工具:使用
javac命令将源代码编译为字节码文件。 - 编译过程:
- 词法分析:将代码分解为标记(如关键字、变量名)。
- 语法分析:生成抽象语法树(AST),验证语法正确性。
- 语义分析:检查类型匹配、变量声明等逻辑错误。
- 生成字节码:转换为 JVM 可执行的
.class文件。
代码示例:
# 编译 HelloWorld.java
javac HelloWorld.java
# 生成 HelloWorld.class
3. 类加载阶段
- 类加载器(ClassLoader):负责将
.class文件加载到内存。 - 加载过程:
- 加载(Loading):查找
.class文件并读取二进制数据。 - 链接(Linking):
- 验证(Verification):检查字节码是否符合 JVM 规范。
- 准备(Preparation):为静态变量分配内存并赋默认值。
- 解析(Resolution):将符号引用转换为直接引用。
- 初始化(Initialization):执行静态代码块和显式赋值。
- 加载(Loading):查找
双亲委派模型:
- 类加载请求优先委派给父加载器,避免重复加载核心类。
- 加载器层次:
Bootstrap → Extension → Application → 自定义加载器。
4. 字节码执行阶段
- 解释器:逐行解释字节码为机器码执行(启动快,执行慢)。
- JIT 编译器(Just-In-Time):将热点代码编译为本地机器码(执行快,启动慢)。
JIT 优化示例:
public class JITDemo {
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
calculate(i); // 高频调用触发 JIT 编译
}
}
static int calculate(int n) {
return n * n;
}
}
5. 程序终止
- 正常终止:
main方法执行完毕或调用System.exit(0)。 - 异常终止:未捕获的异常或调用
System.exit(非0)。
三、JVM 内存结构与程序运行
JVM 内存分为多个区域,各司其职:
1. 堆(Heap)
- 作用:存储所有对象实例和数组。
- 分区:
- 新生代(Young Generation):新创建的对象(Eden、Survivor0、Survivor1)。
- 老年代(Old Generation):长期存活的对象。
- 参数调优:
-Xms256m # 初始堆大小 -Xmx1024m # 最大堆大小
2. 方法区(Method Area)
- 作用:存储类信息、常量、静态变量(JDK8+ 称为元空间,使用本地内存)。
- 参数调优:
-XX:MetaspaceSize=64m # 初始元空间大小 -XX:MaxMetaspaceSize=256m # 最大元空间大小
3. 虚拟机栈(JVM Stack)
- 作用:存储方法调用的栈帧(局部变量表、操作数栈、动态链接、方法出口)。
- 栈溢出:递归调用过深会抛出
StackOverflowError。
4. 本地方法栈(Native Method Stack)
- 作用:支持 Native 方法(如 C/C++ 代码)的执行。
5. 程序计数器(Program Counter Register)
- 作用:记录当前线程执行的字节码指令地址(线程私有)。
四、Java 的跨平台原理
Java 的 “一次编写,到处运行” 依赖于 JVM 的中间层设计:
1. 字节码(.class 文件)
- 字节码是平台无关的中间代码,由 JVM 解释或编译为本地机器码。
- 查看字节码:
javap -c HelloWorld.class
2. JVM 的平台适配
- 不同操作系统(Windows/Linux/Mac)有对应的 JVM 实现。
- JVM 屏蔽底层差异,提供统一的运行时环境。
代码示例(跨平台验证):
public class CrossPlatformDemo {
public static void main(String[] args) {
// 输出当前操作系统名称
String os = System.getProperty("os.name");
System.out.println("当前系统:" + os);
}
}
// Windows 输出:当前系统:Windows 10
// Linux 输出:当前系统:Linux
五、常见问题与调试技巧
1. 类加载问题
- ClassNotFoundException:类路径配置错误或
.class文件缺失。 - NoClassDefFoundError:类加载后初始化失败(如静态代码块抛出异常)。
2. 内存溢出分析
- 堆溢出(OutOfMemoryError):对象过多或内存泄漏。
- 栈溢出(StackOverflowError):无限递归或方法调用链过深。
代码示例(模拟堆溢出):C
import java.util.ArrayList;
import java.util.List;
public class OOMDemo {
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // 不断添加 1MB 数组
}
}
}
// 运行参数:-Xmx10m -XX:+HeapDumpOnOutOfMemoryError
3. JVM 参数调优
- 打印类加载信息:
java -verbose:class HelloWorld - 输出 GC 日志:
java -Xloggc:gc.log -XX:+PrintGCDetails HelloWorld
六、面试题深度解析
1. 类加载过程中静态代码块的执行顺序是什么?
- 答:
- 父类静态代码块 → 子类静态代码块。
- 父类实例代码块 → 父类构造方法 → 子类实例代码块 → 子类构造方法。
代码验证:
class Parent {
static { System.out.println("父类静态代码块"); }
{ System.out.println("父类实例代码块"); }
Parent() { System.out.println("父类构造方法"); }
}
class Child extends Parent {
static { System.out.println("子类静态代码块"); }
{ System.out.println("子类实例代码块"); }
Child() { System.out.println("子类构造方法"); }
}
public class Test {
public static void main(String[] args) {
new Child();
}
}
/* 输出顺序:
父类静态代码块 → 子类静态代码块 →
父类实例代码块 → 父类构造方法 →
子类实例代码块 → 子类构造方法 */
2. JVM 为什么使用双亲委派模型?
- 答:
- 安全性:防止核心类库被篡改(如自定义
java.lang.String)。 - 唯一性:避免重复加载类,保证类在 JVM 中的唯一性。
- 安全性:防止核心类库被篡改(如自定义
3. 如何打破双亲委派模型?
- 答:
自定义类加载器,重写loadClass方法,直接加载指定类。
代码示例:
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 自定义加载逻辑(如从网络加载)
if (name.startsWith("com.example")) {
return findClass(name);
}
return super.loadClass(name, resolve);
}
}
总结
本节全面解析了 Java 程序的结构设计、运行机制及底层原理,涵盖编译、类加载、内存管理、跨平台实现等核心内容,并提供了代码示例与高频面试题解析。理解这些机制是掌握 Java 高级特性的基础,也是优化程序性能和排查问题的关键。
1.2 数据类型与变量
一、数据类型概述
Java 是强类型语言,所有变量必须先声明类型后使用。数据类型分为两大类:
- 基本数据类型(Primitive Types):直接存储数据值,内存分配在栈上。
- 引用数据类型(Reference Types):存储对象的内存地址,内存分配在堆上。
二、基本数据类型(8种)
Java 定义了 8 种基本数据类型,不可再分,其内存占用和特性如下:
1. 整型
| 类型 | 大小(字节) | 取值范围 | 默认值 | 应用场景 |
|---|---|---|---|---|
byte | 1 | -128 ~ 127 | 0 | 文件读写、网络传输(节省内存) |
short | 2 | -32,768 ~ 32,767 | 0 | 较少使用,兼容旧代码 |
int | 4 | -2^31 ~ 2^31-1(约 ±21亿) | 0 | 最常用的整型(循环计数等) |
long | 8 | -2^63 ~ 2^63-1 | 0L | 时间戳、大范围数值计算 |
代码示例:
byte b = 100; // 合法
short s = 20000; // 合法
int i = 2147483647; // int 最大值
long l = 9223372036854775807L; // 必须加 L 后缀
2. 浮点型
| 类型 | 大小(字节) | 精度范围 | 默认值 | 注意事项 |
|---|---|---|---|---|
float | 4 | 约 ±3.4E38(6-7位有效数字) | 0.0f | 必须加 f 后缀(如 3.14f) |
double | 8 | 约 ±1.7E308(15位有效数字) | 0.0d | 默认浮点类型(如 3.14) |
代码示例:
float f = 3.14f; // 必须加 f 后缀
double d1 = 3.14; // 合法
double d2 = 1.0e-5; // 科学计数法表示 0.00001
3. 字符型
| 类型 | 大小(字节) | 取值范围 | 默认值 | 说明 |
|---|---|---|---|---|
char | 2 | 0 ~ 65,535(Unicode) | ‘\u0000’ | 单引号包裹(如 ‘A’) |
代码示例:
char c1 = 'A'; // 字符字面量
char c2 = 65; // ASCII 编码(等价于 'A')
char c3 = '\u0041'; // Unicode 编码(等价于 'A')
char c4 = '中'; // 中文字符(Unicode支持)
4. 布尔型
| 类型 | 大小(bit) | 取值 | 默认值 | 说明 |
|---|---|---|---|---|
boolean | 1(JVM实现相关) | true/false | false | 不能与整数类型相互转换 |
代码示例:
boolean flag1 = true;
boolean flag2 = (10 > 5); // 表达式结果为 true
// int num = flag1; // 错误:无法将 boolean 转为 int
三、引用数据类型
引用类型变量存储对象的堆内存地址,所有引用类型默认值为 null。
1. 类(Class)
- 定义:通过
new关键字实例化对象。 - 示例:
String str = new String("Hello"); // 字符串对象 Date date = new Date(); // 日期对象
2. 接口(Interface)
- 定义:通过实现类实例化。
- 示例:
List<String> list = new ArrayList<>(); // List 是接口,ArrayList 是实现类
3. 数组(Array)
- 定义:固定长度的同类型元素集合。
- 示例:
int[] arr1 = new int[5]; // 动态初始化(默认值 0) int[] arr2 = {1, 2, 3}; // 静态初始化 String[][] matrix = new String[3][3]; // 二维数组
4. 枚举(Enum)
- 定义:预定义的常量集合(JDK5+)。
- 示例:
enum Day { MONDAY, TUESDAY, WEDNESDAY } // 定义枚举 Day today = Day.MONDAY; // 使用枚举常量
四、变量作用域与生命周期
变量的作用域决定了其可访问范围,生命周期取决于其存储位置。
1. 局部变量(Local Variables)
- 定义位置:方法、构造方法或代码块内部。
- 生命周期:从声明处开始,到代码块结束销毁。
- 特点:无默认值,必须显式初始化。
- 示例:
public void calculate() { int sum = 0; // 局部变量 for (int i = 0; i < 10; i++) { // i 是循环内的局部变量 sum += i; } // System.out.println(i); // 错误:i 超出作用域 }
2. 成员变量(Instance Variables)
- 定义位置:类内部,方法外部。
- 生命周期:随对象创建而存在,随对象回收而销毁。
- 特点:有默认值(如
int默认为 0),对象间独立。 - 示例:
public class Person { private String name; // 成员变量(默认值 null) private int age; // 成员变量(默认值 0) }
3. 静态变量(Class Variables)
- 定义位置:类内部,用
static修饰。 - 生命周期:随类加载而存在,随程序结束销毁。
- 特点:类所有实例共享,通过类名直接访问。
- 示例:
public class Counter { public static int count = 0; // 静态变量 } // 访问静态变量 Counter.count = 10;
五、类型转换与运算
1. 自动类型提升(隐式转换)
- 规则:小范围类型 → 大范围类型(无数据丢失)。
- 方向:
byte → short → int → long → float → double。 - 示例:
int a = 100; long b = a; // 自动提升为 long double c = b; // 自动提升为 double
2. 强制类型转换(显式转换)
- 规则:大范围类型 → 小范围类型(可能丢失精度)。
- 语法:
(目标类型) 值。 - 示例:
double d = 3.14; int i = (int) d; // i = 3(丢失小数部分) long l = 3000000000L; int j = (int) l; // j = -1294967296(溢出)
3. 表达式中的类型提升
- 规则:
- byte、short、char 参与运算时自动提升为 int。
- 最终结果为表达式中最大类型。
- 示例:
byte b1 = 10; byte b2 = 20; // byte sum = b1 + b2; // 错误:b1 + b2 结果为 int int sum = b1 + b2; // 正确
六、包装类与自动装箱拆箱
Java 为每个基本类型提供了包装类,支持对象化操作。
1. 包装类对应关系
| 基本类型 | 包装类 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
2. 自动装箱与拆箱
- 装箱:基本类型 → 包装类对象(
Integer.valueOf(10))。 - 拆箱:包装类对象 → 基本类型(
intValue())。 - 代码示例:
Integer num1 = 10; // 自动装箱(等价于 Integer.valueOf(10)) int num2 = num1; // 自动拆箱(等价于 num1.intValue())
3. 注意事项
- 对象比较:使用
equals()而非==。Integer a = 127; Integer b = 127; System.out.println(a == b); // true(缓存范围内) Integer c = 128; Integer d = 128; System.out.println(c == d); // false(超出缓存范围)
七、代码示例与常见问题
1. 变量作用域冲突
public class ScopeConflict {
int num = 10; // 成员变量
public void printNum() {
int num = 20; // 局部变量
System.out.println(num); // 输出 20(就近原则)
System.out.println(this.num); // 输出 10(使用 this 访问成员变量)
}
}
2. 类型转换精度丢失
public class PrecisionLoss {
public static void main(String[] args) {
// 浮点转整型(直接截断)
double d = 3.99;
int i = (int) d; // i = 3(非四舍五入!)
System.out.println(i);
// 大范围整型转小范围(溢出)
long l = 3000000000L;
int j = (int) l; // j = -1294967296
System.out.println(j);
}
}
3. 自动装箱的性能问题
public class AutoBoxingPerformance {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Long sum = 0L; // 错误:使用包装类导致频繁装箱
for (int i = 0; i < Integer.MAX_VALUE; i++) {
sum += i; // 等价于 sum = Long.valueOf(sum.longValue() + i)
}
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
}
}
// 优化:改为基本类型 long sum = 0L;
八、面试题解析
-
Integer a = 100和Integer b = 100用==比较的结果是什么?为什么?- 答:
true。因为 Integer 对 -128~127 的数值做了缓存,直接复用对象。
- 答:
-
Java 中是否存在无符号整型?
- 答:Java 没有无符号整型,但可通过位运算模拟(如
int表示 0~4,294,967,295)。
- 答:Java 没有无符号整型,但可通过位运算模拟(如
-
float f = 3.14是否合法?为什么?- 答:不合法。浮点字面量默认是
double类型,需强制转换或加f后缀。
- 答:不合法。浮点字面量默认是
总结
本章深入解析了 Java 数据类型与变量的核心概念,涵盖基本类型、引用类型、作用域规则、类型转换机制及常见陷阱。理解这些知识是编写高效、健壮 Java 程序的基础,也是面试中高频考察点。
1.3 运算符与表达式
一、运算符分类与优先级
Java 提供丰富的运算符,按功能可分为 算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、条件(三目)运算符 等。运算符的优先级决定了表达式中运算的执行顺序,优先级高的先执行,同级运算符按结合性(左结合或右结合)执行。
1. 运算符优先级表

记忆口诀:单目乘除位关系,逻辑三目后赋值。
二、算术运算符
算术运算符用于数值计算,支持基本数据类型(byte、short、int、long、float、double、char)。
1. 基本算术运算符
| 运算符 | 描述 | 示例 | 注意事项 |
|---|---|---|---|
+ | 加法 | int sum = 5 + 3; | 可连接字符串(如 "A" + 1) |
- | 减法 | int diff = 5 - 3; | |
* | 乘法 | int product = 5 * 3; | |
/ | 除法 | int quotient = 5 / 2; → 2(整数除法舍去小数) | 分母为0抛出 ArithmeticException |
% | 取模 | int mod = 5 % 3; → 2 | 结果符号与被除数一致(如 -5 % 3 = -2) |
代码示例:
int a = 10 / 3; // a = 3(整数除法)
double b = 10 / 3; // b = 3.0(隐式类型提升)
double c = 10.0 / 3; // c = 3.333...
int d = 10 % 3; // d = 1
int e = -10 % 3; // e = -1
2. 自增与自减运算符
| 运算符 | 描述 | 示例(假设 i=5) | 结果(i的值) |
|---|---|---|---|
++i | 先自增后取值 | int a = ++i; | a=6, i=6 |
i++ | 先取值后自增 | int b = i++; | b=5, i=6 |
--i | 先自减后取值 | int c = --i; | c=4, i=4 |
i-- | 先取值后自减 | int d = i--; | d=5, i=4 |
代码示例:
int x = 5;
int y = x++ + ++x; // y = 5 + 7 = 12, x=7
三、关系运算符
关系运算符用于比较两个值的大小或相等性,返回布尔值 true 或 false。
| 运算符 | 描述 | 示例 | 结果(假设 a=5, b=3) |
|---|---|---|---|
== | 等于 | a == b | false |
!= | 不等于 | a != b | true |
> | 大于 | a > b | true |
< | 小于 | a < b | false |
>= | 大于等于 | a >= 5 | true |
<= | 小于等于 | a <= 3 | false |
代码示例:
boolean isEven = (10 % 2 == 0); // true
boolean isPositive = (x > 0); // 判断是否为正数
四、逻辑运算符
逻辑运算符用于组合布尔表达式,支持短路特性(Short-Circuit)。
1. 基本逻辑运算符
| 运算符 | 描述 | 示例 | 短路特性 | ||||
|---|---|---|---|---|---|---|---|
&& | 逻辑与 | a > 0 && b > 0 | 左操作数为 false 时跳过右操作数 | ||||
|| | 逻辑或 | a > 0 || b > 0 | 左操作数为 true 时跳过右操作数 | ||||
! | 逻辑非 | !(a > 0) | 单目运算符 |
代码示例(短路特性验证):
public class ShortCircuitDemo {
public static void main(String[] args) {
int x = 5;
// 短路与:左侧为 false,右侧不执行
boolean result1 = (x < 0) && (x++ > 0);
System.out.println("result1=" + result1 + ", x=" + x); // result1=false, x=5
// 非短路与:右侧始终执行
boolean result2 = (x < 0) & (x++ > 0);
System.out.println("result2=" + result2 + ", x=" + x); // result2=false, x=6
}
}
2. 位运算符(逻辑扩展)
| 运算符 | 描述 | 示例 | 说明 | ||
|---|---|---|---|---|---|
& | 按位与 | 5 & 3 → 1 | 两位均为1时结果为1 | ||
| | 按位或 | 5 | 3→7 | 任一位为1时结果为1 | ||
^ | 按位异或 | 5 ^ 3 → 6 | 两位不同时结果为1 | ||
~ | 按位取反 | ~5 → -6 | 所有位取反 |
代码示例(权限控制):
// 定义权限标志位
final int READ = 1 << 0; // 0001
final int WRITE = 1 << 1; // 0010
final int EXECUTE = 1 << 2;// 0100
// 用户权限组合
int userPermissions = READ | WRITE; // 0011
// 检查是否有写权限
boolean canWrite = (userPermissions & WRITE) != 0; // true
// 移除写权限
userPermissions &= ~WRITE; // 0011 & 1101 = 0001
五、赋值运算符
赋值运算符用于为变量赋值,支持复合赋值(如 +=)。

代码示例:
int x = 10;
x += 5; // x = 15
x <<= 2; // x = 60(15 << 2 = 60)
六、条件(三目)运算符
三目运算符是 Java 中唯一的条件运算符,简化 if-else 逻辑。
语法:
条件 ? 表达式1 : 表达式2
执行逻辑:
- 若条件为
true,返回表达式1的值。 - 若条件为
false,返回表达式2的值。
代码示例:
int a = 10, b = 20;
int max = (a > b) ? a : b; // max = 20
String result = (a % 2 == 0) ? "偶数" : "奇数"; // "偶数"
七、表达式与类型转换
表达式是由变量、常量和运算符组成的计算式,其结果的类型由操作数和运算符决定。
1. 表达式中的类型提升
- 规则:
byte、short、char参与运算时自动提升为int。- 最终结果类型为表达式中最大范围类型。
代码示例:
byte b1 = 10;
byte b2 = 20;
// byte sum = b1 + b2; // 错误:b1 + b2 结果为 int
int sum = b1 + b2; // 正确
double d = 10 + 3.14; // 结果为 double
2. 强制类型转换
- 语法:
(目标类型) 表达式 - 风险:可能导致精度丢失或溢出。
代码示例:
double pi = 3.14159;
int intPi = (int) pi; // intPi = 3(截断小数)
long bigValue = 3000000000L;
int badCast = (int) bigValue; // badCast = -1294967296(溢出)
八、常见问题与面试题
1. i++ 与 ++i 的区别是什么?
- 答:
i++返回自增前的值,++i返回自增后的值。
代码验证:
int i = 5;
int a = i++; // a=5, i=6
int b = ++i; // b=7, i=7
2. 如何避免浮点数运算的精度问题?
- 答:使用
BigDecimal类进行精确计算。
示例:
import java.math.BigDecimal;
public class PrecisionDemo {
public static void main(String[] args) {
BigDecimal d1 = new BigDecimal("0.1");
BigDecimal d2 = new BigDecimal("0.2");
System.out.println(d1.add(d2)); // 0.3(精确)
}
}
3. 以下代码输出什么?为什么?
System.out.println(1 + 2 + "3"); // "33"
System.out.println("1" + 2 + 3); // "123"
- 答:
- 第一个表达式从左到右计算:
1 + 2 = 3→3 + "3" = "33"。 - 第二个表达式从左到右计算:
"1" + 2 = "12"→"12" + 3 = "123"。
- 第一个表达式从左到右计算:
4. 如何用位运算实现快速乘除?
- 答:左移一位等价于乘2,右移一位等价于除2。
代码示例:
int x = 8;
int multiplyBy4 = x << 2; // 32
int divideBy2 = x >> 1; // 4
总结
本章全面解析了 Java 运算符与表达式的核心知识,涵盖算术、关系、逻辑、位运算等操作,并结合代码示例与高频面试题强化理解。掌握运算符的优先级、类型转换规则及常见陷阱,是编写高效、健壮 Java 程序的关键。
1.4 控制流语句
一、控制流概述
控制流语句是编程语言的核心逻辑工具,用于控制代码的执行顺序和流程分支。Java 提供了丰富的控制结构,包括 条件判断、循环控制、跳转语句 等,帮助开发者实现复杂业务逻辑。本章将深入解析以下内容:
- 条件语句:
if-else、switch-case - 循环语句:
for、while、do-while、增强型for - 跳转控制:
break、continue、return - 嵌套与标签:多层循环控制
- 控制流优化:避免死循环、性能陷阱与最佳实践
二、条件语句
条件语句根据布尔表达式的结果决定代码执行路径。
1. if-else 语句
语法:
if (布尔表达式) {
// 条件为 true 时执行
} else if (布尔表达式) {
// 其他条件检查
} else {
// 所有条件为 false 时执行
}
执行流程:

代码示例:
int score = 85;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B"); // 输出 B
} else {
System.out.println("C");
}
注意事项:
else if可无限级联,但应避免超过 3 层(改用switch或策略模式)。- 使用大括号
{}明确代码块范围,单行语句也建议添加。
2. switch-case 语句
语法:
switch (表达式) {
case 值1:
// 匹配值1时执行
break;
case 值2:
// 匹配值2时执行
break;
default:
// 默认执行
}
执行规则:
- 表达式类型:
byte、short、int、char、String(JDK7+)、枚举(JDK5+)。 - case 穿透:未加
break时,会继续执行后续case代码。 default:非必须,但建议始终包含。
代码示例(带穿透效果):
int day = 3;
switch (day) {
case 1:
case 2:
case 3:
case 4:
case 5:
System.out.println("工作日"); // day=3 时输出
break;
case 6:
case 7:
System.out.println("周末");
break;
default:
System.out.println("无效输入");
}
JDK14+ 增强特性:
- 箭头语法:简化代码,自动阻断穿透。
yield返回值:可作为表达式使用。
String result = switch (day) {
case 1, 2, 3, 4, 5 -> "工作日";
case 6, 7 -> "周末";
default -> {
yield "无效输入";
}
};
三、循环语句
循环语句用于重复执行代码块,直到满足终止条件。
1. for 循环
语法:
for (初始化; 布尔表达式; 迭代) {
// 循环体
}
执行流程:
代码示例(打印九九乘法表):
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
System.out.print(j + "×" + i + "=" + (i * j) + "\t");
}
System.out.println();
}
增强型 for(for-each):
- 适用场景:遍历数组或集合(实现
Iterable接口的对象)。 - 语法:
for (元素类型 变量 : 集合或数组) { // 使用变量 }
代码示例:
int[] numbers = {1, 2, 3};
for (int num : numbers) {
System.out.println(num);
}
// 等价于传统 for 循环:
for (int i = 0; i < numbers.length; i++) {
int num = numbers[i];
System.out.println(num);
}
2. while 循环
语法:
while (布尔表达式) {
// 循环体
}
特点:先判断条件,后执行循环体(可能一次都不执行)。
代码示例(计算数字位数):
int num = 12345;
int count = 0;
while (num != 0) {
num /= 10;
count++;
}
System.out.println("位数:" + count); // 输出 5
3. do-while 循环
语法:
do {
// 循环体
} while (布尔表达式);
特点:先执行循环体,再判断条件(至少执行一次)。
代码示例(密码输入验证):
Scanner scanner = new Scanner(System.in);
String password;
do {
System.out.print("请输入密码:");
password = scanner.nextLine();
} while (!"123456".equals(password));
System.out.println("登录成功!");
四、跳转控制语句
跳转语句用于显式改变代码执行顺序。
1. break
- 作用:退出当前循环或
switch语句。 - 带标签的
break:跳出多层嵌套循环。
代码示例(标签跳出外层循环):
outerLoop:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
break outerLoop; // 直接跳出外层循环
}
System.out.println(i + "," + j);
}
}
/* 输出:
0,0
0,1
0,2
1,0 */
2. continue
- 作用:跳过本次循环剩余代码,进入下一次迭代。
- 带标签的
continue:跳转到指定循环的下一轮。
代码示例(跳过偶数):
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue;
}
System.out.print(i + " "); // 输出 1 3 5 7 9
}
3. return
- 作用:结束当前方法执行,返回结果(若方法有返回类型)。
- 注意:在
void方法中可单独使用,无需返回值。
代码示例:
public int max(int a, int b) {
if (a > b) {
return a;
}
return b;
}
五、控制流高级应用
1. 死循环与规避
- 典型死循环:
while (true) { ... } // 依赖内部 break 退出 for (;;) { ... } // 等效写法 - 规避策略:
- 设置合理的循环终止条件。
- 使用
break或return及时退出。
代码示例(安全读取用户输入):
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("输入 exit 退出:");
String input = scanner.nextLine();
if ("exit".equalsIgnoreCase(input)) {
break;
}
// 处理其他逻辑
}
2. 控制流性能优化
- 减少循环嵌套:多层嵌套复杂度为 O(n^k),尽量优化为单层。
- 避免重复计算:将循环内不变的计算移至外部。
- 使用增强型
for:遍历集合时代码更简洁,但无法修改集合。
代码示例(优化循环性能):
// 优化前:每次循环计算 size()
for (int i = 0; i < list.size(); i++) { ... }
// 优化后:预先获取 size
int size = list.size();
for (int i = 0; i < size; i++) { ... }
3. 递归与栈溢出
- 递归:方法调用自身,需定义终止条件。
- 风险:过深递归会导致
StackOverflowError。
代码示例(递归计算阶乘):
public static int factorial(int n) {
if (n == 1) {
return 1;
}
return n * factorial(n - 1);
}
// factorial(5) = 120
栈溢出示例:
public static void infiniteRecursion() {
infiniteRecursion(); // 无限递归,抛出 StackOverflowError
}
六、常见问题与面试题
1. switch 能否使用 long 作为条件?
- 答:不能。
switch条件仅支持int、char、String、枚举等类型。
2. 如何避免 switch 的 case 穿透问题?
- 答:每个
case后添加break,或使用 JDK14+ 的箭头语法。
3. for 和 while 如何选择?
- 答:已知循环次数用
for,未知次数用while。
4. 以下代码输出什么?
for (int i = 0; i < 5; i++) {
if (i == 2) {
continue;
}
System.out.print(i + " ");
}
- 答:
0 1 3 4(跳过 i=2)。
5. 如何中断外层循环?
- 答:使用带标签的
break,如break outerLoop;。
总结
本章全面解析了 Java 控制流语句的核心机制,涵盖条件分支、循环结构、跳转控制及高级应用场景,并结合代码示例与高频面试题强化理解。合理运用控制流是编写高效、可维护代码的基础,开发者需特别注意避免死循环、栈溢出等常见陷阱。
1.5 面向对象基础
一、面向对象编程(OOP)核心概念
面向对象编程(Object-Oriented Programming)是 Java 的核心设计思想,围绕 对象 和 类 构建程序,其核心特性包括 封装、继承、多态、抽象。本章将深入解析这些概念及其实现。
二、类与对象
1. 类(Class)的定义
- 类 是对象的模板,定义了对象的属性(成员变量)和行为(方法)。
- 语法:
[访问修饰符] class 类名 { // 成员变量 // 构造方法 // 成员方法 }
示例:
public class Person {
// 成员变量(属性)
private String name;
private int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 成员方法(行为)
public void introduce() {
System.out.println("我叫" + name + ",今年" + age + "岁。");
}
}
2. 对象(Object)的创建
- 对象 是类的实例,通过
new关键字分配内存并初始化。 - 语法:
类名 对象名 = new 类名(构造参数);
代码示例:
Person alice = new Person("Alice", 25);
alice.introduce(); // 输出:我叫Alice,今年25岁。
3. 对象内存模型
- 堆内存:存储对象实例(成员变量)。
- 栈内存:存储对象引用(变量名指向堆地址)。
graph LR
A[栈内存] -->|引用| B[堆内存: Person对象]
B --> C[name: "Alice"]
B --> D[age: 25]
三、封装(Encapsulation)
封装通过访问控制隐藏对象内部细节,提供安全的属性访问方式。
1. 访问修饰符

2. Getter 与 Setter
- Getter:提供私有属性的读取方法。
- Setter:提供私有属性的修改方法(可添加校验逻辑)。
代码示例:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("姓名不能为空!");
}
this.name = name;
}
}
3. 不可变类(Immutable Class)
- 特点:对象创建后状态不可修改(如
String)。 - 实现方式:
- 所有字段
private final。 - 不提供 Setter 方法。
- 返回新对象而非修改原对象。
- 所有字段
代码示例:
public final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
// 返回新对象而非修改当前对象
public ImmutablePoint move(int dx, int dy) {
return new ImmutablePoint(x + dx, y + dy);
}
}
四、继承(Inheritance)
继承允许子类复用父类的属性和方法,并扩展新功能。
1. extends 关键字
- 语法:
class 子类 extends 父类 { ... }
示例:
class Animal {
public void eat() {
System.out.println("动物正在进食...");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("汪汪!");
}
}
// 使用
Dog dog = new Dog();
dog.eat(); // 继承自 Animal
dog.bark(); // 子类扩展方法
2. super 关键字
- 访问父类成员:调用父类构造方法、方法或属性。
- 构造方法调用:子类构造方法首行必须调用
super(...)(默认隐含super())。
代码示例:
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name); // 调用父类构造方法
this.breed = breed;
}
}
3. 方法重写(Override)
- 规则:
- 方法名、参数列表、返回类型相同。
- 访问权限不能低于父类(如父类
protected,子类可为public)。 - 不能重写
private、final、static方法。
代码示例:
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪!");
}
}
4. final 关键字
- 修饰类:类不可被继承(如
String)。 - 修饰方法:方法不可被重写。
- 修饰变量:变量值不可修改(常量)。
代码示例:
final class ImmutableClass { ... } // 不可继承
class Parent {
public final void finalMethod() { ... } // 不可重写
}
五、多态(Polymorphism)
多态允许父类引用指向子类对象,根据实际类型调用方法。
1. 向上转型(Upcasting)
- 定义:父类引用指向子类对象。
- 特点:只能调用父类声明的方法,实际执行子类重写的方法。
代码示例:
Animal animal = new Dog(); // 向上转型
animal.makeSound(); // 调用 Dog 的 makeSound()
// animal.bark(); // 错误:Animal 类无 bark() 方法
2. 动态绑定(Dynamic Binding)
- 机制:运行时根据对象实际类型确定调用方法(非编译时)。
- 实现:JVM 通过方法表(虚方法表)实现。
代码示例:
public class Test {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.makeSound(); // 输出 "汪汪!"
a2.makeSound(); // 输出 "喵喵!"
}
}
3. instanceof 运算符
- 作用:检查对象是否为指定类型或其子类实例。
- 应用场景:向下转型前的类型校验。
代码示例:
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型
dog.bark();
}
六、抽象类与接口
1. 抽象类(Abstract Class)
- 定义:用
abstract修饰,可包含抽象方法(无实现)。 - 特点:
- 不能实例化,需子类继承并实现抽象方法。
- 可包含普通方法、成员变量、构造方法。
代码示例:
abstract class Shape {
private String color;
public Shape(String color) {
this.color = color;
}
// 抽象方法(无实现)
public abstract double area();
// 普通方法
public String getColor() {
return color;
}
}
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
2. 接口(Interface)
- 定义:用
interface定义,默认方法为public abstract。 - 特点(JDK8+):
- 可包含默认方法(
default)、静态方法(static)。 - 支持多实现(类可继承多个接口)。
- 可包含默认方法(
代码示例:
interface Flyable {
// 抽象方法
void fly();
// 默认方法(JDK8+)
default void land() {
System.out.println("正在着陆...");
}
// 静态方法(JDK8+)
static boolean canFly(Flyable obj) {
return obj != null;
}
}
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("鸟儿飞翔");
}
}
3. 抽象类 vs 接口

七、静态成员
1. 静态变量(类变量)
- 定义:用
static修饰,类级别共享。 - 访问:通过类名直接访问(
类名.变量名)。
代码示例:
public class Counter {
public static int count = 0; // 静态变量
public Counter() {
count++;
}
}
// 使用
Counter c1 = new Counter();
Counter c2 = new Counter();
System.out.println(Counter.count); // 输出 2
2. 静态方法
- 定义:用
static修饰,只能访问静态成员。 - 调用:通过类名调用(
类名.方法名())。
代码示例:
public class MathUtils {
public static int max(int a, int b) {
return a > b ? a : b;
}
}
// 调用
int max = MathUtils.max(10, 20);
3. 静态代码块
- 作用:类加载时执行,用于初始化静态资源。
- 语法:
static { // 初始化代码 }
代码示例:
public class DatabaseConfig {
private static Properties props;
static {
props = new Properties();
try {
props.load(new FileInputStream("config.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
八、内部类
内部类定义在另一个类的内部,可访问外部类成员,增强封装性。
1. 成员内部类
- 定义:非静态内部类,依赖外部类实例。
- 访问:通过外部类实例创建内部类对象。
代码示例:
public class Outer {
private int value = 10;
class Inner {
void printValue() {
System.out.println(value); // 直接访问外部类成员
}
}
}
// 使用
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.printValue();
2. 静态内部类
- 定义:用
static修饰,不依赖外部类实例。 - 访问:直接通过外部类名创建。
代码示例:
public class Outer {
static class StaticInner {
void print() {
System.out.println("静态内部类");
}
}
}
// 使用
Outer.StaticInner inner = new Outer.StaticInner();
inner.print();
3. 局部内部类
- 定义:在方法内定义的类,仅方法内可见。
代码示例:
public class Outer {
public void method() {
class LocalInner {
void print() {
System.out.println("局部内部类");
}
}
LocalInner inner = new LocalInner();
inner.print();
}
}
4. 匿名内部类
- 定义:无类名,直接实现接口或继承类。
- 应用场景:事件监听、线程创建等。
代码示例(实现 Runnable 接口):
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类实现线程");
}
}).start();
九、常见面试题解析
1. == 和 equals() 的区别?
- 答:
==比较对象内存地址(基本类型比较值)。equals()默认比较地址,可重写为内容比较(如String)。
2. 方法重写(Override)与重载(Overload)的区别?
- 重写:子类覆盖父类方法,方法签名相同。
- 重载:同一类中方法名相同,参数列表不同。
3. 抽象类与接口的应用场景?
- 抽象类:定义通用模板(如
InputStream)。 - 接口:定义行为规范(如
Comparable、Runnable)。
4. 如何实现单例模式?
- 双重校验锁:
public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
总结
本章系统讲解了面向对象编程的核心概念,涵盖类与对象、封装、继承、多态、抽象类、接口、静态成员及内部类,并结合代码示例与面试题强化理解。掌握这些知识是构建复杂 Java 应用的基础。
1.6 数组与字符串
一、数组(Array)
数组是 Java 中存储 固定长度、相同类型数据 的线性结构,通过索引访问元素。数组在内存中连续分配,支持高效随机访问,但长度不可变。
1. 数组的声明与初始化
声明方式:
// 方式1: 指定类型和长度
数据类型[] 数组名 = new 数据类型[长度];
int[] arr1 = new int[5]; // 默认值: 0
// 方式2: 直接赋值
数据类型[] 数组名 = {元素1, 元素2, ...};
int[] arr2 = {1, 2, 3, 4, 5};
// 方式3: 匿名数组(适用于方法传参)
new int[] {1, 2, 3};
多维数组:
// 二维数组声明
int[][] matrix1 = new int[3][3]; // 3行3列,默认值0
int[][] matrix2 = {{1,2}, {3,4}, {5,6}};
// 不规则数组(各行长度不同)
int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[2];
jaggedArray[1] = new int[3];
jaggedArray[2] = new int[4];
2. 数组的访问与遍历
访问元素:
int[] arr = {10, 20, 30};
int first = arr[0]; // 10
arr[1] = 200; // 修改元素值
遍历方式:
// 1. 普通 for 循环
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// 2. 增强型 for 循环(只读)
for (int num : arr) {
System.out.println(num);
}
// 3. 使用 Arrays.toString() 快速打印
System.out.println(Arrays.toString(arr)); // [10, 200, 30]
3. 数组的常用操作
拷贝数组:
// 1. System.arraycopy(高效)
int[] src = {1, 2, 3};
int[] dest = new int[3];
System.arraycopy(src, 0, dest, 0, src.length);
// 2. Arrays.copyOf(简洁)
int[] copy = Arrays.copyOf(src, src.length);
// 3. clone() 方法
int[] clone = src.clone();
排序与查找:
int[] numbers = {5, 3, 9, 1};
Arrays.sort(numbers); // 排序: [1, 3, 5, 9]
int index = Arrays.binarySearch(numbers, 5); // 二分查找: 索引2
填充与比较:
int[] arr = new int[5];
Arrays.fill(arr, 100); // 所有元素填充为100
boolean isEqual = Arrays.equals(arr1, arr2); // 比较数组内容
4. 数组的局限性
- 长度固定:无法动态扩展,需手动扩容或改用
ArrayList。 - 类型限制:只能存储单一类型数据。
- 内存浪费:预先分配连续内存,可能造成空间冗余。
二、字符串(String)
Java 中的字符串是 java.lang.String 类的对象,具有 不可变性(Immutable),所有修改操作均返回新字符串对象。
1. 字符串的创建
创建方式:
// 方式1: 字面量(推荐,利用字符串常量池)
String s1 = "Hello";
// 方式2: new 关键字(强制创建新对象)
String s2 = new String("Hello");
// 方式3: 字符数组转换
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String s3 = new String(chars);
内存模型:
2. 字符串的不可变性
- 不可变原理:
String类内部使用final char[] value存储字符。- 所有修改操作(如
substring、replace)均生成新对象。
代码示例:
String s1 = "Hello";
String s2 = s1.concat(" World"); // 新对象 "Hello World"
System.out.println(s1); // Hello(原对象未变)
设计优势:
- 线程安全:无需同步即可共享。
- 缓存哈希值:提升
hashCode()性能。 - 安全性:防止敏感数据被篡改(如数据库密码)。
3. 字符串的常用方法
基本操作:
String str = "Hello, Java!";
// 获取长度
int len = str.length(); // 11
// 获取字符
char ch = str.charAt(1); // 'e'
// 子字符串
String sub1 = str.substring(7); // "Java!"
String sub2 = str.substring(7, 9); // "Ja"
// 查找索引
int index1 = str.indexOf('J'); // 7
int index2 = str.lastIndexOf('a'); // 9
字符串转换:
// 大小写转换
String upper = str.toUpperCase(); // "HELLO, JAVA!"
String lower = str.toLowerCase(); // "hello, java!"
// 去除首尾空格
String trimmed = " Hello ".trim(); // "Hello"
// 替换字符
String replaced = str.replace('a', 'A'); // "Hello, JAvA!"
分割与拼接:
// 分割为数组
String[] parts = "A,B,C".split(","); // ["A", "B", "C"]
// 拼接字符串
String joined = String.join("-", "2023", "08", "15"); // "2023-08-15"
4. 字符串比较
equals() vs ==:
equals():比较字符串内容(区分大小写)。==:比较对象内存地址。
代码示例:
String s1 = "Hello";
String s2 = new String("Hello");
String s3 = "Hello";
System.out.println(s1.equals(s2)); // true(内容相同)
System.out.println(s1 == s2); // false(地址不同)
System.out.println(s1 == s3); // true(常量池复用)
忽略大小写比较:
boolean isEqual = "HELLO".equalsIgnoreCase("hello"); // true
5. 可变字符串:StringBuilder 与 StringBuffer
适用场景:
StringBuilder:单线程环境,非线程安全,性能高。StringBuffer:多线程环境,线程安全(方法用synchronized修饰),性能略低。
常用方法:
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // "Hello World"
sb.insert(5, ","); // "Hello, World"
sb.delete(5, 6); // "Hello World"
sb.reverse(); // "dlroW olleH"
String result = sb.toString();
性能对比:
// String 拼接(产生多个中间对象)
String s = "";
for (int i = 0; i < 10000; i++) {
s += i; // 性能差!
}
// StringBuilder 拼接(高效)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
三、字符串的优化与陷阱
1. 字符串常量池(String Pool)
- 位置:堆内存中的特殊区域,存储字符串字面量。
- 复用机制:相同字面量共享同一对象,减少内存消耗。
代码示例:
String s1 = "Hello"; // 常量池新建对象
String s2 = "Hello"; // 复用常量池对象
String s3 = new String("Hello"); // 强制创建新对象
2. intern() 方法
- 作用:将字符串对象添加到常量池(若不存在),并返回池中引用。
示例:
String s1 = new String("Hello").intern();
String s2 = "Hello";
System.out.println(s1 == s2); // true
3. 字符串拼接性能优化
- 避免循环内使用
+:改用StringBuilder。 - 预估容量:初始化时指定
StringBuilder容量,减少扩容次数。
代码示例:
StringBuilder sb = new StringBuilder(100); // 预分配容量
sb.append("Hello").append(" World");
4. 常见陷阱
陷阱1:使用 == 比较字符串内容
String s1 = new String("Hello");
String s2 = "Hello";
System.out.println(s1 == s2); // false(应使用 equals())
陷阱2:频繁修改字符串
// 错误示例:每次循环生成新对象
String result = "";
for (int i = 0; i < 1000; i++) {
result += i; // 性能极差!
}
// 正确做法:使用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String result = sb.toString();
四、综合应用示例
1. 统计字符出现次数
public static Map<Character, Integer> countChars(String str) {
Map<Character, Integer> map = new HashMap<>();
for (char c : str.toCharArray()) {
map.put(c, map.getOrDefault(c, 0) + 1);
}
return map;
}
// 调用
System.out.println(countChars("abracadabra"));
// 输出:{a=5, b=2, r=2, c=1, d=1}
2. 判断回文字符串
public static boolean isPalindrome(String s) {
String cleaned = s.replaceAll("[^A-Za-z0-9]", "").toLowerCase();
int left = 0, right = cleaned.length() - 1;
while (left < right) {
if (cleaned.charAt(left++) != cleaned.charAt(right--)) {
return false;
}
}
return true;
}
// 调用
System.out.println(isPalindrome("A man, a plan, a canal: Panama")); // true
五、面试题解析
1. String s = new String("abc") 创建了几个对象?
- 答:1 或 2 个。若常量池中已存在 “abc”,则只在堆中创建新对象;否则先在常量池创建 “abc”,再在堆中创建对象。
2. 如何实现字符串反转?
// 方法1: StringBuilder
String reversed = new StringBuilder(str).reverse().toString();
// 方法2: 字符数组
char[] chars = str.toCharArray();
int left = 0, right = chars.length - 1;
while (left < right) {
char temp = chars[left];
chars[left++] = chars[right];
chars[right--] = temp;
}
String reversed = new String(chars);
3. 如何判断两个字符串是否为变位词(Anagram)?
public static boolean isAnagram(String s1, String s2) {
if (s1.length() != s2.length()) return false;
char[] arr1 = s1.toCharArray();
char[] arr2 = s2.toCharArray();
Arrays.sort(arr1);
Arrays.sort(arr2);
return Arrays.equals(arr1, arr2);
}
总结
本章深入解析了数组与字符串的核心操作、内存模型及性能优化策略,涵盖数组的声明遍历、字符串的不可变性、StringBuilder 的高效拼接等关键知识点,并通过实际案例与面试题强化理解。合理使用数组和字符串处理工具,是提升 Java 编程效率与代码质量的基础。
Java 基础面试题解析
一、数据类型与变量
1. == 和 equals() 的区别是什么?
- 答:
==比较基本数据类型的值是否相等,或引用类型的内存地址是否相同。equals()默认比较引用地址(与==相同),但可被重写为内容比较(如String、Integer)。
示例:
String s1 = new String("abc"); String s2 = new String("abc"); System.out.println(s1 == s2); // false(地址不同) System.out.println(s1.equals(s2)); // true(内容相同)
2. Java 中基本数据类型有哪些?各占多少字节?
- 答:
类型 字节 默认值 取值范围 byte1 0 -128 ~ 127 short2 0 -32768 ~ 32767 int4 0 -2^31 ~ 2^31-1 long8 0L -2^63 ~ 2^63-1 float4 0.0f ±1.4E-45 ~ ±3.4E38 double8 0.0d ±4.9E-324 ~ ±1.7E308 char2 ‘\u0000’ 0 ~ 65535(Unicode字符) boolean1 bit false true/false
二、字符串
3. String 为什么是不可变的?有什么好处?
- 答:
- 不可变原理:
String内部使用final char[]存储数据,且未暴露修改方法。 - 优点:
- 线程安全:无需同步即可共享。
- 缓存哈希值:提升性能(如作为
HashMap的键)。 - 安全性:防止敏感数据被篡改(如数据库连接字符串)。
- 不可变原理:
4. String s = new String("abc") 创建了几个对象?
- 答:
- 若常量池中无
"abc",则创建 2 个对象:常量池中的"abc"和堆中的String对象。 - 若常量池中已有
"abc",则创建 1 个对象:堆中的String对象。
- 若常量池中无
5. StringBuilder 和 StringBuffer 的区别?
- 答:
- 线程安全:
StringBuffer方法用synchronized修饰,线程安全但性能低;StringBuilder非线程安全,性能高。 - 使用场景:单线程用
StringBuilder,多线程用StringBuffer。
- 线程安全:
三、面向对象
6. 重载(Overload)和重写(Override)的区别?
- 答:
区别点 重载 重写 作用范围 同一类中 子类与父类之间 方法签名 方法名相同,参数列表不同 方法名、参数列表、返回类型相同 访问权限 无限制 子类方法不能更严格 抛出异常 无限制 子类异常不能更宽泛
7. final 关键字的作用?
- 答:
- 修饰类:类不能被继承(如
String)。 - 修饰方法:方法不能被子类重写。
- 修饰变量:变量值不可修改(常量)。
- 修饰类:类不能被继承(如
8. 抽象类和接口的区别?
- 答:
特性 抽象类 接口(JDK8+) 实例化 不能 不能 方法实现 可包含普通方法 默认方法、静态方法 成员变量 任意类型 默认 public static final构造方法 有 无 多继承 单继承 多实现 设计目的 代码复用(is-a 关系) 定义行为规范(has-a 能力)
9. 什么是多态?如何实现多态?
- 答:
- 多态:父类引用指向子类对象,调用方法时执行子类重写的方法。
- 实现方式:继承 + 方法重写 + 向上转型。
示例:
class Animal { void sound() { ... } } class Dog extends Animal { void sound() { System.out.println("汪汪"); } } Animal a = new Dog(); // 向上转型 a.sound(); // 输出 "汪汪"
四、异常处理
10. throw 和 throws 的区别?
- 答:
throw:在方法内手动抛出异常对象(如throw new IOException())。throws:在方法声明中指明可能抛出的异常类型(如public void read() throws IOException)。
11. try-catch-finally 中 finally 是否一定会执行?
- 答:
- 是,除非:
- 在
try或catch中调用System.exit()。 - 线程被终止(如
Thread.stop())。 - JVM 崩溃。
- 在
- 是,除非:
五、集合框架
12. ArrayList 和 LinkedList 的区别?
- 答:
特性 ArrayList LinkedList 底层结构 动态数组 双向链表 随机访问性能 O(1)(快) O(n)(慢) 增删性能 O(n)(需移动元素) O(1)(快,只需调整指针) 内存占用 较小(仅存储数据) 较大(存储节点指针)
13. HashMap 的底层实现原理?
- 答:
- JDK7:数组 + 链表,哈希冲突时链表插入。
- JDK8+:数组 + 链表/红黑树,链表长度 ≥8 时转红黑树,≤6 时转回链表。
- 哈希计算:通过
hashCode()和扰动函数(异或高16位)计算桶位置。
六、JVM 基础
14. JVM 内存分为哪些区域?
- 答:
- 堆(Heap):存储对象实例,分新生代(Eden、Survivor)和老年代。
- 方法区(元空间):存储类信息、常量、静态变量。
- 虚拟机栈:存储方法调用的栈帧(局部变量表、操作数栈等)。
- 本地方法栈:支持 Native 方法执行。
- 程序计数器:记录当前线程执行的字节码行号。
15. 什么是垃圾回收(GC)?如何判断对象可回收?
- 答:
- GC:自动回收堆内存中无用的对象。
- 判断方法:
- 引用计数法:存在循环引用问题,Java 未采用。
- 可达性分析:从 GC Roots(如栈局部变量、静态变量)出发,不可达的对象可回收。
七、综合问题
16. 如何实现单例模式?
- 答:
public class Singleton { private static volatile Singleton instance; private Singleton() {} // 私有构造 public static Singleton getInstance() { if (instance == null) { // 双重检查锁 synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
17. 如何避免死锁?
- 答:
- 预防策略:
- 按固定顺序获取锁。
- 使用超时机制(如
tryLock)。 - 避免嵌套锁。
- 预防策略:
18. 如何解决 ConcurrentModificationException?
- 答:
- 原因:在遍历集合时修改集合结构(如
for-each中删除元素)。 - 解决方案:
- 使用迭代器的
remove()方法。 - 改用线程安全集合(如
CopyOnWriteArrayList)。
- 使用迭代器的
- 原因:在遍历集合时修改集合结构(如
总结
本章总结了 Java 基础面试中的高频问题,涵盖数据类型、字符串、面向对象、集合框架、JVM 等核心知识点,并结合代码示例与原理分析,帮助开发者深入理解底层机制。建议结合实际编码练习与源码阅读巩固知识。
最后
本文简述了 Java 核心语法,涵盖程序结构、数据类型、运算符、控制流、面向对象基础等核心概念,针对更细节知识点,请自行探索学习,须知学无止境,望共勉。

10万+

被折叠的 条评论
为什么被折叠?



