javaSE大总结

Java入门基础

1. Java语言概述

1.1 Java是什么

概念:一种面向对象的高级编程语言,具有跨平台、健壮、安全等特性

核心特点

  • 语法简洁清晰(C++的简化版)

  • "Write once, run anywhere"(一次编写,到处运行)

  • 广泛应用于企业级开发、Android、大数据等领域

代码示例

复制代码
// 第一个Java程序
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, Java!");
    }
}

1.2 Java语言重要性

应用领域:
  1. 企业级应用:银行系统、电商平台(安全稳定)

  2. Web开发:Spring框架、微服务

  3. 移动开发:Android应用

  4. 大数据:Hadoop、Spark(底层用Java)

  5. 云计算:云原生应用

校招需求:
复制代码
企业看重(按重要性排序):
1. 编程语言基础(Java)
2. 数据结构与算法
3. 操作系统原理
4. 计算机网络
5. 数据库知识
6. 项目经验(加分项)

注意:TIOBE排行榜常年前三,但选择语言应考虑:

  • 岗位需求

  • 个人兴趣

  • 发展前景


1.3 Java发展简史

复制代码
1991年:Oak语言(Java前身)
1995年:Java 1.0发布,口号"Write once, run anywhere"
1996年:JDK 1.0发布
2004年:Java 5.0(重大更新,泛型、注解等)
2014年:Java 8(Lambda表达式、Stream API)
2021年:Java 17(长期支持版本)
2023年:Java 21(虚拟线程等新特性)

版本选择建议

  • 学习:Java 8或Java 11(资料丰富)

  • 生产:Java 11或Java 17(长期支持版本)


1.4 Java语言特性(11个)

1. 简单性
复制代码
// 对比C++,Java去掉了很多复杂特性
// 没有:指针、多重继承、操作符重载、头文件等

// Java版:更简洁
class Person {
    private String name;
    public String getName() { return name; }
}

// C++版:更复杂(需要手动管理内存)
class Person {
private:
    char* name;
public:
    char* getName() { return name; }
    ~Person() { delete[] name; }  // 需要析构函数
}
2. 面向对象
复制代码
// 一切皆对象
String str = "hello";  // String是对象
int[] arr = {1, 2, 3}; // 数组也是对象
Integer num = 10;      // 包装类对象

// 通过对象交互完成功能
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();  // 对象调用方法
3. 跨平台性(核心特性)
复制代码
Java源代码 (.java)
     ↓ 编译
Java字节码 (.class)  ← 平台无关的中间代码
     ↓ JVM解释执行
不同平台:
    Windows JVM
    Linux JVM
    Mac JVM

代码示例

复制代码
// 同一份字节码在不同平台运行
public class CrossPlatform {
    public static void main(String[] args) {
        // 获取当前操作系统
        String os = System.getProperty("os.name");
        System.out.println("运行在:" + os);
        // 同一份代码,Windows/Linux/Mac都能运行
    }
}
4. 健壮性
复制代码
// 1. 没有指针,避免内存错误
// C++: int* p = new int[10]; delete[] p; p[0] = 1; // 可能访问已释放内存
// Java: 自动垃圾回收,没有悬空指针

// 2. 强类型检查
int num = "hello";  // 编译错误,类型不匹配

// 3. 异常处理机制
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("除数不能为0!");  // 程序不会崩溃
}
5. 安全性
复制代码
// 1. 字节码验证
// JVM会验证.class文件,防止恶意代码

// 2. 安全沙箱
// Applet运行在受限环境中

// 3. 没有指针运算
// 无法直接操作内存地址
6. 可移植性
复制代码
// 数据类型大小固定
int a = 10;  // 永远是32位(4字节)
long b = 20; // 永远是64位(8字节)

// 对比C++:int可能是16位或32位,取决于编译器
7. 高性能
复制代码
// JIT(即时编译)优化
for (int i = 0; i < 1000000; i++) {
    // 热点代码会被编译为本地机器码
    Math.sqrt(i);
}

// 垃圾回收优化
// 分代收集、并行收集等策略
8. 多线程
复制代码
// 内置多线程支持
public class MultiThreadDemo {
    public static void main(String[] args) {
        // 创建线程
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程运行中: " + i);
            }
        });
        
        thread.start();  // 启动线程
        System.out.println("主线程继续执行");
    }
}
9. 动态性(反射)
复制代码
// 运行时获取类信息
Class<?> clazz = String.class;
System.out.println("类名: " + clazz.getName());
System.out.println("方法数: " + clazz.getMethods().length);

1.5 Java与C/C++区别

特性 Java C/C++
内存管理 自动垃圾回收 手动管理(new/delete, malloc/free)
指针 没有显式指针,只有引用 支持指针运算
多重继承 不支持,通过接口实现 支持
平台依赖 跨平台(字节码+JVM) 平台相关(编译为机器码)
预处理 没有预处理器 有#define、#include等
头文件 没有头文件 需要头文件
操作符重载 不支持(除了String的+) 支持
异常处理 强制异常处理(编译时检查) 可选异常处理
执行方式 解释执行+JIT编译 直接编译执行
性能 稍慢(有JVM开销) 更快(直接机器码)
安全性 更高(字节码验证、沙箱) 较低(直接内存访问)

代码对比

复制代码
// Java:自动内存管理
List<String> list = new ArrayList<>();
list.add("item");
// 不需要手动释放内存,GC自动回收

// C++:手动内存管理
std::vector<std::string>* list = new std::vector<std::string>();
list->push_back("item");
delete list;  // 必须手动释放

2. main方法详解

2.1 main方法结构

复制代码
public class HelloWorld {
    // main方法是Java程序的入口
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

每个关键字的作用

  1. public:访问修饰符,表示方法可以被任意访问

  2. static:静态方法,不创建对象即可调用

  3. void:返回值类型,表示没有返回值

  4. main:方法名,固定名称

  5. String[] args:命令行参数数组

注意易错点

复制代码
// 错误1:缺少public
class Test {
    static void main(String[] args) {  // 编译通过,但无法运行
        System.out.println("错误示例");
    }
}

// 错误2:拼写错误
public class Test {
    public static void mian(String[] args) {  // 不是main
        System.out.println("不会执行");
    }
}

// 错误3:参数类型错误
public class Test {
    public static void main(String args) {  // 缺少[]
        System.out.println("参数类型错误");
    }
}

// 错误4:非静态方法
public class Test {
    public void main(String[] args) {  // 缺少static
        System.out.println("需要static");
    }
}

2.2 命令行参数使用

复制代码
public class ArgsDemo {
    public static void main(String[] args) {
        // 通过命令行传递参数
        // java ArgsDemo hello world 123
        System.out.println("参数个数: " + args.length);
        
        for (int i = 0; i < args.length; i++) {
            System.out.println("参数" + i + ": " + args[i]);
        }
        
        // 示例:计算参数求和
        int sum = 0;
        for (String arg : args) {
            try {
                sum += Integer.parseInt(arg);
            } catch (NumberFormatException e) {
                System.out.println("忽略非数字参数: " + arg);
            }
        }
        System.out.println("数字参数总和: " + sum);
    }
}

2.3 Java程序执行流程

复制代码
1. 编写源代码:HelloWorld.java
2. 编译:javac HelloWorld.java → 生成 HelloWorld.class(字节码)
3. 运行:java HelloWorld → JVM加载执行

流程详解:
.java文件 → 编译器(javac) → .class字节码 → 类加载器 → 字节码验证器
→ 解释器/JIT编译器 → 操作系统 → 硬件

2.4 JDK、JRE、JVM关系

复制代码
JDK (Java Development Kit) 开发工具包
├── JRE (Java Runtime Environment) 运行时环境
│   ├── JVM (Java Virtual Machine) Java虚拟机
│   ├── 核心类库 (rt.jar等)
│   └── 其他支持文件
├── 开发工具
│   ├── javac (编译器)
│   ├── java (启动器)
│   ├── javadoc (文档生成)
│   └── jar (打包工具)
└── 其他工具

面试题回答要点

  • JDK:Java开发工具包,包含JRE+开发工具

  • JRE:Java运行环境,包含JVM+核心类库

  • JVM:Java虚拟机,执行字节码

关系总结

  • 要开发Java程序:需要JDK

  • 要运行Java程序:只需要JRE

  • JVM是JRE的核心,负责执行字节码


3. 注释

3.1 三种注释方式

1. 单行注释(最常用)
复制代码
// 这是单行注释,从//开始到行尾
int age = 20;  // 定义年龄变量
2. 多行注释
复制代码
/*
 * 这是多行注释
 * 可以跨越多行
 * 通常用于方法说明或临时注释代码块
 */
public void method() {
    /*
    System.out.println("被注释的代码");
    System.out.println("不会被执行");
    */
}

注意易错点

复制代码
// 错误:多行注释不能嵌套
/* 
外层注释开始
/* 内层注释开始 */  // 这里就结束了!
外层注释结束  // 编译错误!
*/
3. 文档注释(用于生成API文档)
复制代码
/**
 * 计算两个数的和
 * 
 * @param a 第一个加数
 * @param b 第二个加数
 * @return 两个数的和
 * @throws IllegalArgumentException 如果参数无效
 * 
 * 示例:
 * <pre>
 * int result = add(10, 20);  // 返回30
 * </pre>
 */
public int add(int a, int b) {
    if (a < 0 || b < 0) {
        throw new IllegalArgumentException("参数必须为非负数");
    }
    return a + b;
}

3.2 生成API文档

复制代码
# 使用javadoc生成文档
javadoc -d doc -author -version -encoding UTF-8 HelloWorld.java

# 参数说明:
# -d doc     输出到doc目录
# -author    包含作者信息
# -version   包含版本信息
# -encoding  指定编码

生成的文档包含:

  • 类说明

  • 方法说明

  • 参数说明

  • 返回值说明

  • 异常说明

  • 示例代码

3.3 注释规范

复制代码
/**
 * 用户管理类
 * 负责用户的增删改查操作
 * 
 * @author 张三
 * @version 1.0.0
 * @since 2024-01-01
 */
public class UserManager {
    
    /** 用户列表,存储所有用户信息 */
    private List<User> userList;
    
    /**
     * 添加新用户
     * 
     * @param user 要添加的用户对象,不能为null
     * @return 添加成功返回true,失败返回false
     * @throws NullPointerException 如果user为null
     */
    public boolean addUser(User user) {
        if (user == null) {
            throw new NullPointerException("用户不能为空");
        }
        return userList.add(user);
    }
    
    // 单行注释:重要逻辑说明
    public void process() {
        // 验证用户权限(关键步骤)
        checkPermission();
        
        // 执行核心业务逻辑
        executeBusiness();
        
        // TODO: 这里需要添加日志记录功能
        // FIXME: 这里的性能需要优化
    }
}

注释规范总结

  1. 类注释:说明类的职责

  2. 方法注释:说明功能、参数、返回值、异常

  3. 字段注释:说明字段用途

  4. 行内注释:解释复杂逻辑

  5. TODO/FIXME:标记待办事项

  6. 避免废话:不要写"这个方法用来计算"


4. 标识符

4.1 标识符定义规则

概念:程序中用户自定义的名称(类名、方法名、变量名等)

硬性规则(必须遵守)

  1. 由字母、数字、下划线(_)、美元符($)组成

  2. 不能以数字开头

  3. 不能是Java关键字

  4. 区分大小写

代码示例

复制代码
// 合法的标识符
String userName;     // 字母
int _count;          // 下划线开头
double $price;       // 美元符开头
int MAX_VALUE;       // 大写字母
int age2;            // 包含数字(不在开头)

// 非法的标识符
int 2ndValue;        // 错误:数字开头
float class;         // 错误:class是关键字
String first-name;   // 错误:包含连字符
int user name;       // 错误:包含空格

4.2 命名规范(软性建议)

1. 类名:大驼峰命名法(PascalCase)
复制代码
// 每个单词首字母大写
public class HelloWorld { }
public class UserManager { }
public class OrderService { }
public class StringUtils { }

// 不推荐的类名
public class helloworld { }  // 全部小写
public class HELLOWORLD { }  // 全部大写
public class hello_world { } // 下划线分隔
2. 方法名和变量名:小驼峰命名法(camelCase)
复制代码
// 首字母小写,后续单词首字母大写
public void calculateTotalPrice() { }
private String userName;
protected int maxRetryCount;

// 常量:全部大写,下划线分隔
public static final int MAX_SIZE = 100;
public static final String DEFAULT_NAME = "admin";
3. 包名:全小写,点分隔
复制代码
package com.company.project.utils;   // 推荐
package com.Company.Project.Utils;   // 不推荐(大写)
package COM.COMPANY.PROJECT;         // 不推荐(全大写)
4. 有意义的命名
复制代码
// 好的命名
int studentCount;           // 学生数量
double averageScore;        // 平均分
List<Order> pendingOrders;  // 待处理订单

// 差的命名
int a;                      // 含义不明确
double bbb;                 // 无意义
List<Order> list;           // 类型信息重复

4.3 常见命名示例

复制代码
// 类名示例
class Calculator { }        // 计算器
class DatabaseConnection { } // 数据库连接
class HttpRequestHandler { } // HTTP请求处理器

// 方法名示例
public void sendEmail() { }          // 发送邮件
public boolean isValidUser() { }     // 检查用户有效性
public String formatDate(Date date) { } // 格式化日期

// 变量名示例
int retryCount = 3;                  // 重试次数
String errorMessage = "";            // 错误信息
boolean isConnected = false;         // 是否已连接

// 常量名示例
public static final int MAX_USERS = 1000;
public static final String DATE_FORMAT = "yyyy-MM-dd";
public static final double PI = 3.1415926;

5. 关键字

5.1 关键字列表

Java有50多个关键字,不能用作标识符:

分类 关键字 说明
访问控制 public, protected, private 访问权限修饰符
类、方法、变量修饰符 abstract, class, extends, final, implements, interface, native, new, static, strictfp, synchronized, transient, volatile 修饰符
程序控制 break, case, continue, default, do, else, for, if, instanceof, return, switch, while 流程控制
错误处理 assert, catch, finally, throw, throws, try 异常处理
包相关 import, package 包管理
基本类型 boolean, byte, char, double, float, int, long, short 基本数据类型
变量引用 super, this, void 变量引用
保留字 goto, const 保留未使用
其他 enum 枚举

5.2 关键关键字详解

1. 访问控制关键字
复制代码
public class AccessDemo {
    public int publicVar = 1;      // 任何地方可访问
    protected int protectedVar = 2; // 同包或子类可访问
    int defaultVar = 3;           // 同包可访问(默认)
    private int privateVar = 4;    // 仅本类可访问
    
    public void test() {
        // 所有变量在类内部都可访问
        System.out.println(publicVar);
        System.out.println(protectedVar);
        System.out.println(defaultVar);
        System.out.println(privateVar);
    }
}
2. 类相关关键字
复制代码
// final:不能被继承
final class FinalClass { }  // 不能有子类

// abstract:抽象类,不能实例化
abstract class AbstractClass {
    abstract void abstractMethod(); // 抽象方法,必须被子类实现
}

// class:定义类
class NormalClass { }

// interface:定义接口
interface MyInterface {
    void interfaceMethod();
}
3. 流程控制关键字
复制代码
// if-else
if (condition) {
    // 条件为真执行
} else {
    // 条件为假执行
}

// for循环
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

// while循环
while (condition) {
    // 循环体
}

// switch-case
switch (value) {
    case 1:
        // 处理1
        break;
    case 2:
        // 处理2
        break;
    default:
        // 默认处理
}
4. 变量相关关键字
复制代码
class VariableDemo {
    // static:类变量,所有对象共享
    static int staticVar = 0;
    
    // final:常量,只能赋值一次
    final int finalVar = 100;
    
    // volatile:保证变量可见性(多线程)
    volatile boolean flag = false;
    
    void test() {
        // this:指向当前对象
        this.staticVar = 10;
        
        // super:指向父类对象
        super.toString();
    }
}

5.3 不能用作标识符的示例

复制代码
// 以下都是错误的,因为使用了关键字
int class = 10;         // 错误:class是关键字
String public = "test";  // 错误:public是关键字
boolean if = true;       // 错误:if是关键字
double new = 3.14;       // 错误:new是关键字

// 正确的做法
int classNumber = 10;    // 添加其他单词
String publicInfo = "test";
boolean ifCondition = true;
double newValue = 3.14;

5.4 保留字

复制代码
// goto和const是保留字,但不能使用
// 以下代码会编译错误
// int goto = 10;       // 错误:goto是保留字
// final const = 20;    // 错误:const是保留字

// 虽然不能用,但要认识
// goto:在C语言中用于跳转,Java用break/continue/return代替
// const:在C语言中定义常量,Java用final代替

易错点总结

1. main方法常见错误

复制代码
// 错误示例
class Test {
    // 缺少static
    public void main(String[] args) { }
    
    // 拼写错误
    public static void mian(String[] args) { }
    
    // 参数类型错误
    public static void main(String args) { }
    
    // 不是public
    static void main(String[] args) { }
}

2. 注释嵌套错误

复制代码
/* 
外层注释
/* 尝试嵌套注释 */  // 这里注释就结束了!
剩下的内容 */      // 编译错误:多余的*/
*/

// 正确:使用单行注释
// 第一段注释
// /* 第二段注释内容 */
// 第三段注释

3. 标识符命名错误

复制代码
// 数字开头
int 1stPlace;      // 错误

// 包含特殊字符
String user-name;  // 错误(连字符)
float price$€;     // 错误(欧元符号)

// 使用关键字
int class;         // 错误
String public;     // 错误

// 大小写敏感
int Age = 10;
int age = 20;      // 这是两个不同的变量

4. 文件与类名不匹配

复制代码
// 文件:Hello.java
public class HelloWorld {  // 错误:类名必须与文件名相同
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}

// 文件:HelloWorld.java
public class HelloWorld {  // 正确
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}

实战练习

练习1:验证标识符合法性

判断以下哪些是合法标识符:

  • className

  • _user

  • 123abc✗(数字开头)

  • first-name✗(包含连字符)

  • $price

  • public✗(关键字)

  • MAX_SIZE

  • 2ndValue✗(数字开头)

  • user@name✗(包含@)

练习2:编写完整Java程序

复制代码
/**
 * 学生信息管理程序
 * 
 * @author 学生姓名
 * @version 1.0
 * 演示Java基本语法:类、main方法、变量、注释
 */
public class StudentManager {
    
    // 类常量:最大学生数
    public static final int MAX_STUDENTS = 100;
    
    /**
     * 程序入口
     * 
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 打印欢迎信息
        System.out.println("=== 学生管理系统 ===");
        
        // 处理命令行参数
        if (args.length > 0) {
            System.out.println("接收到的参数:");
            for (String arg : args) {
                System.out.println(" - " + arg);
            }
        }
        
        // 定义学生信息变量
        String studentName = "张三";
        int studentAge = 20;
        double averageScore = 85.5;
        boolean isGraduated = false;
        
        // 打印学生信息
        printStudentInfo(studentName, studentAge, averageScore, isGraduated);
        
        // 演示计算
        double newScore = calculateNewScore(averageScore, 10);
        System.out.println("加分后的成绩: " + newScore);
    }
    
    /**
     * 打印学生信息
     * 
     * @param name 学生姓名
     * @param age 学生年龄
     * @param score 平均成绩
     * @param graduated 是否毕业
     */
    private static void printStudentInfo(String name, int age, 
                                         double score, boolean graduated) {
        System.out.println("\n学生信息:");
        System.out.println("姓名: " + name);
        System.out.println("年龄: " + age);
        System.out.println("平均成绩: " + score);
        System.out.println("是否毕业: " + (graduated ? "是" : "否"));
    }
    
    /**
     * 计算新成绩
     * 
     * @param originalScore 原始成绩
     * @param bonus 加分
     * @return 新成绩,最高不超过100分
     */
    private static double calculateNewScore(double originalScore, double bonus) {
        double newScore = originalScore + bonus;
        // 使用Math.min确保不超过100分
        return Math.min(newScore, 100.0);
    }
}

关键要点总结

  1. Java特性:跨平台、面向对象、自动内存管理、健壮安全

  2. main方法 :程序入口,必须是public static void main(String[] args)

  3. 注释

    • 单行注释://

    • 多行注释:/* */

    • 文档注释:/** */(生成API文档)

  4. 标识符规则

    • 字母、数字、_、$组成

    • 不能数字开头

    • 不能是关键字

    • 区分大小写

  5. 命名规范

    • 类名:大驼峰 HelloWorld

    • 方法/变量:小驼峰 calculateTotal

    • 常量:全大写 MAX_VALUE

  6. 常见错误

    • main方法拼写错误

    • 注释嵌套

    • 标识符使用关键字

    • 类名与文件名不一致

数据类型与变量

1. 字面常量

1.1 概念

字面常量是程序中直接写出来的固定值,在程序运行期间不会改变

1.2 代码示例

复制代码
public class Demo {
    public static void main(String[] args) {
        // 字符串常量
        System.out.println("Hello World!");
        
        // 整型常量
        System.out.println(100);
        System.out.println(-50);
        
        // 浮点型常量
        System.out.println(3.14);
        System.out.println(2.71828);
        
        // 字符常量
        System.out.println('A');
        System.out.println('我');
        
        // 布尔常量
        System.out.println(true);
        System.out.println(false);
    }
}

1.3 字面常量分类

类型 示例 说明
字符串常量 "Hello""123""你好" 双引号括起来
整型常量 100-500 没有小数点
浮点型常量 3.14-0.52.0 有小数点
字符常量 'A''1''中' 单引号括起来的单个字符
布尔常量 truefalse 只有这两个值
空常量 null 表示空引用

1.4 注意易错点

复制代码
// 常见错误示例

// 错误1:单引号不能包含多个字符
// char c = 'AB';  // 编译错误:字符字面量只能有一个字符

// 错误2:布尔值不能是0或1
// boolean b = 0;  // 编译错误:int不能转成boolean
// boolean b = 1;  // 编译错误

// 错误3:字符串必须用双引号
// String s = 'hello';  // 编译错误:应该用双引号

// 正确示例
char c1 = 'A';      // 正确:单个字符
char c2 = '\n';     // 正确:转义字符
boolean b1 = true;  // 正确
boolean b2 = false; // 正确
String s = "hello"; // 正确:双引号表示字符串

2. 数据类型

2.1 基本数据类型(四类八种)

数据类型 关键字 字节数 范围 默认值
字节型 byte 1 -128 ~ 127 0
短整型 short 2 -32768 ~ 32767 0
整型 int 4 -2³¹ ~ 2³¹-1 0
长整型 long 8 -2⁶³ ~ 2⁶³-1 0L
单精度浮点 float 4 约±3.4E38 0.0f
双精度浮点 double 8 约±1.7E308 0.0
字符型 char 2 0 ~ 65535 '\u0000'
布尔型 boolean 1(实际1位) true/false false

2.2 什么是字节(Byte)

概念:字节是计算机存储的基本单位

  • 1字节 = 8位(bit)

  • 1KB = 1024字节

  • 1MB = 1024KB

  • 1GB = 1024MB

  • 8GB内存 ≈ 80亿个字节

注意:Java中数据类型大小与平台无关

  • int固定4字节(32位系统、64位系统都一样)

  • long固定8字节


3. 变量

3.1 变量概念

变量是程序中可以改变的数据存储位置,用于存储经常变化的值

3.2 语法格式

复制代码
// 语法:数据类型 变量名 = 初始值;
int age = 20;           // 定义整型变量age,初始值20
double score = 95.5;    // 定义双精度浮点数
char gender = 'M';      // 定义字符变量
boolean isPass = true;  // 定义布尔变量

// 修改变量的值
age = 25;               // 变量值可以改变
score = 98.0;

// 同时定义多个同类型变量
int a = 10, b = 20, c = 30;

3.3 注意易错点

复制代码
// 错误1:变量使用前必须初始化
int x;
// System.out.println(x);  // 编译错误:变量x未初始化

// 错误2:变量名不能重复定义
int y = 10;
// int y = 20;  // 编译错误:重复定义变量y

// 错误3:变量作用域问题
{
    int z = 30;
    System.out.println(z);  // 正确:在作用域内
}
// System.out.println(z);  // 错误:z的作用域已结束

// 正确示例
int value;          // 声明变量
value = 100;        // 赋值
System.out.println(value);  // 输出:100

4. 整型变量

4.1 int(整型)

复制代码
// 定义int变量
int age = 25;
int count = 100;
int temperature = -10;

// 获取int的范围
System.out.println("int最小值: " + Integer.MIN_VALUE);    // -2147483648
System.out.println("int最大值: " + Integer.MAX_VALUE);    // 2147483647

// 两种定义方式
int a = 10;          // 方式1:定义时初始化
int b;               // 方式2:先声明
b = 20;              // 后赋值

注意易错点

复制代码
// 错误:赋值超过int范围
// int max = 2147483648;  // 编译错误:超出int范围

// 错误:未初始化就使用
int num;
// System.out.println(num);  // 编译错误

// 整数除法注意
int x = 5;
int y = 2;
System.out.println(x / y);  // 输出2,不是2.5!整数除法舍弃小数

4.2 long(长整型)

复制代码
// 定义long变量(建议使用大写的L)
long worldPopulation = 7800000000L;    // 78亿
long lightYear = 9460730472580800L;    // 光年距离

// 获取long的范围
System.out.println("long最小值: " + Long.MIN_VALUE);
System.out.println("long最大值: " + Long.MAX_VALUE);

// 常见错误写法
long a = 100;       // 可以,100在int范围内,自动转换
// long b = 2147483648;  // 错误!默认是int,超出int范围
long b = 2147483648L;  // 正确:加L表示long类型

注意易错点

复制代码
// 错误1:忘记加L(数字超过int范围时)
// long bigNum = 3000000000;  // 编译错误:超出int范围
long bigNum = 3000000000L;    // 正确

// 错误2:使用小写l(容易和数字1混淆)
long num1 = 100l;    // 不推荐:l和1难区分
long num2 = 100L;    // 推荐:使用大写L

// 注意:int自动转long,long不能自动转int
int i = 100;
long l = i;          // 正确:int转long(小转大)
// i = l;            // 错误:long转int需要强制转换
i = (int)l;          // 正确:强制转换

4.3 short(短整型)

复制代码
// 定义short变量
short s1 = 100;
short s2 = -200;

// 获取short范围
System.out.println("short最小值: " + Short.MIN_VALUE);  // -32768
System.out.println("short最大值: " + Short.MAX_VALUE);  // 32767

注意易错点

复制代码
// 错误:赋值超过short范围
// short s = 32768;  // 编译错误:超出范围
short s = 32767;      // 正确:最大值

// 整数默认是int,赋值给short要检查范围
short a = 100;        // 正确:100在short范围内
// short b = 40000;    // 错误:40000超出short范围

// short常用于节省内存的场合(如数组)
short[] scores = new short[1000];  // 比int数组节省一半内存

4.4 byte(字节型)

复制代码
// 定义byte变量
byte b1 = 100;
byte b2 = -50;

// 获取byte范围
System.out.println("byte最小值: " + Byte.MIN_VALUE);  // -128
System.out.println("byte最大值: " + Byte.MAX_VALUE);  // 127

注意易错点

复制代码
// 错误:赋值超过byte范围
// byte b = 128;      // 编译错误:超出范围
byte b = 127;          // 正确:最大值

// 整数默认是int,赋值给byte要检查范围
byte a = 100;          // 正确:100在byte范围内
// byte c = 200;        // 错误:200超出byte范围

// byte常用于文件、网络等二进制数据处理
byte[] buffer = new byte[1024];  // 1KB缓冲区

4.5 为什么需要四种整型?

类型 字节 范围 使用场景
byte 1 -128~127 文件I/O、网络传输、节省内存
short 2 -32768~32767 C语言兼容、节省内存
int 4 -21亿~21亿 最常用,默认整型
long 8 非常大 大整数计算(如时间戳、ID)

类比:买衣服尺码

  • byte → XS号(最小)

  • short → S号(较小)

  • int → M号(标准,最常用)

  • long → XL号(最大)


5. 浮点型变量

5.1 double(双精度浮点型)

复制代码
// 定义double变量(默认浮点型)
double pi = 3.1415926535;
double salary = 10000.50;
double temperature = -15.5;

// 科学计数法表示
double largeNum = 1.23e6;     // 1.23 × 10⁶ = 1230000
double smallNum = 5.67e-3;    // 5.67 × 10⁻³ = 0.00567

// 浮点数计算
double a = 1.0;
double b = 2.0;
System.out.println(a / b);    // 输出0.5

注意易错点

复制代码
// 重要:浮点数的精度问题
double num = 1.1;
System.out.println(num * num);  // 输出1.2100000000000002,不是1.21!

// 浮点数比较不要直接用==
double x = 0.1 + 0.2;
double y = 0.3;
System.out.println(x == y);     // 输出false!
System.out.println(x);          // 输出0.30000000000000004

// 正确比较方式:设置误差范围
double epsilon = 1e-10;  // 误差范围
System.out.println(Math.abs(x - y) < epsilon);  // 输出true

// 整数除法vs浮点数除法
int m = 5;
int n = 2;
System.out.println(m / n);      // 输出2(整数除法)
System.out.println((double)m / n);  // 输出2.5(浮点数除法)

5.2 float(单精度浮点型)

复制代码
// 定义float变量(必须加f或F)
float f1 = 3.14f;       // 正确
float f2 = 3.14F;       // 正确
// float f3 = 3.14;     // 错误!默认double,需要加f

// float精度较低(约6-7位有效数字)
float f = 1234567.89f;
System.out.println(f);  // 输出1234567.9(四舍五入)

注意易错点

复制代码
// 错误:忘记加f/F
// float a = 3.14;      // 编译错误:3.14默认是double
float a = 3.14f;        // 正确

// float精度比double低
float f1 = 1.23456789f;
double d1 = 1.23456789;
System.out.println(f1);  // 输出1.2345679(7位有效)
System.out.println(d1);  // 输出1.23456789(15位有效)

// 工程建议:优先使用double
// float常用于内存紧张或大量计算的场景
float[] positions = new float[1000000];  // 比double节省一半内存

6. 字符型变量

6.1 char类型

复制代码
// 定义char变量
char c1 = 'A';          // 英文字母
char c2 = '1';          // 数字字符
char c3 = '中';         // 中文字符
char c4 = ' ';          // 空格
char c5 = '\n';         // 换行符

// Unicode表示(16进制)
char c6 = '\u0041';     // 'A'的Unicode
char c7 = '\u4e2d';     // '中'的Unicode

// char可以参与整数运算(本质是Unicode编码)
char letter = 'A';
System.out.println(letter);           // 输出A
System.out.println((int)letter);      // 输出65(ASCII码)
System.out.println(letter + 1);       // 输出66(int类型)
System.out.println((char)(letter + 1)); // 输出B

6.2 常用转义字符

复制代码
System.out.println("Hello\tWorld");    // \t:制表符
System.out.println("Hello\nWorld");    // \n:换行符
System.out.println("Hello\rWorld");    // \r:回车符
System.out.println("Hello\\World");    // \\:反斜杠
System.out.println("Hello\"World\"");  // \":双引号
System.out.println("Hello\'World\'");  // \':单引号

// Unicode转义
System.out.println("\u0041");          // A
System.out.println("\u4e2d\u6587");    // 中文

6.3 注意易错点

复制代码
// 错误1:单引号内必须是单个字符
// char c = 'AB';        // 编译错误
// char c = '';          // 编译错误:空字符不允许

// 错误2:中文字符编码问题(保存文件时用UTF-8)
// 编译时加参数:javac -encoding UTF-8 Demo.java

// 错误3:char和String混淆
char c = 'A';           // 单引号,单个字符
String s = "A";         // 双引号,字符串
String s2 = "ABC";      // 字符串可以多个字符

// 正确示例
char grade = 'A';       // 成绩等级
char newline = '\n';    // 换行符
char copyright = '\u00A9';  // ©符号

7. 布尔型变量

7.1 boolean类型

复制代码
// 定义boolean变量
boolean isStudent = true;
boolean hasPassed = false;
boolean isRainy = true;

// 布尔运算
boolean a = true;
boolean b = false;
System.out.println(a && b);   // 逻辑与:false
System.out.println(a || b);   // 逻辑或:true
System.out.println(!a);       // 逻辑非:false

// 条件判断
int score = 85;
boolean isExcellent = score >= 90;
boolean isPass = score >= 60;
System.out.println("优秀: " + isExcellent);  // false
System.out.println("及格: " + isPass);       // true

7.2 注意易错点

复制代码
// 错误1:不能用0/1代替true/false
// boolean flag = 0;       // 编译错误
// boolean flag = 1;       // 编译错误
boolean flag = true;        // 正确

// 错误2:不能和数字运算
int x = 10;
boolean b = true;
// int y = x + b;          // 编译错误
// System.out.println(b + 1);  // 编译错误

// 正确:只能赋值true/false
boolean isRunning = true;
boolean isFinished = false;

// 常用于条件控制
if (isRunning) {
    System.out.println("程序运行中");
} else {
    System.out.println("程序已停止");
}

8. 类型转换

8.1 自动类型转换(隐式转换)

复制代码
// 小类型 → 大类型(自动转换)
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 → int(自动转换)
char c = 'A';
int charCode = c;     // 'A' → 65
System.out.println(charCode);

// 整数 → 浮点数(自动转换)
int count = 100;
double average = count;  // 100 → 100.0

转换规则

复制代码
byte → short → int → long → float → double
          ↑
         char

8.2 强制类型转换(显式转换)

复制代码
// 大类型 → 小类型(需要强制转换)
double d = 3.14;
int i = (int)d;        // 3.14 → 3(舍弃小数)
System.out.println(i); // 输出3

long l = 1000L;
int j = (int)l;        // long → int
short s = (short)j;    // int → short
byte b = (byte)s;      // short → byte

// 浮点数 → 整数(直接截断)
double price = 9.99;
int intPrice = (int)price;  // 9.99 → 9
System.out.println(intPrice);

8.3 注意易错点

复制代码
// 错误1:大类型不能自动转小类型
// int a = 10;
// byte b = a;      // 编译错误
byte b = (byte)a;     // 正确:强制转换

// 错误2:转换可能丢失数据
int bigNum = 300;
byte small = (byte)bigNum;  // 300超出byte范围
System.out.println(small);  // 输出44(数据丢失!)

// 错误3:不相干类型不能转换
// boolean flag = true;
// int num = (int)flag;  // 编译错误
// flag = (boolean)1;    // 编译错误

// 正确:检查范围再转换
int value = 200;
if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
    byte b = (byte)value;  // 安全转换
} else {
    System.out.println("超出byte范围");
}

9. 类型提升

9.1 运算时的类型提升

复制代码
// byte/short/char在运算时提升为int
byte a = 10;
byte b = 20;
// byte c = a + b;      // 编译错误!a+b是int类型
byte c = (byte)(a + b); // 正确:需要强制转换
int d = a + b;          // 正确:自动提升为int

// 不同类型运算,小类型提升为大类型
int x = 10;
long y = 20L;
// int z = x + y;       // 编译错误!x+y是long类型
long z = x + y;         // 正确:int提升为long

float f = 3.14f;
double dd = f + 1.0;    // float提升为double

9.2 类型提升规则

复制代码
// 规则1:byte/short/char → int
byte b1 = 1;
byte b2 = 2;
int result1 = b1 + b2;  // 提升为int

// 规则2:有long参与 → long
int i = 100;
long l = 200L;
long result2 = i + l;   // 提升为long

// 规则3:有float参与 → float
long lo = 1000L;
float fl = 2.5f;
float result3 = lo + fl; // 提升为float

// 规则4:有double参与 → double
float fl2 = 3.14f;
double db = 2.71;
double result4 = fl2 + db; // 提升为double

9.3 注意易错点

复制代码
// 常见错误:byte/short运算
short s1 = 1;
short s2 = 2;
// short s3 = s1 + s2;    // 错误!提升为int
short s3 = (short)(s1 + s2);  // 正确

// 整数除法注意
int a = 5;
int b = 2;
System.out.println(a / b);       // 输出2(整数除法)
System.out.println((double)a / b); // 输出2.5(浮点数除法)

// 混合运算注意
double result = 1 + 2 * 3.0;    // 1 + 6.0 = 7.0
System.out.println(result);

10. 字符串类型(String)

10.1 基本使用

复制代码
// 定义字符串
String name = "张三";
String message = "Hello World";
String empty = "";        // 空字符串
String space = " ";       // 空格字符串

// 字符串拼接
String s1 = "Hello";
String s2 = "World";
String s3 = s1 + " " + s2;  // "Hello World"
System.out.println(s3);

// 字符串与数字拼接
int age = 25;
String info = "年龄: " + age;  // "年龄: 25"
System.out.println(info);

// 连续拼接
String result = "结果是: " + 10 + 20;  // "结果是: 1020"(注意!)
System.out.println(result);
String result2 = "结果是: " + (10 + 20); // "结果是: 30"
System.out.println(result2);

10.2 类型转换

复制代码
// int → String
int num = 100;
String str1 = num + "";               // 方法1:拼接空字符串
String str2 = String.valueOf(num);    // 方法2:valueOf方法
String str3 = Integer.toString(num);  // 方法3:toString方法

// String → int
String str = "123";
int n1 = Integer.parseInt(str);       // 方法1:parseInt
int n2 = Integer.valueOf(str);        // 方法2:valueOf(自动拆箱)
Integer n3 = Integer.valueOf(str);    // 返回Integer对象

// String → double
String strDouble = "3.14";
double d = Double.parseDouble(strDouble);

// 注意:转换失败会抛出异常
String wrong = "abc";
// int error = Integer.parseInt(wrong);  // NumberFormatException

10.3 注意易错点

复制代码
// 错误1:==比较字符串(比较的是地址)
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(比较内容)

// 错误2:null字符串操作
String str = null;
// System.out.println(str.length());  // NullPointerException
System.out.println(str == null);     // true

// 错误3:转换格式错误
String input = "123abc";
// int num = Integer.parseInt(input);  // NumberFormatException

// 正确:先检查再转换
if (input.matches("\\d+")) {  // 全是数字
    int num = Integer.parseInt(input);
} else {
    System.out.println("不是有效的数字");
}

