Java 基础

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);
    }
}

关键组成部分

  1. 类(Class):程序的基本容器,包含属性和方法。
  2. main 方法:程序执行的唯一入口,格式固定为 public static void main(String[] args)
  3. 方法(Method):封装特定功能的代码块,如 printMessage
  4. 语句:以分号 ; 结尾的单行代码,如 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 文件加载到内存。
  • 加载过程
    1. 加载(Loading):查找 .class 文件并读取二进制数据。
    2. 链接(Linking)
      • 验证(Verification):检查字节码是否符合 JVM 规范。
      • 准备(Preparation):为静态变量分配内存并赋默认值。
      • 解析(Resolution):将符号引用转换为直接引用。
    3. 初始化(Initialization):执行静态代码块和显式赋值。

双亲委派模型

  • 类加载请求优先委派给父加载器,避免重复加载核心类。
  • 加载器层次: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. 类加载过程中静态代码块的执行顺序是什么?
    1. 父类静态代码块 → 子类静态代码块。
    2. 父类实例代码块 → 父类构造方法 → 子类实例代码块 → 子类构造方法。

代码验证

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. 整型
类型大小(字节)取值范围默认值应用场景
byte1-128 ~ 1270文件读写、网络传输(节省内存)
short2-32,768 ~ 32,7670较少使用,兼容旧代码
int4-2^31 ~ 2^31-1(约 ±21亿)0最常用的整型(循环计数等)
long8-2^63 ~ 2^63-10L时间戳、大范围数值计算

代码示例

byte b = 100;                // 合法
short s = 20000;             // 合法
int i = 2147483647;          // int 最大值
long l = 9223372036854775807L; // 必须加 L 后缀
2. 浮点型
类型大小(字节)精度范围默认值注意事项
float4约 ±3.4E38(6-7位有效数字)0.0f必须加 f 后缀(如 3.14f)
double8约 ±1.7E308(15位有效数字)0.0d默认浮点类型(如 3.14)

代码示例

float f = 3.14f;            // 必须加 f 后缀
double d1 = 3.14;           // 合法
double d2 = 1.0e-5;         // 科学计数法表示 0.00001
3. 字符型
类型大小(字节)取值范围默认值说明
char20 ~ 65,535(Unicode)‘\u0000’单引号包裹(如 ‘A’)

代码示例

char c1 = 'A';              // 字符字面量
char c2 = 65;               // ASCII 编码(等价于 'A')
char c3 = '\u0041';         // Unicode 编码(等价于 'A')
char c4 = '中';             // 中文字符(Unicode支持)
4. 布尔型
类型大小(bit)取值默认值说明
boolean1(JVM实现相关)true/falsefalse不能与整数类型相互转换

代码示例

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. 包装类对应关系
基本类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean
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;

八、面试题解析
  1. Integer a = 100Integer b = 100== 比较的结果是什么?为什么?

    • 答:true。因为 Integer 对 -128~127 的数值做了缓存,直接复用对象。
  2. Java 中是否存在无符号整型?

    • 答:Java 没有无符号整型,但可通过位运算模拟(如 int 表示 0~4,294,967,295)。
  3. float f = 3.14 是否合法?为什么?

    • 答:不合法。浮点字面量默认是 double 类型,需强制转换或加 f 后缀。

总结

本章深入解析了 Java 数据类型与变量的核心概念,涵盖基本类型、引用类型、作用域规则、类型转换机制及常见陷阱。理解这些知识是编写高效、健壮 Java 程序的基础,也是面试中高频考察点。


1.3 运算符与表达式


一、运算符分类与优先级

Java 提供丰富的运算符,按功能可分为 算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、条件(三目)运算符 等。运算符的优先级决定了表达式中运算的执行顺序,优先级高的先执行,同级运算符按结合性(左结合或右结合)执行。

1. 运算符优先级表

在这里插入图片描述

记忆口诀:单目乘除位关系,逻辑三目后赋值。


二、算术运算符

算术运算符用于数值计算,支持基本数据类型(byteshortintlongfloatdoublechar)。

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

三、关系运算符

