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语言重要性
应用领域:
-
企业级应用:银行系统、电商平台(安全稳定)
-
Web开发:Spring框架、微服务
-
移动开发:Android应用
-
大数据:Hadoop、Spark(底层用Java)
-
云计算:云原生应用
校招需求:
企业看重(按重要性排序):
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");
}
}
每个关键字的作用:
-
public:访问修饰符,表示方法可以被任意访问
-
static:静态方法,不创建对象即可调用
-
void:返回值类型,表示没有返回值
-
main:方法名,固定名称
-
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: 这里的性能需要优化
}
}
注释规范总结:
-
类注释:说明类的职责
-
方法注释:说明功能、参数、返回值、异常
-
字段注释:说明字段用途
-
行内注释:解释复杂逻辑
-
TODO/FIXME:标记待办事项
-
避免废话:不要写"这个方法用来计算"
4. 标识符
4.1 标识符定义规则
概念:程序中用户自定义的名称(类名、方法名、变量名等)
硬性规则(必须遵守):
-
由字母、数字、下划线(_)、美元符($)组成
-
不能以数字开头
-
不能是Java关键字
-
区分大小写
代码示例:
// 合法的标识符
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);
}
}
关键要点总结
-
Java特性:跨平台、面向对象、自动内存管理、健壮安全
-
main方法 :程序入口,必须是
public static void main(String[] args) -
注释:
-
单行注释:
// -
多行注释:
/* */ -
文档注释:
/** */(生成API文档)
-
-
标识符规则:
-
字母、数字、_、$组成
-
不能数字开头
-
不能是关键字
-
区分大小写
-
-
命名规范:
-
类名:大驼峰
HelloWorld -
方法/变量:小驼峰
calculateTotal -
常量:全大写
MAX_VALUE
-
-
常见错误:
-
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、-50、0 |
没有小数点 |
| 浮点型常量 | 3.14、-0.5、2.0 |
有小数点 |
| 字符常量 | 'A'、'1'、'中' |
单引号括起来的单个字符 |
| 布尔常量 | true、false |
只有这两个值 |
| 空常量 | 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. 最佳实践
-
括号优先:复杂表达式用括号明确优先级
-
避免歧义 :不要写
a++ + ++a这样的代码 -
浮点比较 :不用
==,用误差范围比较 -
字符串比较 :用
equals(),不用== -
位运算优化:在合适场景使用位运算
-
代码可读性:三目运算符不要嵌套太深
逻辑控制
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. 最佳实践
-
使用大括号:即使只有一条语句
-
明确优先级:使用括号
-
避免深层嵌套:超过3层考虑重构
-
循环变量命名:i,j,k用于循环,意义明确
-
及时关闭资源:Scanner用完要close()
-
输入验证:先判断再读取
方法的使用
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 方法的优点
-
代码复用:一次定义,多次使用
-
模块化:复杂程序分解为小模块
-
可维护性:修改只需改一处
-
可读性:方法名描述功能,代码更清晰
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 递归基本概念
递归:方法调用自身。需要满足两个条件:
-
递归出口(终止条件)
-
递归公式(问题分解)
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. 注意事项
-
数组越界:访问前检查索引
-
空指针:使用前检查null
-
引用赋值:修改会互相影响
-
长度固定:需要变长时用ArrayList
-
多维数组:实际是数组的数组
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();
}
}
执行结果:
=== 创建子类对象 ===
父类静态代码块:父类静态变量
子类静态代码块:子类静态变量
父类实例代码块:父类实例变量
父类构造方法
子类实例代码块:子类实例变量
子类构造方法
执行顺序总结:
-
父类静态代码块和静态变量
-
子类静态代码块和静态变量
-
父类实例代码块和实例变量
-
父类构造方法
-
子类实例代码块和实例变量
-
子类构造方法
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. 最佳实践
-
单一职责:一个类只做一件事
-
封装数据:属性私有,提供公有方法
-
不可变性:尽量使用final
-
防御性编程:验证参数,处理异常
-
重写toString:方便调试
-
合理使用static:减少内存占用
-
使用构造方法链:避免代码重复
继承和多态
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有参构造:测试
初始化顺序总结:
-
父类静态成员(变量、代码块)→ 子类静态成员
-
父类实例成员(变量、代码块)→ 父类构造方法
-
子类实例成员(变量、代码块)→ 子类构造方法
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. 多态实现条件
-
继承关系:必须有父子类
-
方法重写:子类重写父类方法
-
向上转型:父类引用指向子类对象
-
动态绑定:运行时确定调用哪个方法
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. 最佳实践
-
多用组合,少用继承:组合更灵活
-
访问权限最小化:用private,必要时用protected
-
使用final:提高安全性和性能
-
避免构造方法中调用可重写方法
-
使用@Override注解:编译器检查
-
合理使用多态:提高代码扩展性
抽象类和接口
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. 最佳实践
-
接口优先:优先使用接口定义API
-
抽象类提供实现:用抽象类实现接口,提供通用实现
-
正确重写equals/hashCode:遵守契约
-
慎用clone:注意深浅拷贝
-
组合优于继承:多用接口,少用继承
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[];
}
-
final class:不能被继承 -
final char[]:value引用不能指向其他数组,但数组内容理论上可修改(实际被设计为不可修改)
代码示例:
String s = "hello";
s = s + " world"; // 创建了新对象,s指向新对象
为什么设计为不可变:
-
便于实现字符串常量池
-
线程安全
-
缓存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;
}
关键要点总结
-
String不可变:修改操作返回新对象,原对象不变
-
比较字符串 :
==比较地址,equals比较内容 -
效率问题:频繁修改用StringBuilder/StringBuffer
-
线程安全:单线程用StringBuilder,多线程用StringBuffer
-
常量池 :
""直接赋值使用常量池,new String()在堆中创建 -
常用操作:查找、替换、拆分、截取、转换要熟练掌握
-
特殊字符:拆分时注意转义字符问题
异常
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];
}
注意易错点:
-
throw必须在方法体内
-
只能抛出Throwable或其子类对象
-
抛出RuntimeException可不处理
-
抛出编译时异常必须处理
-
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("文件不存在");
}
// 读取文件...
}
注意易错点:
-
跟在参数列表后
-
可声明多个异常,用逗号分隔
-
有父子关系时,声明父类即可
-
调用者必须处理或继续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. 异常处理流程
完整流程:
-
执行try中代码
-
如果发生异常,跳转到匹配的catch
-
执行匹配的catch块
-
执行finally块
-
继续执行后续代码
调用栈传递:
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("登录流程结束");
}
}
}
最佳实践:
-
提供有意义的异常信息
-
可添加额外字段存储上下文信息
-
考虑异常链(cause参数)
-
覆盖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. 异常处理原则
-
具体明确:捕获具体异常,不要用Exception一把抓
-
及时处理:不要吞掉异常(空catch块)
-
记录日志:记录异常上下文信息
-
资源释放:在finally中释放资源
-
异常转换:底层异常转换为业务异常
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中关闭
关键总结
-
异常分类:
-
Error:JVM级,无法处理
-
Exception:可处理
-
RuntimeException:运行时异常
-
其他Exception:编译时异常
-
-
-
处理方式:
-
throw:抛出异常
-
throws:声明异常
-
try-catch-finally:捕获处理
-
-
自定义异常:
-
继承Exception或RuntimeException
-
提供构造方法
-
添加业务信息
-
-
最佳实践:
-
不要忽略异常
-
使用具体异常类型
-
清理资源用finally或try-with-resources
-
记录完整的异常信息
-