关键总结

1. 数据类型选择原则

  • 整数:优先用int,大数用long,节省内存用byte/short

  • 小数:优先用double,节省内存用float

  • 字符:单个字符用char,多个字符用String

  • 真假:只能用boolean(true/false)

2. 类型转换要点

  • 自动转换:小类型→大类型(不丢失精度)

  • 强制转换:大类型→小类型(可能丢失数据)

  • 运算提升:byte/short/char→int,不同类型→最大的类型

3. 常见陷阱

复制代码
// 陷阱1:整数除法
System.out.println(5 / 2);      // 2,不是2.5

// 陷阱2:浮点数精度
System.out.println(0.1 + 0.2);  // 0.30000000000000004

// 陷阱3:byte/short运算
byte a = 100;
byte b = 100;
// byte c = a + b;  // 错误!需要强转

// 陷阱4:字符串拼接顺序
System.out.println(1 + 2 + "3");   // "33"
System.out.println("1" + 2 + 3);   // "123"

4. 最佳实践

运算符

1. 算术运算符

1.1 概念

用于数学运算的符号,包括加减乘除、取模等

1.2 基本四则运算符

复制代码
public class ArithmeticDemo {
    public static void main(String[] args) {
        int a = 20;
        int b = 10;
        
        // 加法
        System.out.println(a + b);  // 30
        
        // 减法
        System.out.println(a - b);  // 10
        
        // 乘法
        System.out.println(a * b);  // 200
        
        // 除法
        System.out.println(a / b);  // 2
        
        // 取模(求余数)
        System.out.println(a % b);  // 0
        
        // 混合运算
        System.out.println(a + b * 2);  // 40
        System.out.println((a + b) * 2); // 60
    }
}

1.3 注意易错点

复制代码
// 错误1:整数除法会舍弃小数
int a = 5;
int b = 2;
System.out.println(a / b);  // 输出2,不是2.5!

// 正确:获取正确的小数结果
double result = (double)a / b;  // 2.5
System.out.println(result);

// 错误2:除以0会抛出异常
int x = 10;
int y = 0;
// System.out.println(x / y);  // ArithmeticException: / by zero

// 浮点数除以0得到无穷大
double d1 = 10.0;
double d2 = 0.0;
System.out.println(d1 / d2);  // Infinity

// 取模运算
System.out.println(10 % 3);   // 1
System.out.println(-10 % 3);  // -1
System.out.println(10 % -3);  // 1
System.out.println(-10 % -3); // -1

1.4 增量运算符

复制代码
public class IncrementalDemo {
    public static void main(String[] args) {
        int a = 10;
        
        // +=
        a += 5;  // 等价于 a = a + 5
        System.out.println("a += 5: " + a);  // 15
        
        // -=
        a -= 3;  // 等价于 a = a - 3
        System.out.println("a -= 3: " + a);  // 12
        
        // *=
        a *= 2;  // 等价于 a = a * 2
        System.out.println("a *= 2: " + a);  // 24
        
        // /=
        a /= 4;  // 等价于 a = a / 4
        System.out.println("a /= 4: " + a);  // 6
        
        // %=
        a %= 4;  // 等价于 a = a % 4
        System.out.println("a %= 4: " + a);  // 2
        
        // 复合赋值
        int b = 10;
        b += 3 * 2;  // 等价于 b = b + (3 * 2)
        System.out.println("b: " + b);  // 16
    }
}

注意易错点

复制代码
// 增量运算会进行隐式类型转换
byte b = 10;
// b = b + 1;  // 编译错误:需要强制转换
b += 1;       // 正确:自动类型转换
System.out.println(b);  // 11

short s = 100;
s += 200;     // 正确
// s = s + 200;  // 编译错误:需要强制转换

1.5 自增/自减运算符

复制代码
public class AutoIncrementDemo {
    public static void main(String[] args) {
        int a = 10;
        
        // 后置++
        System.out.println(a++);  // 输出10,然后a变成11
        System.out.println(a);    // 输出11
        
        // 前置++
        int b = 10;
        System.out.println(++b);  // 输出11,b先变成11再输出
        System.out.println(b);    // 输出11
        
        // 自减
        int c = 10;
        System.out.println(c--);  // 输出10,然后c变成9
        System.out.println(--c);  // 输出8,c先变成8再输出
        
        // 复杂表达式
        int x = 5;
        int y = x++ + ++x;  // 5 + 7 = 12
        System.out.println("x=" + x + ", y=" + y);  // x=7, y=12
        
        int m = 3;
        int n = (m++) + (m++) + (++m);  // 3 + 4 + 6 = 13
        System.out.println("m=" + m + ", n=" + n);  // m=6, n=13
    }
}

注意易错点

复制代码
// 错误:常量不能自增
// 5++;  // 编译错误

// 注意运算顺序
int i = 1;
int j = i++ + i++ + i++;
System.out.println("i=" + i + ", j=" + j);  // i=4, j=6

// 不要在同一个表达式中对同一个变量多次自增
int k = 1;
// int result = k++ + k++;  // 不推荐,可读性差

2. 关系运算符

2.1 概念

用于比较两个值的大小关系,返回boolean类型的结果

2.2 代码示例

复制代码
public class RelationDemo {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        
        // 等于
        System.out.println(a == b);  // false
        
        // 不等于
        System.out.println(a != b);  // true
        
        // 小于
        System.out.println(a < b);   // true
        
        // 大于
        System.out.println(a > b);   // false
        
        // 小于等于
        System.out.println(a <= b);  // true
        
        // 大于等于
        System.out.println(a >= b);  // false
        
        // 浮点数比较
        double x = 1.1;
        double y = 1.2;
        System.out.println(x < y);   // true
        
        // 字符比较(比较ASCII码)
        char c1 = 'A';
        char c2 = 'B';
        System.out.println(c1 < c2);  // true
    }
}

2.3 注意易错点

复制代码
// 错误1:= 和 == 混淆
int a = 10;
int b = 20;
// if (a = b) {  // 编译错误:int不能转boolean
//     System.out.println("相等");
// }

// 正确
if (a == b) {
    System.out.println("相等");
}

// 错误2:浮点数精度比较
double d1 = 0.1 + 0.2;
double d2 = 0.3;
System.out.println(d1 == d2);  // false!应该用误差比较

// 正确比较浮点数
double epsilon = 1e-10;
System.out.println(Math.abs(d1 - d2) < epsilon);  // true

// 字符串比较不能用==
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(内容比较)

3. 逻辑运算符

3.1 概念

用于布尔值的逻辑运算,包括与(&&)、或(||)、非(!)

3.2 逻辑与(&&)

复制代码
public class LogicAndDemo {
    public static void main(String[] args) {
        boolean a = true;
        boolean b = false;
        
        // 真 && 真 = 真
        System.out.println(true && true);    // true
        
        // 真 && 假 = 假
        System.out.println(true && false);   // false
        
        // 假 && 真 = 假
        System.out.println(false && true);   // false
        
        // 假 && 假 = 假
        System.out.println(false && false);  // false
        
        // 实际应用
        int age = 18;
        boolean hasLicense = true;
        
        if (age >= 18 && hasLicense) {
            System.out.println("可以开车");  // 输出
        } else {
            System.out.println("不能开车");
        }
        
        // 多个条件
        int score = 85;
        boolean passedExam = score >= 60;
        boolean paidFee = true;
        
        if (passedExam && paidFee) {
            System.out.println("颁发证书");
        }
    }
}

3.3 逻辑或(||)

复制代码
public class LogicOrDemo {
    public static void main(String[] args) {
        // 真 || 真 = 真
        System.out.println(true || true);    // true
        
        // 真 || 假 = 真
        System.out.println(true || false);   // true
        
        // 假 || 真 = 真
        System.out.println(false || true);   // true
        
        // 假 || 假 = 假
        System.out.println(false || false);  // false
        
        // 实际应用
        boolean isWeekend = false;
        boolean isHoliday = true;
        
        if (isWeekend || isHoliday) {
            System.out.println("休息日");  // 输出
        } else {
            System.out.println("工作日");
        }
        
        // 支付方式:现金或信用卡
        boolean hasCash = false;
        boolean hasCreditCard = true;
        
        if (hasCash || hasCreditCard) {
            System.out.println("可以付款");  // 输出
        }
    }
}

3.4 逻辑非(!)

复制代码
public class LogicNotDemo {
    public static void main(String[] args) {
        // 非真 = 假
        System.out.println(!true);   // false
        
        // 非假 = 真
        System.out.println(!false);  // true
        
        // 实际应用
        boolean isRaining = false;
        
        if (!isRaining) {
            System.out.println("可以去公园");  // 输出
        } else {
            System.out.println("在家待着");
        }
        
        // 双重否定
        boolean flag = true;
        System.out.println(!!flag);  // true
    }
}

3.5 短路求值

复制代码
public class ShortCircuitDemo {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        
        // && 短路:左边为false,右边不计算
        if (a > 20 && b++ > 10) {  // b++不会执行
            System.out.println("条件成立");
        }
        System.out.println("b=" + b);  // b=20,没有自增
        
        // || 短路:左边为true,右边不计算
        if (a < 20 || b++ > 10) {  // b++不会执行
            System.out.println("条件成立");
        }
        System.out.println("b=" + b);  // b=20,没有自增
        
        // 验证短路效应
        int x = 1;
        int y = 2;
        
        // 不会抛出异常,因为左边为false
        if (x > 2 && (y / 0) > 1) {
            System.out.println("不会执行到这里");
        }
        System.out.println("程序继续执行");
        
        // 会抛出异常,因为使用了非短路版本
        // if (x > 2 & (y / 0) > 1) {  // ArithmeticException
        //     System.out.println("不会执行");
        // }
    }
}

3.6 注意易错点

复制代码
// 错误1:混淆 && 和 &
boolean b1 = true;
boolean b2 = false;
System.out.println(b1 & b2);   // false(非短路)
System.out.println(b1 && b2);  // false(短路)

int x = 1;
int y = 2;
// System.out.println(x > 0 & (y/0) > 1);  // 异常
System.out.println(x > 0 && (y/0) > 1);  // 短路,不会异常

// 错误2:运算优先级
boolean a = true;
boolean b = false;
boolean c = true;
boolean result = a || b && c;  // 等价于 a || (b && c)
System.out.println(result);    // true

// 使用括号明确优先级
boolean result2 = (a || b) && c;
System.out.println(result2);   // true

// 错误3:连续比较
int num = 10;
// if (5 < num < 20) {  // 编译错误
//     System.out.println("在范围内");
// }

// 正确写法
if (num > 5 && num < 20) {
    System.out.println("在范围内");
}

4. 位运算符

4.1 概念

对二进制位进行运算的运算符,包括与(&)、或(|)、非(~)、异或(^)

4.2 按位与(&)

复制代码
public class BitAndDemo {
    public static void main(String[] args) {
        int a = 10;  // 二进制: 1010
        int b = 6;   // 二进制: 0110
        
        // 按位与运算
        int result = a & b;  // 1010 & 0110 = 0010
        System.out.println(a + " & " + b + " = " + result);  // 2
        
        // 二进制验证
        System.out.println(Integer.toBinaryString(a));  // 1010
        System.out.println(Integer.toBinaryString(b));  // 0110
        System.out.println(Integer.toBinaryString(result)); // 0010
        
        // 应用:判断奇偶
        int num = 15;
        if ((num & 1) == 0) {
            System.out.println(num + "是偶数");
        } else {
            System.out.println(num + "是奇数");  // 输出
        }
        
        // 应用:取特定位
        int data = 0b11011010;  // 218
        int mask = 0b00001111;  // 取低4位
        int low4 = data & mask;  // 1010 = 10
        System.out.println("低4位: " + low4);
    }
}

4.3 按位或(|)

复制代码
public class BitOrDemo {
    public static void main(String[] args) {
        int a = 10;  // 1010
        int b = 6;   // 0110
        
        // 按位或运算
        int result = a | b;  // 1010 | 0110 = 1110
        System.out.println(a + " | " + b + " = " + result);  // 14
        
        // 应用:设置特定位为1
        int flags = 0b00001000;  // 初始标志位
        int mask = 0b00000100;   // 要将第2位设为1
        flags = flags | mask;    // 设置标志位
        System.out.println("设置后: " + Integer.toBinaryString(flags));
    }
}

4.4 按位取反(~)

复制代码
public class BitNotDemo {
    public static void main(String[] args) {
        int a = 10;  // 0000...1010
        
        // 按位取反
        int result = ~a;  // 1111...0101
        System.out.println("~" + a + " = " + result);
        
        // 验证
        System.out.println("a的二进制: " + Integer.toBinaryString(a));
        System.out.println("~a的二进制: " + Integer.toBinaryString(result));
        
        // 注意:取反包括符号位
        int b = 0;
        System.out.println("~0 = " + ~b);  // -1
        
        // 应用:求相反数-1
        int x = 5;
        int y = ~x;  // 相当于 -x-1
        System.out.println("~" + x + " = " + y + " = -" + x + "-1");
    }
}

4.5 按位异或(^)

复制代码
public class BitXorDemo {
    public static void main(String[] args) {
        int a = 10;  // 1010
        int b = 6;   // 0110
        
        // 按位异或
        int result = a ^ b;  // 1010 ^ 0110 = 1100
        System.out.println(a + " ^ " + b + " = " + result);  // 12
        
        // 特性1:相同为0,不同为1
        System.out.println("a ^ a = " + (a ^ a));  // 0
        System.out.println("a ^ 0 = " + (a ^ 0));  // a
        
        // 特性2:交换律
        System.out.println("a ^ b = " + (a ^ b));
        System.out.println("b ^ a = " + (b ^ a));
        
        // 应用:交换两个数
        int x = 5;
        int y = 8;
        System.out.println("交换前: x=" + x + ", y=" + y);
        
        x = x ^ y;
        y = x ^ y;  // y = (x^y)^y = x^(y^y) = x^0 = x
        x = x ^ y;  // x = (x^y)^x = y^(x^x) = y^0 = y
        
        System.out.println("交换后: x=" + x + ", y=" + y);
        
        // 应用:找只出现一次的数字
        int[] nums = {1, 2, 3, 2, 1};
        int single = 0;
        for (int num : nums) {
            single ^= num;
        }
        System.out.println("只出现一次的数字: " + single);  // 3
    }
}

4.6 注意易错点

复制代码
// 注意区分逻辑运算和位运算
boolean b1 = true;
boolean b2 = false;
System.out.println(b1 & b2);   // false(逻辑运算,非短路)
System.out.println(b1 && b2);  // false(逻辑运算,短路)

int x = 5;  // 0101
int y = 3;  // 0011
System.out.println(x & y);     // 1(位运算)

// 注意负数表示
int a = -1;
System.out.println(Integer.toBinaryString(a));  // 11111111111111111111111111111111
System.out.println(~a);  // 0

// 注意运算优先级
int result = 1 + 2 & 3;  // 等价于 (1 + 2) & 3
System.out.println(result);  // 3

// 使用括号明确优先级
int result2 = 1 + (2 & 3);
System.out.println(result2);  // 2

5. 移位运算符

5.1 概念

对二进制位进行左移或右移操作

5.2 左移(<<)

复制代码
public class LeftShiftDemo {
    public static void main(String[] args) {
        int a = 5;  // 二进制: 0000 0101
        
        // 左移1位
        int b = a << 1;  // 0000 1010
        System.out.println(a + " << 1 = " + b);  // 10
        
        // 左移2位
        int c = a << 2;  // 0001 0100
        System.out.println(a + " << 2 = " + c);  // 20
        
        // 左移n位相当于乘以2的n次方
        int num = 3;
        System.out.println(num + " << 1 = " + (num << 1));  // 6 (3 * 2)
        System.out.println(num + " << 2 = " + (num << 2));  // 12 (3 * 4)
        System.out.println(num + " << 3 = " + (num << 3));  // 24 (3 * 8)
        
        // 注意溢出
        int large = 0x40000000;  // 2^30
        System.out.println("large: " + large);
        System.out.println("large << 1: " + (large << 1));  // 负数
    }
}

5.3 右移(>>)

复制代码
public class RightShiftDemo {
    public static void main(String[] args) {
        int a = 20;  // 二进制: 0001 0100
        
        // 右移1位
        int b = a >> 1;  // 0000 1010
        System.out.println(a + " >> 1 = " + b);  // 10
        
        // 右移2位
        int c = a >> 2;  // 0000 0101
        System.out.println(a + " >> 2 = " + c);  // 5
        
        // 负数右移(符号位扩展)
        int negative = -20;
        System.out.println(negative + " >> 1 = " + (negative >> 1));  // -10
        System.out.println(negative + " >> 2 = " + (negative >> 2));  // -5
        
        // 右移n位相当于除以2的n次方(向下取整)
        int num = 15;
        System.out.println(num + " >> 1 = " + (num >> 1));  // 7 (15/2)
        System.out.println(num + " >> 2 = " + (num >> 2));  // 3 (15/4)
    }
}

5.4 无符号右移(>>>)

复制代码
public class UnsignedRightShiftDemo {
    public static void main(String[] args) {
        int a = 20;  // 0001 0100
        
        // 无符号右移1位
        int b = a >>> 1;  // 0000 1010
        System.out.println(a + " >>> 1 = " + b);  // 10
        
        // 负数无符号右移
        int negative = -20;
        System.out.println(negative + " >>> 1 = " + (negative >>> 1));
        // 结果很大,因为符号位补0
        
        // 与>>的区别
        int num = -1;
        System.out.println(num + " >> 1 = " + (num >> 1));   // -1
        System.out.println(num + " >>> 1 = " + (num >>> 1)); // 2147483647
        
        // 二进制查看
        System.out.println("-1二进制: " + Integer.toBinaryString(num));
        System.out.println(">>1二进制: " + Integer.toBinaryString(num >> 1));
        System.out.println(">>>1二进制: " + Integer.toBinaryString(num >>> 1));
    }
}

5.5 注意易错点

复制代码
// 错误1:移位位数过大
int a = 1;
System.out.println(a << 31);  // 正确
// System.out.println(a << 32);  // 只取低5位,32%32=0,相当于没移
System.out.println(a << 33);  // 33%32=1,相当于<<1

// 错误2:负数移位
int b = -1;
System.out.println(b >> 1);   // -1(符号位扩展)
System.out.println(b >>> 1);  // 2147483647(补0)

// 注意:byte/short移位会先转int
byte byteVal = 64;  // 0100 0000
int shifted = byteVal << 1;  // 128
System.out.println(shifted);

// 移位优先级
int x = 1;
int y = 2;
int z = x << 1 + y;  // 等价于 x << (1 + y) = 1 << 3 = 8
System.out.println(z);

// 使用括号明确
int z2 = (x << 1) + y;  // 4
System.out.println(z2);

6. 条件运算符(三目运算符)

6.1 概念

条件 ? 表达式1 : 表达式2,根据条件选择执行表达式1或表达式2

6.2 代码示例

复制代码
public class ConditionalOperatorDemo {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        
        // 求最大值
        int max = a > b ? a : b;
        System.out.println("最大值: " + max);  // 20
        
        // 求最小值
        int min = a < b ? a : b;
        System.out.println("最小值: " + min);  // 10
        
        // 判断奇偶
        int num = 15;
        String result = (num % 2 == 0) ? "偶数" : "奇数";
        System.out.println(num + "是" + result);  // 奇数
        
        // 嵌套三目运算符
        int score = 85;
        String grade = score >= 90 ? "A" : 
                      score >= 80 ? "B" : 
                      score >= 70 ? "C" : 
                      score >= 60 ? "D" : "F";
        System.out.println("成绩等级: " + grade);  // B
        
        // 返回不同类型(需要可自动转换)
        boolean flag = true;
        Object obj = flag ? "字符串" : 100;  // Object可接收任意类型
        System.out.println(obj);
    }
}

6.3 注意易错点

复制代码
// 错误1:结果必须被使用
int x = 10;
int y = 20;
// x > y ? x : y;  // 编译错误:不是语句

// 正确
int max = x > y ? x : y;
System.out.println(x > y ? x : y);

// 错误2:类型不匹配
int a = 10;
double b = 20.5;
// int result = a > 5 ? b : a;  // 编译错误:double不能转int
double result = a > 5 ? b : a;  // 正确

// 注意:三目运算有类型提升
char c = 'A';
int i = 65;
System.out.println(true ? c : i);  // 输出65,c提升为int
System.out.println(false ? c : i); // 输出65

// 注意空指针
String str = null;
String result = str != null ? str.toUpperCase() : "默认";
System.out.println(result);  // 默认

7. 运算符优先级

7.1 概念

运算符的执行顺序,优先级高的先执行

7.2 优先级表(从高到低)

优先级 运算符 结合性 说明
1 ()``[]``. 左→右 括号、数组访问、成员访问
2 !``~``++``--``+``- 右→左 一元运算符
3 *``/``% 左→右 乘除模
4 +``- 左→右 加减
5 <<``>>``>>> 左→右 移位
6 <``<=``>``>=``instanceof 左→右 关系
7 ==``!= 左→右 相等
8 & 左→右 按位与
9 ^ 左→右 按位异或
10 | 左→右 按位或
11 && 左→右 逻辑与
12 || 左→右 逻辑或
13 ?: 右→左 条件
14 =``+=``-=``*=``/=``%= 右→左 赋值

7.3 代码示例

复制代码
public class PriorityDemo {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int c = 30;
        
        // 示例1:算术运算符优先级
        int result1 = a + b * c;  // 10 + 20 * 30 = 10+600=610
        System.out.println("a + b * c = " + result1);
        
        int result2 = (a + b) * c;  // (10+20)*30=30 * 30=900
        System.out.println("(a + b) * c = " + result2);
        
        // 示例2:关系运算符优先级
        boolean bool1 = a + b > c && b < c;  // 30>30 && 20<30 = false
        System.out.println("a + b > c && b < c = " + bool1);
        
        // 示例3:混合运算
        int x = 1;
        int y = 2;
        int z = 3;
        int r = x << 1 + y;  // 1 << (1+2) = 1<<3 = 8
        System.out.println("x << 1 + y = " + r);
        
        // 示例4:自增优先级
        int i = 1;
        int j = 2;
        int k = ++i + j++;  // 2 + 2 = 4
        System.out.println("++i + j++ = " + k);
        System.out.println("i=" + i + ", j=" + j);  // i=2, j=3
    }
}

7.4 注意易错点

复制代码
// 常见错误:优先级理解错误
int a = 1, b = 2, c = 3;
int result = a + b << c;  // 等价于 (a+b) << c
System.out.println(result);  // 24

// 位运算优先级低于算术运算
int x = 5;
int y = 3;
int z = 2;
System.out.println(x & y + z);  // 等价于 x & (y+z) = 5 & 5 = 5

// 逻辑运算优先级
boolean b1 = true;
boolean b2 = false;
boolean b3 = true;
System.out.println(b1 || b2 && b3);  // true || (false && true) = true
System.out.println((b1 || b2) && b3);  // (true || false) && true = true

// 赋值运算优先级最低
int m = 1, n = 2;
int p = m = n = 3;  // 从右向左:n=3, m=3, p=3
System.out.println("m=" + m + ", n=" + n + ", p=" + p);

// 最佳实践:使用括号明确优先级
int complex = (a + b) * (c - d) / e;  // 明确,易读

关键总结

1. 运算符分类记忆

  • 算术+ - * / % ++ --

  • 关系== != < > <= >=

  • 逻辑&& || !(注意短路)

  • 位运算& | ~ ^ << >> >>>

  • 赋值= += -= *= /= %=

  • 条件? :

2. 重要特性

复制代码
// 1. 整数除法舍弃小数
System.out.println(5 / 2);  // 2

// 2. 自增自减区别
int a = 1;
int b = a++;  // b=1, a=2
int c = ++a;  // c=3, a=3

// 3. 短路求值
if (a > 0 && b++ > 0) {  // 左边false,右边不执行

// 4. 位运算应用
// 判断奇偶:n & 1
// 交换两数:a ^= b; b ^= a; a ^= b;
// 乘除2:n << 1, n >> 1

// 5. 类型提升
byte b = 10;
b = b + 1;  // 错误
b += 1;     // 正确

3. 常见面试题

复制代码
// 题1:交换两个数(不用临时变量)
int x = 5, y = 8;
x = x ^ y;
y = x ^ y;
x = x ^ y;

// 题2:判断2的幂
int n = 16;
boolean isPowerOfTwo = (n & (n - 1)) == 0;

// 题3:求绝对值
int num = -10;
int abs = (num ^ (num >> 31)) - (num >> 31);

// 题4:三目运算类型
Object o = true ? new Integer(1) : new Double(2.0);
System.out.println(o);  // 1.0(类型提升)

4. 最佳实践

  1. 括号优先:复杂表达式用括号明确优先级

  2. 避免歧义 :不要写a++ + ++a这样的代码

  3. 浮点比较 :不用==,用误差范围比较

  4. 字符串比较 :用equals(),不用==

  5. 位运算优化:在合适场景使用位运算

  6. 代码可读性:三目运算符不要嵌套太深

逻辑控制

1. 顺序结构

1.1 概念

程序按照代码的书写顺序从上到下依次执行

1.2 代码示例

复制代码
public class SequenceDemo {
    public static void main(String[] args) {
        // 顺序执行
        System.out.println("第一步:起床");
        System.out.println("第二步:洗漱");
        System.out.println("第三步:吃早饭");
        System.out.println("第四步:上课");
        
        // 计算顺序
        int a = 10;
        int b = 20;
        int c = a + b;  // 先计算a+b,再赋值给c
        System.out.println("a + b = " + c);
        
        // 改变顺序会改变结果
        a = 5;
        b = a * 2;  // 先计算a*2,再赋值给b
        System.out.println("b = " + b);  // 10
    }
}

1.3 注意易错点

复制代码
// 错误:使用未初始化的变量
int x;
// System.out.println(x);  // 编译错误:x未初始化
x = 10;  // 先初始化
System.out.println(x);  // 正确

// 注意赋值顺序
int a = 10;
int b = 20;
a = b;  // a变成20
b = a;  // b变成20(不是交换!)
System.out.println("a=" + a + ", b=" + b);  // a=20, b=20

// 正确交换
int x = 10, y = 20;
int temp = x;  // 需要临时变量
x = y;
y = temp;
System.out.println("x=" + x + ", y=" + y);  // x=20, y=10

2. 分支结构

2.1 if语句

2.1.1 单if语句
复制代码
public class IfDemo {
    public static void main(String[] args) {
        // 基本语法
        int score = 95;
        
        if (score >= 90) {
            System.out.println("优秀!奖励鸡腿一个!");
        }
        
        // 不带大括号(只能跟一条语句)
        int age = 20;
        if (age >= 18)
            System.out.println("成年了");
        // 下面这行不在if作用域内
        System.out.println("这句总是执行");
        
        // 实际应用:数据验证
        int input = 150;
        if (input > 100 || input < 0) {
            System.out.println("输入数据无效!");
        }
    }
}
2.1.2 if-else语句
复制代码
public class IfElseDemo {
    public static void main(String[] args) {
        int score = 85;
        
        if (score >= 60) {
            System.out.println("恭喜,及格了!");
        } else {
            System.out.println("不及格,继续努力!");
        }
        
        // 嵌套if-else
        int num = 10;
        if (num > 0) {
            System.out.println("正数");
        } else {
            if (num < 0) {
                System.out.println("负数");
            } else {
                System.out.println("零");
            }
        }
    }
}
2.1.3 if-else if-else语句
复制代码
public class IfElseIfDemo {
    public static void main(String[] args) {
        int score = 85;
        
        if (score >= 90) {
            System.out.println("优秀");
        } else if (score >= 80) {  // 80 <= score < 90
            System.out.println("良好");
        } else if (score >= 70) {  // 70 <= score < 80
            System.out.println("中等");
        } else if (score >= 60) {  // 60 <= score < 70
            System.out.println("及格");
        } else {                   // score < 60
            System.out.println("不及格");
        }
        
        // 简化写法(因为前面的条件不满足才到这里)
        int age = 25;
        if (age < 18) {
            System.out.println("少年");
        } else if (age < 30) {  // 隐含 age >= 18
            System.out.println("青年");
        } else if (age < 50) {  // 隐含 age >= 30
            System.out.println("中年");
        } else {                // 隐含 age >= 50
            System.out.println("老年");
        }
    }
}

2.2 注意易错点

复制代码
// 错误1:if后面多加分号
int x = 10;
if (x > 5); {  // 分号结束了if语句
    System.out.println("x大于5");  // 这行总是执行
}

// 错误2:悬垂else(else和最近的if匹配)
int a = 10, b = 20;
if (a > 5)
    if (b > 10)
        System.out.println("a>5且b>10");
else
    System.out.println("a<=5");  // 实际上和第二个if匹配!

// 正确:使用大括号明确范围
if (a > 5) {
    if (b > 10) {
        System.out.println("a>5且b>10");
    }
} else {
    System.out.println("a<=5");
}

// 错误3:使用=而不是==
int num = 5;
// if (num = 10) {  // 编译错误:int不能转boolean
//     System.out.println("num等于10");
// }

// 正确
if (num == 10) {
    System.out.println("num等于10");
}

// 注意:浮点数比较
double d1 = 0.1 + 0.2;
double d2 = 0.3;
if (d1 == d2) {  // false!应该用误差比较
    System.out.println("相等");
}

2.3 switch语句

复制代码
public class SwitchDemo {
    public static void main(String[] args) {
        // 基本用法
        int day = 3;
        
        switch (day) {
            case 1:
                System.out.println("星期一");
                break;
            case 2:
                System.out.println("星期二");
                break;
            case 3:
                System.out.println("星期三");
                break;
            case 4:
                System.out.println("星期四");
                break;
            case 5:
                System.out.println("星期五");
                break;
            case 6:
                System.out.println("星期六");
                break;
            case 7:
                System.out.println("星期日");
                break;
            default:
                System.out.println("输入错误");
                break;
        }
        
        // 多个case合并
        char grade = 'B';
        switch (grade) {
            case 'A':
            case 'B':
            case 'C':
                System.out.println("及格");
                break;
            case 'D':
            case 'F':
                System.out.println("不及格");
                break;
            default:
                System.out.println("无效成绩");
        }
        
        // JDK12+:新语法(需要--enable-preview)
        // int num = 2;
        // String result = switch(num) {
        //     case 1 -> "一";
        //     case 2 -> "二";
        //     default -> "其他";
        // };
    }
}

2.4 switch注意易错点

复制代码
// 错误1:忘记break
int option = 1;
switch (option) {
    case 1:
        System.out.println("选项1");
        // 忘记break,会继续执行case 2
    case 2:
        System.out.println("选项2");
        break;
}
// 输出:选项1 选项2

// 错误2:case值重复
int num = 1;
// switch (num) {
//     case 1: System.out.println("1"); break;
//     case 1: System.out.println("另一个1"); break;  // 编译错误
// }

// 错误3:类型不支持
// long value = 100L;
// switch (value) {  // 编译错误:不支持long
//     case 100L: System.out.println("100"); break;
// }

// 正确:String支持(JDK7+)
String fruit = "apple";
switch (fruit) {
    case "apple":
        System.out.println("苹果");
        break;
    case "banana":
        System.out.println("香蕉");
        break;
    default:
        System.out.println("未知水果");
}

// 注意:null值
String str = null;
// switch (str) {  // 运行时NullPointerException
//     case "test": break;
// }

3. 循环结构

3.1 while循环

复制代码
public class WhileDemo {
    public static void main(String[] args) {
        // 基本用法
        int i = 1;
        while (i <= 5) {
            System.out.println("第" + i + "次循环");
            i++;  // 不要忘记更新循环变量
        }
        
        // 计算1-100的和
        int sum = 0;
        int num = 1;
        while (num <= 100) {
            sum += num;
            num++;
        }
        System.out.println("1-100的和: " + sum);
        
        // 死循环(需要break退出)
        int count = 0;
        while (true) {
            System.out.println("循环中...");
            count++;
            if (count >= 3) {
                break;  // 跳出循环
            }
        }
        
        // 输入验证
        java.util.Scanner scanner = new java.util.Scanner(System.in);
        int input = 0;
        while (input <= 0) {
            System.out.print("请输入正整数: ");
            input = scanner.nextInt();
            if (input <= 0) {
                System.out.println("输入错误,请重新输入!");
            }
        }
        System.out.println("你输入的是: " + input);
    }
}

3.2 for循环

复制代码
public class ForDemo {
    public static void main(String[] args) {
        // 基本用法
        for (int i = 1; i <= 5; i++) {
            System.out.println("i = " + i);
        }
        
        // 计算1-100的和
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        System.out.println("1-100的和: " + sum);
        
        // 多种写法
        // 1. 省略初始化
        int j = 0;
        for (; j < 3; j++) {
            System.out.println("j = " + j);
        }
        
        // 2. 省略更新
        for (int k = 0; k < 3; ) {
            System.out.println("k = " + k);
            k++;  // 在循环体内更新
        }
        
        // 3. 无限循环
        // for (;;) {
        //     System.out.println("无限循环");
        // }
        
        // 嵌套循环:打印九九乘法表
        for (int x = 1; x <= 9; x++) {
            for (int y = 1; y <= x; y++) {
                System.out.printf("%d×%d=%-2d ", y, x, x * y);
            }
            System.out.println();
        }
    }
}

3.3 do-while循环

复制代码
public class DoWhileDemo {
    public static void main(String[] args) {
        // 基本用法:至少执行一次
        int i = 1;
        do {
            System.out.println("i = " + i);
            i++;
        } while (i <= 3);
        
        // 与while的区别:条件不满足时
        int a = 10;
        while (a < 5) {  // 条件一开始就不满足
            System.out.println("while循环");  // 不会执行
        }
        
        int b = 10;
        do {
            System.out.println("do-while循环");  // 会执行一次
        } while (b < 5);
        
        // 实际应用:菜单选择
        java.util.Scanner scanner = new java.util.Scanner(System.in);
        int choice;
        do {
            System.out.println("=== 菜单 ===");
            System.out.println("1. 开始游戏");
            System.out.println("2. 设置");
            System.out.println("0. 退出");
            System.out.print("请选择: ");
            choice = scanner.nextInt();
            
            switch (choice) {
                case 1:
                    System.out.println("开始游戏...");
                    break;
                case 2:
                    System.out.println("进入设置...");
                    break;
                case 0:
                    System.out.println("谢谢使用!");
                    break;
                default:
                    System.out.println("选择无效!");
            }
        } while (choice != 0);
    }
}

3.4 break和continue

复制代码
public class BreakContinueDemo {
    public static void main(String[] args) {
        // break:跳出整个循环
        System.out.println("break示例:");
        for (int i = 1; i <= 5; i++) {
            if (i == 3) {
                break;  // 跳出整个for循环
            }
            System.out.println("i = " + i);
        }
        // 输出:1 2
        
        // continue:跳过本次循环
        System.out.println("\ncontinue示例:");
        for (int i = 1; i <= 5; i++) {
            if (i == 3) {
                continue;  // 跳过i=3的这次循环
            }
            System.out.println("i = " + i);
        }
        // 输出:1 2 4 5
        
        // 带标签的break
        System.out.println("\n带标签的break:");
        outer:  // 标签
        for (int i = 1; i <= 3; i++) {
            for (int j = 1; j <= 3; j++) {
                if (i == 2 && j == 2) {
                    break outer;  // 跳出外层循环
                }
                System.out.println("i=" + i + ", j=" + j);
            }
        }
        
        // 带标签的continue
        System.out.println("\n带标签的continue:");
        outer:
        for (int i = 1; i <= 3; i++) {
            for (int j = 1; j <= 3; j++) {
                if (i == 2 && j == 2) {
                    continue outer;  // 跳到外层循环的下一次
                }
                System.out.println("i=" + i + ", j=" + j);
            }
        }
        
        // 实际应用:查找第一个符合条件的数
        int[] nums = {1, 3, 5, 7, 9, 2, 4, 6, 8};
        for (int num : nums) {
            if (num % 2 == 0) {
                System.out.println("找到第一个偶数: " + num);
                break;
            }
        }
    }
}

3.5 注意易错点

复制代码
// 错误1:while/for后面加分号
int i = 0;
while (i < 3); {  // 分号导致空循环体
    System.out.println("hello");
    i++;
}
// 死循环!

// 错误2:忘记更新循环变量
int j = 0;
while (j < 3) {
    System.out.println(j);
    // 忘记j++,导致死循环
}

// 错误3:数组越界
int[] arr = {1, 2, 3};
for (int k = 0; k <= arr.length; k++) {  // 应该用 <
    // System.out.println(arr[k]);  // 最后一次k=3越界
}

// 正确
for (int k = 0; k < arr.length; k++) {
    System.out.println(arr[k]);
}

// 注意:浮点数循环
// 不要用浮点数控制循环
// for (double d = 0.1; d != 1.0; d += 0.1) {  // 可能无限循环
//     System.out.println(d);
// }

// 正确:用整数控制
for (int n = 1; n <= 10; n++) {
    double d = n * 0.1;
    System.out.println(d);
}

4. 输入输出

4.1 输出到控制台

复制代码
public class OutputDemo {
    public static void main(String[] args) {
        // 1. println:输出并换行
        System.out.println("Hello");  // Hello
        System.out.println("World");  // World(新行)
        
        // 2. print:输出不换行
        System.out.print("Hello ");
        System.out.print("World");    // Hello World(同一行)
        System.out.println();         // 换行
        
        // 3. printf:格式化输出
        String name = "张三";
        int age = 20;
        double score = 95.5;
        
        // 基本格式化
        System.out.printf("姓名: %s, 年龄: %d, 分数: %.2f\n", name, age, score);
        
        // 宽度和对齐
        System.out.printf("姓名: %-10s 年龄: %5d\n", name, age);  // 左对齐
        System.out.printf("分数: %10.2f\n", score);              // 右对齐
        
        // 不同进制
        int num = 255;
        System.out.printf("十进制: %d\n", num);      // 255
        System.out.printf("十六进制: %x\n", num);    // ff
        System.out.printf("八进制: %o\n", num);      // 377
        System.out.printf("二进制: %s\n", Integer.toBinaryString(num));  // 11111111
        
        // 转义字符
        System.out.println("第一行\n第二行");      // 换行
        System.out.println("制表符:\t内容");      // 制表
        System.out.println("反斜杠: \\");         // 反斜杠
        System.out.println("双引号: \"");         // 双引号
        System.out.println("单引号: \'");         // 单引号
    }
}

4.2 从键盘输入

复制代码
import java.util.Scanner;

public class InputDemo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 1. 读取整数
        System.out.print("请输入一个整数: ");
        int num = scanner.nextInt();
        System.out.println("你输入的整数是: " + num);
        
        // 2. 读取浮点数
        System.out.print("请输入一个小数: ");
        double decimal = scanner.nextDouble();
        System.out.println("你输入的小数是: " + decimal);
        
        // 3. 读取字符串
        scanner.nextLine();  // 消耗换行符
        System.out.print("请输入你的名字: ");
        String name = scanner.nextLine();  // 读取整行
        System.out.println("你好, " + name + "!");
        
        // 4. 读取单个单词
        System.out.print("请输入一句话: ");
        String word = scanner.next();  // 只读取到空格
        System.out.println("第一个单词是: " + word);
        
        // 5. 读取字符
        System.out.print("请输入一个字符: ");
        char ch = scanner.next().charAt(0);  // 取第一个字符
        System.out.println("你输入的字符是: " + ch);
        
        // 6. 循环读取多个数字
        System.out.println("请输入多个数字(输入非数字结束):");
        int sum = 0, count = 0;
        while (scanner.hasNextInt()) {
            int n = scanner.nextInt();
            sum += n;
            count++;
        }
        if (count > 0) {
            System.out.printf("总和: %d, 平均值: %.2f\n", sum, (double)sum / count);
        }
        
        scanner.close();  // 关闭Scanner
    }
}