关系运算符用于比较两个值的大小或相等性,返回布尔值 truefalse

运算符描述示例结果(假设 a=5, b=3
==等于a == bfalse
!=不等于a != btrue
>大于a > btrue
<小于a < bfalse
>=大于等于a >= 5true
<=小于等于a <= 3false

代码示例

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 & 31两位均为1时结果为1
|按位或5 | 37任一位为1时结果为1
^按位异或5 ^ 36两位不同时结果为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. 表达式中的类型提升
  • 规则
    • byteshortchar 参与运算时自动提升为 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 = 33 + "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-elseswitch-case
  • 循环语句forwhiledo-while、增强型 for
  • 跳转控制breakcontinuereturn
  • 嵌套与标签:多层循环控制
  • 控制流优化:避免死循环、性能陷阱与最佳实践

二、条件语句

条件语句根据布尔表达式的结果决定代码执行路径。

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 (表达式) {
    case1:
        // 匹配值1时执行
        break;
    case2:
        // 匹配值2时执行
        break;
    default:
        // 默认执行
}

执行规则

  • 表达式类型byteshortintcharString(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 (初始化; 布尔表达式; 迭代) {
    // 循环体
}

执行流程

Yes
No
初始化
条件 true?
执行循环体
迭代
结束

代码示例(打印九九乘法表):

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 (;;) { ... }      // 等效写法
    
  • 规避策略
    • 设置合理的循环终止条件。
    • 使用 breakreturn 及时退出。

代码示例(安全读取用户输入):

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 条件仅支持 intcharString、枚举等类型。
2. 如何避免 switch 的 case 穿透问题?
  • :每个 case 后添加 break,或使用 JDK14+ 的箭头语法。
3. forwhile 如何选择?
  • :已知循环次数用 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)。
    • 不能重写 privatefinalstatic 方法。

代码示例

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)。
  • 接口:定义行为规范(如 ComparableRunnable)。
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);

内存模型

引用
栈内存
堆内存: String对象
value: char数组
'H'
'e'
'l'
'l'
'o'

2. 字符串的不可变性
  • 不可变原理
    • String 类内部使用 final char[] value 存储字符。
    • 所有修改操作(如 substringreplace)均生成新对象。

代码示例

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() 默认比较引用地址(与 == 相同),但可被重写为内容比较(如 StringInteger)。
      示例
    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 中基本数据类型有哪些?各占多少字节?
  • 类型字节默认值取值范围
    byte10-128 ~ 127
    short20-32768 ~ 32767
    int40-2^31 ~ 2^31-1
    long80L-2^63 ~ 2^63-1
    float40.0f±1.4E-45 ~ ±3.4E38
    double80.0d±4.9E-324 ~ ±1.7E308
    char2‘\u0000’0 ~ 65535(Unicode字符)
    boolean1 bitfalsetrue/false

二、字符串
3. String 为什么是不可变的?有什么好处?
    • 不可变原理String 内部使用 final char[] 存储数据,且未暴露修改方法。
    • 优点
      • 线程安全:无需同步即可共享。
      • 缓存哈希值:提升性能(如作为 HashMap 的键)。
      • 安全性:防止敏感数据被篡改(如数据库连接字符串)。
4. String s = new String("abc") 创建了几个对象?
    • 若常量池中无 "abc",则创建 2 个对象:常量池中的 "abc" 和堆中的 String 对象。
    • 若常量池中已有 "abc",则创建 1 个对象:堆中的 String 对象。
5. StringBuilderStringBuffer 的区别?
    • 线程安全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. throwthrows 的区别?
    • throw:在方法内手动抛出异常对象(如 throw new IOException())。
    • throws:在方法声明中指明可能抛出的异常类型(如 public void read() throws IOException)。
11. try-catch-finallyfinally 是否一定会执行?
    • ,除非:
      • trycatch 中调用 System.exit()
      • 线程被终止(如 Thread.stop())。
      • JVM 崩溃。

五、集合框架
12. ArrayListLinkedList 的区别?
  • 特性ArrayListLinkedList
    底层结构动态数组双向链表
    随机访问性能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个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值