4.3 注意易错点

复制代码
import java.util.Scanner;

public class InputErrorDemo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        // 错误1:nextInt后接nextLine
        System.out.print("请输入年龄: ");
        int age = sc.nextInt();
        System.out.print("请输入姓名: ");
        // String name = sc.nextLine();  // 会直接读取换行符,得到空字符串
        
        // 解决方案1:额外调用一次nextLine消耗换行符
        sc.nextLine();  // 消耗换行符
        System.out.print("请输入姓名: ");
        String name = sc.nextLine();
        System.out.println("姓名: " + name);
        
        // 解决方案2:统一用nextLine然后转换
        System.out.print("请输入分数: ");
        String scoreStr = sc.nextLine();
        double score = Double.parseDouble(scoreStr);
        
        // 错误2:输入类型不匹配
        System.out.print("请输入一个整数: ");
        // int num = sc.nextInt();  // 如果输入"abc",会抛出InputMismatchException
        
        // 解决方案:先判断再读取
        if (sc.hasNextInt()) {
            int num = sc.nextInt();
            System.out.println("整数: " + num);
        } else {
            System.out.println("输入的不是整数!");
            sc.next();  // 消耗错误输入
        }
        
        // 错误3:忘记关闭Scanner(会有警告)
        sc.close();  // 记得关闭
        
        // 注意:System.in关闭后不能重新打开
        // Scanner sc2 = new Scanner(System.in);  // 错误:System.in已关闭
    }
}

5. 猜数字游戏

复制代码
import java.util.Random;
import java.util.Scanner;

public class GuessNumberGame {
    public static void main(String[] args) {
        Random random = new Random();
        Scanner scanner = new Scanner(System.in);
        
        // 生成1-100的随机数
        int secretNumber = random.nextInt(100) + 1;
        int guessCount = 0;
        int maxAttempts = 7;  // 最多尝试次数
        
        System.out.println("=== 猜数字游戏 ===");
        System.out.println("我已经想好了一个1-100之间的数字。");
        System.out.println("你有" + maxAttempts + "次机会猜中它。");
        System.out.println("让我们开始吧!\n");
        
        while (guessCount < maxAttempts) {
            guessCount++;
            System.out.print("第" + guessCount + "次猜测,请输入你的数字: ");
            
            // 输入验证
            if (!scanner.hasNextInt()) {
                System.out.println("请输入有效的数字!");
                scanner.next();  // 消耗无效输入
                guessCount--;    // 不计入尝试次数
                continue;
            }
            
            int guess = scanner.nextInt();
            
            // 判断猜测结果
            if (guess < 1 || guess > 100) {
                System.out.println("请输入1-100之间的数字!");
                guessCount--;  // 不计入尝试次数
            } else if (guess < secretNumber) {
                System.out.println("太低了!");
            } else if (guess > secretNumber) {
                System.out.println("太高了!");
            } else {
                System.out.println("恭喜你!猜对了!");
                System.out.println("你用了" + guessCount + "次猜中。");
                break;
            }
            
            // 提示剩余次数
            int remaining = maxAttempts - guessCount;
            if (remaining > 0 && guess != secretNumber) {
                System.out.println("还有" + remaining + "次机会。\n");
            }
        }
        
        // 游戏结束判断
        if (guessCount == maxAttempts) {
            System.out.println("\n很遗憾,机会用完了!");
            System.out.println("正确的数字是: " + secretNumber);
        }
        
        scanner.close();
        System.out.println("\n游戏结束,谢谢参与!");
    }
}

6. 练习解答

练习1:年龄分段

复制代码
public class AgeGroup {
    public static void main(String[] args) {
        int age = 25;
        
        if (age <= 18) {
            System.out.println("少年");
        } else if (age <= 28) {  // 19-28
            System.out.println("青年");
        } else if (age <= 55) {  // 29-55
            System.out.println("中年");
        } else {                 // 56以上
            System.out.println("老年");
        }
    }
}

练习2-3:素数判断

复制代码
public class PrimeNumber {
    public static void main(String[] args) {
        // 练习2:判断单个数字是否是素数
        int num = 17;
        boolean isPrime = true;
        
        if (num <= 1) {
            isPrime = false;
        } else {
            // 优化:只需检查到sqrt(num)
            for (int i = 2; i * i <= num; i++) {
                if (num % i == 0) {
                    isPrime = false;
                    break;
                }
            }
        }
        
        System.out.println(num + (isPrime ? "是素数" : "不是素数"));
        
        // 练习3:打印1-100之间的所有素数
        System.out.println("\n1-100之间的素数:");
        int count = 0;
        for (int n = 2; n <= 100; n++) {
            boolean prime = true;
            for (int i = 2; i * i <= n; i++) {
                if (n % i == 0) {
                    prime = false;
                    break;
                }
            }
            if (prime) {
                System.out.print(n + " ");
                count++;
                if (count % 10 == 0) {  // 每10个换行
                    System.out.println();
                }
            }
        }
        System.out.println("\n共有" + count + "个素数");
    }
}

练习4:闰年判断

复制代码
public class LeapYear {
    public static void main(String[] args) {
        // 闰年规则:能被4整除但不能被100整除,或者能被400整除
        System.out.println("1000-2000之间的闰年:");
        int count = 0;
        
        for (int year = 1000; year <= 2000; year++) {
            if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
                System.out.print(year + " ");
                count++;
                if (count % 10 == 0) {
                    System.out.println();
                }
            }
        }
        System.out.println("\n共有" + count + "个闰年");
    }
}

练习5:乘法口诀表

复制代码
public class MultiplicationTable {
    public static void main(String[] args) {
        // 正三角
        System.out.println("正三角乘法表:");
        for (int i = 1; i <= 9; i++) {
            for (int j = 1; j <= i; j++) {
                System.out.printf("%d×%d=%-2d ", j, i, i * j);
            }
            System.out.println();
        }
        
        // 倒三角
        System.out.println("\n倒三角乘法表:");
        for (int i = 9; i >= 1; i--) {
            for (int j = 1; j <= i; j++) {
                System.out.printf("%d×%d=%-2d ", j, i, i * j);
            }
            System.out.println();
        }
        
        // 完整矩形
        System.out.println("\n完整矩形乘法表:");
        for (int i = 1; i <= 9; i++) {
            for (int j = 1; j <= 9; j++) {
                System.out.printf("%d×%d=%-2d ", j, i, i * j);
            }
            System.out.println();
        }
    }
}

练习6:最大公约数

复制代码
public class GCD {
    public static void main(String[] args) {
        int a = 24, b = 36;
        
        // 方法1:辗转相除法
        int m = a, n = b;
        while (n != 0) {
            int temp = m % n;
            m = n;
            n = temp;
        }
        System.out.println(a + "和" + b + "的最大公约数(辗转相除法): " + m);
        
        // 方法2:更相减损术
        m = a; n = b;
        while (m != n) {
            if (m > n) {
                m = m - n;
            } else {
                n = n - m;
            }
        }
        System.out.println(a + "和" + b + "的最大公约数(更相减损术): " + m);
        
        // 方法3:暴力法
        int gcd = 1;
        int min = Math.min(a, b);
        for (int i = 2; i <= min; i++) {
            if (a % i == 0 && b % i == 0) {
                gcd = i;
            }
        }
        System.out.println(a + "和" + b + "的最大公约数(暴力法): " + gcd);
    }
}

练习7:水仙花数

复制代码
public class NarcissisticNumber {
    public static void main(String[] args) {
        System.out.println("0-999之间的水仙花数:");
        
        for (int num = 0; num <= 999; num++) {
            int original = num;
            int sum = 0;
            int digits = 0;
            
            // 计算位数
            int temp = num;
            while (temp != 0) {
                digits++;
                temp /= 10;
            }
            
            // 计算各位数字的幂和
            temp = num;
            while (temp != 0) {
                int digit = temp % 10;
                sum += Math.pow(digit, digits);
                temp /= 10;
            }
            
            if (sum == original) {
                System.out.println(original);
            }
        }
        
        // 优化:直接按位数计算
        System.out.println("\n三位水仙花数:");
        for (int i = 1; i <= 9; i++) {      // 百位
            for (int j = 0; j <= 9; j++) {  // 十位
                for (int k = 0; k <= 9; k++) {  // 个位
                    int num = i * 100 + j * 10 + k;
                    int sum = i*i*i + j*j*j + k*k*k;
                    if (num == sum && num >= 100) {
                        System.out.println(num);
                    }
                }
            }
        }
    }
}

练习8:二进制中1的个数

复制代码
public class CountBits {
    public static void main(String[] args) {
        int n = 15;  // 1111
        
        // 方法1:移位统计
        int count1 = 0;
        for (int i = 0; i < 32; i++) {
            if (((n >> i) & 1) == 1) {
                count1++;
            }
        }
        System.out.println("方法1: " + n + "的二进制有" + count1 + "个1");
        
        // 方法2:n & (n-1)技巧
        int count2 = 0;
        int temp = n;
        while (temp != 0) {
            temp &= (temp - 1);  // 每次消去最右边的1
            count2++;
        }
        System.out.println("方法2: " + n + "的二进制有" + count2 + "个1");
        
        // 方法3:使用Integer.bitCount
        int count3 = Integer.bitCount(n);
        System.out.println("方法3: " + n + "的二进制有" + count3 + "个1");
    }
}

练习9:二进制奇偶位

复制代码
public class BinaryBits {
    public static void main(String[] args) {
        int num = 0b10101100;  // 二进制: 1010 1100
        
        System.out.println("数字: " + num);
        System.out.println("二进制: " + Integer.toBinaryString(num));
        
        // 打印奇数位(从1开始计数,实际是偶数索引)
        System.out.print("奇数位: ");
        for (int i = 31; i >= 0; i--) {
            if (i % 2 == 1) {  // 奇数位
                int bit = (num >> i) & 1;
                System.out.print(bit);
            }
        }
        System.out.println();
        
        // 打印偶数位
        System.out.print("偶数位: ");
        for (int i = 31; i >= 0; i--) {
            if (i % 2 == 0) {  // 偶数位
                int bit = (num >> i) & 1;
                System.out.print(bit);
            }
        }
        System.out.println();
        
        // 更清晰的方法
        System.out.println("\n方法2:");
        System.out.print("奇数位序列: ");
        for (int i = 7; i >= 0; i--) {  // 只看后8位演示
            int bit = (num >> (2*i + 1)) & 1;  // 奇数位
            System.out.print(bit);
        }
        System.out.print("\n偶数位序列: ");
        for (int i = 7; i >= 0; i--) {
            int bit = (num >> (2*i)) & 1;  // 偶数位
            System.out.print(bit);
        }
    }
}

关键总结

1. 选择合适的分支结构

  • 简单判断:if

  • 二选一:if-else

  • 多选一:if-else if-else 或 switch

  • switch适用:等值判断、有限个离散值

  • if适用:范围判断、复杂条件

2. 选择循环结构

  • 知道次数:for循环

  • 不知道次数:while循环

  • 至少执行一次:do-while

  • 遍历集合:for-each(后面学)

3. 控制循环

  • 结束循环:break

  • 跳过本次:continue

  • 结束多层:带标签的break/continue

4. 输入输出要点

  • Scanner读取:注意类型匹配

  • 换行问题:nextInt后接nextLine要处理

  • 格式化输出:printf控制格式

  • 输入验证:hasNextXxx()先判断

5. 常见错误

复制代码
// 1. 悬垂else
if (a) if (b) x(); else y();  // else和第二个if匹配

// 2. switch忘记break
case 1: doSomething();  // 会继续执行case 2

// 3. 浮点数循环
for (double d = 0; d != 1.0; d += 0.1)  // 可能无限循环

// 4. 数组越界
for (int i = 0; i <= arr.length; i++)  // 应该用<

// 5. 死循环
while (true) {  // 没有break或条件变化
    // 循环体
}

6. 最佳实践

  1. 使用大括号:即使只有一条语句

  2. 明确优先级:使用括号

  3. 避免深层嵌套:超过3层考虑重构

  4. 循环变量命名:i,j,k用于循环,意义明确

  5. 及时关闭资源:Scanner用完要close()

  6. 输入验证:先判断再读取

方法的使用

1. 方法的概念

1.1 概念

方法(Method)是一段可重复使用的代码块,用于执行特定任务。类似于数学中的函数。

1.2 为什么需要方法

代码示例:重复计算闰年

复制代码
// 没有使用方法(重复代码)
public class WithoutMethod {
    public static void main(String[] args) {
        // 判断2000年是否是闰年
        int year1 = 2000;
        if ((year1 % 4 == 0 && year1 % 100 != 0) || year1 % 400 == 0) {
            System.out.println(year1 + "是闰年");
        } else {
            System.out.println(year1 + "不是闰年");
        }
        
        // 判断2024年是否是闰年(重复代码)
        int year2 = 2024;
        if ((year2 % 4 == 0 && year2 % 100 != 0) || year2 % 400 == 0) {
            System.out.println(year2 + "是闰年");
        } else {
            System.out.println(year2 + "不是闰年");
        }
        
        // 判断2100年是否是闰年(再次重复)
        int year3 = 2100;
        if ((year3 % 4 == 0 && year3 % 100 != 0) || year3 % 400 == 0) {
            System.out.println(year3 + "是闰年");
        } else {
            System.out.println(year3 + "不是闰年");
        }
    }
}

使用方法后的代码

复制代码
public class WithMethod {
    public static void main(String[] args) {
        // 只需调用方法,代码更简洁
        System.out.println(isLeapYear(2000) ? "2000是闰年" : "2000不是闰年");
        System.out.println(isLeapYear(2024) ? "2024是闰年" : "2024不是闰年");
        System.out.println(isLeapYear(2100) ? "2100是闰年" : "2100不是闰年");
    }
    
    // 定义判断闰年的方法
    public static boolean isLeapYear(int year) {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }
}

1.3 方法的优点

  1. 代码复用:一次定义,多次使用

  2. 模块化:复杂程序分解为小模块

  3. 可维护性:修改只需改一处

  4. 可读性:方法名描述功能,代码更清晰


2. 方法的定义与使用

2.1 方法定义语法

复制代码
修饰符 返回值类型 方法名(参数类型 参数名, ...) {
    // 方法体
    return 返回值;
}

2.2 代码示例

复制代码
public class MethodDemo {
    public static void main(String[] args) {
        // 1. 无参数无返回值的方法
        printHello();
        
        // 2. 有参数无返回值的方法
        printMessage("欢迎学习Java方法");
        
        // 3. 有参数有返回值的方法
        int sum = add(10, 20);
        System.out.println("10 + 20 = " + sum);
        
        // 4. 有返回值但无参数的方法
        double pi = getPi();
        System.out.println("π的值是: " + pi);
        
        // 5. 方法嵌套调用
        int result = multiply(add(5, 3), 2);
        System.out.println("(5+3)*2 = " + result);
    }
    
    // 1. 无参数无返回值
    public static void printHello() {
        System.out.println("Hello, World!");
    }
    
    // 2. 有参数无返回值
    public static void printMessage(String message) {
        System.out.println("消息: " + message);
    }
    
    // 3. 有参数有返回值
    public static int add(int a, int b) {
        return a + b;
    }
    
    // 4. 有返回值无参数
    public static double getPi() {
        return 3.14159;
    }
    
    // 5. 复杂方法
    public static int multiply(int a, int b) {
        int product = a * b;
        return product;
    }
}

2.3 注意易错点

复制代码
// 错误1:方法不能嵌套定义
public class ErrorDemo1 {
    public static void main(String[] args) {
        // 正确:方法可以嵌套调用
        int result = add(5, multiply(2, 3));
    }
    
    public static int add(int a, int b) {
        // 错误:不能在方法内定义方法
        // public static int multiply(int x, int y) {
        //     return x * y;
        // }
        return a + b;
    }
    
    // 正确:在类中并列定义
    public static int multiply(int x, int y) {
        return x * y;
    }
}

// 错误2:缺少return语句
public class ErrorDemo2 {
    public static int getNumber(boolean flag) {
        if (flag) {
            return 10;
        }
        // 编译错误:缺少返回语句
        // 非void方法必须保证所有路径都有返回值
        else {
            return 20;  // 添加这个return就正确了
        }
    }
}

// 错误3:void方法使用return返回值
public class ErrorDemo3 {
    public static void printNumber(int num) {
        System.out.println("数字是: " + num);
        // return num;  // 错误:void方法不能返回值
        return;  // 正确:可以提前结束方法
    }
}

// 错误4:方法调用不匹配
public class ErrorDemo4 {
    public static void main(String[] args) {
        // printMessage();  // 错误:缺少参数
        // printMessage(123);  // 错误:参数类型不匹配
        printMessage("正确调用");  // 正确
        
        // int result = add(10, 20.5);  // 错误:参数类型不匹配
        int result = add(10, 20);  // 正确
    }
    
    public static void printMessage(String msg) {
        System.out.println(msg);
    }
    
    public static int add(int a, int b) {
        return a + b;
    }
}

3. 方法执行过程

3.1 方法调用栈

复制代码
public class MethodCallStack {
    public static void main(String[] args) {
        System.out.println("main方法开始");
        method1();
        System.out.println("main方法结束");
    }
    
    public static void method1() {
        System.out.println("method1开始");
        method2();
        System.out.println("method1结束");
    }
    
    public static void method2() {
        System.out.println("method2开始");
        method3();
        System.out.println("method2结束");
    }
    
    public static void method3() {
        System.out.println("method3执行");
    }
}

执行顺序

复制代码
main开始 → method1开始 → method2开始 → method3执行 → method2结束 → method1结束 → main结束

3.2 递归方法执行过程

复制代码
public class RecursionDemo {
    public static void main(String[] args) {
        System.out.println("5! = " + factorial(5));
    }
    
    public static int factorial(int n) {
        System.out.println("进入factorial(" + n + ")");
        
        if (n == 1) {
            System.out.println("递归出口:factorial(1) = 1");
            return 1;
        }
        
        int result = n * factorial(n - 1);
        System.out.println("返回:factorial(" + n + ") = " + result);
        return result;
    }
}

执行结果

复制代码
进入factorial(5)
进入factorial(4)
进入factorial(3)
进入factorial(2)
进入factorial(1)
递归出口:factorial(1) = 1
返回:factorial(2) = 2
返回:factorial(3) = 6
返回:factorial(4) = 24
返回:factorial(5) = 120
5! = 120

3.3 注意易错点

复制代码
// 错误:无限递归(缺少递归出口)
public class InfiniteRecursion {
    public static void main(String[] args) {
        // infinite();  // StackOverflowError
    }
    
    public static void infinite() {
        System.out.println("无限递归...");
        infinite();  // 没有退出条件
    }
}

// 正确:有递归出口
public class SafeRecursion {
    public static void main(String[] args) {
        countDown(5);
    }
    
    public static void countDown(int n) {
        if (n <= 0) {  // 递归出口
            System.out.println("结束!");
            return;
        }
        System.out.println(n + "...");
        countDown(n - 1);  // 递归调用
    }
}

4. 参数传递(值传递 vs 引用传递)

4.1 基本数据类型:值传递

复制代码
public class ValuePassing {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        
        System.out.println("交换前: a=" + a + ", b=" + b);
        swap(a, b);
        System.out.println("交换后: a=" + a + ", b=" + b);  // a,b值不变
    }
    
    public static void swap(int x, int y) {
        System.out.println("方法内交换前: x=" + x + ", y=" + y);
        int temp = x;
        x = y;
        y = temp;
        System.out.println("方法内交换后: x=" + x + ", y=" + y);
    }
}

输出

复制代码
交换前: a=10, b=20
方法内交换前: x=10, y=20
方法内交换后: x=20, y=10
交换后: a=10, b=20  // a,b没有改变!

4.2 引用数据类型:引用传递(传递的是引用值的拷贝)

复制代码
public class ReferencePassing {
    public static void main(String[] args) {
        // 1. 数组:可以修改内容
        int[] arr = {10, 20};
        System.out.println("修改前: arr[0]=" + arr[0] + ", arr[1]=" + arr[1]);
        modifyArray(arr);
        System.out.println("修改后: arr[0]=" + arr[0] + ", arr[1]=" + arr[1]);
        
        // 2. 对象
        Person p = new Person("张三", 20);
        System.out.println("修改前: " + p);
        modifyPerson(p);
        System.out.println("修改后: " + p);
        
        // 3. 字符串:不可变对象
        String str = "hello";
        System.out.println("修改前: " + str);
        modifyString(str);
        System.out.println("修改后: " + str);  // 不变
    }
    
    public static void modifyArray(int[] array) {
        array[0] = 100;
        array[1] = 200;
    }
    
    public static void modifyPerson(Person person) {
        person.setAge(30);  // 可以修改对象内容
        // person = new Person("李四", 40);  // 这不会影响main中的person
    }
    
    public static void modifyString(String s) {
        s = "world";  // 创建新对象,不影响原来的
    }
}

class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

输出

复制代码
修改前: arr[0]=10, arr[1]=20
修改后: arr[0]=100, arr[1]=200  // 数组内容被修改
修改前: Person{name='张三', age=20}
修改后: Person{name='张三', age=30}  // 对象内容被修改
修改前: hello
修改后: hello  // 字符串不变

4.3 注意易错点

复制代码
// 误区:以为能交换基本数据类型
public class SwapDemo {
    public static void main(String[] args) {
        int a = 10, b = 20;
        swap(a, b);  // 无法交换
        System.out.println("a=" + a + ", b=" + b);  // 还是10,20
        
        // 正确做法:使用数组或对象
        int[] nums = {10, 20};
        swapArray(nums);
        System.out.println("nums[0]=" + nums[0] + ", nums[1]=" + nums[1]);  // 20,10
    }
    
    // 错误:不能交换基本数据类型
    public static void swap(int x, int y) {
        int temp = x;
        x = y;
        y = temp;
    }
    
    // 正确:通过数组交换
    public static void swapArray(int[] arr) {
        int temp = arr[0];
        arr[0] = arr[1];
        arr[1] = temp;
    }
}

5. 方法重载

5.1 概念

方法重载(Overloading):同一个类中,方法名相同但参数列表不同的多个方法。

5.2 代码示例

复制代码
public class OverloadDemo {
    public static void main(String[] args) {
        // 1. 参数个数不同
        System.out.println(add(10, 20));      // 调用add(int, int)
        System.out.println(add(10, 20, 30));  // 调用add(int, int, int)
        
        // 2. 参数类型不同
        System.out.println(add(10.5, 20.5));  // 调用add(double, double)
        
        // 3. 参数顺序不同
        printInfo("张三", 20);
        printInfo(20, "张三");
        
        // 4. 可变参数
        System.out.println(sum(1, 2, 3));           // 6
        System.out.println(sum(1, 2, 3, 4, 5));     // 15
        System.out.println(sum());                  // 0
    }
    
    // 重载方法1:两个int参数
    public static int add(int a, int b) {
        System.out.println("调用add(int, int)");
        return a + b;
    }
    
    // 重载方法2:三个int参数
    public static int add(int a, int b, int c) {
        System.out.println("调用add(int, int, int)");
        return a + b + c;
    }
    
    // 重载方法3:两个double参数
    public static double add(double a, double b) {
        System.out.println("调用add(double, double)");
        return a + b;
    }
    
    // 重载方法4:参数顺序不同
    public static void printInfo(String name, int age) {
        System.out.println("姓名: " + name + ", 年龄: " + age);
    }
    
    public static void printInfo(int age, String name) {
        System.out.println("年龄: " + age + ", 姓名: " + name);
    }
    
    // 可变参数
    public static int sum(int... numbers) {
        int total = 0;
        for (int num : numbers) {
            total += num;
        }
        return total;
    }
}

5.3 注意易错点

复制代码
// 错误1:仅返回值类型不同,不构成重载
public class ErrorOverload1 {
    // public static int getValue() { return 10; }
    // public static double getValue() { return 10.5; }  // 编译错误
    
    // 正确:参数不同
    public static int getValue(int x) { return x; }
    public static double getValue(double x) { return x; }
}

// 错误2:参数名不同不算重载
public class ErrorOverload2 {
    // public static void test(int a) {}
    // public static void test(int b) {}  // 编译错误:重复定义
    
    // 正确:参数类型不同
    public static void test(int a) {}
    public static void test(long a) {}
}

// 注意:自动类型转换可能引起歧义
public class AmbiguityDemo {
    public static void print(int x) {
        System.out.println("int: " + x);
    }
    
    public static void print(double x) {
        System.out.println("double: " + x);
    }
    
    public static void main(String[] args) {
        print(10);      // 调用print(int)
        print(10.5);    // 调用print(double)
        // print(10L);  // 编译错误:模糊调用,可以转int或double
    }
}

6. 递归

6.1 递归基本概念

递归:方法调用自身。需要满足两个条件:

  1. 递归出口(终止条件)

  2. 递归公式(问题分解)

6.2 递归示例

示例1:阶乘
复制代码
public class Factorial {
    public static void main(String[] args) {
        System.out.println("5! = " + factorial(5));
        System.out.println("0! = " + factorial(0));
        
        // 打印计算过程
        System.out.println("\n计算过程:");
        factorialDetail(5);
    }
    
    public static int factorial(int n) {
        // 递归出口
        if (n <= 1) {
            return 1;
        }
        // 递归公式:n! = n * (n-1)!
        return n * factorial(n - 1);
    }
    
    // 详细展示递归过程
    public static int factorialDetail(int n) {
        System.out.println("调用 factorial(" + n + ")");
        if (n <= 1) {
            System.out.println("返回 1");
            return 1;
        }
        int result = n * factorialDetail(n - 1);
        System.out.println("factorial(" + n + ") = " + n + " * factorial(" + (n-1) + ") = " + result);
        return result;
    }
}
示例2:斐波那契数列(递归-低效)
复制代码
public class FibonacciRecursive {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            System.out.print(fib(i) + " ");
        }
        System.out.println();
        
        // 计算第40项(非常慢)
        long start = System.currentTimeMillis();
        long result = fib(40);
        long end = System.currentTimeMillis();
        System.out.println("fib(40) = " + result + ", 耗时: " + (end - start) + "ms");
    }
    
    public static long fib(int n) {
        // 递归出口
        if (n == 1 || n == 2) {
            return 1;
        }
        // 递归公式:fib(n) = fib(n-1) + fib(n-2)
        return fib(n - 1) + fib(n - 2);
    }
}
示例3:斐波那契数列(循环-高效)
复制代码
public class FibonacciLoop {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            System.out.print(fib(i) + " ");
        }
        System.out.println();
        
        // 计算第40项(非常快)
        long start = System.currentTimeMillis();
        long result = fib(40);
        long end = System.currentTimeMillis();
        System.out.println("fib(40) = " + result + ", 耗时: " + (end - start) + "ms");
        
        // 计算更大项
        System.out.println("fib(100) = " + fib(100));
    }
    
    public static long fib(int n) {
        if (n <= 2) return 1;
        
        long a = 1, b = 1, c = 0;
        for (int i = 3; i <= n; i++) {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
}
示例4:汉诺塔
复制代码
public class Hanoi {
    public static void main(String[] args) {
        hanoi(3, 'A', 'B', 'C');
    }
    
    /**
     * 将n个盘子从A移动到C,借助B
     */
    public static void hanoi(int n, char from, char to, char aux) {
        if (n == 1) {
            // 递归出口:只有一个盘子
            System.out.println("移动盘子1从 " + from + " 到 " + to);
            return;
        }
        
        // 将n-1个盘子从A移动到B,借助C
        hanoi(n - 1, from, aux, to);
        
        // 将第n个盘子从A移动到C
        System.out.println("移动盘子" + n + "从 " + from + " 到 " + to);
        
        // 将n-1个盘子从B移动到C,借助A
        hanoi(n - 1, aux, to, from);
    }
}

6.3 递归练习解答

练习1:按位打印数字
复制代码
public class PrintDigits {
    public static void main(String[] args) {
        printDigits(1234);  // 输出: 1 2 3 4
        System.out.println();
        printDigitsReverse(1234);  // 输出: 4 3 2 1
    }
    
    // 正向打印(递归在打印前)
    public static void printDigits(int n) {
        if (n > 9) {
            printDigits(n / 10);  // 先处理高位
        }
        System.out.print(n % 10 + " ");  // 打印当前位
    }
    
    // 反向打印(递归在打印后)
    public static void printDigitsReverse(int n) {
        System.out.print(n % 10 + " ");  // 先打印当前位
        if (n > 9) {
            printDigitsReverse(n / 10);  // 再处理高位
        }
    }
}
练习2:数字各位之和
复制代码
public class DigitSum {
    public static void main(String[] args) {
        System.out.println("1729各位之和: " + digitSum(1729));  // 19
        System.out.println("123各位之和: " + digitSum(123));    // 6
        System.out.println("0各位之和: " + digitSum(0));        // 0
    }
    
    public static int digitSum(int n) {
        if (n < 10) {
            return n;  // 递归出口:只有一位数
        }
        return n % 10 + digitSum(n / 10);  // 最后一位 + 前面各位的和
    }
}
练习3:青蛙跳台阶
复制代码
public class FrogJump {
    public static void main(String[] args) {
        // 问题:青蛙可以跳1级或2级台阶,跳n级有多少种跳法
        for (int i = 1; i <= 10; i++) {
            System.out.println("台阶" + i + "级: " + jump(i) + "种跳法");
        }
    }
    
    public static int jump(int n) {
        if (n <= 2) {
            return n;  // 1级:1种,2级:2种
        }
        // 最后一步跳1级 + 最后一步跳2级
        return jump(n - 1) + jump(n - 2);
    }
    
    // 优化版本:避免重复计算
    public static int jumpOptimized(int n) {
        if (n <= 2) return n;
        
        int a = 1, b = 2, c = 0;
        for (int i = 3; i <= n; i++) {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
}

6.4 递归注意易错点

复制代码
// 错误1:缺少递归出口
public class NoBaseCase {
    public static void infiniteRecursion(int n) {
        System.out.println("n = " + n);
        infiniteRecursion(n + 1);  // 无限递归
    }
    
    // 正确:有递归出口
    public static void finiteRecursion(int n) {
        if (n > 10) {  // 递归出口
            return;
        }
        System.out.println("n = " + n);
        finiteRecursion(n + 1);
    }
}

// 错误2:递归深度太大
public class DeepRecursion {
    public static void main(String[] args) {
        // factorial(10000);  // StackOverflowError
        System.out.println("使用循环计算大数阶乘");
    }
    
    public static long factorial(int n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1);  // n太大导致栈溢出
    }
}

// 正确:尾递归优化(Java不支持真正的尾递归优化)
public class TailRecursion {
    public static long factorial(int n) {
        return factorialTail(n, 1);
    }
    
    private static long factorialTail(int n, long result) {
        if (n <= 1) return result;
        return factorialTail(n - 1, n * result);  // 尾递归
    }
}

7. 方法签名与javap工具

7.1 方法签名

方法签名 = 方法名 + 参数类型列表(不包括返回值类型和访问修饰符)

复制代码
public class MethodSignature {
    // 方法签名: add(int,int)
    public static int add(int a, int b) {
        return a + b;
    }
    
    // 方法签名: add(double,double)
    public static double add(double a, double b) {
        return a + b;
    }
    
    // 方法签名: add(int,int,int)
    public static int add(int a, int b, int c) {
        return a + b + c;
    }
    
    // 以下编译错误:方法签名冲突
    // public static double add(int x, int y) { return x + y; }
}

7.2 使用javap查看字节码

复制代码
# 1. 编译Java文件
javac MethodSignature.java

# 2. 查看字节码
javap -c MethodSignature

# 3. 查看详细信息(包括方法签名)
javap -v MethodSignature

输出示例

复制代码
public static int add(int, int);
  descriptor: (II)I
  // ...

public static double add(double, double);
  descriptor: (DD)D
  // ...

public static int add(int, int, int);
  descriptor: (III)I
  // ...

7.3 方法重载的选择

复制代码
public class OverloadSelection {
    public static void main(String[] args) {
        // 精确匹配优先
        test(10);      // 调用test(int)
        test(10.0);    // 调用test(double)
        test(10L);     // 调用test(long)
        
        // 自动类型转换
        byte b = 10;
        test(b);       // 调用test(int) - byte自动转int
        test(10.5f);   // 调用test(double) - float自动转double
        
        // 多参数匹配
        test(10, 20);        // 调用test(int, int)
        test(10, 20.0);      // 调用test(int, double)
        test(10.0, 20);      // 调用test(double, int)
        // test(10.0, 20.0); // 编译错误:ambiguous
    }
    
    public static void test(int x) {
        System.out.println("test(int): " + x);
    }
    
    public static void test(double x) {
        System.out.println("test(double): " + x);
    }
    
    public static void test(long x) {
        System.out.println("test(long): " + x);
    }
    
    public static void test(int x, int y) {
        System.out.println("test(int, int): " + x + ", " + y);
    }
    
    public static void test(int x, double y) {
        System.out.println("test(int, double): " + x + ", " + y);
    }
    
    public static void test(double x, int y) {
        System.out.println("test(double, int): " + x + ", " + y);
    }
}

8. 综合练习

练习1:计算器

复制代码
public class Calculator {
    public static void main(String[] args) {
        System.out.println("加法: " + calculate(10, 5, '+'));
        System.out.println("减法: " + calculate(10, 5, '-'));
        System.out.println("乘法: " + calculate(10, 5, '*'));
        System.out.println("除法: " + calculate(10, 5, '/'));
        System.out.println("取模: " + calculate(10, 3, '%'));
    }
    
    public static double calculate(double a, double b, char op) {
        switch (op) {
            case '+':
                return add(a, b);
            case '-':
                return subtract(a, b);
            case '*':
                return multiply(a, b);
            case '/':
                if (b == 0) {
                    throw new ArithmeticException("除数不能为0");
                }
                return divide(a, b);
            case '%':
                return modulus(a, b);
            default:
                throw new IllegalArgumentException("不支持的操作符: " + op);
        }
    }
    
    private static double add(double a, double b) {
        return a + b;
    }
    
    private static double subtract(double a, double b) {
        return a - b;
    }
    
    private static double multiply(double a, double b) {
        return a * b;
    }
    
    private static double divide(double a, double b) {
        return a / b;
    }
    
    private static double modulus(double a, double b) {
        return a % b;
    }
}

练习2:验证回文数

复制代码
public class Palindrome {
    public static void main(String[] args) {
        System.out.println(isPalindrome(12321));  // true
        System.out.println(isPalindrome(12345));  // false
        System.out.println(isPalindrome(0));      // true
        System.out.println(isPalindrome(11));     // true
    }
    
    // 方法1:转换为字符串
    public static boolean isPalindrome(int num) {
        String str = Integer.toString(num);
        return isPalindrome(str, 0, str.length() - 1);
    }
    
    private static boolean isPalindrome(String str, int left, int right) {
        if (left >= right) {
            return true;
        }
        if (str.charAt(left) != str.charAt(right)) {
            return false;
        }
        return isPalindrome(str, left + 1, right - 1);
    }
    
    // 方法2:数学方法
    public static boolean isPalindromeMath(int num) {
        if (num < 0) return false;
        if (num < 10) return true;
        
        int original = num;
        int reversed = 0;
        
        while (num > 0) {
            reversed = reversed * 10 + num % 10;
            num /= 10;
        }
        
        return original == reversed;
    }
}

练习3:最大公约数和最小公倍数

复制代码
public class MathUtils {
    public static void main(String[] args) {
        int a = 24, b = 36;
        
        System.out.println("GCD(" + a + ", " + b + ") = " + gcd(a, b));
        System.out.println("LCM(" + a + ", " + b + ") = " + lcm(a, b));
        
        // 重载方法测试
        System.out.println("GCD of three numbers: " + gcd(12, 18, 24));
    }
    
    // 辗转相除法求最大公约数(递归)
    public static int gcd(int a, int b) {
        if (b == 0) {
            return a;
        }
        return gcd(b, a % b);
    }
    
    // 辗转相除法求最大公约数(循环)
    public static int gcdIterative(int a, int b) {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }
    
    // 最小公倍数
    public static int lcm(int a, int b) {
        return a * b / gcd(a, b);
    }
    
    // 重载:求三个数的最大公约数
    public static int gcd(int a, int b, int c) {
        return gcd(gcd(a, b), c);
    }
}

关键总结

1. 方法定义要点

  • 方法必须定义在类中

  • 方法不能嵌套定义

  • 方法签名 = 方法名 + 参数类型列表

  • 返回类型为void时,可以没有return或只有return;

2. 参数传递规则

  • 基本类型:值传递(传递值的拷贝)

  • 引用类型:引用传递(传递引用的拷贝)

  • String类型:虽然是引用类型,但不可变,类似值传递

3. 方法重载条件

  • 方法名相同

  • 参数列表不同(类型、个数、顺序)

  • 与返回类型、访问修饰符无关

4. 递归要点

  • 必须有递归出口(终止条件)

  • 必须有递归公式(问题分解)

  • 注意递归深度(可能栈溢出)

  • 某些问题用循环更高效

5. 最佳实践

数组的定义与使用

1. 数组的基本概念

1.1 概念

数组是存储相同类型 元素的连续 内存空间,每个元素通过下标访问。

1.2 为什么需要数组

不使用数组的代码

复制代码
public class WithoutArray {
    public static void main(String[] args) {
        // 存储5个学生成绩
        int score1 = 70;
        int score2 = 80;
        int score3 = 85;
        int score4 = 60;
        int score5 = 90;
        
        // 输出成绩
        System.out.println(score1);
        System.out.println(score2);
        System.out.println(score3);
        System.out.println(score4);
        System.out.println(score5);
        
        // 问题:如果100个学生怎么办?
        // 需要定义100个变量,100行输出代码!
    }
}

使用数组的代码

复制代码
public class WithArray {
    public static void main(String[] args) {
        // 用数组存储5个学生成绩
        int[] scores = {70, 80, 85, 60, 90};
        
        // 输出所有成绩
        for (int score : scores) {
            System.out.println(score);
        }
        
        // 即使100个学生也很简单
        int[] manyScores = new int[100];
        // 可以方便地进行批量操作
    }
}

2. 数组的创建与初始化

2.1 数组创建方式

复制代码
public class ArrayCreation {
    public static void main(String[] args) {
        // 方式1:动态初始化(指定长度)
        int[] arr1 = new int[5];  // 创建长度为5的int数组
        String[] names = new String[3];  // 创建长度为3的String数组
        
        // 方式2:静态初始化(指定元素)
        int[] arr2 = new int[]{1, 2, 3, 4, 5};
        String[] fruits = new String[]{"apple", "banana", "orange"};
        
        // 方式3:静态初始化简写
        int[] arr3 = {1, 2, 3, 4, 5};  // 最常用
        double[] prices = {9.9, 19.9, 29.9};
        
        // 方式4:先声明,后初始化
        int[] arr4;  // 声明数组
        arr4 = new int[]{10, 20, 30};  // 初始化
        // arr4 = {10, 20, 30};  // 错误!简写不能分开
        
        // 方式5:不推荐,类似C语言风格
        int arr5[] = {1, 2, 3};  // 不推荐
    }
}

2.2 数组默认值

复制代码
public class ArrayDefaultValue {
    public static void main(String[] args) {
        // 整型数组:默认值为0
        int[] intArr = new int[3];
        System.out.println("int[0] = " + intArr[0]);  // 0
        System.out.println("int[1] = " + intArr[1]);  // 0
        
        // 浮点数组:默认值为0.0
        double[] doubleArr = new double[2];
        System.out.println("double[0] = " + doubleArr[0]);  // 0.0
        
        // 布尔数组:默认值为false
        boolean[] boolArr = new boolean[2];
        System.out.println("boolean[0] = " + boolArr[0]);  // false
        
        // 字符数组:默认值为'\u0000'(空字符)
        char[] charArr = new char[2];
        System.out.println("char[0] = '" + charArr[0] + "'");  // 空字符
        
        // 引用类型数组:默认值为null
        String[] strArr = new String[2];
        System.out.println("String[0] = " + strArr[0]);  // null
        Integer[] integerArr = new Integer[2];
        System.out.println("Integer[0] = " + integerArr[0]);  // null
        
        // 验证null
        // System.out.println(strArr[0].length());  // NullPointerException!
    }
}

2.3 注意易错点

复制代码
// 错误1:数组大小不能是变量(除非是final)
int size = 5;
// int[] arr = new int[size];  // Java中允许,但C语言不允许

// 错误2:静态初始化必须指定元素
// int[] arr = new int[];  // 编译错误
int[] arr = new int[]{};  // 正确:创建空数组
int[] arr2 = {};  // 正确:创建空数组

// 错误3:不能修改数组长度
int[] arr3 = {1, 2, 3};
// arr3.length = 5;  // 错误:length是final的

// 错误4:数组越界
int[] arr4 = {1, 2, 3};
// System.out.println(arr4[3]);  // ArrayIndexOutOfBoundsException
// System.out.println(arr4[-1]);  // ArrayIndexOutOfBoundsException

// 正确:获取长度
System.out.println("数组长度: " + arr4.length);  // 3

3. 数组的使用

3.1 访问和修改数组元素

复制代码
public class ArrayAccess {
    public static void main(String[] args) {
        int[] scores = {85, 92, 78, 90, 88};
        
        // 1. 访问元素
        System.out.println("第一个成绩: " + scores[0]);  // 85
        System.out.println("第三个成绩: " + scores[2]);  // 78
        
        // 2. 修改元素
        scores[1] = 95;  // 将92改为95
        System.out.println("修改后第二个成绩: " + scores[1]);  // 95
        
        // 3. 计算总分和平均分
        int sum = 0;
        for (int i = 0; i < scores.length; i++) {
            sum += scores[i];
        }
        double average = (double) sum / scores.length;
        System.out.println("总分: " + sum);
        System.out.println("平均分: " + average);
        
        // 4. 查找最高分
        int max = scores[0];
        for (int i = 1; i < scores.length; i++) {
            if (scores[i] > max) {
                max = scores[i];
            }
        }
        System.out.println("最高分: " + max);
    }
}

3.2 数组遍历的多种方式

复制代码
public class ArrayTraversal {
    public static void main(String[] args) {
        int[] arr = {10, 20, 30, 40, 50};
        
        // 方式1:for循环(最常用)
        System.out.println("方式1:for循环");
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
        
        // 方式2:for-each循环(增强for循环)
        System.out.println("方式2:for-each循环");
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
        
        // 方式3:while循环
        System.out.println("方式3:while循环");
        int i = 0;
        while (i < arr.length) {
            System.out.print(arr[i] + " ");
            i++;
        }
        System.out.println();
        
        // 方式4:do-while循环
        System.out.println("方式4:do-while循环");
        int j = 0;
        do {
            System.out.print(arr[j] + " ");
            j++;
        } while (j < arr.length);
        System.out.println();
        
        // 方式5:使用Arrays.toString()(调试用)
        System.out.println("方式5:Arrays.toString()");
        System.out.println(java.util.Arrays.toString(arr));
    }
}

3.3 注意易错点

复制代码
// 错误1:数组越界
int[] arr = {1, 2, 3};
for (int i = 0; i <= arr.length; i++) {  // 应该是 i < arr.length
    // System.out.println(arr[i]);  // 最后一次i=3,越界
}

// 错误2:for-each不能修改元素
int[] nums = {1, 2, 3};
for (int num : nums) {
    num = num * 2;  // 只修改了临时变量num,不会影响数组
}
System.out.println(nums[0]);  // 还是1,不是2

// 正确:通过索引修改
for (int i = 0; i < nums.length; i++) {
    nums[i] = nums[i] * 2;  // 真正修改数组元素
}
System.out.println(nums[0]);  // 2

// 错误3:遍历时修改数组长度
int[] arr2 = {1, 2, 3};
// 不要在遍历时添加/删除元素

4. 数组是引用类型

4.1 基本类型 vs 引用类型

复制代码
public class ReferenceType {
    public static void main(String[] args) {
        // 基本类型:值传递
        int a = 10;
        int b = a;  // 复制值
        b = 20;
        System.out.println("a = " + a);  // 10(不变)
        System.out.println("b = " + b);  // 20
        
        // 引用类型:传递引用
        int[] arr1 = {1, 2, 3};
        int[] arr2 = arr1;  // 复制引用(地址),指向同一个数组
        arr2[0] = 100;
        
        System.out.println("arr1[0] = " + arr1[0]);  // 100(跟着变了)
        System.out.println("arr2[0] = " + arr2[0]);  // 100
        
        // 验证是同一个数组
        System.out.println("arr1 == arr2: " + (arr1 == arr2));  // true
    }
}

4.2 内存图解

复制代码
public class MemoryDiagram {
    public static void main(String[] args) {
        // 栈内存:存储基本类型和引用
        int x = 10;         // 栈中直接存储10
        int[] arr = null;   // 栈中存储null
        arr = new int[3];   // 堆中创建数组,栈中存储堆地址
        
        // 赋值
        arr[0] = 1;
        arr[1] = 2;
        arr[2] = 3;
        
        // 另一个引用指向同一个数组
        int[] arr2 = arr;   // arr2和arr指向同一个数组
        arr2[0] = 100;      // 通过arr2修改,arr也能看到
        
        // 创建新数组
        int[] arr3 = new int[]{4, 5, 6};  // 新的堆空间
    }
}

内存图示

复制代码
栈(stack)                 堆(heap)
+--------+               +------------+
| x=10   |               | 数组1      |
+--------+               | [100,2,3] |
| arr ---|-------------> +------------+
+--------+               ^
| arr2 --|---------------+
+--------+               +------------+
| arr3 ---|------------> | 数组2      |
+--------+               | [4,5,6]   |
                         +------------+

4.3 null值

复制代码
public class NullDemo {
    public static void main(String[] args) {
        // 1. 声明为null
        int[] arr = null;
        System.out.println("arr = " + arr);  // null
        
        // 2. 空指针异常
        // System.out.println(arr[0]);  // NullPointerException
        // System.out.println(arr.length);  // NullPointerException
        
        // 3. 安全的检查
        if (arr != null) {
            System.out.println("数组长度: " + arr.length);
        } else {
            System.out.println("数组为null");
        }
        
        // 4. 与0长度数组的区别
        int[] emptyArr = {};  // 空数组,不是null
        System.out.println("emptyArr长度: " + emptyArr.length);  // 0
        
        int[] nullArr = null;  // null,没有数组对象
        // System.out.println("nullArr长度: " + nullArr.length);  // 异常
        
        // 5. 比较
        System.out.println("emptyArr == null? " + (emptyArr == null));  // false
        System.out.println("nullArr == null? " + (nullArr == null));    // true
    }
}

5. 数组作为方法参数和返回值

5.1 数组作为方法参数

复制代码
public class ArrayAsParameter {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        
        // 传递数组给方法
        printArray(arr);
        
        // 修改数组内容
        System.out.println("修改前: " + java.util.Arrays.toString(arr));
        multiplyByTwo(arr);
        System.out.println("修改后: " + java.util.Arrays.toString(arr));
        
        // 基本类型参数(对比)
        int num = 10;
        System.out.println("修改前num: " + num);
        changeValue(num);
        System.out.println("修改后num: " + num);  // 不变
    }
    
    // 打印数组
    public static void printArray(int[] array) {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }
    
    // 修改数组元素(会影响原数组)
    public static void multiplyByTwo(int[] array) {
        for (int i = 0; i < array.length; i++) {
            array[i] = array[i] * 2;
        }
    }
    
    // 修改基本类型(不会影响原值)
    public static void changeValue(int x) {
        x = 100;
    }
}

5.2 数组作为返回值

复制代码
public class ArrayAsReturn {
    public static void main(String[] args) {
        // 1. 获取斐波那契数列
        int[] fibonacci = getFibonacci(10);
        System.out.print("斐波那契数列: ");
        printArray(fibonacci);
        
        // 2. 获取随机数数组
        int[] randomNumbers = getRandomArray(5, 1, 100);
        System.out.print("随机数组: ");
        printArray(randomNumbers);
        
        // 3. 合并两个数组
        int[] arr1 = {1, 2, 3};
        int[] arr2 = {4, 5, 6};
        int[] merged = mergeArrays(arr1, arr2);
        System.out.print("合并数组: ");
        printArray(merged);
    }
    
    // 生成斐波那契数列
    public static int[] getFibonacci(int n) {
        if (n <= 0) {
            return new int[0];  // 返回空数组
        }
        
        int[] fib = new int[n];
        if (n >= 1) fib[0] = 1;
        if (n >= 2) fib[1] = 1;
        
        for (int i = 2; i < n; i++) {
            fib[i] = fib[i-1] + fib[i-2];
        }
        
        return fib;
    }
    
    // 生成随机数组
    public static int[] getRandomArray(int size, int min, int max) {
        int[] arr = new int[size];
        java.util.Random rand = new java.util.Random();
        
        for (int i = 0; i < size; i++) {
            arr[i] = rand.nextInt(max - min + 1) + min;
        }
        
        return arr;
    }
    
    // 合并两个数组
    public static int[] mergeArrays(int[] arr1, int[] arr2) {
        int[] result = new int[arr1.length + arr2.length];
        
        // 复制第一个数组
        for (int i = 0; i < arr1.length; i++) {
            result[i] = arr1[i];
        }
        
        // 复制第二个数组
        for (int i = 0; i < arr2.length; i++) {
            result[arr1.length + i] = arr2[i];
        }
        
        return result;
    }
    
    public static void printArray(int[] arr) {
        System.out.println(java.util.Arrays.toString(arr));
    }
}

5.3 注意易错点

复制代码
// 错误1:返回局部数组的引用
public class ErrorReturn {
    public static int[] createArray() {
        int[] localArr = {1, 2, 3};  // 局部变量
        return localArr;  // 正确:数组在堆上,可以返回
    }
    
    public static int[] createArrayError() {
        int x = 10;  // 局部基本类型
        // return &x;  // 错误:不能返回局部基本类型的地址(Java中不支持)
        
        int[] arr = new int[3];
        return arr;  // 正确:数组在堆上
    }
}

// 错误2:修改数组引用不会影响原数组
public class ModifyReference {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        System.out.println("修改前: " + java.util.Arrays.toString(arr));
        
        tryToChangeReference(arr);
        System.out.println("修改后: " + java.util.Arrays.toString(arr));  // 还是[1,2,3]
    }
    
    public static void tryToChangeReference(int[] array) {
        // 这只会改变局部引用,不影响main中的arr
        array = new int[]{10, 20, 30};
        array[0] = 100;  // 修改新数组
    }
}

6. 数组常用操作

6.1 数组转字符串

复制代码
import java.util.Arrays;

public class ArrayToString {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        
        // 1. 使用Arrays.toString()(推荐)
        String str1 = Arrays.toString(arr);
        System.out.println("Arrays.toString(): " + str1);
        
        // 2. 自己实现
        String str2 = myToString(arr);
        System.out.println("myToString(): " + str2);
        
        // 3. 使用StringBuilder
        String str3 = arrayToString(arr);
        System.out.println("arrayToString(): " + str3);
    }
    
    // 自己实现的toString方法
    public static String myToString(int[] arr) {
        if (arr == null) {
            return "null";
        }
        
        if (arr.length == 0) {
            return "[]";
        }
        
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        
        for (int i = 0; i < arr.length; i++) {
            sb.append(arr[i]);
            if (i < arr.length - 1) {
                sb.append(", ");
            }
        }
        
        sb.append("]");
        return sb.toString();
    }
    
    // 另一种实现
    public static String arrayToString(int[] arr) {
        if (arr == null) return "null";
        
        String result = "[";
        for (int i = 0; i < arr.length; i++) {
            result += arr[i];
            if (i != arr.length - 1) {
                result += ", ";
            }
        }
        result += "]";
        return result;
    }
}

6.2 数组拷贝

复制代码
import java.util.Arrays;

public class ArrayCopy {
    public static void main(String[] args) {
        int[] original = {1, 2, 3, 4, 5};
        
        // 1. 使用Arrays.copyOf(最常用)
        int[] copy1 = Arrays.copyOf(original, original.length);
        System.out.println("copy1: " + Arrays.toString(copy1));
        
        // 拷贝部分元素
        int[] copy2 = Arrays.copyOf(original, 3);  // 只拷贝前3个
        System.out.println("copy2: " + Arrays.toString(copy2));
        
        // 扩展数组
        int[] copy3 = Arrays.copyOf(original, 10);  // 扩展为长度10
        System.out.println("copy3: " + Arrays.toString(copy3));
        
        // 2. 使用Arrays.copyOfRange
        int[] copy4 = Arrays.copyOfRange(original, 1, 4);  // 下标[1,4)
        System.out.println("copy4: " + Arrays.toString(copy4));
        
        // 3. 使用System.arraycopy
        int[] copy5 = new int[original.length];
        System.arraycopy(original, 0, copy5, 0, original.length);
        System.out.println("copy5: " + Arrays.toString(copy5));
        
        // 4. 使用clone()方法
        int[] copy6 = original.clone();
        System.out.println("copy6: " + Arrays.toString(copy6));
        
        // 5. 手动拷贝
        int[] copy7 = manualCopy(original);
        System.out.println("copy7: " + Arrays.toString(copy7));
        
        // 验证是深拷贝
        original[0] = 100;
        System.out.println("修改原数组后:");
        System.out.println("original[0] = " + original[0]);  // 100
        System.out.println("copy1[0] = " + copy1[0]);        // 1(不变)
    }
    
    // 手动实现数组拷贝
    public static int[] manualCopy(int[] arr) {
        if (arr == null) {
            return null;
        }
        
        int[] copy = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            copy[i] = arr[i];
        }
        return copy;
    }
}

6.3 数组查找

复制代码
import java.util.Arrays;

public class ArraySearch {
    public static void main(String[] args) {
        int[] arr = {3, 7, 2, 9, 5, 1, 8, 4, 6};
        
        // 1. 顺序查找
        int target = 5;
        int index1 = sequentialSearch(arr, target);
        System.out.println(target + "的顺序查找结果: " + index1);
        
        // 2. 二分查找(需要有序数组)
        Arrays.sort(arr);  // 先排序
        System.out.println("排序后数组: " + Arrays.toString(arr));
        
        int index2 = binarySearch(arr, target);
        System.out.println(target + "的二分查找结果: " + index2);
        
        int index3 = binarySearchRecursive(arr, target);
        System.out.println(target + "的递归二分查找结果: " + index3);
        
        // 3. 使用Arrays.binarySearch
        int index4 = Arrays.binarySearch(arr, target);
        System.out.println("Arrays.binarySearch结果: " + index4);
        
        // 查找不存在的元素
        int index5 = Arrays.binarySearch(arr, 10);
        System.out.println("查找10的结果: " + index5);  // 负数
    }
    
    // 顺序查找
    public static int sequentialSearch(int[] arr, int target) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == target) {
                return i;
            }
        }
        return -1;  // 没找到
    }
    
    // 二分查找(非递归)
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2;  // 防止溢出
            
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                left = mid + 1;  // 目标在右边
            } else {
                right = mid - 1;  // 目标在左边
            }
        }
        
        return -1;  // 没找到
    }
    
    // 二分查找(递归)
    public static int binarySearchRecursive(int[] arr, int target) {
        return binarySearchRecursive(arr, target, 0, arr.length - 1);
    }
    
    private static int binarySearchRecursive(int[] arr, int target, int left, int right) {
        if (left > right) {
            return -1;
        }
        
        int mid = left + (right - left) / 2;
        
        if (arr[mid] == target) {
            return mid;
        } else if (arr[mid] < target) {
            return binarySearchRecursive(arr, target, mid + 1, right);
        } else {
            return binarySearchRecursive(arr, target, left, mid - 1);
        }
    }
}

6.4 数组排序

复制代码
import java.util.Arrays;
import java.util.Random;

public class ArraySort {
    public static void main(String[] args) {
        int[] arr = generateRandomArray(10, 1, 100);
        System.out.println("原始数组: " + Arrays.toString(arr));
        
        // 1. 冒泡排序
        int[] arr1 = arr.clone();
        bubbleSort(arr1);
        System.out.println("冒泡排序: " + Arrays.toString(arr1));
        
        // 2. 选择排序
        int[] arr2 = arr.clone();
        selectionSort(arr2);
        System.out.println("选择排序: " + Arrays.toString(arr2));
        
        // 3. 插入排序
        int[] arr3 = arr.clone();
        insertionSort(arr3);
        System.out.println("插入排序: " + Arrays.toString(arr3));
        
        // 4. 使用Arrays.sort(优化过的快速排序)
        int[] arr4 = arr.clone();
        Arrays.sort(arr4);
        System.out.println("Arrays.sort: " + Arrays.toString(arr4));
        
        // 5. 降序排序
        int[] arr5 = arr.clone();
        Arrays.sort(arr5);
        reverseArray(arr5);
        System.out.println("降序排序: " + Arrays.toString(arr5));
        
        // 6. 使用Arrays.sort降序
        Integer[] arr6 = Arrays.stream(arr).boxed().toArray(Integer[]::new);
        Arrays.sort(arr6, (a, b) -> b - a);  // 降序
        System.out.println("Arrays.sort降序: " + Arrays.toString(arr6));
    }
    
    // 生成随机数组
    public static int[] generateRandomArray(int size, int min, int max) {
        int[] arr = new int[size];
        Random rand = new Random();
        
        for (int i = 0; i < size; i++) {
            arr[i] = rand.nextInt(max - min + 1) + min;
        }
        
        return arr;
    }
    
    // 冒泡排序
    public static void bubbleSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            boolean swapped = false;
            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;
                    swapped = true;
                }
            }
            // 如果没有发生交换,说明已经有序
            if (!swapped) break;
        }
    }
    
    // 选择排序
    public static void selectionSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            // 交换
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
    
    // 插入排序
    public static void insertionSort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            int key = arr[i];
            int j = i - 1;
            
            // 将比key大的元素向后移动
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    }
    
    // 数组反转
    public static void reverseArray(int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        
        while (left < right) {
            int temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
            left++;
            right--;
        }
    }
}

6.5 数组常见操作练习

复制代码
public class ArrayExercises {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
        
        // 1. 求平均值
        System.out.println("平均值: " + getAverage(arr));
        
        // 2. 求最大值和最小值
        int[] minMax = getMinMax(arr);
        System.out.println("最小值: " + minMax[0] + ", 最大值: " + minMax[1]);
        
        // 3. 数组反转
        int[] reversed = reverseCopy(arr);
        System.out.println("反转数组: " + Arrays.toString(reversed));
        
        // 4. 查找元素
        System.out.println("查找7的位置: " + findElement(arr, 7));
        System.out.println("查找10的位置: " + findElement(arr, 10));
        
        // 5. 统计偶数个数
        System.out.println("偶数个数: " + countEvenNumbers(arr));
        
        // 6. 复制数组
        int[] copy = copyArray(arr);
        System.out.println("复制的数组: " + Arrays.toString(copy));
        
        // 7. 数组去重
        int[] arrWithDuplicates = {1, 2, 2, 3, 4, 4, 5};
        int[] unique = removeDuplicates(arrWithDuplicates);
        System.out.println("去重后: " + Arrays.toString(unique));
    }
    
    public static double getAverage(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0.0;
        }
        
        int sum = 0;
        for (int num : arr) {
            sum += num;
        }
        return (double) sum / arr.length;
    }
    
    public static int[] getMinMax(int[] arr) {
        if (arr == null || arr.length == 0) {
            return new int[]{0, 0};
        }
        
        int min = arr[0];
        int max = arr[0];
        
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] < min) {
                min = arr[i];
            }
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        
        return new int[]{min, max};
    }
    
    public static int[] reverseCopy(int[] arr) {
        if (arr == null) {
            return null;
        }
        
        int[] result = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            result[i] = arr[arr.length - 1 - i];
        }
        return result;
    }
    
    public static int findElement(int[] arr, int target) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == target) {
                return i;
            }
        }
        return -1;
    }
    
    public static int countEvenNumbers(int[] arr) {
        int count = 0;
        for (int num : arr) {
            if (num % 2 == 0) {
                count++;
            }
        }
        return count;
    }
    
    public static int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        
        int[] copy = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            copy[i] = arr[i];
        }
        return copy;
    }
    
    public static int[] removeDuplicates(int[] arr) {
        if (arr == null || arr.length == 0) {
            return new int[0];
        }
        
        // 先排序
        Arrays.sort(arr);
        
        // 计算不重复元素个数
        int uniqueCount = 1;
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] != arr[i - 1]) {
                uniqueCount++;
            }
        }
        
        // 创建新数组
        int[] result = new int[uniqueCount];
        result[0] = arr[0];
        int index = 1;
        
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] != arr[i - 1]) {
                result[index++] = arr[i];
            }
        }
        
        return result;
    }
}

7. 二维数组

7.1 二维数组的概念和创建

复制代码
import java.util.Arrays;

public class TwoDArray {
    public static void main(String[] args) {
        // 1. 声明和初始化
        // 方式1:动态初始化
        int[][] arr1 = new int[3][4];  // 3行4列
        
        // 方式2:静态初始化
        int[][] arr2 = new int[][]{
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };
        
        // 方式3:简写
        int[][] arr3 = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };
        
        // 方式4:不规则数组
        int[][] arr4 = new int[3][];  // 只指定行数
        arr4[0] = new int[]{1, 2};
        arr4[1] = new int[]{3, 4, 5};
        arr4[2] = new int[]{6, 7, 8, 9};
        
        // 2. 访问元素
        System.out.println("arr2[0][0] = " + arr2[0][0]);  // 1
        System.out.println("arr2[1][2] = " + arr2[1][2]);  // 6
        
        // 3. 修改元素
        arr2[0][0] = 100;
        System.out.println("修改后 arr2[0][0] = " + arr2[0][0]);  // 100
        
        // 4. 获取数组长度
        System.out.println("arr2的行数: " + arr2.length);  // 3
        System.out.println("arr2[0]的列数: " + arr2[0].length);  // 3
        System.out.println("arr4[2]的列数: " + arr4[2].length);  // 4
        
        // 5. 打印二维数组
        printArray(arr2);
        printArray(arr4);
    }
    
    public static void printArray(int[][] arr) {
        if (arr == null) {
            System.out.println("null");
            return;
        }
        
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == null) {
                System.out.println("null");
                continue;
            }
            
            for (int j = 0; j < arr[i].length; j++) {
                System.out.print(arr[i][j] + "\t");
            }
            System.out.println();
        }
    }
}

7.2 二维数组遍历

复制代码
public class TwoDArrayTraversal {
    public static void main(String[] args) {
        int[][] matrix = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };
        
        // 1. 普通for循环
        System.out.println("普通for循环:");
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                System.out.print(matrix[i][j] + " ");
            }
            System.out.println();
        }
        
        // 2. 增强for循环
        System.out.println("\n增强for循环:");
        for (int[] row : matrix) {
            for (int num : row) {
                System.out.print(num + " ");
            }
            System.out.println();
        }
        
        // 3. 使用Arrays.deepToString
        System.out.println("\nArrays.deepToString:");
        System.out.println(Arrays.deepToString(matrix));
        
        // 4. 计算总和
        int sum = 0;
        for (int[] row : matrix) {
            for (int num : row) {
                sum += num;
            }
        }
        System.out.println("总和: " + sum);
        
        // 5. 转置矩阵
        int[][] transposed = transpose(matrix);
        System.out.println("转置矩阵:");
        printArray(transposed);
    }
    
    public static int[][] transpose(int[][] matrix) {
        if (matrix == null || matrix.length == 0) {
            return new int[0][0];
        }
        
        int rows = matrix.length;
        int cols = matrix[0].length;
        int[][] result = new int[cols][rows];
        
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                result[j][i] = matrix[i][j];
            }
        }
        
        return result;
    }
    
    public static void printArray(int[][] arr) {
        for (int[] row : arr) {
            for (int num : row) {
                System.out.print(num + "\t");
            }
            System.out.println();
        }
    }
}

7.3 二维数组应用示例

复制代码
public class MatrixOperations {
    public static void main(String[] args) {
        int[][] matrixA = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };
        
        int[][] matrixB = {
            {9, 8, 7},
            {6, 5, 4},
            {3, 2, 1}
        };
        
        // 1. 矩阵相加
        System.out.println("矩阵相加:");
        int[][] sum = addMatrices(matrixA, matrixB);
        printMatrix(sum);
        
        // 2. 矩阵相乘
        System.out.println("\n矩阵相乘:");
        int[][] product = multiplyMatrices(matrixA, matrixB);
        printMatrix(product);
        
        // 3. 对角线之和
        System.out.println("\n主对角线之和: " + sumMainDiagonal(matrixA));
        System.out.println("副对角线之和: " + sumAntiDiagonal(matrixA));
        
        // 4. 查找最大值
        int[] maxInfo = findMax(matrixA);
        System.out.printf("\n最大值: %d, 位置: [%d][%d]\n", 
                         maxInfo[0], maxInfo[1], maxInfo[2]);
    }
    
    public static int[][] addMatrices(int[][] A, int[][] B) {
        int rows = A.length;
        int cols = A[0].length;
        int[][] result = new int[rows][cols];
        
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                result[i][j] = A[i][j] + B[i][j];
            }
        }
        return result;
    }
    
    public static int[][] multiplyMatrices(int[][] A, int[][] B) {
        int rowsA = A.length;
        int colsA = A[0].length;
        int colsB = B[0].length;
        int[][] result = new int[rowsA][colsB];
        
        for (int i = 0; i < rowsA; i++) {
            for (int j = 0; j < colsB; j++) {
                for (int k = 0; k < colsA; k++) {
                    result[i][j] += A[i][k] * B[k][j];
                }
            }
        }
        return result;
    }
    
    public static int sumMainDiagonal(int[][] matrix) {
        int sum = 0;
        for (int i = 0; i < matrix.length; i++) {
            sum += matrix[i][i];
        }
        return sum;
    }
    
    public static int sumAntiDiagonal(int[][] matrix) {
        int sum = 0;
        int n = matrix.length;
        for (int i = 0; i < n; i++) {
            sum += matrix[i][n - 1 - i];
        }
        return sum;
    }
    
    public static int[] findMax(int[][] matrix) {
        int max = matrix[0][0];
        int maxRow = 0;
        int maxCol = 0;
        
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                if (matrix[i][j] > max) {
                    max = matrix[i][j];
                    maxRow = i;
                    maxCol = j;
                }
            }
        }
        
        return new int[]{max, maxRow, maxCol};
    }
    
    public static void printMatrix(int[][] matrix) {
        for (int[] row : matrix) {
            for (int num : row) {
                System.out.printf("%4d", num);
            }
            System.out.println();
        }
    }
}

8. 注意易错点总结

8.1 常见错误

复制代码
// 1. 数组越界
int[] arr = {1, 2, 3};
// System.out.println(arr[3]);  // ArrayIndexOutOfBoundsException

// 2. 空指针异常
int[] arr2 = null;
// System.out.println(arr2.length);  // NullPointerException
// System.out.println(arr2[0]);      // NullPointerException

// 3. 数组长度不可变
int[] arr3 = {1, 2, 3};
// arr3.length = 5;  // 错误:length是final的

// 4. 错误的多维数组声明
// int[][] arr4 = new int[][3];  // 错误:必须指定行数
int[][] arr4 = new int[3][];    // 正确

// 5. 数组赋值是引用赋值
int[] a = {1, 2, 3};
int[] b = a;  // b和a指向同一个数组
b[0] = 100;
System.out.println(a[0]);  // 100,a也被修改了

// 6. 使用==比较数组
int[] arr5 = {1, 2, 3};
int[] arr6 = {1, 2, 3};
System.out.println(arr5 == arr6);  // false,比较的是地址
System.out.println(Arrays.equals(arr5, arr6));  // true,比较内容

8.2 最佳实践

复制代码
// 1. 使用前检查null
public static void printArray(int[] arr) {
    if (arr == null) {
        System.out.println("数组为null");
        return;
    }
    // 处理数组
}

// 2. 使用System.arraycopy进行数组拷贝
int[] source = {1, 2, 3, 4, 5};
int[] dest = new int[source.length];
System.arraycopy(source, 0, dest, 0, source.length);

// 3. 使用Arrays类工具方法
int[] arr = {5, 3, 1, 4, 2};
Arrays.sort(arr);                    // 排序
int index = Arrays.binarySearch(arr, 3);  // 二分查找
int[] copy = Arrays.copyOf(arr, arr.length);  // 拷贝
System.out.println(Arrays.toString(arr));  // 转字符串

// 4. 使用增强for循环遍历
int[] numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
    System.out.println(num);
}

// 5. 二维数组使用Arrays.deepToString打印
int[][] matrix = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepToString(matrix));

// 6. 合理使用数组初始化
int[] arr1 = new int[10];  // 全0
int[] arr2 = new int[]{1, 2, 3};  // 指定元素
int[] arr3 = {1, 2, 3};  // 简写

9. 综合练习

练习1:杨辉三角

复制代码
public class YangHuiTriangle {
    public static void main(String[] args) {
        int n = 10;  // 行数
        int[][] triangle = generateYangHui(n);
        printYangHui(triangle);
    }
    
    public static int[][] generateYangHui(int n) {
        int[][] triangle = new int[n][];
        
        for (int i = 0; i < n; i++) {
            triangle[i] = new int[i + 1];
            triangle[i][0] = 1;  // 每行第一个元素是1
            triangle[i][i] = 1;  // 每行最后一个元素是1
            
            for (int j = 1; j < i; j++) {
                triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j];
            }
        }
        
        return triangle;
    }
    
    public static void printYangHui(int[][] triangle) {
        int n = triangle.length;
        
        for (int i = 0; i < n; i++) {
            // 打印空格对齐
            for (int k = 0; k < n - i - 1; k++) {
                System.out.print("  ");
            }
            
            // 打印数字
            for (int j = 0; j <= i; j++) {
                System.out.printf("%4d", triangle[i][j]);
            }
            System.out.println();
        }
    }
}

练习2:井字棋游戏

复制代码
import java.util.Scanner;

public class TicTacToe {
    private static final int SIZE = 3;
    private static char[][] board = new char[SIZE][SIZE];
    private static char currentPlayer = 'X';
    
    public static void main(String[] args) {
        initializeBoard();
        printBoard();
        
        Scanner scanner = new Scanner(System.in);
        
        while (true) {
            System.out.println("玩家 " + currentPlayer + " 的回合");
            System.out.print("请输入行(1-3): ");
            int row = scanner.nextInt() - 1;
            System.out.print("请输入列(1-3): ");
            int col = scanner.nextInt() - 1;
            
            if (isValidMove(row, col)) {
                makeMove(row, col);
                printBoard();
                
                if (checkWin()) {
                    System.out.println("玩家 " + currentPlayer + " 获胜!");
                    break;
                }
                
                if (isBoardFull()) {
                    System.out.println("平局!");
                    break;
                }
                
                switchPlayer();
            } else {
                System.out.println("无效的移动,请重试!");
            }
        }
        
        scanner.close();
    }
    
    private static void initializeBoard() {
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                board[i][j] = ' ';
            }
        }
    }
    
    private static void printBoard() {
        System.out.println("  1 2 3");
        for (int i = 0; i < SIZE; i++) {
            System.out.print((i + 1) + " ");
            for (int j = 0; j < SIZE; j++) {
                System.out.print(board[i][j]);
                if (j < SIZE - 1) {
                    System.out.print("|");
                }
            }
            System.out.println();
            if (i < SIZE - 1) {
                System.out.println("  -+-+-");
            }
        }
        System.out.println();
    }
    
    private static boolean isValidMove(int row, int col) {
        return row >= 0 && row < SIZE && col >= 0 && col < SIZE && board[row][col] == ' ';
    }
    
    private static void makeMove(int row, int col) {
        board[row][col] = currentPlayer;
    }
    
    private static void switchPlayer() {
        currentPlayer = (currentPlayer == 'X') ? 'O' : 'X';
    }
    
    private static boolean checkWin() {
        // 检查行
        for (int i = 0; i < SIZE; i++) {
            if (board[i][0] == currentPlayer && board[i][1] == currentPlayer && 
                board[i][2] == currentPlayer) {
                return true;
            }
        }
        
        // 检查列
        for (int j = 0; j < SIZE; j++) {
            if (board[0][j] == currentPlayer && board[1][j] == currentPlayer && 
                board[2][j] == currentPlayer) {
                return true;
            }
        }
        
        // 检查对角线
        if (board[0][0] == currentPlayer && board[1][1] == currentPlayer && 
            board[2][2] == currentPlayer) {
            return true;
        }
        
        if (board[0][2] == currentPlayer && board[1][1] == currentPlayer && 
            board[2][0] == currentPlayer) {
            return true;
        }
        
        return false;
    }
    
    private static boolean isBoardFull() {
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                if (board[i][j] == ' ') {
                    return false;
                }
            }
        }
        return true;
    }
}

关键总结

1. 数组核心概念

  • 连续内存:元素在内存中连续存储

  • 相同类型:所有元素类型必须相同

  • 下标访问:从0开始,通过下标快速访问

  • 固定长度:创建后长度不可变

2. 重要特性

  • 默认值:数值类型为0,boolean为false,引用类型为null

  • length属性:数组长度,不可修改

  • 引用类型:数组变量存储的是引用(地址)

  • 参数传递:传递的是引用,可以修改数组内容

3. 常用操作

复制代码
// 1. 创建数组
int[] arr1 = new int[5];
int[] arr2 = {1, 2, 3};
int[] arr3 = new int[]{1, 2, 3};

// 2. 遍历数组
for (int i = 0; i < arr.length; i++)  // 需要索引时
for (int num : arr)                   // 不需要索引时
Arrays.toString(arr)                  // 快速转字符串

// 3. 数组拷贝
int[] copy1 = Arrays.copyOf(arr, arr.length);
int[] copy2 = arr.clone();
System.arraycopy(src, 0, dest, 0, src.length);

// 4. 数组操作
Arrays.sort(arr)                    // 排序
Arrays.binarySearch(arr, key)       // 二分查找
Arrays.equals(arr1, arr2)           // 比较数组
Arrays.fill(arr, value)             // 填充数组

4. 注意事项

  1. 数组越界:访问前检查索引

  2. 空指针:使用前检查null

  3. 引用赋值:修改会互相影响

  4. 长度固定:需要变长时用ArrayList

  5. 多维数组:实际是数组的数组

5. 性能考虑

  • 数组访问:O(1)时间复杂度

  • 顺序查找:O(n)时间复杂度

  • 二分查找:O(log n)时间复杂度(需有序)

  • 数组拷贝:O(n)时间复杂度

  • 内存连续:缓存友好,访问速度快

类和对象

1. 面向对象初步认知

1.1 概念

面向对象(Object Oriented Programming,OOP)是一种编程思想,将现实世界的事物抽象为程序中的对象,通过对象之间的交互来解决问题。

面向对象 vs 面向过程对比

复制代码
// 面向过程:关注步骤
public class ProcessOriented {
    public static void main(String[] args) {
        // 洗衣服的过程
        打水();
        放衣服();
        倒洗衣粉();
        搓洗(20, "顺时针");
        漂洗(3);
        拧干();
        晾晒();
    }
    
    public static void 搓洗(int 时间, String 方向) {
        System.out.println("搓洗" + 时间 + "分钟,方向:" + 方向);
    }
    // 其他方法...
}

// 面向对象:关注对象
public class ObjectOriented {
    public static void main(String[] args) {
        // 创建洗衣机对象
        洗衣机 我的洗衣机 = new 洗衣机("海尔", "滚筒");
        
        // 创建衣服对象
        衣服 脏衣服 = new 衣服("白色", "棉质");
        
        // 对象交互
        我的洗衣机.洗衣(脏衣服, "标准模式");
    }
}

class 洗衣机 {
    String 品牌;
    String 类型;
    
    public 洗衣机(String 品牌, String 类型) {
        this.品牌 = 品牌;
        this.类型 = 类型;
    }
    
    public void 洗衣(衣服 衣服对象, String 模式) {
        System.out.println(品牌 + "洗衣机正在以" + 模式 + "洗衣服...");
        // 内部处理洗衣服的细节
    }
}

1.2 面向对象三大特性

  • 封装:隐藏对象的属性和实现细节

  • 继承:基于现有类创建新类

  • 多态:同一方法在不同对象上有不同行为


2. 类的定义与使用

2.1 类的基本定义

复制代码
// 定义一个学生类
public class Student {
    // 成员变量(属性)
    public String name;      // 姓名
    public int age;         // 年龄
    public String className; // 班级
    public double score;    // 成绩
    
    // 成员方法(行为)
    public void study() {
        System.out.println(name + "正在学习...");
        score += 0.5;  // 学习增加0.5分
    }
    
    public void exam() {
        System.out.println(name + "正在考试...");
        // 考试逻辑
    }
    
    public void showInfo() {
        System.out.println("姓名:" + name + ",年龄:" + age + 
                          ",班级:" + className + ",成绩:" + score);
    }
}

2.2 类的使用

复制代码
public class Main {
    public static void main(String[] args) {
        // 创建对象(实例化)
        Student student1 = new Student();
        
        // 设置属性
        student1.name = "张三";
        student1.age = 18;
        student1.className = "高三(1)班";
        student1.score = 85.5;
        
        // 调用方法
        student1.study();
        student1.showInfo();
        
        // 创建另一个对象
        Student student2 = new Student();
        student2.name = "李四";
        student2.age = 17;
        student2.showInfo();  // 成绩默认为0.0
    }
}

2.3 注意易错点

复制代码
// 错误1:同一个文件中多个public类
// public class A {}  // 错误:只能有一个public类
// public class B {}

// 正确:一个public类,其他用默认访问权限
class Helper {  // 正确:不是public类
    // ...
}

// 错误2:类名与文件名不匹配
// 文件:MyClass.java
// public class YourClass {}  // 错误:类名必须与文件名相同

// 正确
// 文件:MyClass.java
public class MyClass {}  // 正确

// 错误3:在方法内定义类
public class Outer {
    public void method() {
        // class Inner {}  // 可以,但这是局部内部类
    }
}

3. 构造方法与初始化

3.1 构造方法基本使用

复制代码
public class Person {
    // 成员变量
    public String name;
    public int age;
    public String gender;
    
    // 1. 无参构造方法
    public Person() {
        this.name = "未知";
        this.age = 0;
        this.gender = "未知";
        System.out.println("调用无参构造方法");
    }
    
    // 2. 有参构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        this.gender = "未知";
        System.out.println("调用两个参数的构造方法");
    }
    
    // 3. 全参构造方法
    public Person(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        System.out.println("调用全参构造方法");
    }
    
    public void showInfo() {
        System.out.println("姓名:" + name + ",年龄:" + age + ",性别:" + gender);
    }
}

3.2 构造方法调用

复制代码
public class Main {
    public static void main(String[] args) {
        // 使用不同构造方法创建对象
        Person p1 = new Person();              // 无参构造
        p1.showInfo();
        
        Person p2 = new Person("张三", 20);    // 两个参数
        p2.showInfo();
        
        Person p3 = new Person("李四", 25, "男");  // 全参
        p3.showInfo();
    }
}

3.3 构造方法重载和this调用

复制代码
public class Student {
    public String name;
    public int age;
    public String className;
    
    // 构造方法1:无参
    public Student() {
        this("未知", 0, "未分班");  // 调用三参构造方法
        System.out.println("调用无参构造方法");
    }
    
    // 构造方法2:两个参数
    public Student(String name, int age) {
        this(name, age, "未分班");  // 调用三参构造方法
        System.out.println("调用两参构造方法");
    }
    
    // 构造方法3:三个参数
    public Student(String name, int age, String className) {
        this.name = name;
        this.age = age;
        this.className = className;
        System.out.println("调用三参构造方法");
    }
    
    // 错误:构造方法循环调用
    /*
    public Student() {
        this("默认", 0);  // 调用两参构造
    }
    
    public Student(String name, int age) {
        this();  // 调用无参构造,形成循环!
    }
    */
}

3.4 注意易错点

复制代码
// 错误1:构造方法有返回值类型
class ErrorClass1 {
    // public void ErrorClass1() {  // 这不是构造方法,是普通方法!
    //     System.out.println("这是普通方法");
    // }
    
    public ErrorClass1() {  // 正确:没有返回值类型
        // ...
    }
}

// 错误2:忘记写构造方法时的默认值
class Person2 {
    String name;     // 默认null
    int age;         // 默认0
    boolean isAdult; // 默认false
    
    public void show() {
        System.out.println("name=" + name + ", age=" + age + ", isAdult=" + isAdult);
    }
}

// 错误3:使用未初始化的对象
class Test {
    public static void main(String[] args) {
        Person2 p;  // 只是声明,没有初始化
        // p.show();  // 编译错误:变量p可能未初始化
    }
}

4. this关键字

4.1 this基本使用

复制代码
public class ThisDemo {
    public String name;
    public int age;
    
    // 1. 区分成员变量和局部变量
    public void setInfo(String name, int age) {
        this.name = name;  // this.name是成员变量,name是参数
        this.age = age;    // this.age是成员变量,age是参数
    }
    
    // 2. 返回当前对象
    public ThisDemo incrementAge() {
        this.age++;
        return this;  // 返回当前对象,支持链式调用
    }
    
    // 3. 调用其他构造方法
    public ThisDemo() {
        this("默认姓名", 0);  // 调用两参构造方法
    }
    
    public ThisDemo(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public void show() {
        System.out.println("姓名:" + this.name + ",年龄:" + this.age);
    }
}

4.2 链式调用

复制代码
public class ChainCall {
    public static void main(String[] args) {
        ThisDemo demo = new ThisDemo("张三", 20);
        
        // 链式调用
        demo.setInfo("李四", 25)
            .incrementAge()
            .incrementAge()
            .show();
    }
}

4.3 注意易错点

复制代码
// 错误1:在静态方法中使用this
class StaticError {
    public String name;
    
    public static void staticMethod() {
        // this.name = "张三";  // 错误:不能在静态方法中使用this
        // System.out.println(this);  // 错误
    }
    
    public void instanceMethod() {
        this.name = "张三";  // 正确
    }
}

// 错误2:this调用必须在第一行
class ConstructorError {
    public String name;
    public int age;
    
    /*
    public ConstructorError() {
        System.out.println("开始构造");  // 错误:this必须在第一行
        this("默认", 0);
    }
    */
    
    public ConstructorError(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

// 正确使用
class CorrectUsage {
    private int x;
    
    public void setX(int x) {
        this.x = x;  // 正确:区分成员变量和参数
    }
    
    public int getX() {
        return this.x;  // 明确表示返回成员变量
    }
}

5. 封装

5.1 封装的基本实现

复制代码
public class Student {
    // 私有属性:外部不能直接访问
    private String name;
    private int age;
    private double score;
    private String id;  // 学号
    
    // 公有构造方法
    public Student(String name, int age, String id) {
        this.name = name;
        this.setAge(age);  // 通过setter设置,可以添加验证逻辑
        this.id = id;
        this.score = 0.0;
    }
    
    // Getter方法:获取属性值
    public String getName() {
        return this.name;
    }
    
    public int getAge() {
        return this.age;
    }
    
    public double getScore() {
        return this.score;
    }
    
    public String getId() {
        return this.id;
    }
    
    // Setter方法:设置属性值(可添加验证逻辑)
    public void setName(String name) {
        if (name != null && !name.trim().isEmpty()) {
            this.name = name;
        } else {
            System.out.println("姓名不能为空!");
        }
    }
    
    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            System.out.println("年龄必须在0-150之间!");
            this.age = 0;  // 设置默认值
        }
    }
    
    public void setScore(double score) {
        if (score >= 0 && score <= 100) {
            this.score = score;
        } else {
            System.out.println("成绩必须在0-100之间!");
        }
    }
    
    // 学号不允许修改,所以不提供setter
    // public void setId(String id) {  // 不提供
    //     this.id = id;
    // }
    
    // 其他业务方法
    public void study(double hours) {
        this.score += hours * 0.5;
        if (this.score > 100) {
            this.score = 100;
        }
        System.out.println(this.name + "学习了" + hours + "小时,当前成绩:" + this.score);
    }
    
    public void showInfo() {
        System.out.println("学号:" + this.id + ",姓名:" + this.name + 
                          ",年龄:" + this.age + ",成绩:" + this.score);
    }
}

5.2 封装的使用

复制代码
public class TestEncapsulation {
    public static void main(String[] args) {
        // 创建对象
        Student stu = new Student("张三", 18, "2023001");
        
        // 通过getter获取属性
        System.out.println("姓名:" + stu.getName());
        System.out.println("年龄:" + stu.getAge());
        System.out.println("学号:" + stu.getId());
        
        // 通过setter修改属性
        stu.setName("");  // 验证:空姓名
        stu.setName("张三丰");  // 正确
        stu.setAge(200);  // 验证:非法年龄
        stu.setAge(25);   // 正确
        stu.setScore(150); // 验证:非法成绩
        stu.setScore(85.5); // 正确
        
        // 调用业务方法
        stu.study(2.5);
        stu.showInfo();
        
        // 不能直接访问私有属性
        // System.out.println(stu.name);  // 编译错误
        // stu.score = 100;  // 编译错误
    }
}

5.3 包(package)的使用

复制代码
// 文件:com/company/model/Person.java
package com.company.model;  // 声明包

public class Person {
    private String name;
    private int age;
    
    // 四个访问修饰符的示例
    public String publicField = "public";       // 任何地方都可访问
    protected String protectedField = "protected"; // 同包或子类可访问
    String defaultField = "default";           // 同包可访问
    private String privateField = "private";   // 仅本类可访问
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    
    protected int getAge() {
        return age;
    }
    
    void showDefault() {  // 默认访问权限
        System.out.println("default method");
    }
    
    private void showPrivate() {
        System.out.println("private method");
    }
}

// 文件:com/company/test/TestPerson.java
package com.company.test;  // 不同包

import com.company.model.Person;  // 导入Person类

public class TestPerson {
    public static void main(String[] args) {
        Person p = new Person("张三", 20);
        
        // 测试访问权限
        System.out.println(p.publicField);     // 可以访问
        // System.out.println(p.protectedField);  // 编译错误:不同包的非子类
        // System.out.println(p.defaultField);    // 编译错误:不同包
        // System.out.println(p.privateField);    // 编译错误:私有
        
        System.out.println(p.getName());       // 可以访问
        // System.out.println(p.getAge());        // 编译错误:protected
        // p.showDefault();                       // 编译错误:default
        // p.showPrivate();                       // 编译错误:private
    }
}

5.4 注意易错点

复制代码
// 错误1:忘记使用getter/setter
class BadEncapsulation {
    public String name;  // 不应该用public
    public int age;
    
    // 没有getter/setter,外部可以直接修改
}

// 正确:使用私有属性和公有方法
class GoodEncapsulation {
    private String name;
    private int age;
    
    public String getName() { return name; }
    public void setName(String name) { 
        if (name != null) this.name = name; 
    }
    
    public int getAge() { return age; }
    public void setAge(int age) { 
        if (age > 0) this.age = age; 
    }
}

// 错误2:setter没有验证
class WeakValidation {
    private int score;
    
    public void setScore(int score) {
        this.score = score;  // 没有验证
    }
    
    // 正确:添加验证
    public void setScore2(int score) {
        if (score >= 0 && score <= 100) {
            this.score = score;
        }
    }
}

6. static关键字

6.1 static修饰成员变量

复制代码
public class Student {
    // 实例变量:每个对象都有自己的副本
    private String name;
    private int age;
    
    // 静态变量(类变量):所有对象共享
    public static String school = "清华大学";
    private static int totalStudents = 0;  // 记录创建的学生总数
    
    // 常量:static final
    public static final double PI = 3.14159;
    public static final int MAX_AGE = 100;
    
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        totalStudents++;  // 每创建一个学生,总数加1
    }
    
    // 实例方法
    public void showInfo() {
        // 实例方法可以访问静态和非静态成员
        System.out.println("姓名:" + this.name + ",年龄:" + this.age + 
                          ",学校:" + school);
    }
    
    // 静态方法
    public static void showSchool() {
        System.out.println("学校:" + school);
        // 静态方法中不能访问实例成员
        // System.out.println(this.name);  // 错误
        // System.out.println(name);       // 错误
    }
    
    public static int getTotalStudents() {
        return totalStudents;
    }
    
    public static void changeSchool(String newSchool) {
        school = newSchool;  // 修改静态变量
    }
}

6.2 static使用示例

复制代码
public class TestStatic {
    public static void main(String[] args) {
        // 访问静态变量
        System.out.println("学校:" + Student.school);
        System.out.println("学生总数:" + Student.getTotalStudents());
        
        // 修改静态变量
        Student.school = "北京大学";
        Student.changeSchool("复旦大学");
        
        // 创建对象
        Student stu1 = new Student("张三", 20);
        Student stu2 = new Student("李四", 21);
        Student stu3 = new Student("王五", 22);
        
        // 通过对象访问静态成员(不推荐)
        System.out.println("通过对象访问:" + stu1.school);
        
        // 通过类名访问静态成员(推荐)
        System.out.println("通过类名访问:" + Student.school);
        System.out.println("学生总数:" + Student.getTotalStudents());
        
        // 调用静态方法
        Student.showSchool();
        
        // 访问常量
        System.out.println("PI = " + Student.PI);
        System.out.println("最大年龄 = " + Student.MAX_AGE);
        
        // 常量不能修改
        // Student.PI = 3.14;  // 编译错误
    }
}

6.3 static代码块

复制代码
public class StaticBlockDemo {
    // 静态变量
    public static String school;
    public static int[] numbers;
    
    // 静态代码块:类加载时执行,只执行一次
    static {
        System.out.println("静态代码块1执行");
        school = "清华大学";
        numbers = new int[10];
        
        // 初始化数组
        for (int i = 0; i < numbers.length; i++) {
            numbers[i] = i * 10;
        }
    }
    
    // 可以有多个静态代码块
    static {
        System.out.println("静态代码块2执行");
        // 多个静态代码块按顺序执行
    }
    
    // 实例代码块:每次创建对象时执行
    {
        System.out.println("实例代码块执行");
    }
    
    // 构造方法
    public StaticBlockDemo() {
        System.out.println("构造方法执行");
    }
    
    public static void main(String[] args) {
        System.out.println("main方法开始");
        System.out.println("学校:" + school);
        
        // 创建对象
        new StaticBlockDemo();
        new StaticBlockDemo();
    }
}

执行顺序

复制代码
静态代码块1执行
静态代码块2执行
main方法开始
学校:清华大学
实例代码块执行
构造方法执行
实例代码块执行
构造方法执行

6.4 注意易错点

复制代码
// 错误1:在静态方法中访问实例成员
class StaticError1 {
    private String name;  // 实例变量
    
    public static void staticMethod() {
        // System.out.println(name);  // 错误:不能访问实例变量
        // instanceMethod();          // 错误:不能调用实例方法
    }
    
    public void instanceMethod() {
        System.out.println(name);  // 正确
    }
}

// 错误2:在实例方法中错误使用静态成员
class StaticError2 {
    private static int count = 0;
    private int id;
    
    public StaticError2() {
        id = ++count;  // 正确:实例方法可以访问静态成员
    }
    
    public void show() {
        System.out.println("ID: " + id + ", Count: " + count);
    }
}

// 正确:工具类使用static
class MathUtils {
    // 私有构造方法,防止实例化
    private MathUtils() {}
    
    public static int add(int a, int b) {
        return a + b;
    }
    
    public static int multiply(int a, int b) {
        return a * b;
    }
    
    public static double circleArea(double radius) {
        return Math.PI * radius * radius;
    }
}

// 使用
class TestUtils {
    public static void main(String[] args) {
        int sum = MathUtils.add(10, 20);
        double area = MathUtils.circleArea(5.0);
        
        // MathUtils utils = new MathUtils();  // 错误:构造方法私有
    }
}

7. 代码块

7.1 各种代码块示例

复制代码
public class CodeBlockDemo {
    // 静态变量
    public static String staticField = "静态变量";
    
    // 实例变量
    public String instanceField = "实例变量";
    
    // 静态代码块
    static {
        System.out.println("静态代码块执行");
        System.out.println("staticField = " + staticField);
        // System.out.println(instanceField);  // 错误:不能访问实例成员
    }
    
    // 实例代码块
    {
        System.out.println("实例代码块执行");
        System.out.println("instanceField = " + instanceField);
        System.out.println("staticField = " + staticField);  // 可以访问静态成员
    }
    
    // 构造方法
    public CodeBlockDemo() {
        System.out.println("构造方法执行");
    }
    
    // 构造方法重载
    public CodeBlockDemo(String msg) {
        this();  // 调用无参构造
        System.out.println("带参构造:" + msg);
    }
    
    // 普通代码块(方法内)
    public void method() {
        System.out.println("方法开始");
        
        {  // 普通代码块
            int x = 10;
            System.out.println("普通代码块,x = " + x);
        }
        
        // System.out.println(x);  // 错误:x已超出作用域
        
        int x = 20;  // 可以重新定义
        System.out.println("x = " + x);
    }
    
    public static void main(String[] args) {
        System.out.println("=== 创建第一个对象 ===");
        CodeBlockDemo obj1 = new CodeBlockDemo();
        
        System.out.println("\n=== 创建第二个对象 ===");
        CodeBlockDemo obj2 = new CodeBlockDemo("测试");
        
        System.out.println("\n=== 调用方法 ===");
        obj1.method();
    }
}

7.2 执行顺序验证

复制代码
class Parent {
    // 父类静态变量
    public static String parentStatic = "父类静态变量";
    
    // 父类静态代码块
    static {
        System.out.println("父类静态代码块:" + parentStatic);
    }
    
    // 父类实例变量
    public String parentField = "父类实例变量";
    
    // 父类实例代码块
    {
        System.out.println("父类实例代码块:" + parentField);
    }
    
    // 父类构造方法
    public Parent() {
        System.out.println("父类构造方法");
    }
}

class Child extends Parent {
    // 子类静态变量
    public static String childStatic = "子类静态变量";
    
    // 子类静态代码块
    static {
        System.out.println("子类静态代码块:" + childStatic);
    }
    
    // 子类实例变量
    public String childField = "子类实例变量";
    
    // 子类实例代码块
    {
        System.out.println("子类实例代码块:" + childField);
    }
    
    // 子类构造方法
    public Child() {
        System.out.println("子类构造方法");
    }
}

public class ExecutionOrder {
    public static void main(String[] args) {
        System.out.println("=== 创建子类对象 ===");
        Child child = new Child();
    }
}

执行结果

复制代码
=== 创建子类对象 ===
父类静态代码块:父类静态变量
子类静态代码块:子类静态变量
父类实例代码块:父类实例变量
父类构造方法
子类实例代码块:子类实例变量
子类构造方法

执行顺序总结

  1. 父类静态代码块和静态变量

  2. 子类静态代码块和静态变量

  3. 父类实例代码块和实例变量

  4. 父类构造方法

  5. 子类实例代码块和实例变量

  6. 子类构造方法

7.3 注意易错点

复制代码
// 错误1:代码块中使用未初始化的变量
class BlockError1 {
    {
        // System.out.println(x);  // 错误:x未初始化
        int x = 10;  // 先初始化
        System.out.println(x);  // 正确
    }
    
    private int x = 5;
}

// 错误2:静态代码块中使用实例变量
class BlockError2 {
    private int instanceVar = 10;
    private static int staticVar = 20;
    
    static {
        // System.out.println(instanceVar);  // 错误
        System.out.println(staticVar);  // 正确
    }
    
    {
        System.out.println(instanceVar);  // 正确
        System.out.println(staticVar);    // 正确
    }
}

// 正确:使用代码块初始化复杂逻辑
class ComplexInit {
    private int[] array;
    private String config;
    
    // 实例代码块初始化数组
    {
        array = new int[10];
        for (int i = 0; i < array.length; i++) {
            array[i] = i * i;
        }
    }
    
    // 静态代码块读取配置
    static {
        // 模拟读取配置文件
        // config = readConfig();  // 实际中可能从文件读取
    }
    
    public void showArray() {
        for (int num : array) {
            System.out.print(num + " ");
        }
        System.out.println();
    }
}

8. 内部类

8.1 成员内部类

复制代码
public class Outer {
    // 外部类成员
    private String outerField = "外部类字段";
    private static String outerStaticField = "外部类静态字段";
    
    // 1. 实例内部类(非静态内部类)
    class InstanceInner {
        private String innerField = "实例内部类字段";
        
        public void show() {
            // 可以访问外部类的所有成员
            System.out.println("outerField = " + outerField);
            System.out.println("outerStaticField = " + outerStaticField);
            System.out.println("innerField = " + this.innerField);
            
            // 访问外部类同名字段
            System.out.println("外部类outerField = " + Outer.this.outerField);
        }
        
        // 不能有静态成员(除常量外)
        // private static int staticVar = 10;  // 错误
        private static final int CONSTANT = 100;  // 正确:常量
    }
    
    // 2. 静态内部类
    static class StaticInner {
        private String innerField = "静态内部类字段";
        private static String staticInnerField = "静态内部类静态字段";
        
        public void show() {
            // 只能访问外部类的静态成员
            // System.out.println(outerField);  // 错误
            System.out.println("outerStaticField = " + outerStaticField);
            System.out.println("innerField = " + this.innerField);
        }
        
        public static void staticShow() {
            System.out.println("静态内部类的静态方法");
        }
    }
    
    // 外部类方法
    public void testInnerClass() {
        // 创建实例内部类对象
        InstanceInner inner1 = new InstanceInner();
        inner1.show();
        
        // 创建静态内部类对象
        StaticInner inner2 = new StaticInner();
        inner2.show();
        StaticInner.staticShow();
    }
    
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.testInnerClass();
        
        // 在其他类中创建内部类对象
        // 1. 创建实例内部类对象
        Outer.InstanceInner inner1 = outer.new InstanceInner();
        
        // 2. 创建静态内部类对象
        Outer.StaticInner inner2 = new Outer.StaticInner();
    }
}

8.2 局部内部类

复制代码
public class LocalInnerClassDemo {
    private String outerField = "外部字段";
    
    public void outerMethod(final int param) {  // JDK8+可以不加final
        String localVar = "局部变量";
        
        // 局部内部类
        class LocalInner {
            private String innerField = "局部内部类字段";
            
            public void show() {
                System.out.println("outerField = " + outerField);
                System.out.println("param = " + param);
                System.out.println("localVar = " + localVar);
                System.out.println("innerField = " + this.innerField);
            }
        }
        
        // 只能在方法内使用
        LocalInner inner = new LocalInner();
        inner.show();
        
        // 局部内部类不能有访问修饰符
        // public class ErrorLocalInner {}  // 错误
    }
    
    public static void main(String[] args) {
        LocalInnerClassDemo demo = new LocalInnerClassDemo();
        demo.outerMethod(100);
    }
}

8.3 匿名内部类

复制代码
// 接口
interface Animal {
    void eat();
    void sleep();
}

// 抽象类
abstract class Vehicle {
    abstract void run();
    
    public void stop() {
        System.out.println("车辆停止");
    }
}

public class AnonymousInnerClass {
    public static void main(String[] args) {
        // 1. 基于接口的匿名内部类
        Animal dog = new Animal() {
            @Override
            public void eat() {
                System.out.println("狗吃骨头");
            }
            
            @Override
            public void sleep() {
                System.out.println("狗睡觉");
            }
            
            // 可以添加额外方法(但外部无法调用)
            public void bark() {
                System.out.println("汪汪");
            }
        };
        
        dog.eat();
        dog.sleep();
        // dog.bark();  // 错误:Animal接口没有bark方法
        
        // 2. 基于抽象类的匿名内部类
        Vehicle car = new Vehicle() {
            @Override
            void run() {
                System.out.println("汽车行驶");
            }
            
            @Override
            public void stop() {
                System.out.println("汽车刹车");
            }
        };
        
        car.run();
        car.stop();
        
        // 3. 基于具体类的匿名内部类
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("线程运行中...");
            }
        };
        
        thread.start();
        
        // 4. 作为方法参数
        testAnimal(new Animal() {
            @Override
            public void eat() {
                System.out.println("匿名动物吃东西");
            }
            
            @Override
            public void sleep() {
                System.out.println("匿名动物睡觉");
            }
        });
    }
    
    public static void testAnimal(Animal animal) {
        animal.eat();
        animal.sleep();
    }
}

8.4 注意易错点

复制代码
// 错误1:在静态方法中创建实例内部类
class OuterError1 {
    class InstanceInner {}
    
    public static void staticMethod() {
        // InstanceInner inner = new InstanceInner();  // 错误
        // 需要先创建外部类对象
        OuterError1 outer = new OuterError1();
        InstanceInner inner = outer.new InstanceInner();  // 正确
    }
}

// 错误2:局部内部类访问非final局部变量
class OuterError2 {
    public void method() {
        int x = 10;  // 在JDK8之前需要加final
        // x = 20;  // 如果修改x,下面的内部类会编译错误
        
        class LocalInner {
            public void show() {
                System.out.println(x);  // 在JDK8+中,x实际上是final的
            }
        }
    }
}

// 正确:使用内部类的最佳实践
class OuterGood {
    // 实例内部类:当内部类需要访问外部类实例成员时
    class InstanceInner {
        public void accessOuter() {
            // 可以访问外部类实例成员
        }
    }
    
    // 静态内部类:当内部类不需要访问外部类实例成员时
    static class StaticInner {
        // 更独立,性能更好
    }
    
    // 方法:返回内部类实例
    public InstanceInner getInnerInstance() {
        return new InstanceInner();
    }
    
    // 使用:外部类控制内部类的创建
    public void useInner() {
        InstanceInner inner = new InstanceInner();
        // 使用内部类
    }
}

9. 对象的打印

9.1 toString方法

复制代码
import java.util.Arrays;

public class Student {
    private String name;
    private int age;
    private String[] courses;
    
    public Student(String name, int age, String[] courses) {
        this.name = name;
        this.age = age;
        this.courses = courses;
    }
    
    // 1. 没有重写toString
    public void showWithoutToString() {
        Student stu = new Student("张三", 20, new String[]{"数学", "英语"});
        System.out.println(stu);  // 输出:Student@1b6d3586(哈希值)
    }
    
    // 2. 重写toString方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", courses=" + Arrays.toString(courses) +
                '}';
    }
    
    // 3. 更完整的toString实现
    @Override
    public String toString2() {
        StringBuilder sb = new StringBuilder();
        sb.append("学生信息:\n");
        sb.append("  姓名:").append(name).append("\n");
        sb.append("  年龄:").append(age).append("\n");
        sb.append("  课程:");
        
        if (courses != null && courses.length > 0) {
            for (int i = 0; i < courses.length; i++) {
                sb.append(courses[i]);
                if (i < courses.length - 1) {
                    sb.append(", ");
                }
            }
        } else {
            sb.append("无");
        }
        
        return sb.toString();
    }
    
    public static void main(String[] args) {
        Student stu = new Student("张三", 20, 
            new String[]{"数学", "英语", "Java编程"});
        
        // 自动调用toString
        System.out.println(stu);
        System.out.println("学生:" + stu);  // 字符串拼接时自动调用
        
        // 直接打印
        stu.showWithoutToString();
    }
}

9.2 调试打印

复制代码
import java.util.ArrayList;
import java.util.List;

public class DebugPrint {
    private int id;
    private String name;
    private List<String> tags;
    private static int counter = 0;
    
    public DebugPrint(String name) {
        this.id = ++counter;
        this.name = name;
        this.tags = new ArrayList<>();
    }
    
    public void addTag(String tag) {
        tags.add(tag);
    }
    
    // 重写toString用于调试
    @Override
    public String toString() {
        return String.format("DebugPrint#%d{name='%s', tags=%s}", 
                            id, name, tags);
    }
    
    // 重写equals和hashCode
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        
        DebugPrint that = (DebugPrint) o;
        return id == that.id;
    }
    
    @Override
    public int hashCode() {
        return Integer.hashCode(id);
    }
    
    public static void main(String[] args) {
        DebugPrint obj1 = new DebugPrint("测试对象1");
        obj1.addTag("重要");
        obj1.addTag("紧急");
        
        DebugPrint obj2 = new DebugPrint("测试对象2");
        obj2.addTag("普通");
        
        System.out.println(obj1);
        System.out.println(obj2);
        
        // 在集合中使用
        List<DebugPrint> list = new ArrayList<>();
        list.add(obj1);
        list.add(obj2);
        
        System.out.println("列表:" + list);
        
        // 测试equals
        DebugPrint obj3 = new DebugPrint("测试对象1");
        System.out.println("obj1.equals(obj3): " + obj1.equals(obj3));  // false
        System.out.println("obj1 == obj3: " + (obj1 == obj3));  // false
    }
}

9.3 注意易错点

复制代码
// 错误1:toString中调用可能引发异常的方法
class ToStringError1 {
    private String[] data;
    
    public ToStringError1(String[] data) {
        this.data = data;
    }
    
    /*
    @Override
    public String toString() {
        // 如果data为null,会抛出NullPointerException
        return "Data: " + data[0];  // 危险!
    }
    */
    
    // 正确:防御性编程
    @Override
    public String toString() {
        if (data == null || data.length == 0) {
            return "Data: []";
        }
        return "Data: " + data[0];
    }
}

// 错误2:toString中修改对象状态
class ToStringError2 {
    private int count = 0;
    
    /*
    @Override
    public String toString() {
        count++;  // 错误:不应该在toString中修改状态
        return "Count: " + count;
    }
    */
    
    // 正确:toString应该是只读的
    @Override
    public String toString() {
        return "Count: " + count;
    }
}

// 错误3:循环引用导致栈溢出
class CircularReference {
    CircularReference other;
    
    public void setOther(CircularReference other) {
        this.other = other;
    }
    
    /*
    @Override
    public String toString() {
        // 如果形成循环引用,会栈溢出
        return "Other: " + other;  // 危险!
    }
    */
    
    // 正确:避免循环引用
    @Override
    public String toString() {
        return "CircularReference@" + hashCode();
    }
}

10. 综合示例

10.1 银行账户系统

复制代码
import java.util.Date;

public class BankAccount {
    // 静态成员
    private static int accountCounter = 1000;  // 账号起始编号
    private static final double INTEREST_RATE = 0.035;  // 年利率
    
    // 实例成员
    private final String accountNumber;  // 账号(不可变)
    private String accountHolder;       // 持卡人
    private double balance;            // 余额
    private Date createDate;          // 开户日期
    private boolean isActive;         // 账户状态
    
    // 静态代码块:初始化静态资源
    static {
        System.out.println("银行账户系统初始化...");
        // 这里可以加载配置文件、连接数据库等
    }
    
    // 构造方法
    public BankAccount(String accountHolder, double initialBalance) {
        this.accountNumber = generateAccountNumber();
        this.accountHolder = accountHolder;
        this.balance = initialBalance;
        this.createDate = new Date();
        this.isActive = true;
        
        System.out.println("账户创建成功:" + this);
    }
    
    // 私有方法:生成账号
    private String generateAccountNumber() {
        return "BANK" + (accountCounter++);
    }
    
    // Getter方法
    public String getAccountNumber() {
        return accountNumber;
    }
    
    public String getAccountHolder() {
        return accountHolder;
    }
    
    public double getBalance() {
        return balance;
    }
    
    public Date getCreateDate() {
        return createDate;
    }
    
    public boolean isActive() {
        return isActive;
    }
    
    // Setter方法(有限制)
    public void setAccountHolder(String accountHolder) {
        if (accountHolder != null && !accountHolder.trim().isEmpty()) {
            this.accountHolder = accountHolder;
        }
    }
    
    // 业务方法
    public void deposit(double amount) {
        if (!isActive) {
            System.out.println("账户已冻结,无法存款");
            return;
        }
        
        if (amount > 0) {
            balance += amount;
            System.out.printf("存款成功:+%.2f,当前余额:%.2f\n", amount, balance);
        } else {
            System.out.println("存款金额必须大于0");
        }
    }
    
    public boolean withdraw(double amount) {
        if (!isActive) {
            System.out.println("账户已冻结,无法取款");
            return false;
        }
        
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.printf("取款成功:-%.2f,当前余额:%.2f\n", amount, balance);
            return true;
        } else {
            System.out.println("取款失败:余额不足或金额无效");
            return false;
        }
    }
    
    public void transfer(BankAccount target, double amount) {
        if (!isActive || !target.isActive) {
            System.out.println("转账失败:账户异常");
            return;
        }
        
        if (withdraw(amount)) {
            target.deposit(amount);
            System.out.printf("转账成功:%.2f 到 %s\n", amount, target.getAccountHolder());
        }
    }
    
    public void applyInterest() {
        if (isActive && balance > 0) {
            double interest = balance * INTEREST_RATE;
            balance += interest;
            System.out.printf("利息结算:+%.2f,当前余额:%.2f\n", interest, balance);
        }
    }
    
    public void closeAccount() {
        if (balance == 0) {
            isActive = false;
            System.out.println("账户已关闭");
        } else {
            System.out.println("账户关闭失败:余额不为0");
        }
    }
    
    // 静态方法
    public static double getInterestRate() {
        return INTEREST_RATE;
    }
    
    public static int getTotalAccounts() {
        return accountCounter - 1000;
    }
    
    // 重写toString
    @Override
    public String toString() {
        return String.format("账户[%s] 户主:%s 余额:%.2f 状态:%s 开户:%tF",
                            accountNumber, accountHolder, balance, 
                            isActive ? "正常" : "冻结", createDate);
    }
    
    // 内部类:交易记录
    public class Transaction {
        private Date time;
        private String type;
        private double amount;
        private double balanceAfter;
        
        public Transaction(String type, double amount) {
            this.time = new Date();
            this.type = type;
            this.amount = amount;
            this.balanceAfter = BankAccount.this.balance;
        }
        
        @Override
        public String toString() {
            return String.format("%tT %s %.2f 余额:%.2f", 
                               time, type, amount, balanceAfter);
        }
    }
    
    // 使用示例
    public static void main(String[] args) {
        System.out.println("=== 银行账户系统演示 ===");
        
        // 创建账户
        BankAccount account1 = new BankAccount("张三", 1000);
        BankAccount account2 = new BankAccount("李四", 500);
        
        // 存款取款
        account1.deposit(500);
        account1.withdraw(200);
        
        // 转账
        account1.transfer(account2, 300);
        
        // 计算利息
        account1.applyInterest();
        account2.applyInterest();
        
        // 显示信息
        System.out.println("\n账户信息:");
        System.out.println(account1);
        System.out.println(account2);
        
        // 静态方法调用
        System.out.println("\n系统信息:");
        System.out.printf("利率:%.1f%%\n", getInterestRate() * 100);
        System.out.println("总账户数:" + getTotalAccounts());
        
        // 使用内部类
        BankAccount.Transaction transaction = account1.new Transaction("存款", 1000);
        System.out.println("\n交易记录:" + transaction);
    }
}

关键总结

1. 类和对象核心概念

  • :对象的蓝图/模板,定义属性和方法

  • 对象:类的实例,具有实际的内存空间

  • 实例化 :使用new关键字创建对象

2. 构造方法要点

复制代码
// 1. 构造方法重载
public class Person {
    public Person() {}  // 无参构造
    public Person(String name) {}  // 有参构造
    public Person(String name, int age) {}  // 多参构造
}

// 2. this关键字
public Person(String name, int age) {
    this.name = name;  // 区分成员变量和参数
    this.age = age;
}

// 3. 构造方法调用
public Person() {
    this("默认", 0);  // 必须在第一行
}

3. 封装原则

复制代码
public class Student {
    // 1. 私有属性
    private String name;
    private int age;
    
    // 2. 公有getter/setter
    public String getName() { return name; }
    public void setName(String name) { 
        // 3. 添加验证
        if (name != null) this.name = name; 
    }
    
    // 4. 构造方法初始化
    public Student(String name, int age) {
        setName(name);
        setAge(age);
    }
}

4. static关键字

复制代码
public class Example {
    // 1. 静态变量:类共享
    public static int count = 0;
    
    // 2. 静态方法:类方法
    public static void showCount() {
        System.out.println(count);
        // 不能访问非静态成员
    }
    
    // 3. 静态代码块:类加载时执行
    static {
        count = 10;
    }
    
    // 4. 静态内部类
    static class Inner {}
}

5. 代码块执行顺序

复制代码
父类静态 → 子类静态 → 父类实例 → 父类构造 → 子类实例 → 子类构造

6. 内部类选择

  • 实例内部类:需要访问外部类实例成员

  • 静态内部类:不需要访问外部类实例成员

  • 局部内部类:只在方法内使用

  • 匿名内部类:一次性使用的类

7. 最佳实践

  1. 单一职责:一个类只做一件事

  2. 封装数据:属性私有,提供公有方法

  3. 不可变性:尽量使用final

  4. 防御性编程:验证参数,处理异常

  5. 重写toString:方便调试

  6. 合理使用static:减少内存占用

  7. 使用构造方法链:避免代码重复

继承和多态

1. 继承

1.1 概念

继承是面向对象编程的三大特性之一,允许子类继承父类的属性和方法,实现代码复用。

1.2 为什么需要继承

不使用继承的代码

复制代码
// Dog类
class Dog {
    String name;
    int age;
    
    public void eat() {
        System.out.println(name + "正在吃饭");
    }
    
    public void sleep() {
        System.out.println(name + "正在睡觉");
    }
    
    public void bark() {
        System.out.println(name + "汪汪汪~~~");
    }
}

// Cat类
class Cat {
    String name;
    int age;
    
    public void eat() {
        System.out.println(name + "正在吃饭");
    }
    
    public void sleep() {
        System.out.println(name + "正在睡觉");
    }
    
    public void mew() {
        System.out.println(name + "喵喵喵~~~");
    }
}

问题:Dog和Cat有大量重复代码(name、age、eat、sleep)

使用继承优化

复制代码
// 父类:Animal
class Animal {
    String name;
    int age;
    
    public void eat() {
        System.out.println(name + "正在吃饭");
    }
    
    public void sleep() {
        System.out.println(name + "正在睡觉");
    }
}

// 子类:Dog
class Dog extends Animal {  // extends表示继承
    public void bark() {
        System.out.println(name + "汪汪汪~~~");
    }
}

// 子类:Cat
class Cat extends Animal {
    public void mew() {
        System.out.println(name + "喵喵喵~~~");
    }
}

1.3 继承使用示例

复制代码
// 父类
class Vehicle {
    protected String brand;  // 品牌
    protected int maxSpeed;  // 最高速度
    
    public Vehicle(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }
    
    public void start() {
        System.out.println(brand + "启动...");
    }
    
    public void stop() {
        System.out.println(brand + "停止...");
    }
    
    public void showInfo() {
        System.out.println("品牌:" + brand + ",最高速度:" + maxSpeed + "km/h");
    }
}

// 子类1:Car
class Car extends Vehicle {
    private int doors;  // 车门数量
    
    public Car(String brand, int maxSpeed, int doors) {
        super(brand, maxSpeed);  // 调用父类构造方法
        this.doors = doors;
    }
    
    public void openDoor() {
        System.out.println(brand + "打开" + doors + "个车门");
    }
    
    // 重写showInfo方法
    @Override
    public void showInfo() {
        super.showInfo();  // 调用父类方法
        System.out.println("车门数量:" + doors);
    }
}

// 子类2:Motorcycle
class Motorcycle extends Vehicle {
    private boolean hasBox;  // 是否有后备箱
    
    public Motorcycle(String brand, int maxSpeed, boolean hasBox) {
        super(brand, maxSpeed);
        this.hasBox = hasBox;
    }
    
    public void wheelie() {
        System.out.println(brand + "抬头特技!");
    }
}

// 测试类
public class InheritanceDemo {
    public static void main(String[] args) {
        Car car = new Car("Toyota", 200, 4);
        Motorcycle bike = new Motorcycle("Honda", 180, true);
        
        car.start();
        car.openDoor();
        car.showInfo();
        car.stop();
        
        System.out.println();
        
        bike.start();
        bike.wheelie();
        bike.showInfo();
        bike.stop();
    }
}

1.4 注意易错点

复制代码
// 错误1:Java不支持多重继承
// class A {}
// class B {}
// class C extends A, B {}  // 编译错误

// 正确:但可以实现多个接口
// interface A {}
// interface B {}
// class C implements A, B {}  // 正确

// 错误2:子类构造方法必须调用父类构造方法
class Parent {
    public Parent(String name) {
        System.out.println("Parent: " + name);
    }
}

class Child extends Parent {
    /*
    public Child() {  // 编译错误:没有调用super
        System.out.println("Child");
    }
    */
    
    // 正确
    public Child() {
        super("默认");  // 必须调用父类构造方法
        System.out.println("Child");
    }
}

// 错误3:子类不能降低父类方法的访问权限
class Parent2 {
    public void method() {}
}

class Child2 extends Parent2 {
    // void method() {}  // 编译错误:不能降低访问权限
    public void method() {}  // 正确:可以是public
    // protected void method() {}  // 错误:不能降低到protected
}

2. super关键字

2.1 super基本使用

复制代码
class Animal {
    protected String name = "动物";
    protected int age = 0;
    
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Animal构造方法");
    }
    
    public void eat() {
        System.out.println(name + "正在吃东西");
    }
    
    public void sleep() {
        System.out.println(name + "正在睡觉");
    }
}

class Dog extends Animal {
    private String name = "狗";  // 与父类同名
    private String breed;  // 品种
    
    public Dog(String name, int age, String breed) {
        super(name, age);  // 调用父类构造方法
        this.breed = breed;
        System.out.println("Dog构造方法");
    }
    
    public void showInfo() {
        // 访问子类自己的name
        System.out.println("子类name: " + this.name);
        
        // 访问父类的name
        System.out.println("父类name: " + super.name);
        
        // 访问父类的age(子类没有同名变量)
        System.out.println("age: " + age);  // 相当于super.age
        
        // 访问品种
        System.out.println("品种: " + breed);
    }
    
    // 重写eat方法
    @Override
    public void eat() {
        System.out.println(name + "(" + breed + ")正在吃狗粮");
    }
    
    public void bark() {
        System.out.println(name + "汪汪汪!");
    }
    
    public void allEat() {
        this.eat();      // 调用子类的eat
        super.eat();     // 调用父类的eat
    }
}

public class SuperDemo {
    public static void main(String[] args) {
        Dog dog = new Dog("旺财", 3, "金毛");
        
        dog.showInfo();
        System.out.println();
        
        dog.allEat();
        System.out.println();
        
        dog.sleep();  // 调用继承的父类方法
        dog.bark();   // 调用子类特有方法
    }
}

2.2 构造方法中的super

复制代码
class Person {
    private String name;
    private int age;
    
    public Person() {
        this("无名氏", 0);  // 调用本类其他构造方法
        System.out.println("Person无参构造");
    }
    
    public Person(String name) {
        this(name, 0);
        System.out.println("Person单参构造");
    }
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person全参构造");
    }
}

class Student extends Person {
    private String studentId;
    
    public Student() {
        // 隐式调用super(),调用父类无参构造
        System.out.println("Student无参构造");
    }
    
    public Student(String name) {
        super(name);  // 显式调用父类单参构造
        System.out.println("Student单参构造");
    }
    
    public Student(String name, int age, String studentId) {
        super(name, age);  // 显式调用父类全参构造
        this.studentId = studentId;
        System.out.println("Student全参构造");
    }
}

public class ConstructorChain {
    public static void main(String[] args) {
        System.out.println("=== 创建无参Student ===");
        Student s1 = new Student();
        
        System.out.println("\n=== 创建单参Student ===");
        Student s2 = new Student("张三");
        
        System.out.println("\n=== 创建全参Student ===");
        Student s3 = new Student("李四", 20, "2023001");
    }
}

执行结果

复制代码
=== 创建无参Student ===
Person全参构造
Person无参构造
Student无参构造

=== 创建单参Student ===
Person全参构造
Person单参构造
Student单参构造

=== 创建全参Student ===
Person全参构造
Student全参构造

2.3 注意易错点

复制代码
// 错误1:super和this不能同时使用
class ErrorExample {
    /*
    public ErrorExample() {
        super();
        this();  // 编译错误:super和this不能同时使用
    }
    */
}

// 错误2:super调用必须在第一行
class ErrorExample2 {
    /*
    public ErrorExample2() {
        System.out.println("先做点事");
        super();  // 编译错误:必须是第一行
    }
    */
}

// 正确:使用super访问父类被隐藏的变量
class Parent3 {
    int x = 10;
}

class Child3 extends Parent3 {
    int x = 20;  // 隐藏父类的x
    
    public void show() {
        System.out.println("子类x: " + x);        // 20
        System.out.println("父类x: " + super.x);  // 10
    }
}

3. 访问权限控制

3.1 四种访问权限

复制代码
package com.example.package1;

public class AccessDemo {
    // 四种访问权限修饰符
    public String publicField = "public";       // 任何地方都可访问
    protected String protectedField = "protected"; // 同包或子类可访问
    String defaultField = "default";           // 同包可访问
    private String privateField = "private";   // 仅本类可访问
    
    public void test() {
        // 在类内部,所有权限都可以访问
        System.out.println(publicField);
        System.out.println(protectedField);
        System.out.println(defaultField);
        System.out.println(privateField);
    }
}

// 同包中的其他类
class SamePackageClass {
    public void test() {
        AccessDemo demo = new AccessDemo();
        System.out.println(demo.publicField);     // 可以
        System.out.println(demo.protectedField);  // 可以
        System.out.println(demo.defaultField);    // 可以
        // System.out.println(demo.privateField); // 错误
    }
}

package com.example.package2;

import com.example.package1.AccessDemo;

// 不同包中的子类
class SubClass extends AccessDemo {
    public void test() {
        System.out.println(publicField);     // 可以
        System.out.println(protectedField);  // 可以(是子类)
        // System.out.println(defaultField);  // 错误
        // System.out.println(privateField);  // 错误
    }
}

// 不同包中的非子类
class OtherClass {
    public void test() {
        AccessDemo demo = new AccessDemo();
        System.out.println(demo.publicField);     // 可以
        // System.out.println(demo.protectedField);  // 错误
        // System.out.println(demo.defaultField);    // 错误
        // System.out.println(demo.privateField);    // 错误
    }
}

3.2 protected关键字详解

复制代码
// 父类
class Parent {
    private int privateVar = 1;      // 仅本类
    protected int protectedVar = 2;  // 同包或子类
    int defaultVar = 3;             // 同包
    public int publicVar = 4;       // 任何地方
}

// 同包子类
class SamePackageChild extends Parent {
    public void test() {
        // System.out.println(privateVar);   // 错误
        System.out.println(protectedVar);     // 可以
        System.out.println(defaultVar);       // 可以
        System.out.println(publicVar);        // 可以
    }
}

// 同包非子类
class SamePackageOther {
    public void test() {
        Parent p = new Parent();
        // System.out.println(p.privateVar);   // 错误
        System.out.println(p.protectedVar);   // 可以(同包)
        System.out.println(p.defaultVar);     // 可以
        System.out.println(p.publicVar);      // 可以
    }
}

// 不同包子类
package com.example.package3;
import com.example.package1.Parent;

class DifferentPackageChild extends Parent {
    public void test() {
        // System.out.println(privateVar);     // 错误
        System.out.println(protectedVar);       // 可以(是子类)
        // System.out.println(defaultVar);     // 错误
        System.out.println(publicVar);          // 可以
    }
}

// 不同包非子类
class DifferentPackageOther {
    public void test() {
        Parent p = new Parent();
        // System.out.println(p.privateVar);   // 错误
        // System.out.println(p.protectedVar); // 错误
        // System.out.println(p.defaultVar);   // 错误
        System.out.println(p.publicVar);        // 可以
    }
}

4. 方法重写(Override)

4.1 重写基本概念

复制代码
class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
    
    // 父类方法
    public void makeSound() {
        System.out.println("动物发出声音");
    }
    
    public void eat() {
        System.out.println(name + "正在吃东西");
    }
    
    public void move() {
        System.out.println(name + "正在移动");
    }
    
    // 私有方法不能重写
    private void secretMethod() {
        System.out.println("父类私有方法");
    }
    
    // final方法不能重写
    public final void cannotOverride() {
        System.out.println("这是final方法,不能重写");
    }
    
    // 静态方法不能重写(会隐藏)
    public static void staticMethod() {
        System.out.println("父类静态方法");
    }
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
    
    // 1. 重写makeSound方法
    @Override
    public void makeSound() {
        System.out.println(name + "汪汪叫");
    }
    
    // 2. 重写eat方法,添加特有逻辑
    @Override
    public void eat() {
        super.eat();  // 先调用父类方法
        System.out.println(name + "正在吃狗粮");
    }
    
    // 3. 重写move方法,完全改变实现
    @Override
    public void move() {
        System.out.println(name + "用四条腿奔跑");
    }
    
    // 4. 重写方法的访问权限不能降低
    // void makeSound() { ... }  // 错误:不能从public降低到default
    
    // 5. 可以抛出的异常范围不能扩大
    /*
    @Override
    public void makeSound() throws Exception {  // 错误:异常范围扩大
        System.out.println(name + "汪汪叫");
    }
    */
    
    // 6. 返回值类型可以是父类方法返回类型的子类(协变返回类型)
    public Animal getAnimal() {
        return new Animal("普通动物");
    }
}

class SpecialDog extends Dog {
    public SpecialDog(String name) {
        super(name);
    }
    
    // 协变返回类型:返回值可以是父类方法返回类型的子类
    @Override
    public Dog getAnimal() {  // 返回Dog,是Animal的子类
        return new Dog("特殊狗");
    }
}

4.2 @Override注解的重要性

复制代码
class Base {
    public void show() {
        System.out.println("Base show");
    }
    
    public void display(String message) {
        System.out.println("Base display: " + message);
    }
}

class Derived extends Base {
    // 没有@Override注解,拼写错误不会被发现
    public void shwo() {  // 应该是show,但编译器不会报错
        System.out.println("我想重写show,但拼写错了");
    }
    
    // 使用@Override注解,编译器会检查是否正确重写
    @Override
    public void show() {  // 正确
        System.out.println("Derived show");
    }
    
    /*
    @Override
    public void Show() {  // 编译错误:没有找到要重写的方法
        System.out.println("大小写错误");
    }
    
    @Override
    public void display() {  // 编译错误:参数列表不匹配
        System.out.println("缺少参数");
    }
    */
}

public class OverrideAnnotation {
    public static void main(String[] args) {
        Base base = new Derived();
        base.show();  // 调用Derived的show
    }
}

4.3 注意易错点

复制代码
// 错误1:重写时降低访问权限
class ErrorParent {
    public void method() {}
}

class ErrorChild extends ErrorParent {
    // void method() {}  // 编译错误
    public void method() {}  // 正确
}

// 错误2:重写静态方法(实际上是隐藏)
class StaticParent {
    public static void staticMethod() {
        System.out.println("父类静态方法");
    }
}

class StaticChild extends StaticParent {
    // 这不是重写,是隐藏
    public static void staticMethod() {
        System.out.println("子类静态方法");
    }
}

// 错误3:重写final方法
class FinalParent {
    public final void finalMethod() {
        System.out.println("final方法");
    }
}

class FinalChild extends FinalParent {
    /*
    public void finalMethod() {  // 编译错误
        System.out.println("尝试重写final方法");
    }
    */
}

// 错误4:重写构造方法(构造方法不能被继承,所以也不能被重写)
class ConstructorParent {
    public ConstructorParent() {}
}

class ConstructorChild extends ConstructorParent {
    // 这不是重写,是定义自己的构造方法
    public ConstructorChild() {
        super();
    }
}

5. 多态

5.1 多态基本概念

复制代码
// 父类
class Shape {
    public void draw() {
        System.out.println("绘制形状");
    }
    
    public double getArea() {
        return 0.0;
    }
}

// 子类1:圆形
class Circle extends Shape {
    private double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制圆形,半径:" + radius);
    }
    
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
    
    // 子类特有方法
    public double getCircumference() {
        return 2 * Math.PI * radius;
    }
}

// 子类2:矩形
class Rectangle extends Shape {
    private double width;
    private double height;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制矩形,宽:" + width + ",高:" + height);
    }
    
    @Override
    public double getArea() {
        return width * height;
    }
    
    // 子类特有方法
    public double getPerimeter() {
        return 2 * (width + height);
    }
}

// 子类3:三角形
class Triangle extends Shape {
    private double base;
    private double height;
    
    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制三角形,底:" + base + ",高:" + height);
    }
    
    @Override
    public double getArea() {
        return 0.5 * base * height;
    }
}

// 测试多态
public class PolymorphismDemo {
    public static void main(String[] args) {
        // 1. 向上转型:父类引用指向子类对象
        Shape shape1 = new Circle(5.0);
        Shape shape2 = new Rectangle(4.0, 6.0);
        Shape shape3 = new Triangle(3.0, 4.0);
        
        // 2. 多态调用:运行时确定调用哪个方法
        shape1.draw();  // 调用Circle的draw
        System.out.println("面积:" + shape1.getArea());
        
        shape2.draw();  // 调用Rectangle的draw
        System.out.println("面积:" + shape2.getArea());
        
        shape3.draw();  // 调用Triangle的draw
        System.out.println("面积:" + shape3.getArea());
        
        System.out.println("\n=== 使用数组和循环 ===");
        
        // 3. 使用数组存储不同子类对象
        Shape[] shapes = {
            new Circle(3.0),
            new Rectangle(4.0, 5.0),
            new Triangle(6.0, 8.0),
            new Circle(2.0)
        };
        
        // 4. 遍历调用,体现多态
        double totalArea = 0;
        for (Shape shape : shapes) {
            shape.draw();
            double area = shape.getArea();
            System.out.println("面积:" + area);
            totalArea += area;
        }
        System.out.println("总面积:" + totalArea);
        
        System.out.println("\n=== 使用方法参数多态 ===");
        
        // 5. 方法参数使用父类类型
        printShapeInfo(new Circle(10.0));
        printShapeInfo(new Rectangle(7.0, 8.0));
    }
    
    // 方法参数使用父类类型,可以接受任何子类对象
    public static void printShapeInfo(Shape shape) {
        shape.draw();
        System.out.println("面积:" + shape.getArea());
    }
}

5.2 向上转型和向下转型

复制代码
class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void eat() {
        System.out.println(name + "正在吃东西");
    }
}

class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }
    
    @Override
    public void eat() {
        System.out.println(name + "正在吃鱼");
    }
    
    public void catchMouse() {
        System.out.println(name + "抓老鼠");
    }
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
    
    @Override
    public void eat() {
        System.out.println(name + "正在吃骨头");
    }
    
    public void watchHouse() {
        System.out.println(name + "看家");
    }
}

public class UpDownCasting {
    public static void main(String[] args) {
        // 1. 向上转型:自动类型转换
        Animal animal1 = new Cat("咪咪");
        Animal animal2 = new Dog("旺财");
        
        // 可以调用父类方法
        animal1.eat();  // 多态:调用Cat的eat
        animal2.eat();  // 多态:调用Dog的eat
        
        // 不能调用子类特有方法
        // animal1.catchMouse();  // 编译错误
        
        System.out.println("\n=== 向下转型 ===");
        
        // 2. 向下转型:需要强制类型转换
        if (animal1 instanceof Cat) {
            Cat cat = (Cat) animal1;  // 安全转型
            cat.catchMouse();  // 可以调用子类特有方法
        }
        
        if (animal2 instanceof Dog) {
            Dog dog = (Dog) animal2;  // 安全转型
            dog.watchHouse();
        }
        
        // 3. 不安全的向下转型会抛出ClassCastException
        // Cat badCat = (Cat) animal2;  // 运行时异常:animal2实际是Dog
        
        System.out.println("\n=== 使用instanceof判断 ===");
        
        // 4. 使用instanceof进行类型判断
        Animal[] animals = {
            new Cat("小花"),
            new Dog("小黑"),
            new Cat("小白"),
            new Dog("大黄")
        };
        
        for (Animal animal : animals) {
            animal.eat();
            
            if (animal instanceof Cat) {
                Cat cat = (Cat) animal;
                cat.catchMouse();
            } else if (animal instanceof Dog) {
                Dog dog = (Dog) animal;
                dog.watchHouse();
            }
            
            System.out.println("---");
        }
    }
    
    // 方法参数使用父类类型
    public static void animalEat(Animal animal) {
        animal.eat();  // 多态调用
    }
}

5.3 多态的优势

复制代码
// 不使用多态的代码
class NoPolymorphism {
    public static void drawShape(String type) {
        if ("circle".equals(type)) {
            System.out.println("绘制圆形");
        } else if ("rectangle".equals(type)) {
            System.out.println("绘制矩形");
        } else if ("triangle".equals(type)) {
            System.out.println("绘制三角形");
        } else {
            System.out.println("未知形状");
        }
    }
    
    public static double calculateArea(String type, double... params) {
        if ("circle".equals(type) && params.length == 1) {
            return Math.PI * params[0] * params[0];
        } else if ("rectangle".equals(type) && params.length == 2) {
            return params[0] * params[1];
        } else if ("triangle".equals(type) && params.length == 2) {
            return 0.5 * params[0] * params[1];
        }
        return 0.0;
    }
}

// 使用多态的代码
abstract class Shape2 {
    public abstract void draw();
    public abstract double getArea();
}

class Circle2 extends Shape2 {
    private double radius;
    
    public Circle2(double radius) {
        this.radius = radius;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制圆形,半径:" + radius);
    }
    
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle2 extends Shape2 {
    private double width;
    private double height;
    
    public Rectangle2(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制矩形,宽:" + width + ",高:" + height);
    }
    
    @Override
    public double getArea() {
        return width * height;
    }
}

public class PolymorphismAdvantage {
    public static void main(String[] args) {
        System.out.println("=== 不使用多态 ===");
        NoPolymorphism.drawShape("circle");
        double area1 = NoPolymorphism.calculateArea("circle", 5.0);
        System.out.println("面积:" + area1);
        
        System.out.println("\n=== 使用多态 ===");
        Shape2[] shapes = {
            new Circle2(5.0),
            new Rectangle2(4.0, 6.0)
        };
        
        for (Shape2 shape : shapes) {
            shape.draw();
            System.out.println("面积:" + shape.getArea());
        }
        
        // 多态的优势:易于扩展
        System.out.println("\n=== 添加新形状(扩展性)===");
        Shape2 newShape = new Triangle2(3.0, 4.0);
        newShape.draw();
        System.out.println("面积:" + newShape.getArea());
    }
}

// 新添加的形状类
class Triangle2 extends Shape2 {
    private double base;
    private double height;
    
    public Triangle2(double base, double height) {
        this.base = base;
        this.height = height;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制三角形,底:" + base + ",高:" + height);
    }
    
    @Override
    public double getArea() {
        return 0.5 * base * height;
    }
}

5.4 注意易错点

复制代码
// 错误1:属性没有多态性
class ParentField {
    public String field = "父类属性";
    
    public void showField() {
        System.out.println("ParentField: " + field);
    }
}

class ChildField extends ParentField {
    public String field = "子类属性";  // 隐藏父类的field
    
    @Override
    public void showField() {
        System.out.println("ChildField: " + field);
    }
}

public class FieldPolymorphism {
    public static void main(String[] args) {
        ParentField obj = new ChildField();
        System.out.println(obj.field);  // 输出:"父类属性"(属性没有多态性)
        obj.showField();               // 输出:"ChildField: 子类属性"(方法有多态性)
    }
}

// 错误2:静态方法没有多态性
class ParentStatic {
    public static void staticMethod() {
        System.out.println("父类静态方法");
    }
    
    public void instanceMethod() {
        System.out.println("父类实例方法");
    }
}

class ChildStatic extends ParentStatic {
    public static void staticMethod() {
        System.out.println("子类静态方法");
    }
    
    @Override
    public void instanceMethod() {
        System.out.println("子类实例方法");
    }
}

public class StaticPolymorphism {
    public static void main(String[] args) {
        ParentStatic obj = new ChildStatic();
        
        // 静态方法:编译时绑定
        obj.staticMethod();  // 输出:"父类静态方法"
        ParentStatic.staticMethod();  // 输出:"父类静态方法"
        ChildStatic.staticMethod();   // 输出:"子类静态方法"
        
        // 实例方法:运行时绑定
        obj.instanceMethod();  // 输出:"子类实例方法"(多态)
    }
}

// 错误3:私有方法没有多态性
class ParentPrivate {
    private void privateMethod() {
        System.out.println("父类私有方法");
    }
    
    public void callPrivate() {
        privateMethod();
    }
}

class ChildPrivate extends ParentPrivate {
    // 这不是重写,是定义新方法
    private void privateMethod() {
        System.out.println("子类私有方法");
    }
    
    // 重写public方法
    @Override
    public void callPrivate() {
        System.out.println("子类调用");
        // privateMethod();  // 不能直接调用父类私有方法
    }
}

public class PrivatePolymorphism {
    public static void main(String[] args) {
        ParentPrivate obj = new ChildPrivate();
        obj.callPrivate();  // 输出:"子类调用"(多态)
    }
}

6. final关键字

6.1 final修饰类、方法、变量

复制代码
// 1. final修饰类:不能被继承
final class FinalClass {
    public void show() {
        System.out.println("FinalClass的show方法");
    }
}

// class SubClass extends FinalClass {}  // 编译错误

// 2. final修饰方法:不能被重写
class ParentFinalMethod {
    public final void finalMethod() {
        System.out.println("这是final方法,不能重写");
    }
    
    public void normalMethod() {
        System.out.println("普通方法,可以重写");
    }
}

class ChildFinalMethod extends ParentFinalMethod {
    // public void finalMethod() {}  // 编译错误:不能重写final方法
    
    @Override
    public void normalMethod() {
        System.out.println("重写了普通方法");
    }
}

// 3. final修饰变量:常量
class FinalVariable {
    // 实例常量:每个对象一份,创建后不能修改
    public final int instanceFinal = 10;
    
    // 静态常量:类一份,常用
    public static final double PI = 3.14159;
    
    // 空白final:声明时不初始化
    public final String blankFinal;
    
    // 构造方法中初始化空白final
    public FinalVariable(String value) {
        this.blankFinal = value;  // 只能赋值一次
    }
    
    public void test() {
        // instanceFinal = 20;  // 编译错误:不能修改final变量
        // blankFinal = "new";  // 编译错误:只能赋值一次
        
        final int localFinal = 30;  // 局部final变量
        // localFinal = 40;  // 编译错误
    }
}

// 4. final修饰引用类型
class FinalReference {
    public final int[] array = {1, 2, 3};  // final引用
    
    public void test() {
        // array = new int[3];  // 编译错误:不能修改引用
        array[0] = 100;  // 可以:修改引用指向的对象内容
        System.out.println("array[0] = " + array[0]);
    }
}

// 5. final参数
class FinalParameter {
    public void test(final int x) {
        // x = 20;  // 编译错误:不能修改final参数
        System.out.println("x = " + x);
    }
    
    public void test2(final Person p) {
        // p = new Person("李四");  // 编译错误
        p.name = "修改后的名字";  // 可以:修改对象内容
    }
}

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
}

public class FinalDemo {
    public static void main(String[] args) {
        // 使用final变量
        System.out.println("PI = " + FinalVariable.PI);
        
        FinalVariable fv = new FinalVariable("初始化值");
        System.out.println("blankFinal = " + fv.blankFinal);
        
        // 使用final引用
        FinalReference fr = new FinalReference();
        fr.test();
        
        // 使用final参数
        FinalParameter fp = new FinalParameter();
        fp.test(10);
        
        Person p = new Person("张三");
        fp.test2(p);
        System.out.println("修改后:" + p.name);
    }
}

6.2 final与性能优化

复制代码
// 1. 内联优化:final方法可能被内联
class InlineOptimization {
    public final int add(int a, int b) {
        return a + b;
    }
    
    public int subtract(int a, int b) {
        return a - b;
    }
}

// 2. 使用final提高安全性
class SensitiveData {
    private final String password;
    private final Date createTime;
    
    public SensitiveData(String password) {
        this.password = password;
        this.createTime = new Date();  // 防御性拷贝
    }
    
    public String getPassword() {
        return password;  // 返回final,不能被修改
    }
    
    public Date getCreateTime() {
        return new Date(createTime.getTime());  // 返回拷贝
    }
}

// 3. 不可变类(线程安全)
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);
    }
    
    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
}

public class FinalPerformance {
    public static void main(String[] args) {
        // 使用不可变类
        ImmutablePoint p1 = new ImmutablePoint(10, 20);
        System.out.println("p1: " + p1);
        
        ImmutablePoint p2 = p1.move(5, 5);
        System.out.println("p2: " + p2);
        System.out.println("p1: " + p1);  // p1不变
        
        // 线程安全
        Runnable task = () -> {
            ImmutablePoint point = new ImmutablePoint(1, 2);
            System.out.println(Thread.currentThread().getName() + ": " + point);
        };
        
        new Thread(task, "线程1").start();
        new Thread(task, "线程2").start();
    }
}

7. 组合

7.1 组合vs继承

复制代码
// 继承:is-a关系
class Engine {
    public void start() {
        System.out.println("引擎启动");
    }
    
    public void stop() {
        System.out.println("引擎停止");
    }
}

class Wheel {
    private int size;
    
    public Wheel(int size) {
        this.size = size;
    }
    
    public void rotate() {
        System.out.println(size + "寸轮子旋转");
    }
}

// 继承:Car is a Vehicle
class Vehicle {
    protected String brand;
    
    public Vehicle(String brand) {
        this.brand = brand;
    }
    
    public void move() {
        System.out.println(brand + "正在移动");
    }
}

// 组合:Car has a Engine, Car has Wheels
class Car extends Vehicle {
    // 组合:包含其他类的对象
    private Engine engine;
    private Wheel[] wheels;
    private int doorCount;
    
    public Car(String brand, int doorCount) {
        super(brand);
        this.engine = new Engine();
        this.doorCount = doorCount;
        this.wheels = new Wheel[4];
        
        // 初始化轮子
        for (int i = 0; i < wheels.length; i++) {
            wheels[i] = new Wheel(18);  // 18寸轮子
        }
    }
    
    public void start() {
        System.out.println(brand + "汽车:");
        engine.start();
        for (Wheel wheel : wheels) {
            wheel.rotate();
        }
        System.out.println(doorCount + "门汽车已启动");
    }
    
    public void stop() {
        System.out.println(brand + "汽车:");
        engine.stop();
        System.out.println("汽车已停止");
    }
    
    // 特有的方法
    public void openDoor(int doorNumber) {
        if (doorNumber >= 1 && doorNumber <= doorCount) {
            System.out.println("打开第" + doorNumber + "个门");
        } else {
            System.out.println("没有第" + doorNumber + "个门");
        }
    }
}

// 另一个使用组合的例子
class Computer {
    // 组合:计算机由多个部件组成
    private CPU cpu;
    private Memory memory;
    private HardDisk hardDisk;
    
    public Computer(CPU cpu, Memory memory, HardDisk hardDisk) {
        this.cpu = cpu;
        this.memory = memory;
        this.hardDisk = hardDisk;
    }
    
    public void start() {
        System.out.println("计算机启动:");
        cpu.process();
        memory.load();
        hardDisk.read();
        System.out.println("计算机启动完成");
    }
    
    public void showSpec() {
        System.out.println("规格:");
        System.out.println("CPU: " + cpu.getInfo());
        System.out.println("内存: " + memory.getInfo());
        System.out.println("硬盘: " + hardDisk.getInfo());
    }
}

class CPU {
    private String model;
    private double speed;  // GHz
    
    public CPU(String model, double speed) {
        this.model = model;
        this.speed = speed;
    }
    
    public void process() {
        System.out.println("CPU " + model + " 正在处理数据");
    }
    
    public String getInfo() {
        return model + " " + speed + "GHz";
    }
}

class Memory {
    private int size;  // GB
    
    public Memory(int size) {
        this.size = size;
    }
    
    public void load() {
        System.out.println("加载" + size + "GB内存");
    }
    
    public String getInfo() {
        return size + "GB";
    }
}

class HardDisk {
    private int capacity;  // GB
    private String type;   // SSD/HDD
    
    public HardDisk(int capacity, String type) {
        this.capacity = capacity;
        this.type = type;
    }
    
    public void read() {
        System.out.println("从" + type + "硬盘读取数据");
    }
    
    public String getInfo() {
        return capacity + "GB " + type;
    }
}

public class CompositionDemo {
    public static void main(String[] args) {
        System.out.println("=== 组合示例:汽车 ===");
        Car car = new Car("Toyota", 4);
        car.start();
        car.openDoor(2);
        car.stop();
        
        System.out.println("\n=== 组合示例:计算机 ===");
        Computer computer = new Computer(
            new CPU("Intel i7", 3.6),
            new Memory(16),
            new HardDisk(512, "SSD")
        );
        computer.showSpec();
        computer.start();
    }
}

7.2 继承vs组合的选择

复制代码
// 场景1:使用继承
// 动物分类体系:清晰的is-a关系
abstract class Animal2 {
    protected String name;
    
    public Animal2(String name) {
        this.name = name;
    }
    
    public abstract void makeSound();
    
    public void breathe() {
        System.out.println(name + "正在呼吸");
    }
}

class Mammal extends Animal2 {
    public Mammal(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + "发出哺乳动物声音");
    }
    
    public void feedMilk() {
        System.out.println(name + "喂奶");
    }
}

class Dog2 extends Mammal {
    public Dog2(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + "汪汪叫");
    }
    
    public void fetch() {
        System.out.println(name + "捡球");
    }
}

// 场景2:使用组合
// 游戏角色:has-a关系更好
interface Weapon {
    void attack();
}

class Sword implements Weapon {
    @Override
    public void attack() {
        System.out.println("挥剑攻击");
    }
}

class Bow implements Weapon {
    @Override
    public void attack() {
        System.out.println("射箭攻击");
    }
}

class MagicWand implements Weapon {
    @Override
    public void attack() {
        System.out.println("释放魔法");
    }
}

class GameCharacter {
    private String name;
    private int level;
    private Weapon weapon;  // 组合:角色有武器
    
    public GameCharacter(String name, int level) {
        this.name = name;
        this.level = level;
    }
    
    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }
    
    public void attack() {
        System.out.print(name + "(等级" + level + ")");
        if (weapon != null) {
            weapon.attack();
        } else {
            System.out.println("徒手攻击");
        }
    }
    
    public void levelUp() {
        level++;
        System.out.println(name + "升级到" + level + "级");
    }
}

// 场景3:错误使用继承的例子
class WrongInheritance {
    // Stack不是Vector,继承关系不合适
    /*
    class MyStack extends Vector {
        public void push(Object item) {
            add(item);
        }
        
        public Object pop() {
            return remove(size() - 1);
        }
    }
    */
    
    // 应该使用组合
    class MyStack {
        private java.util.List list = new java.util.ArrayList();
        
        public void push(Object item) {
            list.add(item);
        }
        
        public Object pop() {
            if (list.isEmpty()) {
                return null;
            }
            return list.remove(list.size() - 1);
        }
    }
}

public class ChooseDemo {
    public static void main(String[] args) {
        System.out.println("=== 继承:动物分类 ===");
        Dog2 dog = new Dog2("旺财");
        dog.breathe();
        dog.makeSound();
        dog.feedMilk();
        dog.fetch();
        
        System.out.println("\n=== 组合:游戏角色 ===");
        GameCharacter hero = new GameCharacter("勇者", 1);
        hero.attack();
        
        hero.setWeapon(new Sword());
        hero.attack();
        
        hero.levelUp();
        hero.setWeapon(new MagicWand());
        hero.attack();
        
        // 灵活更换武器
        hero.setWeapon(new Bow());
        hero.attack();
    }
}

8. 初始化顺序

8.1 包含继承的初始化顺序

复制代码
class A {
    // 静态变量
    private static String staticFieldA = getStaticField("A静态变量");
    
    // 实例变量
    private String instanceFieldA = getInstanceField("A实例变量");
    
    // 静态代码块
    static {
        System.out.println("A静态代码块");
    }
    
    // 实例代码块
    {
        System.out.println("A实例代码块");
    }
    
    // 构造方法
    public A() {
        System.out.println("A构造方法");
    }
    
    // 静态方法
    public static String getStaticField(String msg) {
        System.out.println(msg);
        return msg;
    }
    
    // 实例方法
    public String getInstanceField(String msg) {
        System.out.println(msg);
        return msg;
    }
}

class B extends A {
    // 静态变量
    private static String staticFieldB = getStaticField("B静态变量");
    
    // 实例变量
    private String instanceFieldB = getInstanceField("B实例变量");
    
    // 静态代码块
    static {
        System.out.println("B静态代码块");
    }
    
    // 实例代码块
    {
        System.out.println("B实例代码块");
    }
    
    // 构造方法
    public B() {
        System.out.println("B构造方法");
    }
}

class C extends B {
    // 静态变量
    private static String staticFieldC = getStaticField("C静态变量");
    
    // 实例变量
    private String instanceFieldC = getInstanceField("C实例变量");
    
    // 静态代码块
    static {
        System.out.println("C静态代码块");
    }
    
    // 实例代码块
    {
        System.out.println("C实例代码块");
    }
    
    // 构造方法
    public C() {
        System.out.println("C构造方法");
    }
    
    // 重载构造方法
    public C(String msg) {
        this();  // 调用本类无参构造
        System.out.println("C有参构造:" + msg);
    }
}

public class InitializationOrder {
    public static void main(String[] args) {
        System.out.println("=== 第一次创建C对象 ===");
        C c1 = new C();
        
        System.out.println("\n=== 第二次创建C对象 ===");
        C c2 = new C("测试");
        
        System.out.println("\n=== 只使用静态成员,不创建对象 ===");
        // 触发类加载,但不创建对象
        System.out.println("访问静态成员...");
    }
}

执行结果

复制代码
=== 第一次创建C对象 ===
A静态变量
A静态代码块
B静态变量
B静态代码块
C静态变量
C静态代码块
A实例变量
A实例代码块
A构造方法
B实例变量
B实例代码块
B构造方法
C实例变量
C实例代码块
C构造方法

=== 第二次创建C对象 ===
A实例变量
A实例代码块
A构造方法
B实例变量
B实例代码块
B构造方法
C实例变量
C实例代码块
C构造方法
C有参构造:测试

初始化顺序总结

  1. 父类静态成员(变量、代码块)→ 子类静态成员

  2. 父类实例成员(变量、代码块)→ 父类构造方法

  3. 子类实例成员(变量、代码块)→ 子类构造方法

8.2 注意易错点

复制代码
// 错误示例:在构造方法中调用可重写的方法
class BaseError {
    public BaseError() {
        // 危险:调用可重写的方法
        printMessage();
    }
    
    public void printMessage() {
        System.out.println("Base构造方法中调用");
    }
}

class DerivedError extends BaseError {
    private String message = "Derived的消息";
    
    @Override
    public void printMessage() {
        // 此时message还没有初始化!
        System.out.println("Derived的消息:" + message);
    }
}

public class ConstructorError {
    public static void main(String[] args) {
        DerivedError obj = new DerivedError();
        // 输出:Derived的消息:null
        // message还没有初始化,因为先执行父类构造方法
    }
}

// 正确做法
class BaseSafe {
    public BaseSafe() {
        // 调用final或private方法
        safeMethod();
    }
    
    private void safeMethod() {
        System.out.println("Base安全方法");
    }
    
    public final void finalMethod() {
        System.out.println("Base final方法");
    }
}

class DerivedSafe extends BaseSafe {
    private String message = "已初始化";
    
    public void show() {
        System.out.println("消息:" + message);
    }
}

public class ConstructorSafe {
    public static void main(String[] args) {
        DerivedSafe obj = new DerivedSafe();
        obj.show();  // 输出:消息:已初始化
    }
}

9. 综合示例

9.1 员工管理系统

复制代码
import java.util.ArrayList;
import java.util.List;

// 员工基类
abstract class Employee {
    protected String name;
    protected int id;
    protected double baseSalary;
    
    public Employee(String name, int id, double baseSalary) {
        this.name = name;
        this.id = id;
        this.baseSalary = baseSalary;
    }
    
    // 抽象方法:计算工资(多态)
    public abstract double calculateSalary();
    
    // 显示信息
    public void displayInfo() {
        System.out.println("ID: " + id + ", 姓名: " + name + 
                          ", 基本工资: " + baseSalary);
    }
    
    // 工作方法
    public void work() {
        System.out.println(name + "正在工作...");
    }
    
    // getter
    public String getName() { return name; }
    public int getId() { return id; }
}

// 普通员工
class RegularEmployee extends Employee {
    private int overtimeHours;  // 加班小时
    
    public RegularEmployee(String name, int id, double baseSalary, int overtimeHours) {
        super(name, id, baseSalary);
        this.overtimeHours = overtimeHours;
    }
    
    @Override
    public double calculateSalary() {
        // 基本工资 + 加班费(每小时50)
        return baseSalary + overtimeHours * 50;
    }
    
    @Override
    public void displayInfo() {
        super.displayInfo();
        System.out.println("加班小时: " + overtimeHours + ", 实发工资: " + calculateSalary());
    }
    
    public void requestOvertime(int hours) {
        overtimeHours += hours;
        System.out.println(name + "申请加班" + hours + "小时");
    }
}

// 经理
class Manager extends Employee {
    private double bonus;  // 奖金
    private List<Employee> subordinates;  // 下属
    
    public Manager(String name, int id, double baseSalary, double bonus) {
        super(name, id, baseSalary);
        this.bonus = bonus;
        this.subordinates = new ArrayList<>();
    }
    
    @Override
    public double calculateSalary() {
        // 基本工资 + 奖金
        return baseSalary + bonus;
    }
    
    @Override
    public void displayInfo() {
        super.displayInfo();
        System.out.println("奖金: " + bonus + ", 实发工资: " + calculateSalary());
        System.out.println("管理" + subordinates.size() + "名员工");
    }
    
    @Override
    public void work() {
        System.out.println(name + "正在管理团队...");
    }
    
    public void addSubordinate(Employee employee) {
        subordinates.add(employee);
        System.out.println(name + "添加了下属: " + employee.getName());
    }
    
    public void holdMeeting() {
        System.out.println(name + "正在召开团队会议");
    }
}

// 销售
class SalesPerson extends Employee {
    private double salesAmount;  // 销售额
    private double commissionRate;  // 提成比例
    
    public SalesPerson(String name, int id, double baseSalary, 
                      double commissionRate) {
        super(name, id, baseSalary);
        this.commissionRate = commissionRate;
        this.salesAmount = 0;
    }
    
    public void makeSale(double amount) {
        salesAmount += amount;
        System.out.println(name + "完成销售: ¥" + amount);
    }
    
    @Override
    public double calculateSalary() {
        // 基本工资 + 销售提成
        return baseSalary + salesAmount * commissionRate;
    }
    
    @Override
    public void displayInfo() {
        super.displayInfo();
        System.out.println("销售额: " + salesAmount + 
                          ", 提成比例: " + (commissionRate * 100) + "%, " +
                          "实发工资: " + calculateSalary());
    }
    
    @Override
    public void work() {
        System.out.println(name + "正在拜访客户...");
    }
}

// 公司
class Company {
    private String name;
    private List<Employee> employees;
    
    public Company(String name) {
        this.name = name;
        this.employees = new ArrayList<>();
    }
    
    public void hire(Employee employee) {
        employees.add(employee);
        System.out.println(employee.getName() + "加入" + name);
    }
    
    public void fire(int id) {
        employees.removeIf(e -> e.getId() == id);
    }
    
    public void showAllEmployees() {
        System.out.println("\n=== " + name + " 员工列表 ===");
        for (Employee emp : employees) {
            emp.displayInfo();
            System.out.println("---");
        }
    }
    
    public void calculateTotalSalary() {
        double total = 0;
        for (Employee emp : employees) {
            total += emp.calculateSalary();
        }
        System.out.println("公司总工资支出: ¥" + total);
    }
    
    public void startWorkDay() {
        System.out.println("\n=== 工作日开始 ===");
        for (Employee emp : employees) {
            emp.work();
        }
    }
    
    // 多态:根据员工类型执行不同操作
    public void processEmployee(Employee emp) {
        System.out.println("\n处理员工: " + emp.getName());
        emp.work();
        
        if (emp instanceof Manager) {
            Manager manager = (Manager) emp;
            manager.holdMeeting();
        } else if (emp instanceof SalesPerson) {
            SalesPerson sales = (SalesPerson) emp;
            sales.makeSale(10000);
        } else if (emp instanceof RegularEmployee) {
            RegularEmployee regular = (RegularEmployee) emp;
            regular.requestOvertime(2);
        }
    }
}

public class EmployeeSystem {
    public static void main(String[] args) {
        // 创建公司
        Company company = new Company("比特科技");
        
        // 创建员工
        Manager manager = new Manager("张总", 1001, 15000, 5000);
        RegularEmployee emp1 = new RegularEmployee("小王", 1002, 8000, 10);
        RegularEmployee emp2 = new RegularEmployee("小李", 1003, 7500, 5);
        SalesPerson sales = new SalesPerson("小赵", 1004, 6000, 0.1);
        
        // 雇佣员工
        company.hire(manager);
        company.hire(emp1);
        company.hire(emp2);
        company.hire(sales);
        
        // 设置经理下属
        manager.addSubordinate(emp1);
        manager.addSubordinate(emp2);
        manager.addSubordinate(sales);
        
        // 销售做业务
        sales.makeSale(50000);
        sales.makeSale(30000);
        
        // 显示所有员工
        company.showAllEmployees();
        
        // 计算总工资
        company.calculateTotalSalary();
        
        // 工作日
        company.startWorkDay();
        
        // 多态处理
        System.out.println("\n=== 多态处理示例 ===");
        company.processEmployee(manager);
        company.processEmployee(sales);
        company.processEmployee(emp1);
        
        // 使用数组存储不同员工
        System.out.println("\n=== 使用数组存储员工 ===");
        Employee[] employeeArray = {manager, emp1, emp2, sales};
        
        double totalSalary = 0;
        for (Employee emp : employeeArray) {
            emp.displayInfo();
            totalSalary += emp.calculateSalary();
        }
        System.out.println("数组总工资: ¥" + totalSalary);
    }
}

关键总结

1. 继承核心要点

复制代码
// 1. 语法
class Child extends Parent {
    // 子类可以访问父类的protected和public成员
    // 子类可以重写父类方法
    // 子类可以添加新成员
}

// 2. 构造方法链
class Child extends Parent {
    public Child() {
        super();  // 必须第一行,默认隐式调用
    }
}

// 3. 访问权限
// private: 仅本类
// default: 同包
// protected: 同包 + 子类
// public: 任何地方

2. 多态实现条件

  1. 继承关系:必须有父子类

  2. 方法重写:子类重写父类方法

  3. 向上转型:父类引用指向子类对象

  4. 动态绑定:运行时确定调用哪个方法

3. 重写规则

  • 方法名、参数列表必须相同

  • 返回值类型相同或是其子类(协变)

  • 访问权限不能降低

  • 不能重写private、final、static方法

  • 抛出异常不能扩大

4. 重要区别

复制代码
// 1. 重写 vs 重载
@Override
public void method() {}  // 重写:父子类,相同签名

public void method(int x) {}  // 重载:同类,不同签名

// 2. 继承 vs 组合
class Car extends Vehicle {}  // 继承:is-a
class Car {                   // 组合:has-a
    private Engine engine;
}

// 3. 向上转型 vs 向下转型
Animal a = new Dog();  // 向上转型:自动
Dog d = (Dog) a;       // 向下转型:强制,需要instanceof检查

5. 初始化顺序

复制代码
父类静态 → 子类静态 → 父类实例 → 父类构造 → 子类实例 → 子类构造

6. 最佳实践

  1. 多用组合,少用继承:组合更灵活

  2. 访问权限最小化:用private,必要时用protected

  3. 使用final:提高安全性和性能

  4. 避免构造方法中调用可重写方法

  5. 使用@Override注解:编译器检查

  6. 合理使用多态:提高代码扩展性

抽象类和接口

1. 抽象类

1.1 概念

抽象类是不能被实例化的类,用于定义子类必须实现的方法模板。

1.2 基本语法

复制代码
// 抽象类
public abstract class Shape {
    // 抽象方法:没有方法体
    public abstract void draw();
    
    // 抽象方法:计算面积
    public abstract double calculateArea();
    
    // 普通方法:有具体实现
    public void showInfo() {
        System.out.println("这是一个形状");
    }
    
    // 属性
    protected String color = "黑色";
    
    // 构造方法
    public Shape(String color) {
        this.color = color;
    }
    
    // 静态方法
    public static void description() {
        System.out.println("所有形状的基类");
    }
}

1.3 抽象类使用示例

复制代码
// 抽象类
abstract class Animal {
    protected String name;
    protected int age;
    
    // 构造方法
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 抽象方法:必须被子类实现
    public abstract void makeSound();
    
    // 抽象方法
    public abstract void move();
    
    // 普通方法:被子类继承
    public void eat() {
        System.out.println(name + "正在吃东西");
    }
    
    // 普通方法
    public void sleep() {
        System.out.println(name + "正在睡觉");
    }
    
    // 静态方法
    public static void showAnimalCount() {
        System.out.println("动物计数");
    }
}

// 具体子类:狗
class Dog extends Animal {
    private String breed;  // 品种
    
    public Dog(String name, int age, String breed) {
        super(name, age);
        this.breed = breed;
    }
    
    // 必须实现父类的抽象方法
    @Override
    public void makeSound() {
        System.out.println(name + "(" + breed + ")汪汪叫");
    }
    
    @Override
    public void move() {
        System.out.println(name + "用四条腿跑");
    }
    
    // 可以添加子类特有的方法
    public void fetch() {
        System.out.println(name + "捡球");
    }
    
    // 可以重写父类方法
    @Override
    public void eat() {
        super.eat();  // 调用父类方法
        System.out.println(name + "正在吃狗粮");
    }
}

// 具体子类:猫
class Cat extends Animal {
    private String furColor;  // 毛色
    
    public Cat(String name, int age, String furColor) {
        super(name, age);
        this.furColor = furColor;
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + "(" + furColor + ")喵喵叫");
    }
    
    @Override
    public void move() {
        System.out.println(name + "悄悄走路");
    }
    
    public void catchMouse() {
        System.out.println(name + "抓老鼠");
    }
}

// 抽象子类:鸟(没有实现所有抽象方法)
abstract class Bird extends Animal {
    public Bird(String name, int age) {
        super(name, age);
    }
    
    @Override
    public void move() {
        System.out.println(name + "用翅膀飞");
    }
    // 没有实现makeSound(),所以Bird也必须是abstract
}

// 具体子类:麻雀
class Sparrow extends Bird {
    public Sparrow(String name, int age) {
        super(name, age);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + "叽叽喳喳");
    }
    
    public void buildNest() {
        System.out.println(name + "筑巢");
    }
}

public class AbstractClassDemo {
    public static void main(String[] args) {
        // 不能实例化抽象类
        // Animal animal = new Animal("动物", 1);  // 编译错误
        
        // 创建具体子类对象
        Dog dog = new Dog("旺财", 3, "金毛");
        Cat cat = new Cat("咪咪", 2, "白色");
        Sparrow sparrow = new Sparrow("小麻雀", 1);
        
        // 多态:父类引用指向子类对象
        Animal[] animals = {dog, cat, sparrow};
        
        for (Animal animal : animals) {
            animal.makeSound();  // 多态调用
            animal.move();
            animal.eat();
            System.out.println("---");
        }
        
        // 调用子类特有方法需要向下转型
        if (dog instanceof Dog) {
            Dog d = (Dog) dog;
            d.fetch();
        }
        
        // 调用静态方法
        Animal.showAnimalCount();
        
        // 使用抽象类作为方法参数
        animalShow(dog);
        animalShow(cat);
    }
    
    public static void animalShow(Animal animal) {
        System.out.println("\n表演开始:");
        animal.makeSound();
        animal.move();
    }
}

1.4 抽象类特性详解

复制代码
// 1. 抽象类可以有构造方法
abstract class Vehicle {
    protected String brand;
    
    public Vehicle(String brand) {
        this.brand = brand;
        System.out.println("Vehicle构造方法:" + brand);
    }
    
    public abstract void run();
}

class Car extends Vehicle {
    public Car(String brand) {
        super(brand);  // 必须调用父类构造方法
    }
    
    @Override
    public void run() {
        System.out.println(brand + "汽车在路上跑");
    }
}

// 2. 抽象类可以有非抽象方法
abstract class Database {
    // 抽象方法
    public abstract void connect();
    
    // 普通方法
    public void disconnect() {
        System.out.println("断开连接");
    }
    
    // 静态方法
    public static void showVersion() {
        System.out.println("Database 1.0");
    }
    
    // final方法
    public final void showInfo() {
        System.out.println("这是数据库基类");
    }
}

// 3. 抽象类可以有成员变量
abstract class Employee {
    protected String name;
    protected double baseSalary;
    
    public Employee(String name, double baseSalary) {
        this.name = name;
        this.baseSalary = baseSalary;
    }
    
    public abstract double calculateSalary();
}

// 4. 抽象类可以没有抽象方法(但不常见)
abstract class NoAbstractMethod {
    public void normalMethod() {
        System.out.println("普通方法");
    }
}

// 5. 抽象类可以有main方法
abstract class AbstractWithMain {
    public abstract void doSomething();
    
    public static void main(String[] args) {
        System.out.println("抽象类的main方法");
    }
}

// 6. 错误示例
abstract class ErrorExample {
    // 错误:抽象方法不能是private
    // private abstract void method1();
    
    // 错误:抽象方法不能是static
    // static abstract void method2();
    
    // 错误:抽象方法不能是final
    // public final abstract void method3();
    
    // 错误:抽象方法必须有方法体声明
    // public abstract void method4() { }  // 不能有方法体
    
    // 正确
    public abstract void correctMethod();
}

1.5 注意易错点

复制代码
// 错误1:尝试实例化抽象类
abstract class AbstractClass {
    public abstract void method();
}

class Test1 {
    public static void main(String[] args) {
        // AbstractClass obj = new AbstractClass();  // 编译错误
    }
}

// 错误2:子类没有实现所有抽象方法
abstract class Parent {
    public abstract void methodA();
    public abstract void methodB();
}

// class Child extends Parent {  // 编译错误:必须实现所有抽象方法
//     @Override
//     public void methodA() {}
//     // 缺少methodB的实现
// }

// 正确:声明为抽象类
abstract class Child extends Parent {
    @Override
    public void methodA() {
        System.out.println("实现methodA");
    }
    // 没有实现methodB,所以Child也必须是abstract
}

// 错误3:降低访问权限
abstract class Parent2 {
    public abstract void publicMethod();
    protected abstract void protectedMethod();
}

class Child2 extends Parent2 {
    // @Override
    // void publicMethod() {  // 编译错误:不能降低访问权限
    //     System.out.println("default access");
    // }
    
    @Override
    public void publicMethod() {  // 正确
        System.out.println("public access");
    }
    
    @Override
    public void protectedMethod() {  // 正确:可以升高访问权限
        System.out.println("protected -> public");
    }
}

2. 接口

2.1 概念

接口是一种特殊的抽象类,定义一组行为的规范,不包含具体实现。

2.2 基本语法

复制代码
// 接口定义
public interface USB {
    // 常量(默认 public static final)
    String VERSION = "3.0";
    int MAX_SPEED = 5000;  // 单位:MB/s
    
    // 抽象方法(默认 public abstract)
    void connect();
    void transferData(String data);
    void disconnect();
    
    // 默认方法(Java 8+)
    default void showVersion() {
        System.out.println("USB " + VERSION);
    }
    
    // 静态方法(Java 8+)
    static void showInfo() {
        System.out.println("通用串行总线接口");
    }
    
    // 私有方法(Java 9+)
    // private void internalMethod() {
    //     System.out.println("内部方法");
    // }
}

2.3 接口使用示例

复制代码
// 1. 定义接口
interface Swimmable {
    void swim();
    
    default void showAbility() {
        System.out.println("我会游泳");
    }
}

interface Flyable {
    void fly();
    
    default void showAbility() {
        System.out.println("我会飞");
    }
}

interface Runnable {
    void run();
}

// 2. 实现接口
class Duck implements Swimmable, Flyable, Runnable {
    private String name;
    
    public Duck(String name) {
        this.name = name;
    }
    
    @Override
    public void swim() {
        System.out.println(name + "在水里游");
    }
    
    @Override
    public void fly() {
        System.out.println(name + "在天上飞");
    }
    
    @Override
    public void run() {
        System.out.println(name + "在地上跑");
    }
    
    // 解决默认方法冲突
    @Override
    public void showAbility() {
        Swimmable.super.showAbility();
        Flyable.super.showAbility();
        System.out.println(name + "是水陆空三栖动物");
    }
    
    // 特有方法
    public void quack() {
        System.out.println(name + "嘎嘎叫");
    }
}

class Fish implements Swimmable {
    private String name;
    
    public Fish(String name) {
        this.name = name;
    }
    
    @Override
    public void swim() {
        System.out.println(name + "在水里游");
    }
    
    public void bubble() {
        System.out.println(name + "吐泡泡");
    }
}

class Airplane implements Flyable {
    private String model;
    
    public Airplane(String model) {
        this.model = model;
    }
    
    @Override
    public void fly() {
        System.out.println(model + "飞机在飞行");
    }
    
    public void takeoff() {
        System.out.println(model + "起飞");
    }
}

// 3. 测试接口
public class InterfaceDemo {
    public static void main(String[] args) {
        // 多态:接口引用指向实现类对象
        Swimmable duck = new Duck("唐老鸭");
        Swimmable fish = new Fish("尼莫");
        
        Flyable flyingDuck = new Duck("飞翔的鸭子");
        Flyable airplane = new Airplane("波音747");
        
        Runnable runningDuck = new Duck("跑步的鸭子");
        
        // 调用接口方法
        duck.swim();
        duck.showAbility();
        
        System.out.println();
        
        flyingDuck.fly();
        airplane.fly();
        
        System.out.println();
        
        runningDuck.run();
        
        // 向下转型调用特有方法
        if (duck instanceof Duck) {
            Duck realDuck = (Duck) duck;
            realDuck.quack();
        }
        
        // 使用接口作为方法参数
        System.out.println("\n=== 方法参数多态 ===");
        showAbility(duck);
        showAbility(flyingDuck);
        
        // 使用接口作为返回值
        System.out.println("\n=== 方法返回值多态 ===");
        Swimmable swimmer = getSwimmer("鱼");
        swimmer.swim();
        
        // 接口常量
        System.out.println("\n=== 接口常量 ===");
        System.out.println("USB版本:" + USB.VERSION);
        System.out.println("USB最大速度:" + USB.MAX_SPEED + "MB/s");
        
        // 接口静态方法
        USB.showInfo();
    }
    
    public static void showAbility(Swimmable swimmer) {
        swimmer.swim();
    }
    
    public static void showAbility(Flyable flyer) {
        flyer.fly();
    }
    
    public static Swimmable getSwimmer(String type) {
        if ("鱼".equals(type)) {
            return new Fish("金鱼");
        } else {
            return new Duck("鸭子");
        }
    }
}

2.4 接口继承

复制代码
// 接口可以多继承
interface Animal {
    void eat();
    void sleep();
}

interface Pet {
    void play();
    
    default void showAffection() {
        System.out.println("表达亲昵");
    }
}

// 接口继承接口
interface DomesticAnimal extends Animal, Pet {
    void provideService();
    
    // 可以添加新方法
    default void liveWithHuman() {
        System.out.println("与人类一起生活");
    }
}

// 实现多重继承的接口
class Dog implements DomesticAnimal {
    private String name;
    
    public Dog(String name) {
        this.name = name;
    }
    
    @Override
    public void eat() {
        System.out.println(name + "吃狗粮");
    }
    
    @Override
    public void sleep() {
        System.out.println(name + "睡觉");
    }
    
    @Override
    public void play() {
        System.out.println(name + "玩球");
    }
    
    @Override
    public void provideService() {
        System.out.println(name + "看家护院");
    }
    
    // 可以重写默认方法
    @Override
    public void showAffection() {
        System.out.println(name + "摇尾巴");
    }
}

// 另一个接口继承例子
interface A {
    void methodA();
}

interface B {
    void methodB();
}

interface C extends A, B {
    void methodC();
}

class D implements C {
    @Override
    public void methodA() {
        System.out.println("实现methodA");
    }
    
    @Override
    public void methodB() {
        System.out.println("实现methodB");
    }
    
    @Override
    public void methodC() {
        System.out.println("实现methodC");
    }
}

public class InterfaceInheritance {
    public static void main(String[] args) {
        DomesticAnimal dog = new Dog("旺财");
        
        // 可以调用所有接口的方法
        dog.eat();
        dog.sleep();
        dog.play();
        dog.provideService();
        dog.showAffection();
        dog.liveWithHuman();
        
        // 多继承测试
        C obj = new D();
        obj.methodA();
        obj.methodB();
        obj.methodC();
        
        // 接口引用可以指向实现了该接口的任何类
        A aRef = new D();
        aRef.methodA();
        // aRef.methodB();  // 错误:A接口没有methodB
    }
}

2.5 接口新特性(Java 8+)

复制代码
interface ModernInterface {
    // 1. 常量
    String NAME = "现代接口";
    
    // 2. 抽象方法
    void abstractMethod();
    
    // 3. 默认方法(Java 8+)
    default void defaultMethod() {
        System.out.println("默认方法实现");
        privateMethod();  // 可以调用私有方法
    }
    
    // 4. 静态方法(Java 8+)
    static void staticMethod() {
        System.out.println("静态方法");
        staticPrivateMethod();  // 可以调用私有静态方法
    }
    
    // 5. 私有方法(Java 9+)
    private void privateMethod() {
        System.out.println("私有实例方法");
    }
    
    // 6. 私有静态方法(Java 9+)
    private static void staticPrivateMethod() {
        System.out.println("私有静态方法");
    }
}

class ModernImpl implements ModernInterface {
    @Override
    public void abstractMethod() {
        System.out.println("实现抽象方法");
    }
    
    // 可以选择性重写默认方法
    @Override
    public void defaultMethod() {
        System.out.println("重写默认方法");
        ModernInterface.super.defaultMethod();  // 调用接口的默认方法
    }
}

public class ModernInterfaceDemo {
    public static void main(String[] args) {
        ModernInterface obj = new ModernImpl();
        
        // 调用抽象方法
        obj.abstractMethod();
        
        // 调用默认方法
        obj.defaultMethod();
        
        // 调用静态方法(通过接口名)
        ModernInterface.staticMethod();
        
        // 访问常量
        System.out.println("接口名称:" + ModernInterface.NAME);
        
        // 不能调用私有方法
        // obj.privateMethod();  // 编译错误
        // ModernInterface.privateMethod();  // 编译错误
    }
}

2.6 注意易错点

复制代码
// 错误1:接口中的方法不能是private(Java 8之前)
interface ErrorInterface1 {
    // private void method();  // 编译错误(Java 8之前)
    // 在Java 9+中,可以有私有方法
}

// 错误2:接口中的方法不能有方法体(除默认方法和静态方法)
interface ErrorInterface2 {
    // void method() {  // 编译错误
    //     System.out.println("方法体");
    // }
    
    // 正确:默认方法
    default void defaultMethod() {
        System.out.println("默认方法");
    }
    
    // 正确:静态方法
    static void staticMethod() {
        System.out.println("静态方法");
    }
}

// 错误3:接口中的变量不能是private
interface ErrorInterface3 {
    // private int x = 10;  // 编译错误
    // 接口变量默认是public static final
    int x = 10;  // 相当于 public static final int x = 10;
}

// 错误4:实现多个接口时有相同默认方法
interface A1 {
    default void method() {
        System.out.println("A的默认方法");
    }
}

interface B1 {
    default void method() {
        System.out.println("B的默认方法");
    }
}

// class C1 implements A1, B1 {  // 编译错误:默认方法冲突
//     // 必须重写冲突的方法
// }

// 正确:重写冲突的方法
class C1 implements A1, B1 {
    @Override
    public void method() {
        System.out.println("C重写的方法");
        A1.super.method();  // 可以选择调用某个接口的方法
    }
}

// 错误5:接口不能有构造方法
interface ErrorInterface4 {
    // public ErrorInterface4() {  // 编译错误
    //     System.out.println("构造方法");
    // }
}

// 错误6:接口不能有实例代码块
interface ErrorInterface5 {
    // {  // 编译错误
    //     System.out.println("实例代码块");
    // }
}

3. 抽象类 vs 接口

3.1 区别对比

复制代码
// 抽象类示例
abstract class AbstractVehicle {
    // 可以有成员变量
    protected String brand;
    protected int speed;
    
    // 可以有构造方法
    public AbstractVehicle(String brand) {
        this.brand = brand;
    }
    
    // 可以有抽象方法
    public abstract void start();
    
    // 可以有具体方法
    public void stop() {
        System.out.println(brand + "停止");
        speed = 0;
    }
    
    // 可以有静态方法
    public static void showInfo() {
        System.out.println("交通工具");
    }
    
    // 可以有final方法
    public final void showBrand() {
        System.out.println("品牌:" + brand);
    }
    
    // 可以有静态代码块
    static {
        System.out.println("AbstractVehicle类加载");
    }
    
    // 可以有实例代码块
    {
        System.out.println("AbstractVehicle实例初始化");
    }
}

// 接口示例
interface VehicleInterface {
    // 只能有常量
    int MAX_SPEED = 200;
    
    // 只能有抽象方法、默认方法、静态方法
    void start();
    void stop();
    
    // 默认方法
    default void showMaxSpeed() {
        System.out.println("最大速度:" + MAX_SPEED);
    }
    
    // 静态方法
    static void showType() {
        System.out.println("交通工具接口");
    }
    
    // 不能有构造方法
    // 不能有实例代码块
    // 不能有非final变量
}

// 具体类
class Car extends AbstractVehicle implements VehicleInterface {
    public Car(String brand) {
        super(brand);
    }
    
    @Override
    public void start() {
        System.out.println(brand + "汽车启动");
        speed = 20;
    }
    
    @Override
    public void stop() {
        super.stop();  // 调用父类方法
        System.out.println("手刹拉起");
    }
    
    // VehicleInterface的stop也要实现
    // 但这里使用了继承的stop方法
}

// 使用对比
public class CompareDemo {
    public static void main(String[] args) {
        // 使用抽象类
        AbstractVehicle car1 = new Car("丰田");
        car1.start();
        car1.stop();
        car1.showBrand();
        AbstractVehicle.showInfo();
        
        System.out.println();
        
        // 使用接口
        VehicleInterface car2 = new Car("本田");
        car2.start();
        car2.stop();
        car2.showMaxSpeed();
        VehicleInterface.showType();
        
        // 访问常量
        System.out.println("最大速度限制:" + VehicleInterface.MAX_SPEED);
        
        // 测试多实现
        HybridCar hybrid = new HybridCar("特斯拉");
        hybrid.start();      // 来自AbstractVehicle
        hybrid.charge();     // 来自Electric
        hybrid.refuel();     // 来自Fuel
        hybrid.drive();      // 来自Drivable
    }
}

// 多继承模拟
interface Electric {
    void charge();
}

interface Fuel {
    void refuel();
}

interface Drivable {
    void drive();
    
    default void showType() {
        System.out.println("可驾驶");
    }
}

// 抽象类实现部分接口
abstract class HybridVehicle implements Electric, Fuel {
    protected String model;
    
    public HybridVehicle(String model) {
        this.model = model;
    }
    
    @Override
    public void charge() {
        System.out.println(model + "充电");
    }
    
    @Override
    public void refuel() {
        System.out.println(model + "加油");
    }
    
    public abstract void start();
}

// 具体类继承抽象类并实现更多接口
class HybridCar extends HybridVehicle implements Drivable {
    public HybridCar(String model) {
        super(model);
    }
    
    @Override
    public void start() {
        System.out.println(model + "混合动力启动");
    }
    
    @Override
    public void drive() {
        System.out.println(model + "行驶中");
    }
    
    // 解决默认方法冲突
    @Override
    public void showType() {
        System.out.println(model + "是混合动力汽车");
    }
}

3.2 使用场景对比

复制代码
// 场景1:使用抽象类(is-a关系,有共同属性)
abstract class Employee {
    protected String name;
    protected String id;
    protected double baseSalary;
    
    public Employee(String name, String id, double baseSalary) {
        this.name = name;
        this.id = id;
        this.baseSalary = baseSalary;
    }
    
    // 共同行为
    public void checkIn() {
        System.out.println(name + "打卡上班");
    }
    
    public void checkOut() {
        System.out.println(name + "打卡下班");
    }
    
    // 不同子类有不同的计算方式
    public abstract double calculateSalary();
    
    // 模板方法模式
    public final void workProcess() {
        checkIn();
        doWork();
        checkOut();
    }
    
    protected abstract void doWork();
}

class Developer extends Employee {
    private int overtimeHours;
    
    public Developer(String name, String id, double baseSalary, int overtimeHours) {
        super(name, id, baseSalary);
        this.overtimeHours = overtimeHours;
    }
    
    @Override
    public double calculateSalary() {
        return baseSalary + overtimeHours * 100;
    }
    
    @Override
    protected void doWork() {
        System.out.println(name + "正在编写代码");
    }
}

class Manager extends Employee {
    private double bonus;
    
    public Manager(String name, String id, double baseSalary, double bonus) {
        super(name, id, baseSalary);
        this.bonus = bonus;
    }
    
    @Override
    public double calculateSalary() {
        return baseSalary + bonus;
    }
    
    @Override
    protected void doWork() {
        System.out.println(name + "正在开会");
    }
}

// 场景2:使用接口(具有某种能力)
interface Drawable {
    void draw();
    
    default void show() {
        System.out.println("这是一个可绘制的对象");
    }
}

interface Resizable {
    void resize(double factor);
}

interface Rotatable {
    void rotate(double degrees);
}

// 类可以实现多个接口
class Circle implements Drawable, Resizable {
    private double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制圆形,半径:" + radius);
    }
    
    @Override
    public void resize(double factor) {
        radius *= factor;
        System.out.println("调整大小,新半径:" + radius);
    }
}

class Rectangle implements Drawable, Resizable, Rotatable {
    private double width;
    private double height;
    private double angle = 0;
    
    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }
    
    @Override
    public void draw() {
        System.out.println("绘制矩形,宽:" + width + ",高:" + height);
    }
    
    @Override
    public void resize(double factor) {
        width *= factor;
        height *= factor;
        System.out.println("调整大小,新尺寸:" + width + "x" + height);
    }
    
    @Override
    public void rotate(double degrees) {
        angle = (angle + degrees) % 360;
        System.out.println("旋转角度:" + angle);
    }
}

// 场景3:抽象类和接口结合使用
abstract class Shape implements Drawable {
    protected String color;
    
    public Shape(String color) {
        this.color = color;
    }
    
    public abstract double getArea();
    public abstract double getPerimeter();
    
    public void setColor(String color) {
        this.color = color;
    }
    
    public String getColor() {
        return color;
    }
}

class Triangle extends Shape {
    private double base;
    private double height;
    
    public Triangle(String color, double base, double height) {
        super(color);
        this.base = base;
        this.height = height;
    }
    
    @Override
    public double getArea() {
        return 0.5 * base * height;
    }
    
    @Override
    public double getPerimeter() {
        // 简化计算
        return base + height + Math.sqrt(base * base + height * height);
    }
    
    @Override
    public void draw() {
        System.out.println("绘制" + color + "三角形,底:" + base + ",高:" + height);
    }
}

public class UsageScenario {
    public static void main(String[] args) {
        System.out.println("=== 抽象类使用场景 ===");
        Employee dev = new Developer("张三", "001", 10000, 20);
        Employee mgr = new Manager("李四", "002", 15000, 5000);
        
        dev.workProcess();
        System.out.println("工资:" + dev.calculateSalary());
        
        mgr.workProcess();
        System.out.println("工资:" + mgr.calculateSalary());
        
        System.out.println("\n=== 接口使用场景 ===");
        Drawable[] drawables = {
            new Circle(5.0),
            new Rectangle(4.0, 6.0)
        };
        
        for (Drawable d : drawables) {
            d.draw();
            d.show();
            
            if (d instanceof Resizable) {
                ((Resizable) d).resize(1.5);
            }
        }
        
        System.out.println("\n=== 抽象类+接口使用场景 ===");
        Shape triangle = new Triangle("红色", 3.0, 4.0);
        triangle.draw();
        System.out.println("面积:" + triangle.getArea());
        System.out.println("周长:" + triangle.getPerimeter());
    }
}

3.3 选择原则

复制代码
/*
选择抽象类的情况:
1. 需要在多个相关类之间共享代码
2. 需要定义非static或非final的字段
3. 需要定义public以外的访问权限
4. 需要定义构造方法
5. 有is-a关系,有共同的属性和行为

选择接口的情况:
1. 不相关的类需要实现相同的行为
2. 需要实现多重继承
3. 只想定义行为规范,不关心具体实现
4. 有has-a/can-do关系,表示具有某种能力

实际开发中常见组合:
1. 抽象类实现接口,提供部分实现
2. 具体类继承抽象类,实现剩余接口
3. 使用接口定义API,抽象类提供默认实现
*/

// 实际例子:集合框架
// List 接口
interface List<E> {
    void add(E element);
    E get(int index);
    int size();
}

// 抽象类提供部分实现
abstract class AbstractList<E> implements List<E> {
    protected int size = 0;
    
    @Override
    public int size() {
        return size;
    }
    
    public boolean isEmpty() {
        return size == 0;
    }
    
    protected void checkIndex(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
    }
}

// 具体实现
class ArrayList<E> extends AbstractList<E> {
    private Object[] elements = new Object[10];
    
    @Override
    public void add(E element) {
        // 实现添加逻辑
        if (size == elements.length) {
            // 扩容
            Object[] newArray = new Object[elements.length * 2];
            System.arraycopy(elements, 0, newArray, 0, elements.length);
            elements = newArray;
        }
        elements[size++] = element;
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public E get(int index) {
        checkIndex(index);
        return (E) elements[index];
    }
}

// 使用
class ListExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("World");
        
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

4. Object类

4.1 Object类概述

复制代码
// Object是所有类的根类
class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 重写toString方法
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
    
    // 重写equals方法
    @Override
    public boolean equals(Object obj) {
        // 1. 检查是否为同一个对象
        if (this == obj) {
            return true;
        }
        
        // 2. 检查是否为null
        if (obj == null) {
            return false;
        }
        
        // 3. 检查是否为同一类型
        if (getClass() != obj.getClass()) {
            return false;
        }
        
        // 4. 类型转换
        Person other = (Person) obj;
        
        // 5. 比较字段
        if (age != other.age) {
            return false;
        }
        
        // 6. 处理可能的null值
        if (name == null) {
            return other.name == null;
        } else {
            return name.equals(other.name);
        }
    }
    
    // 重写hashCode方法
    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + (name == null ? 0 : name.hashCode());
        result = 31 * result + age;
        return result;
    }
}

// Student类继承Person
class Student extends Person {
    private String studentId;
    
    public Student(String name, int age, String studentId) {
        super(name, age);
        this.studentId = studentId;
    }
    
    // 重写toString
    @Override
    public String toString() {
        return super.toString() + ", studentId='" + studentId + "'}";
    }
    
    // 重写equals
    @Override
    public boolean equals(Object obj) {
        if (!super.equals(obj)) {
            return false;
        }
        
        Student other = (Student) obj;
        
        if (studentId == null) {
            return other.studentId == null;
        } else {
            return studentId.equals(other.studentId);
        }
    }
    
    // 重写hashCode
    @Override
    public int hashCode() {
        int result = super.hashCode();
        result = 31 * result + (studentId == null ? 0 : studentId.hashCode());
        return result;
    }
}

public class ObjectClassDemo {
    public static void main(String[] args) {
        // 1. toString方法
        Person p1 = new Person("张三", 20);
        Student s1 = new Student("李四", 18, "2023001");
        
        System.out.println("p1: " + p1.toString());
        System.out.println("s1: " + s1.toString());
        
        // 自动调用toString
        System.out.println("自动调用: " + p1);
        
        // 2. equals方法
        Person p2 = new Person("张三", 20);
        Person p3 = new Person("张三", 21);
        
        System.out.println("\n=== equals比较 ===");
        System.out.println("p1 == p2: " + (p1 == p2));            // false
        System.out.println("p1.equals(p2): " + p1.equals(p2));    // true
        System.out.println("p1.equals(p3): " + p1.equals(p3));    // false
        System.out.println("p1.equals(null): " + p1.equals(null)); // false
        
        // 3. hashCode方法
        System.out.println("\n=== hashCode ===");
        System.out.println("p1.hashCode(): " + p1.hashCode());
        System.out.println("p2.hashCode(): " + p2.hashCode());
        System.out.println("p3.hashCode(): " + p3.hashCode());
        
        // 4. getClass方法
        System.out.println("\n=== getClass ===");
        System.out.println("p1.getClass(): " + p1.getClass());
        System.out.println("p1.getClass().getName(): " + p1.getClass().getName());
        
        // 5. 继承关系
        Object obj1 = p1;  // 向上转型
        Object obj2 = s1;
        
        System.out.println("\n=== 多态 ===");
        System.out.println("obj1: " + obj1.toString());
        System.out.println("obj2: " + obj2.toString());
        
        // 6. 使用数组
        Object[] objects = {p1, s1, "字符串", 123, 45.6};
        
        System.out.println("\n=== Object数组 ===");
        for (Object obj : objects) {
            System.out.println(obj.getClass().getSimpleName() + ": " + obj);
        }
    }
}

4.2 equals和hashCode契约

复制代码
import java.util.HashSet;
import java.util.Objects;

class Product {
    private String name;
    private double price;
    private int quantity;
    
    public Product(String name, double price, int quantity) {
        this.name = name;
        this.price = price;
        this.quantity = quantity;
    }
    
    // 正确实现equals
    @Override
    public boolean equals(Object o) {
        // 快速检查
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        
        Product product = (Product) o;
        
        // 比较所有字段
        return Double.compare(product.price, price) == 0 &&
               quantity == product.quantity &&
               Objects.equals(name, product.name);
    }
    
    // 必须与equals保持一致
    @Override
    public int hashCode() {
        return Objects.hash(name, price, quantity);
    }
    
    @Override
    public String toString() {
        return String.format("Product{name='%s', price=%.2f, quantity=%d}", 
                            name, price, quantity);
    }
}

// 错误示例:没有重写hashCode
class BadProduct {
    private String name;
    private double price;
    
    public BadProduct(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        
        BadProduct that = (BadProduct) o;
        return Double.compare(that.price, price) == 0 &&
               Objects.equals(name, that.name);
    }
    
    // 错误:没有重写hashCode
    // 这将导致在HashSet/HashMap中出现问题
}

public class EqualsHashCodeContract {
    public static void main(String[] args) {
        System.out.println("=== equals和hashCode契约 ===");
        
        // 创建两个相等的对象
        Product p1 = new Product("苹果", 5.0, 10);
        Product p2 = new Product("苹果", 5.0, 10);
        
        System.out.println("p1.equals(p2): " + p1.equals(p2));  // true
        System.out.println("p1.hashCode() == p2.hashCode(): " + 
                          (p1.hashCode() == p2.hashCode()));  // true
        
        // 测试在HashSet中的行为
        HashSet<Product> set = new HashSet<>();
        set.add(p1);
        set.add(p2);
        
        System.out.println("HashSet大小: " + set.size());  // 应该是1
        
        // 修改已添加到集合中的对象(危险!)
        System.out.println("\n=== 修改已添加的对象 ===");
        Product p3 = new Product("香蕉", 3.0, 5);
        set.add(p3);
        System.out.println("添加p3后大小: " + set.size());  // 2
        
        // 理论上不应该修改作为键的对象
        // 这里只是演示
        // p3.quantity = 10;  // 如果quantity参与hashCode计算,这会破坏集合
        
        // 测试contains
        Product p4 = new Product("苹果", 5.0, 10);
        System.out.println("set.contains(p4): " + set.contains(p4));  // true
        
        // Objects工具类
        System.out.println("\n=== Objects工具类 ===");
        Product p5 = null;
        Product p6 = new Product("橙子", 4.0, 8);
        
        System.out.println("Objects.equals(p5, p6): " + Objects.equals(p5, p6));
        System.out.println("Objects.equals(p6, p6): " + Objects.equals(p6, p6));
        System.out.println("Objects.hashCode(p5): " + Objects.hashCode(p5));
        System.out.println("Objects.hashCode(p6): " + Objects.hashCode(p6));
        
        // Objects.hash用于计算hashCode
        System.out.println("Objects.hash: " + Objects.hash("test", 123, 45.6));
    }
}

4.3 clone方法

复制代码
class Address implements Cloneable {
    private String city;
    private String street;
    
    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }
    
    public void setCity(String city) {
        this.city = city;
    }
    
    public void setStreet(String street) {
        this.street = street;
    }
    
    @Override
    public String toString() {
        return city + "市" + street;
    }
    
    // 深拷贝
    @Override
    public Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();  // 不会发生
        }
    }
}

class Person2 implements Cloneable {
    private String name;
    private int age;
    private Address address;  // 引用类型
    
    public Person2(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    
    public void setAddress(Address address) {
        this.address = address;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return "Person2{name='" + name + "', age=" + age + 
               ", address=" + address + "}";
    }
    
    // 浅拷贝
    @Override
    public Person2 clone() throws CloneNotSupportedException {
        return (Person2) super.clone();
    }
    
    // 深拷贝
    public Person2 deepClone() throws CloneNotSupportedException {
        Person2 cloned = (Person2) super.clone();
        cloned.address = this.address.clone();  // 克隆address
        return cloned;
    }
}

public class CloneDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        System.out.println("=== 浅拷贝 vs 深拷贝 ===");
        
        Address addr = new Address("北京", "长安街");
        Person2 p1 = new Person2("张三", 20, addr);
        
        // 浅拷贝
        Person2 shallowCopy = p1.clone();
        
        // 深拷贝
        Person2 deepCopy = p1.deepClone();
        
        System.out.println("原始: " + p1);
        System.out.println("浅拷贝: " + shallowCopy);
        System.out.println("深拷贝: " + deepCopy);
        
        // 修改原对象的address
        p1.getAddress().setCity("上海");
        
        System.out.println("\n修改原对象address后:");
        System.out.println("原始: " + p1);
        System.out.println("浅拷贝: " + shallowCopy);  // 也被修改了!
        System.out.println("深拷贝: " + deepCopy);     // 没有被修改
        
        // 修改原对象的name
        p1.setName("李四");
        
        System.out.println("\n修改原对象name后:");
        System.out.println("原始: " + p1);
        System.out.println("浅拷贝: " + shallowCopy);  // 没有被修改
        System.out.println("深拷贝: " + deepCopy);     // 没有被修改
    }
}

4.4 finalize方法(已过时)

复制代码
class Resource {
    private String name;
    
    public Resource(String name) {
        this.name = name;
        System.out.println(name + " 被创建");
    }
    
    public void use() {
        System.out.println(name + " 被使用");
    }
    
    // finalize方法(已过时,不推荐使用)
    @Override
    protected void finalize() throws Throwable {
        try {
            System.out.println(name + " 被垃圾回收");
        } finally {
            super.finalize();
        }
    }
}

public class FinalizeDemo {
    public static void main(String[] args) {
        System.out.println("=== finalize方法演示 ===");
        
        Resource r1 = new Resource("资源1");
        r1.use();
        
        // 设置为null,使对象可被垃圾回收
        r1 = null;
        
        // 建议垃圾回收(但不保证立即执行)
        System.gc();
        
        // 给垃圾回收器一些时间
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("程序结束");
        
        // 注意:finalize方法在Java 9中已过时
        // 应该使用try-with-resources或实现AutoCloseable接口
    }
}

5. 综合示例

5.1 图形系统设计

复制代码
import java.util.ArrayList;
import java.util.List;

// 接口:可绘制
interface Drawable {
    void draw();
    
    default String getDescription() {
        return "可绘制对象";
    }
}

// 接口:可移动
interface Movable {
    void move(int dx, int dy);
}

// 接口:可缩放
interface Scalable {
    void scale(double factor);
}

// 接口:可旋转
interface Rotatable {
    void rotate(double degrees);
}

// 抽象基类
abstract class Shape implements Drawable {
    protected String name;
    protected String color;
    protected Point position;
    
    public Shape(String name, String color, Point position) {
        this.name = name;
        this.color = color;
        this.position = position;
    }
    
    // 抽象方法
    public abstract double getArea();
    public abstract double getPerimeter();
    
    // 具体方法
    public String getName() {
        return name;
    }
    
    public void setColor(String color) {
        this.color = color;
    }
    
    public String getColor() {
        return color;
    }
    
    public Point getPosition() {
        return position;
    }
    
    public void setPosition(Point position) {
        this.position = position;
    }
    
    @Override
    public String toString() {
        return String.format("%s{name='%s', color='%s', position=%s}", 
                           getClass().getSimpleName(), name, color, position);
    }
}

// 具体类:圆形
class Circle extends Shape implements Scalable {
    private double radius;
    
    public Circle(String name, String color, Point position, double radius) {
        super(name, color, position);
        this.radius = radius;
    }
    
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public double getPerimeter() {
        return 2 * Math.PI * radius;
    }
    
    @Override
    public void draw() {
        System.out.printf("绘制圆形: %s, 半径: %.2f, 位置: %s\n", 
                         name, radius, position);
    }
    
    @Override
    public void scale(double factor) {
        radius *= factor;
        System.out.printf("%s 缩放为原来的%.2f倍, 新半径: %.2f\n", 
                         name, factor, radius);
    }
    
    public double getRadius() {
        return radius;
    }
}

// 具体类:矩形
class Rectangle extends Shape implements Movable, Scalable, Rotatable {
    private double width;
    private double height;
    private double rotation = 0;  // 旋转角度
    
    public Rectangle(String name, String color, Point position, 
                    double width, double height) {
        super(name, color, position);
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double getArea() {
        return width * height;
    }
    
    @Override
    public double getPerimeter() {
        return 2 * (width + height);
    }
    
    @Override
    public void draw() {
        System.out.printf("绘制矩形: %s, 宽: %.2f, 高: %.2f, 位置: %s, 旋转: %.1f度\n", 
                         name, width, height, position, rotation);
    }
    
    @Override
    public void move(int dx, int dy) {
        position.move(dx, dy);
        System.out.printf("%s 移动到: %s\n", name, position);
    }
    
    @Override
    public void scale(double factor) {
        width *= factor;
        height *= factor;
        System.out.printf("%s 缩放为原来的%.2f倍, 新尺寸: %.2fx%.2f\n", 
                         name, factor, width, height);
    }
    
    @Override
    public void rotate(double degrees) {
        rotation = (rotation + degrees) % 360;
        System.out.printf("%s 旋转到: %.1f度\n", name, rotation);
    }
    
    public double getWidth() {
        return width;
    }
    
    public double getHeight() {
        return height;
    }
}

// 具体类:三角形
class Triangle extends Shape implements Movable {
    private double side1;
    private double side2;
    private double side3;
    
    public Triangle(String name, String color, Point position, 
                   double side1, double side2, double side3) {
        super(name, color, position);
        this.side1 = side1;
        this.side2 = side2;
        this.side3 = side3;
    }
    
    @Override
    public double getArea() {
        // 海伦公式
        double s = getPerimeter() / 2;
        return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3));
    }
    
    @Override
    public double getPerimeter() {
        return side1 + side2 + side3;
    }
    
    @Override
    public void draw() {
        System.out.printf("绘制三角形: %s, 边长: %.2f, %.2f, %.2f, 位置: %s\n", 
                         name, side1, side2, side3, position);
    }
    
    @Override
    public void move(int dx, int dy) {
        position.move(dx, dy);
        System.out.printf("%s 移动到: %s\n", name, position);
    }
}

// 复合图形
class CompositeShape extends Shape {
    private List<Shape> shapes = new ArrayList<>();
    
    public CompositeShape(String name, String color, Point position) {
        super(name, color, position);
    }
    
    public void addShape(Shape shape) {
        shapes.add(shape);
    }
    
    public void removeShape(Shape shape) {
        shapes.remove(shape);
    }
    
    @Override
    public double getArea() {
        double totalArea = 0;
        for (Shape shape : shapes) {
            totalArea += shape.getArea();
        }
        return totalArea;
    }
    
    @Override
    public double getPerimeter() {
        // 对于复合图形,周长没有明确定义
        return 0;
    }
    
    @Override
    public void draw() {
        System.out.println("=== 开始绘制复合图形: " + name + " ===");
        for (Shape shape : shapes) {
            shape.draw();
        }
        System.out.println("=== 结束绘制复合图形 ===");
    }
}

// 点类
class Point implements Cloneable {
    private int x;
    private int y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public void move(int dx, int dy) {
        x += dx;
        y += dy;
    }
    
    public int getX() {
        return x;
    }
    
    public int getY() {
        return y;
    }
    
    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
    
    @Override
    public Point clone() {
        try {
            return (Point) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        
        Point point = (Point) obj;
        return x == point.x && y == point.y;
    }
    
    @Override
    public int hashCode() {
        return 31 * x + y;
    }
}

// 图形管理器
class ShapeManager {
    private List<Shape> shapes = new ArrayList<>();
    
    public void addShape(Shape shape) {
        shapes.add(shape);
    }
    
    public void drawAll() {
        System.out.println("\n=== 绘制所有图形 ===");
        for (Shape shape : shapes) {
            shape.draw();
        }
    }
    
    public void showAllInfo() {
        System.out.println("\n=== 所有图形信息 ===");
        for (Shape shape : shapes) {
            System.out.printf("%s - 面积: %.2f, 周长: %.2f\n", 
                            shape.getName(), shape.getArea(), shape.getPerimeter());
        }
    }
    
    public double getTotalArea() {
        double total = 0;
        for (Shape shape : shapes) {
            total += shape.getArea();
        }
        return total;
    }
    
    // 多态处理
    public void processMovables() {
        System.out.println("\n=== 处理可移动图形 ===");
        for (Shape shape : shapes) {
            if (shape instanceof Movable) {
                ((Movable) shape).move(10, 10);
            }
        }
    }
    
    public void processScalables() {
        System.out.println("\n=== 处理可缩放图形 ===");
        for (Shape shape : shapes) {
            if (shape instanceof Scalable) {
                ((Scalable) shape).scale(1.5);
            }
        }
    }
}

// 测试类
public class GraphicsSystem {
    public static void main(String[] args) {
        // 创建图形管理器
        ShapeManager manager = new ShapeManager();
        
        // 创建各种图形
        Circle circle = new Circle("大圆", "红色", new Point(100, 100), 50);
        Rectangle rect = new Rectangle("矩形", "蓝色", new Point(200, 200), 80, 60);
        Triangle triangle = new Triangle("三角形", "绿色", new Point(300, 300), 30, 40, 50);
        
        // 创建复合图形
        CompositeShape composite = new CompositeShape("复合图形", "紫色", new Point(0, 0));
        composite.addShape(new Circle("小圆1", "黄色", new Point(10, 10), 10));
        composite.addShape(new Circle("小圆2", "橙色", new Point(30, 30), 15));
        composite.addShape(new Rectangle("小矩形", "粉色", new Point(50, 50), 20, 30));
        
        // 添加到管理器
        manager.addShape(circle);
        manager.addShape(rect);
        manager.addShape(triangle);
        manager.addShape(composite);
        
        // 显示信息
        manager.showAllInfo();
        
        // 绘制所有图形
        manager.drawAll();
        
        // 处理接口
        manager.processMovables();
        manager.processScalables();
        
        // 测试旋转
        rect.rotate(45);
        
        // 计算总面积
        System.out.printf("\n总面积: %.2f\n", manager.getTotalArea());
        
        // 测试多态
        System.out.println("\n=== 接口多态测试 ===");
        Drawable[] drawables = {circle, rect, triangle, composite};
        
        for (Drawable d : drawables) {
            d.draw();
            System.out.println(d.getDescription());
        }
        
        // 测试对象方法
        System.out.println("\n=== Object方法测试 ===");
        System.out.println("circle.toString(): " + circle);
        System.out.println("rect.toString(): " + rect);
        
        // 测试clone
        Circle circleCopy = circle.clone();
        circleCopy.setColor("黑色");
        System.out.println("\n原始圆颜色: " + circle.getColor());
        System.out.println("拷贝圆颜色: " + circleCopy.getColor());
        
        // 测试equals和hashCode
        Circle sameCircle = new Circle("大圆", "红色", new Point(100, 100), 50);
        System.out.println("\ncircle.equals(sameCircle): " + circle.equals(sameCircle));
        System.out.println("circle.hashCode() == sameCircle.hashCode(): " + 
                          (circle.hashCode() == sameCircle.hashCode()));
    }
}

关键总结

1. 抽象类要点

复制代码
// 1. 语法
abstract class AbstractClass {
    // 可以有抽象方法
    public abstract void method();
    
    // 可以有具体方法
    public void concreteMethod() {}
    
    // 可以有构造方法
    public AbstractClass() {}
    
    // 可以有成员变量
    protected int field;
}

// 2. 使用
class ConcreteClass extends AbstractClass {
    @Override
    public void method() {
        // 必须实现抽象方法
    }
}

2. 接口要点

复制代码
// 1. 语法
interface Interface {
    // 常量
    int CONSTANT = 10;
    
    // 抽象方法
    void abstractMethod();
    
    // 默认方法
    default void defaultMethod() {}
    
    // 静态方法
    static void staticMethod() {}
    
    // 私有方法(Java 9+)
    // private void privateMethod() {}
}

// 2. 实现
class Implementor implements Interface {
    @Override
    public void abstractMethod() {
        // 必须实现
    }
}

3. 选择原则

  • 用抽象类:需要共享代码,有is-a关系,需要非静态成员

  • 用接口:定义规范,有has-a/can-do关系,需要多重继承

  • 结合使用:抽象类实现接口,提供部分实现

4. Object类重要方法

复制代码
class MyClass {
    // 1. toString:返回字符串表示
    @Override
    public String toString() { return "MyClass"; }
    
    // 2. equals:比较对象内容
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        // 比较字段
        return true;
    }
    
    // 3. hashCode:必须与equals一致
    @Override
    public int hashCode() {
        return Objects.hash(field1, field2);
    }
    
    // 4. clone:创建副本
    @Override
    public MyClass clone() {
        try {
            return (MyClass) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

5. 最佳实践

  1. 接口优先:优先使用接口定义API

  2. 抽象类提供实现:用抽象类实现接口,提供通用实现

  3. 正确重写equals/hashCode:遵守契约

  4. 慎用clone:注意深浅拷贝

  5. 组合优于继承:多用接口,少用继承

String类

1. String类的重要性

概念:Java专门提供的字符串类,符合面向对象思想(数据+操作封装在一起)

注意

  • C语言使用字符数组/指针+库函数操作字符串,是面向过程的

  • String在开发、笔试、面试中都非常重要


2. 常用方法

2.1 字符串构造

常用构造方式

复制代码
// 1. 使用常量串构造(最常用)
String s1 = "hello bit";

// 2. 使用new关键字
String s2 = new String("hello bit");

// 3. 使用字符数组构造
char[] array = {'h','e','l','l','o',' ','b','i','t'};
String s3 = new String(array);

注意易错点

  • String是引用类型,存储的是字符串的引用(地址)

  • ""引起来的也是String对象

  • 查看其他构造方法参考官方文档


2.2 String对象的比较

4种比较方式

1. ==比较引用地址
复制代码
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = s1;

System.out.println(s1 == s2);  // false,不同对象
System.out.println(s1 == s3);  // true,同一对象
2. equals()按字典序比较内容
复制代码
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = new String("Hello");

System.out.println(s1.equals(s2));  // true
System.out.println(s1.equals(s3));  // false(大小写敏感)
3. compareTo()按字典序比较,返回int
复制代码
String s1 = "abc";
String s2 = "ac";
String s3 = "abcdef";

System.out.println(s1.compareTo(s2));  // -1,'b'<'c'
System.out.println(s1.compareTo(s3));  // -3,长度差
4. compareToIgnoreCase()忽略大小写比较
复制代码
String s1 = "abc";
String s2 = "ABC";
System.out.println(s1.compareToIgnoreCase(s2));  // 0

注意易错点

  • ==比较地址,equals比较内容

  • compareTo返回差值,可用于排序


2.3 字符串查找

常用查找方法

复制代码
String s = "aaabbbcccaaabbbccc";

System.out.println(s.charAt(3));           // 'b'
System.out.println(s.indexOf('c'));        // 6(第一次出现位置)
System.out.println(s.indexOf("bbb"));      // 3
System.out.println(s.indexOf('c', 10));    // 15(从指定位置开始找)
System.out.println(s.lastIndexOf('c'));    // 17(从后往前找)

注意易错点

  • indexOf找不到返回-1

  • charAt索引越界会抛出IndexOutOfBoundsException


2.4 字符串转化

1. 数值和字符串互转
复制代码
// 数字转字符串
String s1 = String.valueOf(1234);
String s2 = String.valueOf(12.34);

// 字符串转数字
int num1 = Integer.parseInt("1234");
double num2 = Double.parseDouble("12.34");
2. 大小写转换
复制代码
String s1 = "Hello";
System.out.println(s1.toUpperCase());  // "HELLO"
System.out.println(s1.toLowerCase());  // "hello"
3. 字符串与数组互转
复制代码
// 字符串转字符数组
String s = "hello";
char[] ch = s.toCharArray();

// 字符数组转字符串
char[] array = {'h','e','l','l','o'};
String s2 = new String(array);
4. 格式化
复制代码
String s = String.format("%d-%02d-%02d", 2023, 9, 5);
System.out.println(s);  // "2023-09-05"

注意易错点

  • parseInt/parseDouble可能抛出NumberFormatException

  • 格式化字符串语法类似C语言的printf


2.5 字符串替换

复制代码
String str = "helloworld";

System.out.println(str.replaceAll("l", "_"));     // "he__owor_d"
System.out.println(str.replaceFirst("l", "_"));   // "he_loworld"

注意易错点

  • String不可变,替换操作返回新字符串

  • replaceAll支持正则表达式


2.6 字符串拆分

复制代码
String str = "hello world hello bit";

// 按空格拆分
String[] result = str.split(" ");  
// ["hello", "world", "hello", "bit"]

// 限制拆分组数
String[] result2 = str.split(" ", 2);  
// ["hello", "world hello bit"]

// 特殊字符需要转义
String ip = "192.168.1.1";
String[] parts = ip.split("\\.");
// ["192", "168", "1", "1"]

注意易错点

  • .|*等特殊字符需要转义

  • \`需要写成\\`

  • 多分隔符用|连接:split(",|;")


2.7 字符串截取

复制代码
String str = "helloworld";

System.out.println(str.substring(5));      // "world"(从5到末尾)
System.out.println(str.substring(0, 5));   // "hello"([0,5)前闭后开)

注意易错点

  • 索引从0开始

  • 前闭后开区间:substring(begin, end)包含begin,不包含end


2.8 其他操作

复制代码
String str = "  hello world   ";

// 去除首尾空白
System.out.println("[" + str.trim() + "]");  // "[hello world]"

// 大小写转换(只影响字母)
System.out.println("Hello123".toLowerCase());  // "hello123"

3. 字符串的不可变性

概念:String对象一旦创建,内容不可更改

源码关键点

复制代码
public final class String {
    private final char value[];
}
  1. final class:不能被继承

  2. final char[]:value引用不能指向其他数组,但数组内容理论上可修改(实际被设计为不可修改)

代码示例

复制代码
String s = "hello";
s = s + " world";  // 创建了新对象,s指向新对象

为什么设计为不可变

  1. 便于实现字符串常量池

  2. 线程安全

  3. 缓存hash值,提高HashMap效率

注意易错点

  • 修改字符串会创建新对象,频繁修改效率低

  • final修饰引用类型:引用不能变,引用对象的内容可修改(但String内部做了限制)


4. 字符串修改的正确方式

错误方式(效率低)

复制代码
String s = "";
for (int i = 0; i < 10000; i++) {
    s += i;  // 每次循环创建新String对象
}

正确方式(使用StringBuilder/StringBuffer)

复制代码
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);  // 在原有对象上修改
}
String result = sb.toString();

5. StringBuilder和StringBuffer

5.1 常用方法

复制代码
StringBuilder sb = new StringBuilder("hello");

// 追加
sb.append(" world");      // "hello world"
sb.append(123);           // "hello world123"

// 插入
sb.insert(0, "Hi ");      // "Hi hello world123"

// 删除
sb.delete(0, 3);          // "hello world123"
sb.deleteCharAt(0);       // "ello world123"

// 替换
sb.replace(0, 4, "HELLO");  // "HELLO world123"

// 反转
sb.reverse();               // "321dlrow OLLEH"

// 转String
String str = sb.toString();

5.2 String vs StringBuilder vs StringBuffer

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全 是(天然) 是(同步方法)
性能 修改时慢 稍慢(有同步开销)
使用场景 字符串常量 单线程字符串操作 多线程字符串操作

注意易错点

  • String和StringBuilder不能直接转换:

    • String→StringBuilder:构造方法或append()

    • StringBuilder→String:toString()

  • 单线程用StringBuilder,多线程用StringBuffer


6. 字符串常量池

概念:JVM为String开辟的特殊内存区域,避免重复创建相同字符串

复制代码
String s1 = "hello";              // 在常量池创建
String s2 = "hello";              // 直接引用常量池中的对象
String s3 = new String("hello");  // 在堆中创建新对象

System.out.println(s1 == s2);     // true(同一对象)
System.out.println(s1 == s3);     // false(不同对象)

intern()方法:将字符串放入常量池或返回已有引用

复制代码
String s4 = new String("world").intern();
String s5 = "world";
System.out.println(s4 == s5);  // true

注意易错点

  • ""直接赋值使用常量池

  • new String()在堆中创建新对象

  • 大量相同字符串时,常量池可节省内存


7. 常见OJ题示例

7.1 第一个只出现一次的字符

复制代码
public int firstUniqChar(String s) {
    int[] count = new int[256];
    for (char c : s.toCharArray()) {
        count[c]++;
    }
    for (int i = 0; i < s.length(); i++) {
        if (count[s.charAt(i)] == 1) {
            return i;
        }
    }
    return -1;
}

7.2 最后一个单词的长度

复制代码
public int lengthOfLastWord(String s) {
    s = s.trim();
    return s.length() - s.lastIndexOf(' ') - 1;
}

7.3 验证回文串

复制代码
public boolean isPalindrome(String s) {
    s = s.toLowerCase();
    int left = 0, right = s.length() - 1;
    while (left < right) {
        // 跳过非字母数字字符
        while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
        while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;
        
        if (s.charAt(left) != s.charAt(right)) return false;
        left++;
        right--;
    }
    return true;
}

关键要点总结

  1. String不可变:修改操作返回新对象,原对象不变

  2. 比较字符串==比较地址,equals比较内容

  3. 效率问题:频繁修改用StringBuilder/StringBuffer

  4. 线程安全:单线程用StringBuilder,多线程用StringBuffer

  5. 常量池""直接赋值使用常量池,new String()在堆中创建

  6. 常用操作:查找、替换、拆分、截取、转换要熟练掌握

  7. 特殊字符:拆分时注意转义字符问题

异常

1. 异常概念与体系结构

1.1 异常的概念

概念:程序执行过程中发生的不正常行为

常见异常示例

复制代码
// 1. 算术异常
System.out.println(10 / 0);  // ArithmeticException: / by zero

// 2. 数组越界异常
int[] arr = {1, 2, 3};
System.out.println(arr[100]);  // ArrayIndexOutOfBoundsException

// 3. 空指针异常
int[] arr2 = null;
System.out.println(arr2.length);  // NullPointerException

注意

  • 编译错误(语法错误)不是异常

  • 异常发生在运行时


1.2 异常体系结构

复制代码
Throwable
├── Error(错误,JVM无法处理)
│   ├── StackOverflowError
│   └── OutOfMemoryError
│
└── Exception(异常,程序员可处理)
    ├── RuntimeException(运行时异常/非受查异常)
    └── 其他Exception(编译时异常/受查异常)

注意

  • Error:严重问题,程序无法恢复

  • Exception:可通过代码处理


1.3 异常的分类

1. 编译时异常(Checked Exception)
复制代码
// CloneNotSupportedException是编译时异常
@Override
public Person clone() throws CloneNotSupportedException {
    return (Person)super.clone();  // 必须处理异常
}

特点

  • 编译时检查

  • 必须处理(try-catch或throws)

2. 运行时异常(RuntimeException/Unchecked Exception)
复制代码
// 常见运行时异常
int[] arr = {1, 2, 3};
System.out.println(arr[3]);  // 运行时才抛出异常

常见运行时异常

  • NullPointerException

  • ArrayIndexOutOfBoundsException

  • ArithmeticException

  • ClassCastException

  • IllegalArgumentException

注意:RuntimeException及其子类都是运行时异常


2. 异常的处理

2.1 防御式编程

1. LBYL(事前防御)
复制代码
boolean ret = login();
if (!ret) {
    // 处理登录错误
    return;
}
ret = startMatch();
if (!ret) {
    // 处理匹配错误
    return;
}
// ...更多检查

缺点:正常流程和错误处理混在一起

2. EAFP(事后处理)
复制代码
try {
    login();
    startMatch();
    // 正常流程
} catch (LoginException e) {
    // 处理登录异常
} catch (MatchException e) {
    // 处理匹配异常
}

优点:流程清晰,Java主要采用这种方式


2.2 抛出异常(throw)

概念:主动抛出异常对象

语法

复制代码
throw new XXXException("异常原因");

示例

复制代码
public static int getElement(int[] array, int index) {
    if (array == null) {
        throw new NullPointerException("数组为null");
    }
    if (index < 0 || index >= array.length) {
        throw new ArrayIndexOutOfBoundsException("索引越界: " + index);
    }
    return array[index];
}

注意易错点

  1. throw必须在方法体内

  2. 只能抛出Throwable或其子类对象

  3. 抛出RuntimeException可不处理

  4. 抛出编译时异常必须处理

  5. throw后代码不会执行


2.3 异常声明(throws)

概念:声明方法可能抛出的异常,让调用者处理

语法

复制代码
修饰符 返回值类型 方法名(参数) throws 异常类型1, 异常类型2...

示例

复制代码
public void readFile(String filename) throws FileNotFoundException, IOException {
    if (!filename.endsWith(".txt")) {
        throw new IOException("不是文本文件");
    }
    if (!new File(filename).exists()) {
        throw new FileNotFoundException("文件不存在");
    }
    // 读取文件...
}

注意易错点

  1. 跟在参数列表后

  2. 可声明多个异常,用逗号分隔

  3. 有父子关系时,声明父类即可

  4. 调用者必须处理或继续throws

快捷键:Alt + Enter 快速处理异常


2.4 捕获异常(try-catch)

概念:捕获并处理异常

语法

复制代码
try {
    // 可能出错的代码
} catch (异常类型1 e) {
    // 处理异常1
} catch (异常类型2 e) {
    // 处理异常2
} finally {
    // 总会执行的代码
}

示例

复制代码
try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[3]);
} catch (ArrayIndexOutOfBoundsException e) {
    // 处理方式1:打印信息
    System.out.println("错误信息: " + e.getMessage());
    
    // 处理方式2:简单输出
    System.out.println(e);
    
    // 处理方式3:完整堆栈跟踪
    e.printStackTrace();
} finally {
    System.out.println("finally块总执行");
}
System.out.println("异常处理后继续执行");

处理方式

  • 让程序崩溃(严重问题,如支付错误)

  • 记录日志(一般问题,发报警给程序员)

  • 重试操作(网络问题等可恢复问题)

注意易错点

1. 异常类型不匹配
复制代码
try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[3]);  // 抛出ArrayIndexOutOfBoundsException
} catch (NullPointerException e) {  // 类型不匹配,捕获失败
    e.printStackTrace();
}
// 异常继续向外抛出,程序终止
2. 多异常处理
复制代码
// 方式1:多个catch块
try {
    // 可能抛出多种异常
} catch (ArrayIndexOutOfBoundsException e) {
    // 处理数组越界
} catch (NullPointerException e) {
    // 处理空指针
} catch (Exception e) {  // 父类放最后
    // 处理其他异常
}

// 方式2:合并catch(JDK7+)
try {
    // ...
} catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
    // 处理两种异常
}

// 错误:父类在前,子类永远执行不到
try {
    // ...
} catch (Exception e) {  // 父类
    // 这里会捕获所有异常
} catch (NullPointerException e) {  // 子类,编译错误!
    // 永远执行不到
}
3. finally的特殊情况
复制代码
public static int testFinally() {
    try {
        return 10;
    } finally {
        return 20;  // finally的return会覆盖try的return
    }
}
// 返回20,不是10!

finally面试题

复制代码
// 问题1:下面代码输出什么?
public static int test1() {
    try {
        return func();
    } finally {
        System.out.println("finally");
    }
}
// 先执行finally,再返回func()的值

// 问题2:finally一定会执行吗?
// 答:几乎总是执行,除非:
// 1. System.exit(0)退出程序
// 2. 断电、系统崩溃
// 3. 守护线程被结束

3. 异常处理流程

完整流程

  1. 执行try中代码

  2. 如果发生异常,跳转到匹配的catch

  3. 执行匹配的catch块

  4. 执行finally块

  5. 继续执行后续代码

调用栈传递

复制代码
public class Test {
    public static void main(String[] args) {
        try {
            method1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    static void method1() {
        method2();
    }
    
    static void method2() {
        int[] arr = {1, 2, 3};
        System.out.println(arr[3]);  // 异常从这里抛出
    }
}

输出堆栈跟踪

复制代码
java.lang.ArrayIndexOutOfBoundsException: 3
    at Test.method2(Test.java:18)  ← 异常发生位置
    at Test.method1(Test.java:13)  ← 调用链
    at Test.main(Test.java:7)      ← 调用链

注意:异常会沿着调用栈向上传递,直到被捕获或到达JVM


4. 自定义异常

4.1 创建自定义异常

复制代码
// 1. 继承Exception(编译时异常)
class MyCheckedException extends Exception {
    public MyCheckedException(String message) {
        super(message);
    }
    
    public MyCheckedException(String message, Throwable cause) {
        super(message, cause);
    }
}

// 2. 继承RuntimeException(运行时异常)
class MyRuntimeException extends RuntimeException {
    public MyRuntimeException(String message) {
        super(message);
    }
}

4.2 使用示例:用户登录

复制代码
// 1. 定义异常类
class UsernameException extends Exception {
    public UsernameException(String message) {
        super(message);
    }
}

class PasswordException extends Exception {
    public PasswordException(String message) {
        super(message);
    }
}

// 2. 业务类
class LoginService {
    private String correctUsername = "admin";
    private String correctPassword = "123456";
    
    public void login(String username, String password) 
            throws UsernameException, PasswordException {
        if (!correctUsername.equals(username)) {
            throw new UsernameException("用户名错误: " + username);
        }
        if (!correctPassword.equals(password)) {
            throw new PasswordException("密码错误");
        }
        System.out.println("登录成功!");
    }
}

// 3. 使用
public class Test {
    public static void main(String[] args) {
        LoginService service = new LoginService();
        
        try {
            service.login("admin", "wrong");
        } catch (UsernameException e) {
            System.out.println("用户名异常: " + e.getMessage());
        } catch (PasswordException e) {
            System.out.println("密码异常: " + e.getMessage());
        } finally {
            System.out.println("登录流程结束");
        }
    }
}

最佳实践

  1. 提供有意义的异常信息

  2. 可添加额外字段存储上下文信息

  3. 考虑异常链(cause参数)

  4. 覆盖toString()方便调试


5. 面试常见问题

1. throw vs throws

区别 throw throws
位置 方法体内 方法声明处
数量 一次抛出一个异常 可声明多个异常
作用 主动抛出异常对象 声明可能抛出的异常类型

2. finally执行时机

复制代码
public static int test() {
    try {
        System.out.println("try");
        return 1;          // 1. 计算返回值
    } finally {
        System.out.println("finally");  // 2. 执行finally
        return 2;           // 3. 覆盖返回值
    }
    // 返回2
}

3. 异常处理原则

  1. 具体明确:捕获具体异常,不要用Exception一把抓

  2. 及时处理:不要吞掉异常(空catch块)

  3. 记录日志:记录异常上下文信息

  4. 资源释放:在finally中释放资源

  5. 异常转换:底层异常转换为业务异常

4. 资源管理新方式(try-with-resources)

复制代码
// JDK7+ 自动关闭资源
try (FileInputStream fis = new FileInputStream("test.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}
// 自动调用close(),相当于在finally中关闭

关键总结

  1. 异常分类

    • Error:JVM级,无法处理

    • Exception:可处理

      • RuntimeException:运行时异常

      • 其他Exception:编译时异常

  2. 处理方式

    • throw:抛出异常

    • throws:声明异常

    • try-catch-finally:捕获处理

  3. 自定义异常

    • 继承Exception或RuntimeException

    • 提供构造方法

    • 添加业务信息

  4. 最佳实践

    • 不要忽略异常

    • 使用具体异常类型

    • 清理资源用finally或try-with-resources

    • 记录完整的异常信息

相关推荐
Paraverse_徐志斌2 小时前
针对 SAAS 私有化部署,如何优雅合并微服务
java·微服务·架构·saas·私有化
黎雁·泠崖2 小时前
Java字符串API:String/StringBuffer/StringBuilder详解
java·开发语言
山枕檀痕2 小时前
JPA Projection 详解(接口投影 / 类投影 / 动态投影 / 原生SQL映射)
java·hibernate·jpa
Jack_abu2 小时前
stream().toList()与.collect(Collectors.toList())
java·stream·jdk8
黎雁·泠崖2 小时前
Java核心API之Object类:所有类的根父类
java·开发语言
Remember_9932 小时前
【LeetCode精选算法】位运算专题
java·开发语言·jvm·后端·算法·leetcode
曹牧2 小时前
Java:代理转发配置Nginx
java·开发语言·nginx
洋不写bug2 小时前
JavaEE基础,计算机是如何工作的
java·java-ee·状态模式