
Java核心与AI开发基础
目录
第零章 导学部分
AI 智能应用开发(Java)导学视频 —— 融合 Java 核心技术与 AI 创新能力 多一句没有,少一句不行。用更短的时间,教会更实用的技术
课程介绍
课程定位与能力要求
| 就业方向 | 岗位技术力 | 专业技能 & 项目经验 | 课程阶段 |
|---|---|---|---|
| Java+AI 初级工程师(1~2 年经验) 参与软件需求分析、负责代码模块功能实现、抽象系统能力、参加设计文档及系统能力,重大项目主导设计能力 | 6 门技术 + 整体项目经验 + 第三方方案 | 掌握 Java 核心语法、集合、多线程、IO/NIO、掌握 JVM 内存模型与 GC 机制、熟练使用 Spring Boot + MyBatis 开发 Web 应用、掌握分布式技术架构设计、熟悉 Kafka/MQ/Redis 中间件、掌握 MySQL/MongoDB 分布式存储与读写分离、掌握 Java 集合综合使用…… | 第一阶段:Java 核心与 AI 开发基础 第二阶段:Java 核心与 AI 开发进阶 |
| Java+AI 中级工程师(3~5 年经验) 参与技术方案选型与设计、解决疑难问题、调优与扩展能力、参与重大项目主导设计能力 | 第三方技术 + 分布式项目经验 + AI 智能体应用开发 | 掌握微服务架构、Spring Cloud(Nacos/Sentinel/Seata)、微服务分布式事务、RabbitMQ 消息中间件、Redisson 分布式锁;熟练使用 Java 集合高级 API、Stream API、NIO、Netty;掌握 JVM 调优、SQL 调优;Redis 高级优化等技术;微服务高可用技术;分布式系统架构设计;微服务架构设计 | 第三阶段:AI 智能 Web 开发 第四阶段:企业级智能体项目(神州养车) 第五阶段:微服务 SCA 第六阶段:互联网分布式微服务项目(云岚到家) 第八阶段:沉浸式项目实战(神州养车) 第九阶段:Java AI 智能体应用开发 第十阶段:BAT 大厂 360° 测试开发 / 就业指导 |
| 能够清晰地表达自己的技术能力和项目经验 | — | — | — |
| 构建「Java 核心 + AI 创新」复合竞争力 | — | 掌握核心概念和技术,深入理解大模型智能体技术概念,包括提示词数据微调(Prompt Tuning)和特征改进微调(PET/Tuna)开源模型应用技术 | 雷达拓展课:分布式项目集 雷达拓展课:AI 大模型开发实战 |
课程总目标与时长
| 总目标 | 学习时长 | 课程阶段 | 任务包 | 学习周期 |
|---|---|---|---|---|
| 通过系统学习和实战项目,达到企业招聘需求,实现高薪就业 | 每周 ≥ 24 小时 (视频时长 12 小时,练习时长 12 小时) | 第一阶段:Java 核心与 AI 开发基础 | 19 | 3 周(约 72 小时) |
| 第二阶段:Java 核心与 AI 开发进阶 | 12 | 2 周+(约 56 小时) | ||
| 第三阶段:AI 智能 Web 开发 | 18 | 7 周(约 168 小时) | ||
| 第四阶段:企业级智能体项目(神州养车) | 15 | 4 周(约 96 小时) | ||
| 第五阶段:微服务 SCA | 15 | 3 周(约 72 小时) | ||
| 第六阶段:互联网分布式微服务项目(云岚到家) | 14 | 4 周(约 96 小时) |
学习计划
什么是任务包?
任务包是老师为了帮助我们学好 Java 开发技能,将各阶段内容带分出的具体学习单元。每个任务包围绕学习目标展开,包含一系列具体的学习材料(视频、练习题等)以及预估学习时长,助力我们逐步达成学习目标,扎实吸收知识,实现高效学习。
任务包组成
| 维度 | 组成 | 详细说明 |
|---|---|---|
| 学习目标(技能点) | 学习目标 | 每个任务包都有明确的学习目标,结合具体技能点。告诉你学完后要达到什么水平。例如:「Java 开发环境搭建」任务包中有三个技能点。 |
| 技能点掌握要求 | 技能点 1:JDK 的下载和安装 技能点 2:配置环境变量、path 环境变量配置 技能点 3:使用 IDE 编写并运行 Java 代码 要求:不仅要看得懂,还要弄清楚运行环境中的概念,能够跟别人讲解自己写下来 举例 2:使用 IDEA 编写并运行 Java 代码 备注:通过实际操作,比如编写和运行简单的 Java 程序,确保能灵活运用这些知识解决实际问题。 | |
| 学习材料 | 视频教程 | 详细讲解每个技能点,帮助你理解和掌握知识。 |
| 知识总结 | 通过思维导图,快速复习所学技能点。 | |
| 练习题 | 通过实际操作巩固知识,提升应用能力。 | |
| 质量验收 | 练习题 | 每个任务包都有练习题,帮助我们巩固所学知识。 |
| 章节测评 | 每个关键章节结束后,有测评来检查学习进度。 | |
| 阶段考试 | 每个阶段结束后,老师会和我们对一对面试,检查编写能力;沟通表达能力。确保真正掌握了知识。 | |
| 时间规划 | 任务包总时长 | 明确视频学习、练习和测评的预估时长,帮助我们合理安排时间。 |
| 周计划 | 根据每周可投入的学习时间,合理分配任务包数量,确保学习进度和质量。 |
任务包能带来什么好处?
| 维度 | 好处 |
|---|---|
| 明确学习目标 | 每个任务包都有清晰的目标,结合具体的技能点和掌握要求,让我们清楚地知道要达成什么。集中精力在关键知识点上,提高学习效率。 |
| 系统化学习路径 | 通过分步骤的任务包,循序渐进地完成完整的学习体系,帮助我们更好地理解和掌握复杂内容。 |
| 高效时间管理 | 提供预估的学习时长和周计划,帮助我们合理安排时间,平衡学习与生活。 |
| 提升学习动力 | 每完成一个任务包,我们都能获得成就感,激发和保持热情,让我们设定确保每个阶段都有反馈和奖励。 |
| 易于跟踪进度 | 通过练习题、章节测评和阶段测试,清晰跟踪学习进度,及时调整学习计划,确保始终在正确的轨道上前进。 |
学习平台:博学谷
线上学习灵活,每日任务量可动态调整,但要确保周计划整体完成。 平台提供日历视图,每日展示具体任务(前端学科就业班、Java 后端开发、20 天下载安装、学习阶段汇总、开发 Java 程序等),支持打卡、查看学习路线。
学习方法
flowchart LR
S1["01 STEP 多动手实操"] --> S2["02 STEP 保持连贯性"] --> S3["03 STEP 学会总结"]学习 Java 的三大步骤:多动手实操 → 保持连贯性 → 学会总结
学后测评
| 测评类型 | 用途 |
|---|---|
| 任务包练习 | 巩固任务包所学知识;编程题无需上传代码,提交后即可查看参考答案 |
| 章节测评 | 关键章节结束后的阶段检查 |
| 阶段考试 | 阶段性的综合考试,含面试问答 |
任务包练习示例(Java 核心与 AI 开发基础-任务包一[开发环境搭建]):在 IntelliJ IDEA 中完成以下操作,创建一个 Java 工程并实现一个简单的打印功能。
①创建工程:打开 IntelliJ IDEA,创建一个名为 JavaSePractices 的空工程。 ②创建模块、包、类:在 JavaSePractices 工程中,创建一个名为 chapter01 的模块。在 chapter01 模块下,创建一个名为 com.buxuegu.demo01 的包。在 com.buxuegu.demo01 包中,创建一个名为 Test01 的类。 ③编写代码:在 Test01 类中,使用 main 方法打印
hello world的控制台。 ④运行程序:运行 Test01 类,确保控制台输出以下内容:hello world
Java 的应用领域
Java 语言拥有 27 年发展历史,长盛不衰,是大型软件的服务端开发首选语言。
| 类别 | 典型应用 |
|---|---|
| 互联网应用 | 抖音、京东、快手、QQ、淘宝、美团、微信、今日头条、钉钉 |
| 资讯社区 | 知乎、豆瓣、CSDN、猫眼、中国知网 |
| 游戏 / 工具 | 任天堂 |
Java+AI 工程师的核心能力
Java 核心功夫是王道 —— 不管有没有 AI,写好 Java 代码都是根本!就像学武功,马步得扎稳。你写的 Java 代码就是指挥棒!代码写得好,逻辑清晰,AI 助手才能更好地执行任务,整个系统才稳定可靠。
地基四要素
flowchart LR
Base[("地基 Java 核心功夫")]
Data["数据感"]
Learn["学习力"]
Connect["对接 AI"]
Business["业务眼"]
Base --- Data
Base --- Learn
Base --- Connect
Base --- Business| 要素 | 含义 | Java 中的体现 |
|---|---|---|
| 数据感 | 当好 AI 的原料整理师,把「乱糟糟」变成「整齐齐」 | Java 程序收集用户对商品的评价文字 |
| 学习力 | 拥抱变化的心态 | 持续学习新框架、新工具 |
| 对接 AI | 学会调用 AI「外挂」 | 在 Java 程序里方便地「调用」现成的 AI 功能 |
| 业务眼 | 知道用 AI 解决什么问题 | 区分哪些业务用传统代码、哪些用 AI 服务 |
对接 AI 案例
| 场景 | Java 代码负责 | AI 服务负责 |
|---|---|---|
| 评论情感分析 | 把一段用户评论发给 AI 服务 | 告诉评论的情感是正面还是负面的 |
| 图像识别 | 把一张图片传给 AI 服务 | 识别图片里有什么物体 |
业务眼 — AI 落地的典型场景
| 业务领域 | 描述 |
|---|---|
| 客服系统 | 自动回复、智能问答 |
| 图像与视频分析 | 内容理解、目标检测 |
| 金融交易 | 风控、量化决策 |
| 保险理赔 | 智能核损、定损 |
4 步能力成长路径
flowchart LR
Step1["01 扎扎实实学好 Java 编程基础 入门期间,代码需要自己动手编写 不能依赖 AI 工具"] --> Step2["02 数据意识 收集数据,整理数据,传递数据"]
Step2 --> Step3["03 对接 AI 开发期间,可以调用现成的、强大的 AI 功能 让程序更智能"]
Step3 --> Step4["04 业务观察力、持续学习力 分辨出哪些业务需要传统代码实现 哪些业务可以调用 AI 服务"]课程总览(第一、第二阶段)
| 阶段 | 章节 | 章节名 |
|---|---|---|
| 第一阶段 | 第一章 | Java 开发环境搭建 |
| 第二章 | 基础语法 | |
| 第三章 | 流程控制语句 | |
| 第四章 | 数组 | |
| 第五章 | 面向对象基础 | |
| 第六章 | 面向对象高级 | |
| 第七章 | 常用 API | |
| 第八章 | 综合案例 | |
| 第二阶段 | 第一章 | 集合高级 |
| 第二章 | 异常 | |
| 第三章 | File 类 | |
| 第四章 | 常用 API IO 流 | |
| 第五章 | 多线程 | |
| 第六章 | 网络编程 | |
| 第七章 | Java 高级技术 |
第一章 开发环境搭建
培养 AI 新时代 Java 工程师
Java 背景介绍
Java 语言的历史
Java 是由 James Gosling(詹姆斯·高斯林)于 1995 年在 Sun 公司开发的计算机高级编程语言,在 2009 年被 Oracle 公司收购。
James Gosling —— Java 之父
| 项目 | 内容 |
|---|---|
| 出生 | 1955 年出生于加拿大 |
| 学士 | 1977 年获得卡尔加里大学计算机学士学位 |
| 博士 | 1983 年获得卡内基梅隆大学计算机博士学位 |
| 称号 | Java 之父 |
Java 的应用领域
| 方向 | 典型应用 | 说明 |
|---|---|---|
| 桌面应用开发 | IDEA、Eclipse | 集成开发工具 |
| 企业级应用开发 | 微服务、大型互联网应用 | Java 的主战场 |
| 移动应用开发 | Android | Android 原生应用 |
| 服务器系统 | 应用的后台 | 服务端开发 |
| 大数据开发 | hadoop | 大数据生态 |
| 游戏开发 | 我的世界 MineCraft | 游戏服务端 |
Java 的三大技术平台
| 平台 | 全称 | 中文 | 定位 |
|---|---|---|---|
| JavaSE | Java Standard Edition | 标准版 | Java 技术的核心和基础 |
| JavaEE | Java Enterprise Edition | 企业版 | 企业级应用开发的一套解决方案 |
| JavaME | Java Micro Edition | 小型版 | 针对移动设备应用的解决方案 |
本节复习
| 问题 | 答案 |
|---|---|
| Java 是什么? | 一门高级编程语言 |
| Java 是哪家公司的?现在属于哪家公司? | Sun 公司、Oracle 公司 |
| Java 之父是谁? | 詹姆斯·高斯林 |
| Java 能做什么? | 基本上什么都可以干,主要做企业服务端开发 |
| Java 有哪三大使用平台? | JavaSE(标准版)、JavaEE(企业版)、JavaME(小型版) |
JDK 工具的下载和安装
JDK 简介
开发 Java 程序必须先安装好 JDK(Java Development Kit),也就是 Java 开发工具包。
LTS 版本(Long-Term Support):JDK 8、JDK 11、JDK 17、JDK 21。很多企业还在使用 JDK 8 / JDK 11。
下载步骤
在 Oracle 的官网可以找到 JDK 的下载链接:Java Downloads | Oracle 中国
| 步骤 | 操作 |
|---|---|
| ① | 选择 JDK 版本(推荐 JDK 17) |
| ② | 选择操作系统版本(如 Windows) |
| ③ | 选择安装包类型(如 x64 Installer) |
安装验证
安装完成后,通过 CMD 命令行测试是否安装成功:
| 步骤 | 操作 |
|---|---|
| ① | 按下键盘上的 Win + R 键,会打开一个运行窗口 |
| ② | 在窗口中输入 cmd,然后按下 Enter 键,弹出 DOS 窗口 |
| ③ | 在 DOS 窗口输入命令,测试 Java 工具及其版本是否正确 |
java -versionjavac -versionJDK bin 目录核心工具
| 工具 | 类型 | 作用 |
|---|---|---|
java.exe | 执行工具 | 启动 JVM 运行 class 文件 |
javac.exe | 编译工具 | 将 .java 源文件翻译成 .class 字节码 |
javadoc.exe | 文档工具 | 生成 API 文档 |
我们写好的 Java 程序都是高级语言,计算机底层是硬件,不能识别这些语言,必须先通过 javac 编译工具进行翻译,然后再通过 java 执行工具执行才可以驱动机器干活。
JDK 的组成
flowchart TB
subgraph JDK["JDK(Java Development Kit) Java 开发工具包"]
direction TB
subgraph JRE["JRE(Java Runtime Environment) Java 运行环境"]
direction TB
JVM["JVM(Java Virtual Machine) Java 虚拟机 真正运行 Java 程序的地方"]
CoreLib["核心类库 Java 自己写好的程序 给程序员自己的程序调用"]
JVM --- CoreLib
end
DevTools["开发工具 java、javac..."]
end| 组件 | 含义 | 角色 |
|---|---|---|
| JVM | Java Virtual Machine,Java 虚拟机 | 真正运行 Java 程序的地方 |
| 核心类库 | Java 自己写好的程序 | 给程序员自己的程序调用 |
| JRE | Java Runtime Environment,Java 运行环境 | JVM + 核心类库 |
| JDK | Java Development Kit,Java 开发工具包 | JRE + 开发工具(含 java、javac 等) |
DOS 常用命令
| 常用命令 | 作用 |
|---|---|
盘符: | 切换盘符:D:、E: |
dir | 查看当前路径下的文件信息 |
cd | 进入单级目录:cd itheima / 回退到上一级目录:cd .. / 回退到盘符根目录:cd / |
cls | 清屏 |
tab | 自动补全指定字符开头的单词 |
↑ 键 | 查看历史命令 |
↓ 键 | 查看历史命令 |
exit | 退出 |
本节复习
| 问题 | 答案 |
|---|---|
| 要使用 Java,必须先安装什么?去哪里下载? | JDK(Java Development Kit)开发者工具包;Oracle 官网 |
| LTS 版本有哪些?很多企业还在使用哪个 JDK 版本? | JDK 8、11、17、21;很多企业还在使用 JDK 8 / JDK 11 |
| 如何验证 JDK 是否安装成功了? | 打开命令行窗口,输入 java -version、javac -version 看版本号 |
| JDK 中最重要的 2 个命令行程序是什么?各自的作用是? | javac、java;编译工具、执行工具 |
第一个 Java 程序 Hello World
Java 程序的开发步骤
flowchart LR
A["① 编写代码 HelloWorld.java 源代码文件"] --> B["② 编译代码 javac HelloWorld.java → 生成 HelloWorld.class"]
B --> C["③ 运行代码 java HelloWorld → 输出 Hello World"]| 步骤 | 命令 | 说明 |
|---|---|---|
| ① 编写代码 | — | 用记事本等编辑器写 HelloWorld.java |
| ② 编译代码 | javac HelloWorld.java | 生成 HelloWorld.class 字节码文件 |
| ③ 运行代码 | java HelloWorld | JVM 执行 class,输出 Hello World |
D:\code>javac HelloWorld.javaD:\code>java HelloWorldHello WorldHelloWorld 代码解析
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World !"); }}| 元素 | 含义 |
|---|---|
public class HelloWorld | class 定义一个类,后面跟上的是类名称;public 是权限修饰符(暂时理解:限制类名称和文件名称需要保持一致) |
public static void main(String[] args) | 程序执行时的入口点,main 方法也称之为主方法 |
System.out.println("Hello World !") | 打印语句,使程序在控制台打印双引号所包裹的内容 |
Java 程序执行原理
机器语言与硬件
计算机底层都是硬件电路,可以通过不通电和通电,表示 0、1。 机器语言:由 0、1 组成,如
00011100 00110101。
编程语言发展历程
| 阶段 | 特点 |
|---|---|
| 机器语言 | 由 0/1 组成,计算机直接识别,难读难写 |
| 汇编语言 | 用助记符代替 0/1,仍是低级语言 |
| 高级语言 | 接近人类语言,需翻译成机器语言才能执行 |
高级语言更简单,使用接近人类自己的语言书写代码,再将其翻译成计算机能理解的机器指令。
Java 的跨平台原理
flowchart LR
Java["Java 源代码"] --> Class[".class 字节码"]
Class --> Win["Windows JVM 虚拟机"]
Class --> Linux["Linux JVM 虚拟机"]
Class --> Mac["MacOS JVM 虚拟机"]总结:在需要运行 Java 应用程序的操作系统上,安装一个与操作系统对应的 Java 虚拟机(JVM,Java Virtual Machine) 即可。
本节复习
| 问题 | 答案 |
|---|---|
| Java 程序的执行原理是什么样的? | 不管是什么样高级编程语言,最终要翻译成计算机底层可以识别的机器语言 |
| 机器语言是由什么组成的? | 0 和 1 |
| Java 跨平台指的是什么?原理又是什么? | Java 程序可以在任意操作系统中运行;在不同的操作系统中,安装与之对应版本的 JVM 虚拟机 |
Java 环境配置
Path 环境变量
Path 环境变量用于记住程序路径,方便在命令行窗口的任意目录启动程序。
示例:QQ.exe 在 D:\App\QQ 目录下,从 D: 盘 >QQ、C: 盘 >qq、E: 盘 >qq 都能启动。
Path 环境变量位置:此电脑 → 属性 → 高级系统设置 → 高级 → 环境变量
Path 环境变量的原理:当我们在 Path 中配置某个程序路径后,启动命令行窗口启动程序时,系统会按 Path 中配置的目录顺序查找该程序。
JDK 的环境变量配置
较新版本的 JDK 安装时会自动配置
javac、java程序的路径到 Path 环境变量中去,因此,javac、java可以直接使用。注意:以前的老版本的 JDK 在安装时是没有自动配置 Path 环境变量的,此时需要自己配置 Path 环境变量。
Path 值示例:E:\develop\Java\jdk21\bin(本机路径,不要抄,去复制)
重新配置了环境变量后,必须检测是否配置成功:打开命令行窗口,输入
java -version分别看版本提示。
JAVA_HOME:告诉操作系统 JDK 安装在了哪个位置(将来其他技术要通过这个环境变量找 JDK)。
本节复习
| 问题 | 答案 |
|---|---|
| 什么是 Path 环境变量? | 用于配置程序的路径,方便我们在命令行窗口的任意目录下启动该程序 |
| JDK 安装时,关于环境变量的配置,需要注意什么? | 较新版本的 JDK 会自动配置 PATH 环境变量,较老的 JDK 版本则不会 |
| 建议如何处理? | 建议还是自己配置一下「PATH」、「JAVA_HOME」 |
第二章 IDEA 开发工具
工欲善其事必先利其器
IDEA 介绍和安装
IDE 集成环境
IDE 集成环境:把代码编写,编译,执行,调试等多种功能综合到一起的开发工具。
主流 Java 开发工具
| 工具 | 厂商 | 特点 |
|---|---|---|
| IntelliJ IDEA | JetBrains | 目前使用最多的 Java 开发集成环境 |
| NetBeans | Oracle | 开源 |
| Eclipse | IBM 开源社区 | 老牌 IDE |
官方网站:IntelliJ IDEA – the Leading Java and Kotlin IDE (jetbrains.com)
本节小结
| 关键点 | 内容 |
|---|---|
| IDEA 全称 | IntelliJ IDEA,是目前使用最多的 Java 开发集成环境 |
| IDE 集成环境 | 把代码的编写、编译、运行、调试等多种功能综合到一起的开发工具 |
IDEA 中的第一个代码
IDEA 创建 Java 项目的代码结构
flowchart LR
P["Project 工程"] --> M["Module 模块"]
M --> Pa["Package 包"]
Pa --> C["Class 类"]| 层级 | 含义 | 作用 |
|---|---|---|
| Project | 工程 | 一个项目下可以管理多个模块 |
| Module | 模块 | 独立的业务单元(如商品模块、订单模块) |
| Package | 包 | 组织类,避免类名冲突 |
| Class | 类 | 实际编写代码的地方 |
模块化项目示例
flowchart TB
subgraph Project["Project 项目"]
M1["商品模块"]
M2["搜索模块"]
M3["订单模块"]
M4["购物车模块"]
end模块内部代码结构
每个模块(Module)内部通常按职责划分为:
| 代码类别 | 作用 |
|---|---|
| 前端交互代码 | 接收用户请求、返回响应数据 |
| 业务代码 | 处理业务逻辑(如商品查询、下单) |
| 数据访问代码 | 与数据库交互(增删改查) |
学习中的目录结构
整个基础篇创建一个 Project,今后每一天都创建一个新的 Module
BasicCodes/ ← Project├── .idea/├── day01/ ← Module(第 1 天)├── day02/ ← Module(第 2 天)├── day03/ ← Module(第 3 天)├── BasicCodes.iml├── External Libraries└── Scratches and ConsolesIDEA 的使用操作
常用操作
| 操作类别 | 操作项 |
|---|---|
| 类文件 | 新建类文件、删除类文件、修改类文件 |
| 模块 | 新建模块、删除模块、修改模块、导入模块 |
| 项目 | 关闭项目、打开项目、修改项目、新建项目 |
常用快捷键
| 快捷键 | 功能 |
|---|---|
psvm + 回车 | 快速生成 main 方法 |
sout + 回车 | 快速生成输出语句 |
Ctrl + Alt + L | 格式化代码 |
Alt + Shift + ↑ | 上移当前行 |
Alt + Shift + ↓ | 下移当前行 |
Alt + 回车 | 导入包,自动修正 |
Ctrl + N | 查找类 |
Ctrl + R | 替换文本 |
Ctrl + F | 查找文本 |
Shift + F6 | 重构-重命名 |
Ctrl + X | 剪切当前行 |
Ctrl + D | 复制行 |
Ctrl + / | 批量加入单行注释,再按一次就是取消 |
Ctrl + Shift + / | 批量加入多行注释,再按一次就是取消 |
Alt + 1 | 快速打开或隐藏工程面板 |
第三章 Java 基础语法
根基稳,万事可成
注释
什么是注释
注释是写在程序中对代码进行解释说明的文字,方便自己和其他人查看,以便理解程序的。
Java 注释的三种写法
| 注释类型 | 写法格式 | 特点 |
|---|---|---|
| 单行注释 | // 注释内容 | 只能写一行 |
| 多行注释 | /* 注释内容1<br>注释内容2 */ | 可以写多行 |
| 文档注释 | /** 注释内容<br>注释内容 */ | 可以生成 API 文档 |
/** * 通过class关键字定义了一个类,类名叫做 HelloWorld * public 起到限制作用,限制文件名和类名保持一致 */public class HelloWorld { public static void main(String[] args) { // 这是一个单行注释,有效范围是从 // 开始到当前行结尾 System.out.println("HelloWorld");
/* 这是一个 多行注释 */ System.out.println("HelloWorld"); }}注意事项:注释内容不会参与编译和运行
本节小结
| 问题 | 答案 |
|---|---|
| 注释是什么? | 写在程序中对程序进行解释说明的文字 |
| Java 程序中书写注释的方式有几种,各自有什么不同? | 单行注释://多行注释: /* */文档注释: /** */ |
| 注释有什么特点?为什么? | 不影响程序的执行,编译后的 class 文件中已经没有注释了 |
关键字
关键字介绍
关键字:被 Java 赋予了特定涵义的英文单词。
public class HelloWorld { public static void main(String[] args) { System.out.println("HelloWorld"); }}例如:
class表示”创建类”。
注意事项:Java 中的关键字,已经被赋予了特殊的涵义,这些单词不允许使用。
Java 关键字大全
| 分类 | 关键字 |
|---|---|
| 访问控制 | private protected public |
| 类/方法/变量修饰符 | 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 |
| 包相关 | import package |
| 数据类型 | boolean byte char double float int long short void |
| 错误处理 | assert catch finally throw throws try |
| 其他 | const enum goto super this |
字面量
字面量介绍
字面量:就是程序中能直接书写的数据,学这个知识的重点是:搞清楚 Java 程序中数据的书写格式。
| 常用数据 | 生活中的写法 | 程序中的写法 | 说明 |
|---|---|---|---|
| 整数 | 666、-88 | 666、-88 | 写法一致 |
| 小数 | 13.14、-5.21 | 13.14、-5.21 | 写法一致 |
| 字符串 | 黑马程序员 | "HelloWorld"、"黑马程序员" | 程序中必须使用双引号 |
| 字符 | A、0、我 | 'A'、'0'、'我' | 程序中必须使用单引号,有且仅能一个字符 |
| 布尔值 | 真、假 | true、false | 只有两个值:true 代表真,false 代表假 |
| 空值 | — | 值是:null | 一个特殊的值,空值(后面会讲解作用,暂时不管) |
字面量练习
需求:请将自己的个人信息打印在控制台(姓名,年龄,性别,身高,婚姻状况)
public class Main { public static void main(String[] args) { System.out.println("张三"); System.out.println(23); System.out.println('男'); System.out.println(180.1); System.out.println(false); }}张三23男180.1false
Process finished with exit code 0本节小结
| 问题 | 答案 |
|---|---|
| 关键字指的是什么? | 被 Java 赋予了特定含义的英文单词 |
| 关键字我们能使用吗? | 不允许使用 |
| 字面量这个知识是要求同学们学会什么? | 数据在程序中的书写格式 |
| 字符、字符串在程序中的书写格式有什么要求? | 字符必须单引号围起来,有且仅能一个字符 字符串必须用双引号围起来 |
| 几个常见的特殊值的书写格式是? | true、false、null |
变量
变量的介绍和定义格式
变量就是内存中的一块区域,可以理解成一个盒子,用来装程序要处理的数据的。
flowchart LR
A["数据类型 变量名称 = 变量值;"] --> B["自己起的名字"]
A --> C["赋值"]
A --> D["限制盒子中只能存储某种数据形式 例如:int(整数类型)、double(小数类型)"]| 部分 | 含义 |
|---|---|
| 数据类型 | 限制盒子中只能存储某种数据形式 |
| 变量名称 | 自己起的名字 |
| = | 赋值(将右边的值赋给左边的变量) |
| 变量值 | 实际存储的数据 |
示例:int age = 18;
认识变量
变量有啥特点?变量里装的数据是可以被替换的。
int age = 18;age = 19; // 把 18 替换成 19
System.out.println(age); // 输出 19age = age + 1; // 在 19 的基础上 + 1,变成 20
System.out.println(age); // 输出 20变量有啥应用场景呢?写程序对数据进行处理就很方便了。
本节小结
| 问题 | 答案 |
|---|---|
| 变量是什么,变量的完整定义格式是什么样的? | 变量是内存中的一块区域,可以理解成盒子,用来记住成程序要处理的数据的。 数据类型 变量名称 = 数据; |
| 为啥要用变量,变量有啥好处? | 使用变量记要处理的数据,编写的代码更灵活,管理代码更方便 |
| 变量有什么特点?基于这个特点,变量有啥应用场景? | 变量里装的数据可以被替换 |
变量的注意事项
| 序号 | 注意事项 |
|---|---|
| 1 | 变量要先声明才能使用 |
| 2 | 变量是什么类型,就必须装什么类型的数据 |
| 3 | 变量只在自己所归属的 {} 范围内有效 |
| 4 | 同一个范围内,变量的名称不能一样 |
| 5 | 变量定义的时候可以不赋初始值,但在使用时,变量里必须有值 |
| 6 | 一条语句可以定义多个变量,中间使用逗号分隔 |
标识符
标识符概述
标识符:就是给类,方法,变量等起名字的符号。
public class Demo {
public static void main(String[] args) {
int salary = 12000; System.out.println(salary);
salary = 15000; System.out.println(salary);
int age = 18; System.out.println(age); }}标识符命名规则
硬性要求(不满足则编译报错)
| 规则 | 说明 |
|---|---|
| 组成字符 | 由数字、字母、下划线(_)和美元符($)组成 |
| 开头 | 不能以数字开头 |
| 关键字 | 不能是关键字 |
| 大小写 | 区分大小写 |
判断下列变量名哪些不符合规则(红色 = 不符合):
| 变量名 | 是否符合 | 原因 |
|---|---|---|
bj | ✅ | 符合 |
b2 | ✅ | 符合 |
2b | ❌ | 数字开头 |
class | ❌ | 是关键字 |
_2b | ✅ | 符合 |
#itheima | ❌ | 包含非法字符 # |
ak47 | ✅ | 符合 |
Class | ✅ | 符合(首字母大写但不是关键字) |
helloworld | ✅ | 符合 |
标识符命名规范
软性建议(不满足不报错,但建议遵守)
| 命名法 | 适用场景 | 单单词 | 多单词 |
|---|---|---|---|
| 小驼峰命名法 | 变量、方法 | 所有字母小写:name | 从第二个单词开始,首字母大写:firstName |
| 大驼峰命名法 | 类 | 首字母大写:Student | 每个单词的首字母大写:GoodStudent |
本节小结
| 问题 | 答案 |
|---|---|
| 什么是标识符? | 标识符就是名字 |
| 标识符的规则? | 由数字、字母、下划线、美元符等组成;且不能数字开头,不能用关键字,不能用特殊符号(&、%) |
| 命名建议? | 见名知义,驼峰命名 |
数据类型
数据类型分类
flowchart LR
A["数据类型"] --> B["基本数据类型"]
A --> C["引用数据类型"]八种基本数据类型
| 数据类型 | 关键字 | 取值范围 | 内存占用 |
|---|---|---|---|
| 整数 | byte | -128 ~ 127 | 1 |
| 整数 | short | -32768 ~ 32767 | 2 |
| 整数 | int(默认) | -2147483648 ~ 2147483647(10 位数) | 4 |
| 整数 | long | -9223372036854775808 ~ 9223372036854775807(19 位数) | 8 |
| 浮点数 | float | 1.401298e-45 到 3.402823e+38 | 4 |
| 浮点数 | double(默认) | 4.9000000e-324 到 1.797693e+308 | 8 |
| 字符 | char | 0 ~ 65535 | 2 |
| 布尔 | boolean | true、false | 1 |
说明:
e+38表示是乘以 10 的 38 次方;同样,e-45表示乘以 10 的负 45 次方。
public class Test { public static void main(String[] args) { System.out.println(10); // 整数默认 int 类型 System.out.println(12.3); // 小数默认 double 类型 }}编码表与 ASCII 码表
编码表:是计算机中字节到字符的一套对应关系。
ASCII 码表:American Standard Code for Information Interchange,美国信息交换标准代码。
| 项目 | 内容 |
|---|---|
数字 0 | 对应 ASCII 码 48 |
大写字母 A | 对应 ASCII 码 65 |
小写字母 a | 对应 ASCII 码 97 |
注:表中的 ASCII 字符可以用
ALT + 小键盘上的数字键输入。
数据类型案例
需求:请将自己的个人信息,使用变量保存,并展示在控制台。
| 字段 | 示例值 | 数据类型 |
|---|---|---|
| 姓名 | 黑马谢广坤 | String |
| 年龄 | 18 | int |
| 性别 | 男 | char |
| 身高 | 180.1 | double |
| 是否单身 | 是 | boolean |
本节小结
| 问题 | 答案 |
|---|---|
| Java 中的数据类型大体分为两类,分别是? | 基本数据类型、引用数据类型 |
| 整数类型首选?小数类型首选? | int、double |
| 定义字符串类型的变量,类型选的是? | String |
| 所有整数默认是?小数默认是? | 整数默认 int;小数默认 double |
char 类型的变量,可以接收整数类型的数据吗? | 可以(char 取值范围 0 ~ 65535,对应 ASCII 码) |
第四章 运算符
对变量和字面量操作的符号
算术运算符
运算符分类
| 符号 | 作用 | 说明 |
|---|---|---|
+ | 加 | 参看小学一年级 |
- | 减 | 参看小学一年级 |
* | 乘 | 参看小学二年级,与 ”×” 相同 |
/ | 除 | 参看小学二年级,与 ”÷” 相同 |
% | 取余 | 获取的是两个数据做除法的余数 |
/和%的区别:两个数据做除法,/取结果的商,%取结果的余数。整数操作只能得到整数,要想得到小数,必须有浮点数参与运算。
案例:数值拆分
需求:将数字 123 拆分出个位、十位、百位后,打印在控制台
整数123的个位为:3整数123的十位为:2整数123的百位为:1| 拆分位 | 公式 | 计算过程 |
|---|---|---|
| 个位 | 数值 % 10 | 123 除以 10(商 12,余数为 3) |
| 十位 | 数值 / 10 % 10 | 123 除以 10(商 12),12 除以 10(商 1,余数为 2) |
| 百位 | 数值 / 10 / 10 % 10 | 123 / 10 得到 12,12 / 10 得到 1,1 % 10 得到 1 |
公式总结:
- 个位:数值
% 10- 十位:数值
/ 10 % 10- 百位:数值
/ 10 / 10 % 10- 千位:数值
/ 10 / 10 / 10 % 10- ……
本节小结
| 问题 | 答案 |
|---|---|
| 整数相除可以直接得到小数结果吗?如果不可以该怎么解决? | 不可以,需要加入小数运算 |
| 假设给定一个五位数 65536,获取千位的计算公式是? | 数值 / 10 / 10 / 10 % 10 |
字符串拼接
当
+操作中,遇到了字符串,这时+就是字符串连接符,而不是算术运算。
public class Test { public static void main(String[] args) { System.out.println(1 + 23); // 24 System.out.println("年龄为:" + 23); // 年龄为:23 System.out.println(1 + 99 + "年黑马"); // 100年黑马 }}说明:从左到右依次运算,遇到字符串后,所有后续
+都变为字符串拼接。
public class Test { public static void main(String[] args) { System.out.println("年龄为:" + 23 + 1); // 年龄为:231 }}计算过程:
"年龄为:" + 23→"年龄为:23""年龄为:23" + 1→"年龄为:231"
解决方案:使用小括号改变运算优先级。
public class Test { public static void main(String[] args) { System.out.println("年龄为:" + (23 + 1)); // 年龄为:24 }}当
+号操作中遇到了字符串,就会变成字符串拼接。
自增自减运算符
| 符号 | 作用 | 说明 |
|---|---|---|
++ | 自增 | 变量自身的值加 1 |
-- | 自减 | 变量自身的值减 1 |
++和--既可以放在变量的后边,也可以放在变量的前边(参考购物车 + / - 按钮)。
本节小结
| 场景 | 规则 |
|---|---|
| 单独使用 | ++、-- 单独使用在变量前后无区别 |
| 参与运算 | ++ 在前,先自增再操作;++ 在后,先操作再自增 |
| 操作数限制 | ++、-- 只能操作变量,不能操作字面量 |
类型转换
flowchart LR
A["类型转换"] --> B["隐式转换"]
A --> C["强制转换"]隐式转换介绍
把一个取值范围小的数值或者变量,赋值给另一个取值范围大的变量
public class Test { public static void main(String[] args) { int a = 10; double b = a; // 隐式转换:int → double System.out.println(b); // 10.0 }}flowchart LR
byte --> short --> int --> long --> float --> double
char --> int运算过程中的隐式转换
取值范围小的数据,和取值范围大的数据进行运算,小的会先提升为大的之后,再进行运算
public class Test { public static void main(String[] args) { int a = 10; double b = 12.3; double c = a + b; // 22.3,a 临时提升为 double }}特殊规则:
byteshortchar三种数据在运算的时候,都会先提升为int,然后再进行运算。
public class Test { public static void main(String[] args) { byte a = 10; byte b = 20; byte c = a + b; // 编译报错:a + b 提升为 int,需要强转 }}public class Test { public static void main(String[] args) { byte a = 10; byte b = 20; int c = a + b; // 正确:int 接收 }}
char与int相加时,char会提升为int(按 ASCII 码取值)。
public class Test { public static void main(String[] args) { int a = 1; char b = 'a'; int c = a + b; // 98('a' 对应 ASCII 码 97) System.out.println(c); }}强制类型转换
把一个取值范围大的数值或者变量,赋值给另一个取值范围小的变量
不允许直接赋值,需要加入强制转换
格式:目标数据类型 变量名 = (目标数据类型) 被强转的数据;
public class Test { public static void main(String[] args) { double a = 12.3; int b = (int) a; // 12(精度损失,小数部分丢失) System.out.println(b); }}本节小结
| 类别 | 规则 |
|---|---|
| 基本的隐式转换 | 把一个取值范围小的数值或者变量,赋值给另一个取值范围大的变量 |
| 运算中的隐式转换 | 取值范围小的数据,和取值范围大的数据进行运算,小的会先提升为大的之后,再进行运算;byte、short、char 三种数据运算时会先提升为 int |
| 强制类型转换 | 取值范围大的数据,给取值范围小的变量赋值,需要强制类型转换;强转有可能造成精度损失 |
赋值运算符
基本赋值运算符
| 符号 | 作用 | 说明 |
|---|---|---|
= | 赋值 | a = 10,将 10 赋值给变量 a |
扩展赋值运算符
| 符号 | 作用 | 说明 |
|---|---|---|
+= | 加后赋值 | a += b,将 a + b 的值给 a |
-= | 减后赋值 | a -= b,将 a - b 的值给 a |
*= | 乘后赋值 | a *= b,将 a × b 的值给 a |
/= | 除后赋值 | a /= b,将 a ÷ b 的商给 a |
%= | 取余后赋值 | a %= b,将 a ÷ b 的余数给 a |
注意:扩展赋值运算符隐含了强转效果。
本节小结
| 类别 | 符号 |
|---|---|
| 基本赋值运算符 | = |
| 扩展赋值运算符 | += -= *= /= %=(隐含了强转效果) |
| 关系运算符 | > >= < <= == !=(最终返回 boolean 类型结果) |
警告:
==是关系运算符,=是赋值运算符,不要混淆!
关系运算符
| 符号 | 说明 |
|---|---|
== | a==b,判断 a 和 b 的值是否相等,成立为 true,不成立为 false |
!= | a!=b,判断 a 和 b 的值是否不相等,成立为 true,不成立为 false |
> | a>b,判断 a 是否大于 b,成立为 true,不成立为 false |
>= | a>=b,判断 a 是否大于等于 b,成立为 true,不成立为 false |
< | a<b,判断 a 是否小于 b,成立为 true,不成立为 false |
<= | a<=b,判断 a 是否小于等于 b,成立为 true,不成立为 false |
关系运算符最终返回的都是
boolean类型的结果。
逻辑运算符
把多个条件放在一起运算,最终返回布尔类型的值:
true、false连接布尔类型的表达式,或者是值。
案例:判断成绩是否优秀
需求:在程序中,判断一个学生成绩是否在 90~100 之间,是的话程序输出优秀。
public class Test { public static void main(String[] args) { int score = 95; System.out.println(score >= 90 & score <= 100); // true }}逻辑运算符分类
| 符号 | 介绍 | 说明 |
|---|---|---|
& | 逻辑与 | 并且,遇 false 则 false |
| | 逻辑或 | 或者,遇 true 则 true |
! | 逻辑非 | 取反 |
^ | 逻辑异或 | 相同为 false,不同为 true |
短路逻辑运算符
| 符号 | 介绍 | 说明 |
|---|---|---|
&& | 短路与 | 作用和 & 相同,但是有短路效果 |
|| | 短路或 | 作用和 | 相同,但是有短路效果 |
| 运算符 | 左操作数 | 右操作数 |
|---|---|---|
&(逻辑与) | 无论 true 还是 false | 右边都要执行 |
&&(短路与) | 左为 true | 右边执行 |
&&(短路与) | 左为 false | 右边不执行 |
|(逻辑或) | 无论 true 还是 false | 右边都要执行 |
||(短路或) | 左为 false | 右边执行 |
||(短路或) | 左为 true | 右边不执行 |
flowchart LR
A["& 逻辑与"] --> A1["遇 false 则 false"]
B["&& 短路与"] --> B1["左边为 false 右边不执行"]
C["| 逻辑或"] --> C1["遇 true 则 true"]
D["|| 短路或"] --> D1["左边为 true 右边不执行"]
E["! 逻辑非"] --> E1["取反"]
F["^ 逻辑异或"] --> F1["相同是 false 不同是 true"]注意:实际开发中,常用的逻辑运算符是:
&&、||、!
三元运算符
格式:判断条件 ? 值1 : 值2;
执行流程:
- 首先计算判断条件的值
- 如果值为
true,值 1 就是运算结果 - 如果值为
false,值 2 就是运算结果
flowchart LR
A["判断条件 ?"] --> B{"判断条件的值"}
B -- true --> C["值 1 (运算结果)"]
B -- false --> D["值 2 (运算结果)"]总结:根据判断条件,从两份数据中二者选其一。
运算符优先级
| 优先级 | 运算符 |
|---|---|
| 1 | . () |
| 2 | !、~、++、-- |
| 3 | *、/、% |
| 4 | +、- |
| 5 | <<、>>、>>> |
| 6 | <、<=、>、>=、instanceof |
| 7 | ==、!= |
| 8 | & |
| 9 | ^ |
| 10 | | |
| 11 | && |
| 12 | || |
| 13 | ?: |
| 14 | =、+=、-=、*=、/=、%=、&= |
规律:
&&优先级高于||,赋值运算符优先级最低。
优先级案例解析
public class Test { public static void main(String[] args) { int a = 10; int b = 20; System.out.println(a > b || a < b && a > b); // 解析:&& 优先级高于 ||,先算 a < b && a > b(false),再算 a > b || false(false || false = false) }}小技巧:不确定优先级时,用
()改变优先级,可读性更强。
public class Test { public static void main(String[] args) { int a = 10; int b = 20; System.out.println((a > b || a < b) && a > b); // 显式加括号:先算 a > b || a < b(true),再算 true && a > b(true && false = false) }}Scanner 键盘录入
需求
请在程序中,提示用户通过键盘输入自己的姓名、年龄,并能在程序中收到这些数据。
Java 已经写好了实现程序,我们调用即可。
三个步骤
import java.util.Scanner; // ① 导包:告诉程序去 JDK 的哪个包中找扫描器技术
public class Test { public static void main(String[] args) { Scanner sc = new Scanner(System.in); // ② 创建对象:代表得到键盘扫描器对象
System.out.println("请输入您的年龄:"); int age = sc.nextInt(); // ③ 调用方法:等待接收用户输入数据 System.out.println("年龄是:" + age);
System.out.println("请输入您的名称:"); String name = sc.next(); System.out.println("欢迎:" + name); }}本节小结
Java 程序中如何实现接收用户键盘输入的数据?
使用 Java 提供的类
Scanner来完成,步骤如下:
| 步骤 | 代码 | 说明 |
|---|---|---|
| 1 | import java.util.Scanner; | 导包 |
| 2 | Scanner sc = new Scanner(System.in); | 创建对象 |
| 3 | sc.nextInt() / sc.next() / sc.nextDouble() | 调用方法接收数据 |
| 方法 | 作用 |
|---|---|
sc.nextInt() | 接收整数 |
sc.next() | 接字符串 |
sc.nextDouble() | 接收浮点数 |
第五章 方法
函数,实现特定功能的操作步骤集合
方法的介绍
为什么要使用方法
如果将所有功能都写在
main方法中,代码会臃肿(“臃肿”),不便于维护。
public class Test { public static void main(String[] args) { // ...50 行代码 }}解决方法:将每个功能单独封装到一个方法中,
main中只负责调用。
public class Test { public static void main(String[] args) {
}
/** 查看全部商品 */ public static void showGoodsInfo() { ... }
/** 上架商品 */ public static void addGoods() { ... }
/** 下架商品 */ public static void deleteGoods() { ... }
/** 修改商品 */ public static void updateGoods() { ... }}方法的概念
| 维度 | 内容 |
|---|---|
| 别名 | 方法(method)、函数(function) |
| 定义 | 一段具有独立功能的代码块 |
| 特点 | 不调用就不执行 |
小结
方法(函数)就是将具有独立功能的代码封装起来,组成一个独立的小工具,方便重复使用,提升维护性。
| 维度 | 好处 |
|---|---|
| 代码管理 | 可以将挤在一起的臃肿代码按照功能进行分类管理,提高维护性 |
| 代码复用 | 提高代码的复用性 |
方法的基本定义和调用
定义和调用格式
| 维度 | 格式 |
|---|---|
| 定义格式 | public static void 方法名() { // 方法体 } |
| 调用格式 | 方法名(); |
public class Test { public static void show() { // 方法体 }
public static void main(String[] args) { show(); // 调用 }}案例:方法计算两个整数最大值
需求:定义一个方法,方法中定义两个整数变量,求出最大值并打印在控制台
| 步骤 | 内容 |
|---|---|
| 1 | 根据格式编写方法,方法名自己给出(见名知意 小驼峰命名法) |
| 2 | 方法内部定义出两个 int 类型变量 |
| 3 | 使用三元运算符求出最大值 |
| 4 | 调用该方法,让内部的逻辑执行起来 |
方法的执行流程
方法的调用流程
| 阶段 | 行为 |
|---|---|
| 方法未被调用时 | 字节码文件存放在 方法区 |
| 方法被调用时 | 需要进入到 栈内存 中运行 |
flowchart LR
A["方法区 字节码文件 例如 main"] -->|被调用| B["栈内存 方法运行时进入的内存"]入栈与出栈
| 行为 | 描述 |
|---|---|
| 入栈 | 方法被调用时,进入栈内存 |
| 出栈 | 方法执行完毕后,弹出栈内存 |
public class MethodDemo1 { public static void main(String[] args) { System.out.println("开始"); getMax(); System.out.println("结束"); }
public static void getMax() { int num1 = 10; int num2 = 20; int max = num1 > num2 ? num1 : num2; System.out.println(max); }}执行流程:
flowchart TD
M["main 压栈 执行 println 开始"] --> G["getMax 压栈 求 max = 20"]
G --> G1["println 打印 20"]
G1 --> G2["getMax 执行完毕 出栈"]
G2 --> M1["main 继续执行 println 结束"]
M1 --> M2["main 执行完毕 出栈"]多个方法嵌套调用
public class MethodDemo2 { public static void main(String[] args) { study(); }
public static void sleep() { System.out.println("睡觉"); }
public static void eat() { System.out.println("吃饭"); }
public static void study() { eat(); System.out.println("学习"); sleep(); }}小结
| 内存区域 | 说明 |
|---|---|
| 方法区 | 字节码文件加载时进入的内存 |
| 栈内存 | 方法运行时入栈,方法运行结束出栈 |
带参数的方法
为什么要带参数
方法中写死的数据不灵活,每次只能处理固定的内容。
public class MethodDemo1 { public static void main(String[] args) { getMax(); }
public static void getMax() { int num1 = 10; int num2 = 20; int max = num1 > num2 ? num1 : num2; System.out.println(max); }}上面的
getMax()只能计算 10 和 20 的最大值。
解决办法:让方法可以接收调用方传入的数据 → 形参 + 实参
定义格式
| 参数数量 | 格式 |
|---|---|
| 单个参数 | public static void 方法名 (数据类型 变量名) { // 方法体 } |
| 多个参数 | public static void 方法名 (数据类型 变量名1, 数据类型 变量名2...) { // 方法体 } |
public static void call(String name) { // ...}
public static void add(int a, int b) { // ...}调用格式
| 参数数量 | 格式 |
|---|---|
| 单个参数 | 方法名(参数); |
| 多个参数 | 方法名(参数1, 参数2); |
call("钢门吹雪");add(10, 20);形参和实参
| 名称 | 全称 | 出现时机 | 说明 |
|---|---|---|---|
| 形参 | 形式参数 | 定义方法时 | 方法声明的参数,相当于局部变量的声明 |
| 实参 | 实际参数 | 调用方法时 | 传入的具体数据 |
public class MethodDemo2 { public static void main(String[] args) { getMax(10, 20); // 10、20 是实参 }
public static void getMax(int num1, int num2) { // num1、num2 是形参 int max = num1 > num2 ? num1 : num2; System.out.println(max); }}小结
| 知识点 | 说明 |
|---|---|
| 参数作用 | 带参数的方法可以提高方法的灵活性 |
| 形参 | 方法定义时声明的参数 |
| 实参 | 方法调用时传入的参数 |
带返回值的方法
为什么需要返回值
A 方法产生的数据,B 方法想要使用的话,就需要定义带返回值的方法
// 之前:A 方法中计算 sum,B 方法想用 sum 是用不了的public static void methodA(int a, int b) { int sum = a + b; // ...}
public static void methodB() { System.out.println(sum % 2 == 0); // 报错,拿不到 sum}// 现在:A 方法通过 return 把数据交给 B 方法public static int methodA(int a, int b) { int sum = a + b; return sum;}
public static void methodB() { int result = methodA(10, 20); System.out.println(result % 2 == 0);}定义格式
public static 数据类型 方法名 (数据类型 变量名1, 数据类型 变量名2...) { return 数据值;}public static int add(int a, int b) { int c = a + b; return c;}
public static double getNum() { return 12.3;}调用格式
| 场景 | 格式 |
|---|---|
| 单纯调用 | 方法名(参数...); |
| 接收返回值 | 数据类型 变量名 = 方法名(参数...); |
add(10, 20); // 单纯调用,返回值被丢弃int result = add(10, 20); // 用变量接收小结
| 知识点 | 说明 |
|---|---|
| 使用场景 | A 方法中的数据,如果 B 方法想要使用的话,就需要设计返回值 |
| 关键字 | 通过 return 关键字,将结果数据返回 |
| 返回位置 | 返回到方法的调用处 |
方法通用定义格式
完整格式
public static 返回值类型 方法名(参数) { 方法体; return 数据;}| 组成 | 含义 | 类比 |
|---|---|---|
| 参数 | 制作材料 | 面粉、酵母、糖… |
| 方法体 | 使用材料干活 | 搅拌、烘烤… |
| 返回值 | 成品 | 面包 |
定义方法的两个明确
| 明确项 | 说明 |
|---|---|
| 明确参数 | 主要是明确参数的类型和数量 |
| 明确返回值类型 | 方法操作完毕后是否有结果数据,如果有写对应的数据类型,没有写 void |
调用方法的两种方式
| 方法类型 | 调用方式 |
|---|---|
void 类型的方法 | 直接调用即可:方法名(参数); |
非 void 类型的方法 | 推荐用变量接收调用:数据类型 变量名 = 方法名(参数); |
案例
| 需求编号 | 需求 |
|---|---|
| 需求 1 | 设计一个方法,能够计算出 2 个小数的和 |
| 需求 2 | 设计一个方法,能够计算出 3 个整数的最小值 |
| 需求 3 | 设计一个方法,能够打印出用户的个人信息(姓名,年龄,身高,性别) |
方法注意事项
| 序号 | 注意事项 |
|---|---|
| 1 | 方法不调用就不执行 |
| 2 | 方法与方法之间是平级关系,不能嵌套定义 |
| 3 | 方法的编写顺序和执行顺序无关 |
| 4 | 方法的返回值类型为 void 时,表示该方法没有返回值,没有返回值的方法可以省略 return 语句不写 |
| 5 | 如果要编写 return,后面不能跟具体的数据(void 方法中) |
| 6 | return 语句下面,不能编写代码,因为永远执行不到,属于无效的代码 |
// 错误示例:return 之后还有代码public class MethodDemo { public static void method() { // 代码片段 return; System.out.println("卑微代码"); // ❌ 报错 }}方法重载
引入
需求:提供 4 个计算方法,功能如下
- 2 个
int相加- 2 个
double相加- 3 个
int相加- 3 个
double相加
public class Test { public static int add1(int a, int b) { return a + b; }
public static double add2(double a, double b) { return a + b; }
public static int add3(int a, int b, int c) { return a + b + c; }
public static double add4(double a, double b, double c) { return a + b + c; }}但是 4 个 add1/add2/add3/add4 名字太繁琐了,
能否全部用add同一个名字?→ 方法重载
public class Test { public static int add(int a, int b) { return a + b; } public static double add(double a, double b) { return a + b; } public static int add(int a, int b, int c) { return a + b + c; } public static double add(double a, double b, double c) { return a + b + c; }}flowchart LR
C1["add(10, 20)"] --> M1["add(int, int)"]
C2["add(10, 20, 30)"] --> M2["add(int, int, int)"]
C3["add(12.3, 12.3)"] --> M3["add(double, double)"]
C4["add(12.3, 12.3, 12.3)"] --> M4["add(double, double, double)"]调用方法的时候,Java 虚拟机会通过参数的不同来区分同名的方法
方法重载的定义
| 维度 | 说明 |
|---|---|
| 位置 | 在同一个类中 |
| 方法名 | 多个方法的方法名相同 |
| 参数 | 每个方法具有不同的参数类型或参数个数 |
| 结果 | 这些同名的方法,就构成了重载关系(Overload) |
简单记:同一个类中,方法名相同,参数不同的方法
参数不同:个数不同、类型不同、顺序不同
注意:识别方法之间是否是重载关系,只看方法名和参数,跟返回值无关。
重载判断正误
| 示例 | 是否构成重载 | 原因 |
|---|---|---|
void fn(int a) / int fn(int a) | ❌ | 参数相同,仅返回值不同 |
void fn(int a) / int fn(int a, int b) | ✅ | 参数个数不同 |
void fn(int a) / int fn(double a) | ✅ | 参数类型不同 |
两个类中的 fn(int a) / fn(double a) | ❌ | 不在同一个类中 |
void fn(int a, double b) / void fn(double a, int b) | ✅ | 参数顺序不同 |
void fn(int a, double b) / void fn(int c, double d) | ❌ | 仅形参名不同 |
方法重载的好处
不用记忆过多繁琐的方法名字
public class MethodDemo { public static void main(String[] args) { // 输出调用 System.out.println(10); // 同一个 println,传入 int System.out.println(10.1); // 同一个 println,传入 double System.out.println(true); // 同一个 println,传入 boolean }}小结
| 知识点 | 说明 |
|---|---|
| 方法重载 Overload | 同一个类中,方法名相同,参数不同的方法 |
| 重载看返回值吗? | 不看,只看参数类型、参数个数、参数顺序 |
| 重载有啥好处? | 不需要记忆太多方法名字 |
IDEA 安装 AI 插件
IDEA 集成通义灵码
| 阶段 | 说明 |
|---|---|
| IDEA 之前的编程方式 | 程序的编码工作,还是需要程序员思考,并逐行编写 |
| AI 出现后 | IDEA 可以集成很多辅助编程的 AI 编程插件 |
| 推荐插件 | 阿里巴巴 通义灵码 |
第六章 流程控制语句
控制程序代码执行顺序 多一句没有,少一句不行。用更短的时间,教会更实用的技术
1. 流程控制语句分类
| 分类 | 说明 | 关键字 |
|---|---|---|
| 顺序结构 | Java 程序默认的执行流程,没有特定的语法结构,按照代码的先后顺序,依次执行 | 程序默认 |
| 分支结构 | 根据条件选择性地执行某段代码 | if、switch |
| 循环结构 | 让一段代码重复执行很多次 | for、while、do...while |
flowchart LR
S[开始] --> A[语句A] --> B[语句B] --> C[语句C] --> E[结束]public class Test { public static void main(String[] args) { System.out.println("A"); System.out.println("B"); System.out.println("C"); }}2. 分支结构 if
if 的三种格式
| 格式 | 语法 | 适用场景 |
|---|---|---|
| 格式 1 | if (判断条件) { 语句体; } | 单条件判断 |
| 格式 2 | if (判断条件) { 语句体1; } else { 语句体2; } | 二选一 |
| 格式 3 | if (判断条件1) { 语句体1; } else if (判断条件2) { 语句体2; } ... else { 语句体n+1; } | 多条件区间判断 |
执行流程:
flowchart LR
S[开始] --> C1{判断条件}
C1 -- true --> B1[执行语句体]
C1 -- false --> E[结束]
B1 --> Eflowchart LR
S[开始] --> C1{判断条件}
C1 -- true --> B1[执行语句体1]
C1 -- false --> B2[执行语句体2]
B1 --> E[结束]
B2 --> Eflowchart LR
S[开始] --> C1{判断条件1}
C1 -- true --> B1[执行语句体1]
C1 -- false --> C2{判断条件2}
C2 -- true --> B2[执行语句体2]
C2 -- false --> C3{...}
C3 -- true --> Bn[执行相应语句体]
C3 -- false --> Bx[执行语句体n+1]
B1 --> E[结束]
B2 --> E
Bn --> E
Bx --> E
- if 分支是什么?可以根据条件,选择执行某段程序
- if 分支的写法有几种?三种(见上表)
案例:考试奖励
需求:键盘录入考试成绩,根据成绩所在的区间,程序打印出不同的奖励
| 成绩区间 | 奖励 |
|---|---|
| 95 ~ 100 | 自行车一辆 |
| 90 ~ 94 | 游乐场玩一次 |
| 80 ~ 89 | 变形金刚一个 |
| 80 分以下 | 胖揍一顿 |
Scanner sc = new Scanner(System.in);System.out.print("请输入考试成绩:");int score = sc.nextInt();
if (score >= 95 && score <= 100) { System.out.println("自行车一辆");} else if (score >= 90 && score <= 94) { System.out.println("游乐场玩一次");} else if (score >= 80 && score <= 89) { System.out.println("变形金刚一个");} else { System.out.println("胖揍一顿");}if 语句注意事项
- if 语句中,如果大括号控制的是一条语句,大括号可以省略不写
- if 语句的 () 和 {} 之间不要写分号
- if 语句的 () 中需要产生 boolean 类型的结果,根据结果决定程序的走向
3. 分支结构 switch
通过比较值来决定执行哪条分支
switch 分支的执行流程
- 先计算表达式的值,再拿着这个值去与
case后的值进行匹配 - 与哪个
case后的值匹配为true就执行哪个case块的代码,遇到break就跳出 switch 分支 - 如果全部
case后的值与之匹配都是false,则执行default块的代码
switch (表达式) { case 值1: 语句体1; break; case 值2: 语句体2; break; ... default: 语句体n+1; break;}
- if 适合做条件是区间判断的情况
- switch 适合做:条件是比较值的情况、代码优雅、性能较好
switch 语句注意事项
- 表达式类型只能是
byte、short、int、char,不支持double、float、long- JDK5 开始支持枚举
- JDK7 开始支持 String
case给出的值不允许重复,且只能是字面量,不能是变量- 正常使用 switch 的时候,不要忘记写
break,否则会出现穿透现象
4. Debug 工具
- Debug:是供程序员使用的程序调试工具
- 它可以用于查看程序的执行流程
- 也可以用于追踪程序执行过程来调试程序
- Debug 调试,又被称为断点调试,断点其实是一个标记,告诉 Debug 从标记的地方开始查看
Debug 工具栏
| 操作 | 说明 |
|---|---|
| 程序向下执行一步 | 单步执行(F8) |
| 跳出当前方法 | 从方法中返回到调用处 |
| 进入方法查看详情 | 进入方法体逐行执行(F7) |
| 结束 DeBug 模式 | 终止本次调试 |
| 再点一次取消断点 | 移除该行断点 |
Debug 视图
- Frames:显示正在执行的方法(
main6, Main、getMax:12, Main) - Variables:显示变量的变化过程(
a = 10、b = 20) - Console:控制台输出
Breakpoints 视图
统一管理所有断点(Java Line Breakpoints、Java Exception Breakpoints 等),可设置 Suspend: All / Thread、Condition、Log message、Remove once hit 等高级属性
5. 循环结构 for
循环语句的作用:可以将一段代码重复的执行很多次
for (初始化语句; 条件判断语句; 条件控制语句) { 循环体语句;}跑圈示例
循环任务:跑圈 控制循环:根据心中 1 2 3 4 数字的变化进行控制 循环条件:心中的数小于 4,就开始循环任务
flowchart LR
S[开始] --> I[初始化语句 int i = 1] --> C{i < 4}
C -- true --> B[循环体语句 sout 跑圈] --> U[条件控制语句 i++]
U --> C
C -- false --> E[结束]for 循环小结
循环语句的作用:可以将一段代码重复的执行很多次 for 循环语句格式:
for (初始化语句; 条件判断语句; 条件控制语句) { 循环体语句;}执行流程:
- 初始化语句
- 执行条件判断语句,看结果是 true,还是 false;false 结束,true 继续
- 执行循环体语句
- 执行条件控制语句
- 回到 2 继续
案例:循环打印 10 次「黑马程序员」
需求:请使用循环,在控制台打印出 10 次「黑马程序员」
for (int i = 1; i <= 10; i++) { System.out.println("黑马程序员");}案例:模拟计时器
需求:请使用循环在控制台打印数字 1
3 和 31 的数据
| 思路积累 | 说明 |
|---|---|
| 循环中,用于控制循环的变量还可以放在循环体中,继续进行使用 | — |
条件控制语句,不一定是 ++ | 例如 i-- |
// 打印 1 ~ 3for (int i = 1; i <= 3; i++) { System.out.println(i);}System.out.println("------------");
// 打印 3 ~ 1for (int i = 3; i >= 1; i--) { System.out.println(i);}案例:求和
需求:求 1-100 之间的偶数和,并把求和结果在控制台输出
分析:
- 定义求和变量
sum,准备记录累加的结果 - 使用 for 循环,分别获取数据 1 ~ 100
- 循环使用 if 语句,筛选出偶数数据
- 将每一个偶数数据,和
sum变量进行累加 - 循环结束后,打印求和后的结果
思路积累:今后若遇到的需求是,求 xxx 的和,就要联想到求和变量
sum
int sum = 0;for (int i = 1; i <= 100; i++) { if (i % 2 == 0) { sum += i; }}System.out.println("1-100之间的偶数和是:" + sum);案例:水仙花数
需求:在控制台输出所有的「水仙花数」
| 定义 | 解释 |
|---|---|
| 水仙花数是一个三位数 | 例:111、222、333、370、371、520、999 |
| 个位、十位、百位的数字立方和等于原数 | 例:123 → 1³+2³+3³ = 1+8+27 = 36 ≠ 123(不是水仙花数) 例:371 → 3³+7³+1³ = 27+343+1 = 371 = 371(是水仙花数) |
分析:
- 通过 for 循环,获取到所有的三位数 100 ~ 999
- 在循环内部,将每一个三位数拆分为(个位,十位,百位)
- 加入 if 筛选条件:
if (ge*ge*ge + shi*shi*shi + bai*bai*bai == 原数) - 满足条件则输出水仙花数
for (int i = 100; i <= 999; i++) { int ge = i % 10; int shi = i / 10 % 10; int bai = i / 100 % 10; if (ge*ge*ge + shi*shi*shi + bai*bai*bai == i) { System.out.println(i); }}案例:统计水仙花数
需求:在控制台输出所有的「水仙花数」,并统计出水仙花数的个数
分析:
- 定义计数器变量
count,准备统计水仙花数的个数 - 通过 for 循环,获取到所有的三位数 100 ~ 999
- 在循环内部,将每一个三位数拆分为(个位,十位,百位)
- 加入 if 筛选条件:
if (ge*ge*ge + shi*shi*shi + bai*bai*bai == 原数) - 满足条件则输出水仙花数,
count变量++ - 打印
count所记录的值
思路积累:今后若遇到的需求是,统计 xxx,就要联想到计数器变量
count
int count = 0;for (int i = 100; i <= 999; i++) { int ge = i % 10; int shi = i / 10 % 10; int bai = i / 100 % 10; if (ge*ge*ge + shi*shi*shi + bai*bai*bai == i) { System.out.println(i); count++; }}System.out.println("水仙花数共有:" + count + " 个");for 循环语句注意事项
- 循环 {} 中定义的变量,在每一轮循环结束后,都会从内存中释放
- 循环 () 中定义的变量,在整个循环结束后,都会从内存中释放
- 循环语句 () 和 {} 之间不要写分号
- 循环 () 中定义的变量,在循环结束后还可以继续使用吗?
- 循环 {} 中定义的变量,什么时候从内存中消失?
- 循环 () 和 {} 之间写分号,还可以控制循环体吗?
循环嵌套
循环嵌套:在循环语句中,继续出现循环语句。 外循环执行一次,内循环执行一圈
for (int i = 1; i <= 5; i++) { for (int j = 1; j <= 5; j++) { System.out.println("HelloWorld"); }}案例:循环嵌套
使用
*在控制台打印出 4 行 5 列的矩形
* * * * ** * * * ** * * * ** * * * *使用
*在控制台打印出 5 行的直角三角形
** ** * ** * * ** * * * *// 4 行 5 列矩形for (int i = 1; i <= 4; i++) { for (int j = 1; j <= 5; j++) { System.out.print("* "); } System.out.println();}
// 5 行直角三角形for (int i = 1; i <= 5; i++) { for (int j = 1; j <= i; j++) { System.out.print("* "); } System.out.println();}
- 循环嵌套:在循环中继续编写循环语句
- 执行流程:外循环执行一次,内循环执行一圈
- 思路:如果一个循环任务还要继续执行多次,使用外循环包裹
6. 循环结构 while
初始化语句;while (判断语句) { 循环体语句; 控制语句;}案例:珠穆朗玛峰
需求:世界最高峰珠穆朗玛峰高度是 8848.86 米 = 8848860 毫米,假如我有一张足够大的纸,它的厚度是 0.1 毫米。请问:该纸张折叠多少次,可以折成珠穆朗玛峰的高度?
分析:
- 定义变量存储珠穆朗玛峰的高度、纸张的高度
double peakHeight = 8848860; // 山峰高度double paperThickness = 0.1; // 纸张厚度- 使用 while 循环来控制纸张折叠,循环条件是(纸张厚度 < 山峰高度),循环每执行一次,就表示纸张折叠一次,并把纸张厚度变为原来两倍
- 循环外定义计数变量
count,循环每折叠一次纸张,让count变量 +1
double peakHeight = 8848860;double paperThickness = 0.1;int count = 0;while (paperThickness < peakHeight) { paperThickness *= 2; count++;}System.out.println("需要折叠 " + count + " 次");while 与 for 的区别
- while 循环的格式,执行流程是怎么样的?
初始化语句;while (循环条件) { 循环体语句; 迭代语句;}int i = 1;while (i <= 3) { System.out.println("HelloWorld"); i++;}
- while 和 for 有什么区别?什么时候用 for,什么时候用 while?
- 功能上是完全一样的,for 能解决的 while 也能解决,反之亦然
- 使用规范:知道循环次数,使用 for;不知道循环次数建议使用 while
7. 循环结构 do…while
初始化语句;do { 循环体语句; 条件控制语句;} while (循环条件);int i = 0;do { System.out.println("Hello World!"); i++;} while (i < 3);do-while 循环的特点:先执行后判断
三种循环的区别
| 循环 | 判断时机 | 适用场景 |
|---|---|---|
| for 循环 | 先判断后执行 | 已知循环次数 |
| while 循环 | 先判断后执行 | 不明确循环次数 |
| do…while 循环 | 先执行后判断 | 循环体至少执行一次 |
- for 循环和 while 循环的执行流程是一模一样的,功能上无区别,for 能做的 while 也能做,反之亦然
- 使用规范:如果已知循环次数建议使用 for 循环,如果不清楚要循环多少次建议使用 while 循环
- 其他区别:for 循环中,控制循环的变量只在循环中使用;while 循环中,控制循环的变量在循环后还可以继续使用
// for 循环:循环结束后控制变量不可用for (int i = 0; i < 3; i++) { System.out.println("Hello World");}System.out.println(i); // 报错:i 已不可用
// while 循环:循环结束后控制变量仍可用int i = 0;while (i < 3) { System.out.println("Hello World"); i++;}System.out.println(i); // 输出 38. break、continue
跳转控制语句
| 关键字 | 作用 |
|---|---|
| break | 终止 循环体内容的执行,也就是说结束当前的整个循环 |
| continue | 跳过 某次循环体内容的执行,继续下一次的执行 |
注意事项:
- break:只能在循环,和 switch 当中进行使用
- continue:只能在循环中进行使用
带标号的 break(跳出指定循环)
如果出现循环嵌套,默认操作内层循环,如需改动,可以使用标号控制
lo:for (int i = 1; i <= 5; i++) { for (int j = 1; j <= 5; j++) { if (j == 2) { break lo; } }}9. Random 生成随机数
import java.util.Random;
public class Test { public static void main(String[] args) { Random r = new Random(); int num1 = r.nextInt(10); }}| 步骤 | 说明 |
|---|---|
| ① 导包 | 告诉程序去 JDK 的哪个包中找随机数生成器(import java.util.Random;) |
| ② 抄代码 | 代表得到随机数生成器对象(东西)(Random r = new Random();) |
| ③ 抄代码 | 生成一个随机数,指定随机范围(int num1 = r.nextInt(10);) |
注意:
r.nextInt(n);的范围是 0 ~ n-1,不包含 n
思考:产生一个 1~100 的随机数
r.nextInt(?)传入什么参数?int num = r.nextInt(100) + 1;(nextInt(100)得到 099,加 1 得到 1100)
案例:猜数字小游戏
- 产生一个 1~100 之间的随机数作为中奖数字
- 随后键盘录入用户猜的数字进行比对
- 没有猜中的话给出提示,直到用户猜对为止,程序结束
import java.util.Random;import java.util.Scanner;
public class Test { public static void main(String[] args) { Random r = new Random(); int lucky = r.nextInt(100) + 1;
Scanner sc = new Scanner(System.in); while (true) { System.out.print("请输入:"); int guess = sc.nextInt(); if (guess > lucky) { System.out.println("猜大了"); } else if (guess < lucky) { System.out.println("猜小了"); } else { System.out.println("恭喜,猜中了!"); break; } } }}运行示例:
请输入:50猜大了请输入:25猜小了请输入:15猜小了请输入:10猜小了请输入:12恭喜,猜中了!
第七章 数组
数组指一种容器,可以存储同种类型的多个值
1. 数组介绍
为什么使用数组
销售部门共有 10 个员工,需要录入 10 个人的销售业绩,然后找出最大值、找出最小值、计算销售总额。如果使用普通变量,需要定义 10 个 int 变量存储,繁琐且不利于批量操作。
什么是数组
- 数组是一种容器,可以用来存储同种数据类型的多个值
- 例如:
int a = 10; int b = 20; int c = 30;三个变量分别存储 →int[] arr = {10, 20, 30, ..., 90};一个数组存储
- 数组是什么?能干什么? 答:一种容器,可以存储同种数据类型的多个值
- 什么时候使用数组? 答:如果要操作的数据有多个,并且还属于一个整体
2. 数组定义和静态初始化
数组定义
| 格式 | 语法 | 范例 |
|---|---|---|
| 格式一 | 数据类型[] 数组名; | int[] array1; |
| 格式二 | 数据类型 数组名[]; | int array2[]; |
- 数组的定义格式为?
数据类型[] 数组名;- 数组的静态初始化格式为?
- 完整:
数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, 元素3...};- 简化:
数据类型[] 数组名 = {元素1, 元素2, 元素3...};- 打印数组名会看到什么? 十六进制内存地址
数组静态初始化
初始化:就是在内存中,为数组容器开辟空间,并将数据存入容器中的过程
// 完整格式数据类型[] 数组名 = new 数据类型[]{ 元素1, 元素2, 元素3... };int[] array = new int[]{ 11, 22, 33 };double[] array2 = new double[]{ 11.1, 22.2, 33.3 };
// 简化格式(开发中推荐使用)数据类型[] 数组名 = { 元素1, 元素2, 元素3... };int[] array = { 11, 22, 33 };double[] array2 = { 11.1, 22.2, 33.3 };注意事项:打印数组名,会看到数组在内存中的十六进制地址值 例如:
[I@10f87f48、[D@b4c966a
flowchart LR
subgraph M[内存]
direction TB
V1[11]
V2[22]
V3[33]
end
Arr[数组名] --> M3. 数组元素访问
格式:数组名[索引];
- 索引:就是数组容器中空间的编号,编号从 0 开始,逐个 +1 增长
flowchart LR
subgraph array[array 数组]
direction TB
E0[索引 0] --> E1[索引 1] --> E2[索引 2] --> E3[索引 3]
end
Caller[array 小组,2 号滚出列] --> E2
- 数组元素访问格式为?
数组名[索引];- 索引(角标、下标):指数组中每一个元素的编号,编号从 0 开始,逐个 +1 增长
- 数组名.length:动态获取数组的长度(元素的个数)
4. 数组遍历操作
- 数组遍历:依次访问数组中的每一个元素
数组遍历 - 求偶数和
需求:已知数组元素为
{11, 22, 33, 44, 55},请将数组中偶数元素取出并求和,最后打印求和结果
分析:
- 静态初始化数组
- 定义求和变量
- 遍历数组取出每一个元素
- 判断当前元素是否是偶数,是的话跟求和变量做累加操作
- 打印求和变量
int[] arr = {11, 22, 33, 44, 55};int sum = 0;for (int i = 0; i < arr.length; i++) { if (arr[i] % 2 == 0) { sum += arr[i]; }}System.out.println("数组中所有偶数的和为:" + sum); // 66数组遍历 - 求最大值
需求:已知数组元素为
{5, 44, 33, 55, 22},请找出数组中最大值并打印在控制台
| 选手 | 战力 | 索引 |
|---|---|---|
| 选手 0 | 5 | 0 |
| 选手 1 | 44 | 1 |
| 选手 2 | 33 | 2 |
| 选手 3 | 55 | 3 |
| 选手 4 | 22 | 4 |
分析:
- 定义
max变量来记录擂台上不断变化的数据 - 数组中 0 号选手先上台,因此
int max = arr[0]; - 遍历数组取出每一个元素,索引从 1 开始
- 逐个进行比较,找到更大的,
max变量记录更大的数据 - 遍历后打印
max变量所记录的值
int[] arr = {5, 44, 33, 55, 22};int max = arr[0];for (int i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; }}System.out.println("数组最大值为:" + max); // 55数组元素反转
需求:已知一个数组
arr = {11, 22, 33, 44, 55};,用程序实现把数组中的元素值交换,交换后的数组arr = {55, 44, 33, 22, 11};,并在控制台输出交换后的数组元素。
两变量交换(借助第三变量)
需求:实现两个变量的数据交换
int a = 10;int b = 20;
// 定义第三个变量,完成交换int c = a;a = b;b = c;思路:使用一个空杯(第三个变量)作为中转,先把 a 倒到 c,再把 b 倒到 a,最后把 c 倒到 b,完成交换
反转过程
| 步骤 | 操作 | 数组状态 | 指针位置 |
|---|---|---|---|
| 初始 | int start = 0, end = arr.length - 1; | 11 22 33 44 55 66 | start=0, end=5 |
| 第一次 | 交换 arr[0] 和 arr[5] | 66 22 33 44 55 11 | start=1, end=4 |
| 第二次 | 交换 arr[1] 和 arr[4] | 66 55 33 44 22 11 | start=2, end=3 |
| 第三次 | 交换 arr[2] 和 arr[3] | 66 55 44 33 22 11 | start=3, end=2(停止) |
flowchart LR
subgraph 0[交换前]
A0[11] --> A1[22] --> A2[33] --> A3[44] --> A4[55] --> A5[66]
end
subgraph 1[第三次后]
B0[66] --> B1[55] --> B2[44] --> B3[33] --> B4[22] --> B5[11]
end
0 --> 1// 交换条件:start < end// 交换:arr[start] 和 arr[end]// 交换后:start++, end--
int[] arr = {11, 22, 33, 44, 55, 66};
for (int start = 0, end = arr.length - 1; start < end; start++, end--) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp;}
for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " ");}// 66 55 44 33 22 115. 数组动态初始化
动态初始化:初始化时只指定数组长度,由系统为数组分配初始值
格式:数据类型[] 数组名 = new 数据类型[数组长度];范例:int[] arr = new int[3];| 数据类型 | 明细 | 默认值 |
|---|---|---|
| 基本数据类型 | 整数 | 0 |
| 小数 | 0.0 | |
| 字符 | '\u0000'(常见体现为空白字符) | |
| 布尔 | false | |
| 引用数据类型 | 类、接口、数组 | null |
动态初始化 vs 静态初始化
| 维度 | 动态初始化 | 静态初始化 |
|---|---|---|
| 格式 | 数据类型[] 数组名 = new 数据类型[长度]; | 数据类型[] 数组名 = {元素1, 元素2, 元素3...}; |
| 特点 | 手动指定长度,系统自动分配默认初始化值 | 手动指定元素,系统计算出数组长度 |
| 场景 | 不明确要操作元素的时候 | 明确具体要操作的元素 |
案例:评委打分
需求:在编程竞赛中,有 6 个评委为参赛的选手打分,分数为 0-100 的整数分。选手的最后得分为:去掉一个最高分和一个最低分后的 4 个评委平均值
6. 数组内存图
Java 内存分配介绍
| 区域 | 作用 |
|---|---|
| 栈 | 方法运行时所进入的内存(局部变量) |
| 堆 | new 出来的东西会在这块内存中开辟空间,并产生地址 |
| 方法区 | 字节码文件加载时进入的内存(包含本地方法栈、寄存器) |
一个数组内存图
简化格式只是简化了代码书写,真正运行期间还是按照完整格式运行的:
int[] arr = new int[]{11,22,33};
flowchart LR
A["Test.class main"]
B["main int[] arr → 10f87f48"]
C["10f87f48 int[] length=3 0:44 1:55 2:66"]
A --> B --> C输出结果:
44 55 66
两个数组指向相同内存图
重要:两个数组名指向同一个数组时,操作的是同一块内存空间
int[] array1 = {11, 22, 33};int[] array2 = array1; // array2 指向 array1 的地址array2[0] = 100; // 通过 array2 修改元素// array1[0] 也是 100方法参数传递内存图
| 数据类型 | 是否改变原值 | 原因 |
|---|---|---|
| 基本数据类型 | 不改变 | 传递的是数据值,修改形参不影响实参(除非用 return 把结果返回) |
| 引用数据类型 | 改变 | 传递的是地址值,方法内通过地址操作堆内存数据 |
// 基本数据类型 - 形参改变不影响实参public static void change(int number) { number = 200;}// 调用前:100,调用后:100
// 基本数据类型 - 用 return 返回public static int change(int number) { number = 200; return number;}// 调用前:100,调用后:200
// 引用数据类型 - 通过形参影响实参public static void change(int[] arr) { arr[0] = 66;}// 调用前:11,调用后:667. 数组常见问题
索引越界异常
- ArrayIndexOutOfBoundsException:当访问了数组中不存在的索引,就会引发索引越界异常
空指针异常
- 当引用数据类型变量被赋值为
null之后,地址的指向被切断,还继续访问堆内存数据,就会引发空指针异常
public static void main(String[] args) { int[] arr = new int[2]; // 把 null 赋值给数组 arr = null; // 输出元素 System.out.println(arr[0]); // NullPointerException}flowchart LR
M1["方法: main int[] arr = null"]
H1["10f87f48 int[] length=2 0:0 1:0"]
M1 -. × .- H18. 二维数组
- 二维数组是一种容器,该容器用于存储一维数组
使用场景
要操作的一维数组有多个,并且属于同一个整体,可以考虑使用二维数组
// 销售部门四个季度的销售业绩int[] arr = { {22, 66, 44}, {77, 33, 88}, {25, 45, 65}, {11, 66, 99} };二维数组静态初始化
| 格式 | 语法 | 范例 |
|---|---|---|
| 完整 | 数据类型[][] 数组名 = new 数据类型[][]{...}; | int[][] arr = new int[][]{{11,22},{33,44}}; |
| 简化 | 数据类型[][] 数组名 = {{...},{...}}; | int[][] arr = {{11,22},{33,44}}; |
二维数组元素访问
格式:数组名[索引][索引];范例:arr[1][0];案例:二维数组遍历
需求:已知一个二维数组
arr = {{11, 22, 33}, {33, 44, 55}};,遍历该数组,取出所有元素并打印
分析:
- 遍历二维数组,取出里面每一个一维数组
- 在遍历的过程中,对每一个一维数组继续完成遍历,获取内部存储的每一个元素
int[][] arr = {{11, 22, 33}, {33, 44, 55}};
for (int i = 0; i < arr.length; i++) { // arr[i] 就是每一个一维数组 for (int j = 0; j < arr[i].length; j++) { System.out.print(arr[i][j] + " "); } System.out.println();}// 11 22 33// 33 44 55第八章 面向对象基础
万物皆对象 面向对象是一种编程的指导思想 以什么形式 组织代码 / 以什么思路 解决问题
1. 面向对象介绍
面向对象:并不是一个技术,而是一种编程的指导思想 以什么形式 组织代码 / 以什么思路 解决问题 因为生活中,我们解决问题时,就是采用这种指导思想去解决的 所以,我们写程序去解决问题时,如果也能采用这种指导思想,就会使编程变得非常简单,程序也便于人理解
flowchart LR
A[面向对象] --> B[一种编程的指导思想]
A --> C[以什么形式 组织代码]
A --> D[以什么思路 解决问题]万物皆对象 试卷、钱、手机、人、试柜 → 都是对象 老师、学生 → 也是对象 老师 t = new 老师(); 试卷 s = t.出题(); 学生 stu = new 学生(); stu.考试(s); t.批卷(s);
public class Test { public static void main(String[] args) { 老师 t = new 老师(); 试卷 s = t.出题(); 学生 stu = new 学生(); stu.考试(s); t.批卷(s); }}面向对象,重点学什么?
- 学习自己如何设计对象
- 学习已有的对象如何使用
学习目标:
- 自己设计对象使用
- 掌握已有对象的使用
2. 类和对象
- Java 中想要创建对象,必须先要有类的存在
- 类指的是一组相关属性和行为的集合,我们将其理解为是一张对象的设计图
类和对象的关系:
- Java 中需要根据类,创建对象
- 一个类,可以创建出多个对象
类的组成
类由两部分组成:属性 + 行为
| 组成 | 语法要点 | 范例 |
|---|---|---|
| 属性(成员变量) | 跟之前定义变量的格式一样,只不过位置需要放在方法的外面 | String name = "张三";int age = 23; |
| 行为(成员方法) | 跟之前定义方法的格式一样,只不过需要去掉 static 关键字 | public void study() { System.out.println("学习Java"); }public void eat() { System.out.println("吃饭"); } |
创建对象、使用对象
| 步骤 | 语法 | 范例 |
|---|---|---|
| 创建对象 | 类名 对象名 = new 类名(); | Student stu = new Student(); |
| 使用成员变量 | 对象名.变量名; | stu.namestu.age = 24; |
| 使用成员方法 | 对象名.方法名(实际参数); | stu.study();stu.eat(); |
小结:
- 类的组成:属性、行为
- 属性:成员变量,描述事物的名词
- 行为:成员方法,描述事物的动词
- 创建对象格式:
类名 对象名 = new 类名();- 使用对象格式:
对象名.变量名; 对象名.方法名();
练习:类的定义与对象的创建和使用
需求:定义一个手机类
Phone,包含属性(品牌 brand、颜色 color、价格 price)和行为(打电话 call、发短信 sendMessage),并编写一个手机测试类PhoneTest创建两个手机对象,赋值后打印并调用成员方法。
public class Phone { String brand; String color; int price;
public void call(String name) { System.out.println("给 " + name + " 打电话"); }
public void sendMessage() { System.out.println("群发短信"); }}
public class PhoneTest { public static void main(String[] args) { Phone p1 = new Phone(); p1.brand = "小米"; p1.color = "白色"; p1.price = 4999; System.out.println(p1.brand + " " + p1.color + " " + p1.price);
Phone p2 = new Phone(); p2.brand = "华为"; p2.color = "黑色"; p2.price = 6999; System.out.println(p2.brand + " " + p2.color + " " + p2.price);
p1.call("张总"); p2.sendMessage(); }}3. 类和对象内存图
一个对象的内存图 → 栈内存(局部变量 + 引用)→ 堆内存(new 出来的对象,成员变量 + 成员方法引用)→ 方法区(字节码)
flowchart LR
subgraph 方法区
M[TestStudent.class main]
end
subgraph 栈内存
S1[main Student stu = 7ef20235]
end
subgraph 堆内存
H1[7ef20235 String name: 张三 int age: 23 成员方法引用地址]
end
方法区 --> 栈内存 --> 堆内存flowchart LR
subgraph 方法区
M[Test.class main]
end
subgraph 栈内存
S1[main Student stu1 = 7ef20235]
S2[Student stu2 = 27d6c5e0]
end
subgraph 堆内存
H1[7ef20235 name: 张三 age: 23]
H2[27d6c5e0 name: 李四 age: 24]
end
M --> S1 --> H1
S1 --> H2flowchart LR
subgraph 方法区
M[Test.class main]
end
subgraph 栈内存
S1[main Student stu1 = 7ef20235 Student stu2 = 7ef20235]
end
subgraph 堆内存
H1[7ef20235 name: 钢门吹雪]
end
M --> S1 --> H14. 成员变量和局部变量区别
| 区别 | 成员变量 | 局部变量 |
|---|---|---|
| 类中位置 | 方法外 | 方法中 |
| 初始化值 | 有默认初始化值 | 没有,使用前需要完成赋值 |
| 内存位置 | 堆内存 | 栈内存 |
| 生命周期 | 随着对象的创建而存在,随着对象的消失而消失 | 随着方法的调用而存在,随着方法的运行结束而消失 |
| 作用域 | 在自己所属类的大括号中 | 在自己所归属的大括号中 |
5. this 关键字
this 可以解决的问题
当局部变量和成员变量出现了重名的情况,Java 使用的是就近原则 但是我就非要用成员变量 → 用
this.成员变量
public class Student { String name; int age;
public void sayHello(String name) { System.out.println(this.name); // 我就非要用成员变量 }}this 介绍
this 代表当前类对象的引用(地址)
flowchart LR
subgraph 栈内存
S[main Student stu = 0011]
end
subgraph 堆内存
H[0011 this → 0011 name: 钢门吹雪 age: 23]
end
S --> Hthis 小结
- 当局部变量和成员变量重名的时候,Java 使用的是就近原则
- this 关键字的作用是:调用本类成员
this.本类成员变量;—— 如果不涉及变量重复,this.可以省略this.本类成员方法();—— 没有前提条件,this.可以直接省略- this 代表什么呢?代表当前类对象的引用(地址)
6. 构造方法
- 构造器:初始化一个新构建的对象,构建、创建对象的时候,所调用的方法
- 格式:
- 方法名与类名相同,大小写也要一致
- 没有返回值类型,连 void 都没有
- 没有具体的返回值(不能由 return 带回结果数据)
- 执行时机:
- 创建对象的时候调用,每创建一次对象,就会执行一次构造方法
- 不能手动调用构造方法
public class Test { public static void main(String[] args) { Student stu = new Student(); }}构造方法作用
- 本质作用:创建对象
- 结合构造方法执行时机:给对象中的属性(成员变量)进行初始化
构造方法注意事项
- 构造方法的创建:
- 如果没有定义构造方法,系统将给出一个默认的无参数构造方法
- 如果定义了构造方法,系统将不再提供默认的构造方法
- 构造方法的重载:构造方法也是方法,允许重载关系出现
- 推荐的使用方式:无参数构造方法,和带参数构造方法,都自己手动给出
class Student { int age; public Student() {} public Student(int age) { this.age = 18; }}构造方法执行流程
flowchart LR
subgraph 栈内存
M[main Student stu = 0011 构造方法 Student 调用者 this→0011 String name = 钢门吹雪 int age = 23]
end
subgraph 堆内存
H[0011 this → 0011 name: 钢门吹雪 age: 23]
end
M --> Hpublic class Student { String name; int age; public Student() {} public Student(String name, int age) { this.name = name; this.age = age; } public void show() { System.out.println(name + "---" + age); }}构造方法小结
- 构造方法的作用:1. 创建对象 2. 给成员变量初始化
- 构造方法的格式:1. 方法名与类名相同,大小写也要一致 2. 没有返回值类型,连 void 都没有 3. 没有具体的返回值
- 构造方法的注意事项:1. 如果一个类中没有写构造方法,系统会提供一个默认的无参数构造方法 2. 如果手动编写了构造方法,系统将不会提供默认的那个无参构造方法,建议无参构造、带参构造全部手动给出
7. 封装
面向对象三大特征:封装 / 继承 / 多态
封装思想
- 封装就是将数据(属性)和操作数据的方法捆绑在一起
- 组成一个整体,也就是类
- 同时,通过访问修饰符来控制对类中属性和方法的访问权限,从而实现信息隐藏
- 更好的维护数据
- 使用者无需关心内部实现,只要知道如何使用即可
- 核心:合理隐藏,合理暴露
案例:学生成绩
需求:计算每一名学生的总成绩,展示学生的所有信息
| id | 姓名 | 年龄 | 数学成绩 | 语文成绩 |
|---|---|---|---|---|
| 1 | 张三 | 23 | 90 | 87 |
| 2 | 李四 | 24 | 69 | 91 |
public class Student { int id; String name; int age; double mathScore; double chineseScore;
public Student(int id, String name, int age, double mathScore, double chineseScore) { this.id = id; this.name = name; this.age = age; this.mathScore = mathScore; this.chineseScore = chineseScore; }
// 省略构造方法 public double getTotalScore() { return mathScore + chineseScore; }
public void showStudentInfos() { System.out.println("学号:" + id); System.out.println("姓名:" + name); System.out.println("年龄:" + age); System.out.println("数学成绩:" + mathScore); System.out.println("语文成绩:" + chineseScore); }}public class Test { public static void main(String[] args) { Student stu1 = new Student(1, "张三", 23, 90, 87); stu1.showStudentInfos();
Student stu2 = new Student(2, "李四", 24, 69, 91); stu2.showStudentInfos(); }}案例:电话簿
合理隐藏:
saveLog()方法设为private;合理暴露:call()方法设为public
public class Phone { public void call(String name) { System.out.println("给 " + name + " 打电话"); saveLog(); }
private void saveLog() { System.out.println("通话记录"); System.out.println("将联系人 - 时间 - 通话时长保存到本地"); }}权限修饰符
| 修饰符 | 可修饰 |
|---|---|
| private | 成员变量、成员方法、构造方法 |
| (default) | 成员变量、成员方法、构造方法 |
| protected | 成员变量、成员方法、构造方法 |
| public | 成员变量、成员方法、构造方法 |
8. JavaBean
标准 JavaBean:
- 这个类中的成员变量都要私有,并且要对外提供相应的 getXxx、setXxx 方法
- 类中提供无参,带参构造方法
- 实体类的应用场景:实体类只负责数据存取,而对数据的处理交给其他类来完成,以实现数据和数据业务处理相分离
public class Student { private String name; private double score;
// getter setter 方法 // 计算学生总成绩 // 展示学生所有信息 // ...}
public class StudentOperator { private Student s; public StudentOperator(Student s) { this.s = s; }}9. static 关键字
- static 是静态的意思,可以修饰成员变量,也可以修饰成员方法
static 修饰成员的特点
- 被其修饰的成员,被该类的所有对象所共享
- 多了一种调用方式,可以通过类名调用
- 随着类的加载而加载,优先于对象存在
| 类型 | 用途 |
|---|---|
| static 成员变量 | 共享数据 |
| static 成员方法 | 常用于工具类 |
static 内存图
flowchart LR
subgraph 栈内存
M[main User u1, u2]
end
subgraph 堆内存
H1[u1: name=张三 age=23]
H2[u2: name=李四 age=24]
SH[User 静态成员变量区 int onLineNumber = 2]
end
subgraph 方法区
Z[StaticTest1.class main]
end
M --> H1
M --> H2
H1 --> SH
H2 --> SH
Z --> M注意事项
- static 方法中,只能访问静态成员(直接访问)
- static 中不允许使用 this 关键字
案例:数组工具类
需求:编写一个类
ArrayTools,内部编写三个方法:
- 从数组中找最大值
- 从数组中找最小值
- 打印出数组中所有的元素
public class ArrayTools { public static int getMax(int[] arr) { int max = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] > max) max = arr[i]; } return max; }
public static int getMin(int[] arr) { int min = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] < min) min = arr[i]; } return min; }
public static void printArray(int[] arr) { for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } System.out.println(); }}重新认识 main 方法
public class HelloWorld { public static void main(String[] args) { System.out.println("HelloWorld"); }}| 关键字 | 含义 |
|---|---|
| public | 被 JVM 调用,访问权限足够大 |
| static | 被 JVM 调用,不用创建对象 因为 main 方法是静态的,所以测试类中其他方法也需要是静态的 |
| void | 被 JVM 调用,不需要给 JVM 返回值 |
| main | 一个通用的名称,虽然不是关键字,但是被 JVM 识别 |
| String[] args | 以前用于接收键盘录入数据的,现在没用 |
第九章 面向对象高级
1. 继承
继承介绍
继承是面向对象三大特征之一,可以让类跟类之间产生父子关系 子类可以直接使用父类中非私有的成员
继承的好处与弊端
继承的好处:
- 提高代码的复用性
- 提高了代码的维护性
- 让类与类之间产生了关系,是多态的前提
继承的弊端:
- 继承让类与类之间产生了关系,耦合性增强
- 开发原则:高内聚,低耦合
继承的语法格式
继承用关键字
extends
public class 子类 extends 父类 { }继承后成员访问特点
| 成员 | 访问特点 |
|---|---|
| 成员变量 | 就近原则:先在子类局部范围找,再到子类成员范围找,最后到父类成员范围找 |
| 成员方法 | 就近原则 |
| 构造方法 | 子类的所有构造方法默认都会访问父类的无参构造方法 |
继承中变量访问
- 子类局部范围有就使用子类的
- 子类成员范围有就使用子类的
- 父类成员范围有就使用父类的
- 如果都没有就报错(不用考虑父类的父类)
class Fu { String name = "Fu";}
class Zi extends Fu { String name = "Zi";
public void show() { String name = "局部"; System.out.println(name); // 局部 System.out.println(this.name); // Zi System.out.println(super.name); // Fu }}super 关键字
super代表的是父类对象的引用this代表的是当前对象的引用
| 关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
|---|---|---|---|
this. | 本类的成员变量 | 本类的成员方法 | this(...) 访问本类构造 |
super. | 父类的成员变量 | 父类的成员方法 | super(...) 访问父类构造 |
继承中构造方法访问
- 子类所有构造方法的第一行,默认都有一个
super()调用父类无参构造- 如果父类没有无参构造,子类构造方法中必须显式调用父类的有参构造
super(参数)- 子类构造方法中,
this()和super()不能同时存在,因为它们都必须放在第一行- 子类构造方法中,默认调用父类无参构造是 Java 帮我们加上的
flowchart TB
A[创建子类对象] --> B[先执行父类构造]
B --> C[再执行子类构造]继承的特点
- Java 只支持单继承,不支持多继承(一个子类只能有一个父类)
- Java 支持多层继承(父类可以有父类)
- 一个父类可以有多个子类
- 顶层父类是
Object,所有类都直接或间接继承 Object
classDiagram
A <|-- B
B <|-- C
A : Object
B : 父类
C : 子类方法重写
方法重写:在子类中,出现和父类一模一样的方法声明(方法名、参数列表、返回值类型都相同) 方法重写的意义:子类可以覆盖父类的实现,提供自己的逻辑
方法重写的注意事项:
- 私有方法不能被重写(父类私有成员子类不能继承)
- 子类重写父类方法时,访问权限不能更低(public > 默认 > private)
- 子类重写父类方法时,建议加上
@Override注解来校验
class Fu { public void show() { System.out.println("Fu show"); }}
class Zi extends Fu { @Override public void show() { System.out.println("Zi show"); }}继承的练习
需求:定义老师类和学生类,找出他们的共同特征,抽到父类中 共同特征:姓名、年龄、吃饭 特有功能:老师 - 讲课;学生 - 学习
classDiagram
class Person {
+String name
+int age
+eat()
}
class Teacher {
+teach()
}
class Student {
+study()
}
Person <|-- Teacher
Person <|-- Student2. Object 类
Object 类介绍
Object 是所有类的顶层父类,所有类都直接或间接继承 Object 一个类如果没有显式继承其他类,默认继承 Object
常用方法
| 方法 | 说明 |
|---|---|
public String toString() | 返回对象的字符串表示,默认打印的是地址值 |
public boolean equals(Object obj) | 比较两个对象是否相等,默认比较的是地址值 |
public Class<?> getClass() | 返回对象的字节码文件对象 |
protected Object clone() | 克隆对象 |
toString 方法的重写
打印对象时,会自动调用
toString()方法 默认toString()返回的是:类名@十六进制地址值,没有意义 所以需要重写toString(),返回对象的属性值
@Overridepublic String toString() { return "Student{name=" + name + ", age=" + age + "}";}equals 方法的重写
默认
equals()比较的是对象的地址值 业务中通常需要比较对象的内容是否相同,需要重写equals()
@Overridepublic boolean equals(Object o) { // 如果两个对象的地址相同,代表是同一块内存控件,里面的内容肯定相同 if (this == o) return true;
// 代码如果可以执行到这里,说明stu1肯定不是null // stu1不是null,stu2为null,直接返回fallse // 比较零个对象的字节码,如果字节码不相同,代表类型不一致 if (o == null || getClass() != o.getClass()) return false;
// 向下转型,调用子类特有的属性 Student student = (Student) o;
// 比较两个对象的属性值 return age == student.age && Objects.equals(name, student.name);}快捷键:
Alt + Insert→toString()/equals()可自动生成
Objects 类
Objects类与Object仍然是继承关系,Objects类是从 JDK 1.7 开始之后才有的工具类 位于java.util.Objects,是一个工具类,提供一些静态方法来操作对象
Objects 常见方法
| 方法名 | 说明 |
|---|---|
public static boolean equals(Object a, Object b) | 比较两个对象,底层会先进行非空判断,从而可以避免空指针异常,再进行 equals 比较 |
public static boolean isNull(Object obj) | 判断变量是否为 null |
Objects.equals 源码分析
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b));}关键点:先比较
a == b(地址),如果为 true 直接返回;否则判断a != null后再调用a.equals(b),避免空指针异常
Objects 小结
Objects 中的
equals方法仅仅是个工具类中的方法,底层依赖于我们自己编写的equals方法 优势:加入了健壮性判断(提前做非空判断,避免空指针异常)
3. final 关键字
final 介绍
final 是最终的意思,可以修饰类、方法、变量
final 修饰的特点
| 修饰对象 | 效果 |
|---|---|
| 修饰类 | 该类不能被继承(太监类) |
| 修饰方法 | 该方法不能被重写 |
| 修饰变量 | 该变量不能被重新赋值,只能赋值一次 |
final class Fu { } // 该类不能被继承class Zi { public final void show() { } // 该方法不能被重写}final 修饰变量的细节
- 修饰基本数据类型:变量存储的数据值不能发生改变
- 修饰引用数据类型:变量存储的地址值不能发生改变,对象的内容可以改变
- 变量是成员变量时,由于有默认值,用 final 修饰后必须手动赋值(构造方法或直接赋值)
- final 修饰的变量一般叫常量,命名规范:所有字母都大写,多个单词用下划线分隔
final int AGE = 18; // AGE 是常量final int[] ARR = {1, 2, 3};// ARR = new int[5]; // 错误,地址值不能改ARR[0] = 100; // 正确,内容可以改4. 抽象类
抽象类介绍
抽象类是一种特殊的父类,里面可以定义没有方法体的抽象方法
抽象方法
抽象方法:将共性的行为(方法)抽取到父类之后,发现每个子类的执行逻辑不一样,此时在父类中不能给出具体的方法体,这种方法就是抽象方法
public abstract 返回值类型 方法名(参数列表); // 抽象方法,没有方法体抽象类定义格式
public abstract class 类名 { // 抽象类 public abstract 返回值类型 方法名(参数列表); // 抽象方法}抽象类特点
- 抽象类不能实例化(不能创建对象)
- 抽象类中可以有构造方法,供子类访问父类数据初始化
- 抽象类中可以有非抽象方法,提高代码复用性
- 抽象类的子类必须重写所有抽象方法,否则子类也必须是抽象类
classDiagram
class Animal {
<<abstract>>
+name: String
+eat()*
+drink() 抽象方法
}
class Dog {
+eat()
+drink()
}
Animal <|-- Dog抽象类的意义
强制子类必须按照父类的格式定义方法 抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义
public abstract class Animal { private String name;
public Animal() { } public Animal(String name) { this.name = name; }
public abstract void eat(); // 抽象方法
public void sleep() { // 普通方法 System.out.println("睡觉"); }}
public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃骨头"); }}抽象类练习
需求:定义一个抽象类
Shape,包含抽象方法getArea()和getPerimeter(),让Circle和Rectangle继承
classDiagram
class Shape {
<<abstract>>
+getArea()*
+getPerimeter()*
}
class Circle {
-radius: double
+getArea()
+getPerimeter()
}
class Rectangle {
-length: double
-width: double
+getArea()
+getPerimeter()
}
Shape <|-- Circle
Shape <|-- Rectangle5. 接口
接口介绍
接口是一种规则,是对行为的抽象 Java 中的接口更多体现的是一种规范(约定),是一种引用数据类型
接口的定义格式
public interface 接口名 { // 成员变量:只能是常量,默认修饰 public static final // 成员方法:只能是抽象方法,默认修饰 public abstract // JDK8:可以定义默认方法、静态方法 // JDK9:可以定义私有方法}接口的实现
public class 类名 implements 接口名 { // 重写接口中所有抽象方法}接口的成员特点
| 成员 | 特点 |
|---|---|
| 成员变量 | 只能是常量,默认修饰符 public static final |
| 成员方法 | 只能是抽象方法,默认修饰符 public abstract(JDK8 前) |
| 构造方法 | 没有构造方法 |
public interface Animal { public static final int AGE = 10; // 常量 public abstract void eat(); // 抽象方法}接口的关系
| 关系 | 写法 |
|---|---|
| 类与类 | 继承关系(extends),单继承或多层继承 |
| 类与接口 | 实现关系(implements),可以单实现,也可以多实现 |
| 接口与接口 | 继承关系(extends),可以单继承,也可以多继承 |
// 类多实现接口class A implements B, C { }
// 接口多继承接口interface D extends B, C { }JDK8 接口新特性
JDK8 之后,接口中可以定义默认方法和静态方法,用来解决接口升级的问题
| 方法类型 | 格式 | 特点 |
|---|---|---|
| 默认方法 | public default 返回值类型 方法名(参数) { } | 有方法体;实现类可以继承,也可以重写 |
| 静态方法 | public static 返回值类型 方法名(参数) { } | 有方法体;只能通过接口名调用,不能通过实现类调用 |
public interface MyInterface { default void show() { // 默认方法 System.out.println("默认方法"); }
static void staticMethod() { // 静态方法 System.out.println("静态方法"); }}
// 调用MyInterface.staticMethod();JDK9 接口新特性
JDK9 之后,接口中可以定义私有方法,用来抽取默认方法或静态方法中重复的代码
public interface MyInterface { default void show1() { methodCommon(); System.out.println("show1"); }
default void show2() { methodCommon(); System.out.println("show2"); }
private void methodCommon() { // 私有方法,供默认方法使用 System.out.println("公共代码"); }}6. 抽象类和接口的区别
| 成员 | 抽象类 | 接口 |
|---|---|---|
| 成员变量 | 可以是变量,也可以是常量 | 只能是 public static final 常量 |
| 成员方法 | 可以是抽象方法,也可以是非抽象方法 | JDK8 前:只能是 public abstract 抽象方法JDK8 后:可以加 default / static JDK9 后:可以加 private |
| 构造方法 | 有 | 无 |
| 使用场景 | 抽取共性代码(is a) | 定义规则(like a) |
classDiagram
class 抽象类 {
+构造方法
+普通方法
+抽象方法
+普通变量
+常量
}
class 接口 {
+常量
+抽象方法
+默认方法(JDK8)
+静态方法(JDK8)
+私有方法(JDK9)
}设计原则:接口优先于抽象类。当两者都能满足需求时,优先使用接口
7. 多态
多态介绍
多态是面向对象三大特征之一,是指同一行为,通过不同的事物,可以体现出不同的形态 多态是方法的多态,跟属性无关
多态的前提
- 要有继承或实现关系
- 要有方法重写
- 要父类引用指向子类对象:
Fu f = new Zi();
Animal a = new Dog(); // 父类引用指向子类对象(向上转型)a.eat(); // 调用的是 Dog 重写后的方法多态中成员访问特点
| 成员 | 访问特点 |
|---|---|
| 成员变量 | 编译看左边,运行也看左边(访问的是父类的成员变量) |
| 成员方法 | 编译看左边,运行看右边(访问的是子类重写后的方法) |
记忆口诀:成员方法看右边(运行),其他成员看左边(编译)
多态的好处和弊端
好处:
- 提高了程序的扩展性(定义方法时,使用父类作为参数,使用时传入子类对象)
弊端:
- 多态情况下,不能访问子类特有的成员
多态中的转型
向上转型:从子到父(自动转换),父类引用指向子类对象 向下转型:从父到子(强制转换),子类引用 指向 父类对象(实际还是子类)
Animal a = new Dog(); // 向上转型(自动)Dog d = (Dog) a; // 向下转型(强制)d.watchHouse(); // 调用 Dog 特有方法instanceof 关键字
用于判断某个对象是否属于某个类型,避免类型转换异常
ClassCastException
if (a instanceof Dog) { Dog d = (Dog) a; d.watchHouse();} else if (a instanceof Cat) { Cat c = (Cat) a; c.catchMouse();}多态内存图
flowchart LR
subgraph stack[栈]
F[Fu 类型引用 a]
end
subgraph heap[堆]
Z[Zi 类对象]
end
F --> Z
Z -.继承自 Fu.-> FU[父类成员]
Z -.重写.-> FM[方法区]多态练习
需求:定义动物类,猫和狗继承动物类,都有 eat 方法;定义测试类,传入任意动物对象调用 eat 方法
classDiagram
class Animal {
<<abstract>>
+eat()*
}
class Cat {
+eat()
}
class Dog {
+eat()
}
Animal <|-- Cat
Animal <|-- Dog8. 代码块
代码块介绍
在 Java 中,使用
{}括起来的代码被称为代码块
代码块的分类
| 类型 | 位置 | 特点 |
|---|---|---|
| 局部代码块 | 方法内部 | 限定变量的生命周期,及早释放内存 |
| 构造代码块 | 类中方法外 | 每次创建对象都会执行,优先于构造方法执行 |
| 静态代码块 | 类中方法外,加 static | 随着类加载而执行,只执行一次,优先于构造代码块执行 |
public class Student { static { System.out.println("静态代码块"); // 加载类时执行 }
{ System.out.println("构造代码块"); // 每次 new 都执行 }
public Student() { System.out.println("构造方法"); }}静态代码块的应用
用于类的初始化,加载驱动、加载配置文件等
public class JDBCUtils { static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } }}9. 包
什么是包
包(package)本质就是文件夹,用来管理类文件的 通过包,可以将不同功能的类进行分类管理,避免类重名的问题
包的定义格式
package 公司域名倒写.技术名称.包名;// 范例package com.itheima.pojo;
public class Student { }规范:包名建议全部英文小写,且具备意义,一般使用公司域名倒写 + 项目名 + 功能名 注意:package 语句必须是程序的第一行可执行代码
导包
相同包下的类可以直接访问,不同包下的类必须导包,才可以使用!
import 包名.类名; // 导包格式import com.itheima.pojo.Student; // 范例
- 如果导入的类在
java.lang包下(核心包),就不需要编写 import 导包代码- 假如一个类中需要用到不同类,而这两个类的名称是一样的,那么默认只能导入一个类,另一个类要带包名访问
导包练习
// 1. 手动导包import java.util.Scanner;
// 2. 通配符 * 导入整个包的所有类(不建议)import java.util.*;10. 内部类
内部类介绍
内部类就是在一个类的内部又定义了另一个类 例如:在一个类 A 的内部定义一个类 B,B 就被称为内部类,A 称为外部类
内部类的分类
| 类型 | 位置 | 说明 |
|---|---|---|
| 成员内部类 | 类中方法外 | 与成员变量、成员方法同级 |
| 静态内部类 | 类中方法外,加 static | 静态的成员内部类 |
| 局部内部类 | 方法内 | 定义在方法中的类 |
| 匿名内部类 | 方法内 | 没有名字的内部类,本质是一个对象 |
成员内部类
public class Outer { private int num = 10;
public class Inner { public void show() { System.out.println(num); // 内部类可以直接访问外部类成员 } }}
// 创建对象Outer.Inner oi = new Outer().new Inner();oi.show();成员内部类访问特点
- 内部类可以直接访问外部类的所有成员(包括私有)
- 外部类要访问内部类的成员,必须创建对象
- 内部类中如果定义了和外部类同名的成员变量,默认访问内部类的
- 访问外部类同名成员:
外部类名.this.成员变量
静态内部类
静态内部类只能访问外部类的静态成员
public class Outer { static class Inner { public void show() { System.out.println("静态内部类"); } }}
// 创建对象Outer.Inner oi = new Outer.Inner();oi.show();局部内部类
定义在方法中的类,外界无法直接使用,需要在方法内部创建对象使用 可以直接访问外部类的成员,也可以访问方法内的局部变量(final 修饰的常量,JDK8 后 final 可省略)
public class Outer { public void method() { int num = 10; class Inner { public void show() { System.out.println(num); // 访问方法中的局部变量 } } new Inner().show(); }}匿名内部类
匿名内部类是局部内部类的一种特殊写法,本质是一个对象
new 接口名/类名() { // 重写方法}.方法名();// 案例new Inter() { @Override public void show() { System.out.println("匿名内部类"); }}.show();匿名内部类的应用场景
当方法的参数是接口或抽象类时,可以直接传递匿名内部类作为实参
public static void main(String[] args) { useInter(new Inter() { @Override public void show() { System.out.println("show"); } });}
public static void useInter(Inter i) { i.show();}11. Lambda 表达式
Lambda 表达式介绍
Lambda 表达式是 JDK8 之后的新特性,可以用来简化匿名内部类的书写 只能简化函数式接口的匿名内部类
函数式接口:有且仅有一个抽象方法的接口(可以有其他非抽象方法),可以加
@FunctionalInterface注解
Lambda 表达式的标准格式
() -> { }| 部分 | 说明 |
|---|---|
() | 形参列表,接口中抽象方法的形参列表 |
-> | 固定写法,代表指向动作 |
{} | 方法体,接口中抽象方法的方法体 |
Lambda 表达式练习
- 抽象方法无参无返回值
- 抽象方法有参无返回值
- 抽象方法有参有返回值
// 案例 1:无参无返回值useInter(() -> System.out.println("Lambda"));
// 案例 2:有参无返回值useInter((int num) -> System.out.println(num));
// 案例 3:有参有返回值useInter((int a, int b) -> a + b);Lambda 表达式的省略规则
- 参数类型可以省略,但多个参数不能只省略一个
- 如果参数只有一个,小括号可以省略
- 如果方法体只有一句话,大括号可以省略,且如果有
return也要省略
// 完整写法(int a, int b) -> { return a + b; }
// 省略参数类型(a, b) -> { return a + b; }
// 省略大括号和 return(a, b) -> a + b;Lambda 表达式与匿名内部类的区别
| 对比项 | 匿名内部类 | Lambda 表达式 |
|---|---|---|
| 类型 | 编译后产生单独的字节码文件(Outer$1.class) | 编译后不产生单独的字节码文件,对应的字节码会在运行时动态生成 |
| 使用范围 | 所有接口和抽象类都可以使用 | 只能简化函数式接口的匿名内部类 |
第十章 常用 API
1. 常用 API 概述
什么是 API
API(Application Programming Interface)—— 应用程序编程接口 就是别人写好的一些类,给咱们程序员直接拿去调用即可解决问题的
已经学习过的 API
| API | 作用 |
|---|---|
| Scanner | 键盘录入 |
| Random | 随机数 |
JDK-API 帮助文档:JDK 提供的类的使用说明书,所有 API 都可以在这里查到
2. String 类
String 类的特点
- Java 程序中所有双引号字符串,都是 String 类的对象
- 字符串在创建之后,其内容不可更改
- 字符串虽然不可改变,但是可以被共享
String s = "abc"; // 字符串"abc"是 String 类的对象,s 指向它s = "def"; // 不是修改内容,而是把 s 的指向改为新的"def"String 类的常见构造方法
| 构造方法 | 说明 |
|---|---|
public String() | 创建空白字符串,不含任何内容 |
public String(String original) | 根据传入的字符串,创建字符串对象 |
public String(char[] chs) | 根据字符数组,创建字符串对象 |
// 两种最常用的方式String s1 = new String("abc"); // 通过构造方法String s2 = "abc"; // 直接赋值(推荐)String 类的常见面试题(== 比较)
- 字符串字面量赋值,先在字符串常量池中查找; 如果存在,则直接共享,不创建新对象;不存在则创建新对象并存入池中
String s1 = "abc";String s2 = "abc";System.out.println(s1 == s2); // true(共享常量池中同一个对象)
- 通过
new关键字创建的字符串对象,每次都会在堆内存中开辟新空间
String s1 = "abc";String s2 = new String("abc");System.out.println(s1 == s2); // false(地址不同)
- 字符串拼接
+的底层会创建StringBuilder对象,拼接后再toString();
结果是新对象,所以和常量池中的不相等
String s1 = "abc";String s2 = "ab" + "c"; // 常量拼接,编译期优化 → trueString s3 = s2 + "c"; // 变量拼接 → new StringBuilder().toString() → falseSystem.out.println(s1 == s3); // falseflowchart LR
subgraph heap[堆内存]
ST[StringTable 字符串常量池]
H[new String 对象]
end
subgraph stack[栈内存]
A[s1]
B[s2]
end
A --> ST
B --> H
H -.不共享.-> ST结论:比较字符串内容是否相同,请使用
equals()方法,而不是==
String 用于比较的方法
| 方法 | 说明 |
|---|---|
public boolean equals(要比较的字符串) | 完全一样结果才是 true,否则为 false |
public boolean equalsIgnoreCase(要比较的字符串) | 忽略大小写的比较 |
String s1 = "abc";String s2 = "ABC";System.out.println(s1.equals(s2)); // falseSystem.out.println(s1.equalsIgnoreCase(s2)); // true案例:模拟用户登录
需求:已知正确的用户名和密码,请用程序实现模拟用户登录。总共给三次机会,登录之后,给出相应的提示
import java.util.Scanner;
public class Login { public static void main(String[] args) { String rightUsername = "itheima"; String rightPassword = "123456"; Scanner sc = new Scanner(System.in);
for (int i = 0; i < 3; i++) { System.out.print("请输入用户名:"); String username = sc.next(); System.out.print("请输入密码:"); String password = sc.next();
if (rightUsername.equals(username) && rightPassword.equals(password)) { System.out.println("登录成功!"); break; } else { System.out.println("登录失败,还有 " + (2 - i) + " 次机会"); } } }}String 字符串的遍历
| 方法 | 说明 |
|---|---|
public char[] toCharArray() | 将字符串转换为字符数组 |
public char charAt(int index) | 根据索引找字符 |
public int length() | 返回字符串的长度 |
// 方式一:charAt + lengthString s = "abcde";for (int i = 0; i < s.length(); i++) { System.out.println(s.charAt(i));}
// 方式二:toCharArrayfor (char c : s.toCharArray()) { System.out.println(c);}案例:统计字符次数
需求:键盘录入一个字符串,统计该字符串中大写字母字符、小写字母字符、数字字符出现的次数(不考虑其他字符) 例如:
aAb3&c2B*4CD1→ 小写:3 个;大写:4 个;数字:4 个
String s = new Scanner(System.in).next();int upper = 0, lower = 0, digit = 0;for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c >= 'A' && c <= 'Z') upper++; else if (c >= 'a' && c <= 'z') lower++; else if (c >= '0' && c <= '9') digit++;}System.out.println("大写:" + upper + ",小写:" + lower + ",数字:" + digit);String 字符串的截取方法
| 方法 | 说明 |
|---|---|
public String substring(int beginIndex, int endIndex) | 截取;包头不包尾,只有返回值才是截取的小串 |
public String substring(int beginIndex) | 从 beginIndex 截取到末尾 |
String s = "abcdef";System.out.println(s.substring(1, 3)); // bc(包左不包右)System.out.println(s.substring(2)); // cdef案例:手机号屏蔽
需求:以字符串的形式从键盘接受一个手机号,将中间四位号码屏蔽 最终效果为:
156****1234
String phone = "15612341234";String result = phone.substring(0, 3) + "****" + phone.substring(7);System.out.println(result); // 156****1234String 字符串的替换方法
| 方法 | 说明 |
|---|---|
public String replace(旧值, 新值) | 返回值才是替换之后的结果,原串不变 |
String s = "abcabc";String r = s.replace("a", "x");System.out.println(r); // xbcxbcSystem.out.println(s); // abcabc(不变)案例:敏感词替换
需求:键盘录入一个字符串,如果字符串中包含
(TMD),则使用***替换
String s = new Scanner(System.in).next();String r = s.replace("TMD", "***");System.out.println(r);String 字符串的切割方法
| 方法 | 说明 |
|---|---|
public String[] split(String regex) | 根据传入的字符串作为规则进行切割; 将切割后的内容存入字符串数组中,并将字符串数组返回 |
String s = "a,b,c,d";String[] arr = s.split(",");for (String x : arr) { System.out.println(x);}注意:切割时如果中间存在空格,需要用
, +或","处理;
切割规则如果是特殊字符(如.、|),需要转义(加\\)
String 方法小结
| 方法 | 说明 |
|---|---|
equals(要比较的字符串) | 比较内容 |
equalsIgnoreCase(要比较的字符串) | 比较内容,忽略大小写 |
toCharArray() | 将字符串转换为字符数组 |
charAt(int index) | 根据索引找字符 |
length() | 返回字符串的长度 |
substring(int beginIndex) | 截取到末尾 |
substring(int beginIndex, int endIndex) | 根据开始和结束索引做截取,包含头不包含尾 |
replace(旧值, 新值) | 替换 |
split(String regex) | 切割 |
3. StringBuilder 类
StringBuilder 引入
StringBuilder 可以大幅度提高字符串操作效率 如果在大量拼接字符串的场景下,使用 String 的
+会反复创建对象,效率低,推荐用 StringBuilder
StringBuilder 介绍
- StringBuilder 是字符串的缓冲区,我们可以将其理解为是一种容器
- StringBuilder 是一种可变的字符序列
StringBuilder 构造方法
| 构造方法 | 说明 |
|---|---|
public StringBuilder() | 创建一个空的字符串缓冲区(容器) |
public StringBuilder(String str) | 创建一个字符串缓冲区,并初始化好指定的参数内容 |
StringBuilder sb1 = new StringBuilder();StringBuilder sb2 = new StringBuilder("abc");StringBuilder 常用方法
| 方法名 | 说明 |
|---|---|
public StringBuilder append(任意类型) | 添加数据,并返回对象本身(支持链式编程) |
public StringBuilder reverse() | 反转容器中的内容 |
public int length() | 返回长度(字符出现的个数) |
public String toString() | 通过 toString() 就可以实现把 StringBuilder 转换为 String |
StringBuilder sb = new StringBuilder();sb.append("a").append("b").append("c"); // 链式编程System.out.println(sb); // abcSystem.out.println(sb.length()); // 3
String s = sb.toString(); // 转成 String案例:回文字符串
需求:键盘接受一个字符串,程序判断出该字符串是否是对称字符串,并在控制台打印是或不是 对称字符串:
123321、111非对称字符串:123123
String s = new Scanner(System.in).next();StringBuilder sb = new StringBuilder(s);String reverse = sb.reverse().toString();
if (s.equals(reverse)) { System.out.println("是对称字符串");} else { System.out.println("不是对称字符串");}案例:按照指定的格式拼接字符串
需求:定义一个方法,把
int数组中的数据按照指定的格式拼接成一个字符串返回。调用该方法,并在控制台输出结果 例如:数组为int[] arr = {1,2,3};→ 输出[1, 2, 3]
public static String arrayToString(int[] arr) { StringBuilder sb = new StringBuilder("["); for (int i = 0; i < arr.length; i++) { if (i == arr.length - 1) { sb.append(arr[i]).append("]"); } else { sb.append(arr[i]).append(", "); } } return sb.toString();}StringBuilder 原理(一个加号,堆内存中俩对象)
当执行
String s = s1 + s2;时,底层会做:
- 创建一个
StringBuilder对象- 调用
append()把 s1、s2 加入- 调用
toString()创建新的 String 对象返回
flowchart LR
subgraph stack[栈内存]
s1[s1 = 'a']
s2[s2 = s1 + 'b']
s3[s3 = s2 + 'c']
end
subgraph heap[堆内存]
SB1[StringBuilder 'ab']
ST1[String 'ab']
SB2[StringBuilder 'abc']
ST2[String 'abc']
end
s1 --> H1[StringTable 'a']
s2 --> SB1
SB1 -.toString.-> ST1
s2 --> SB2
SB2 -.toString.-> ST2
s3 --> ST2结论:每次
+都会产生一个 StringBuilder + 一个新 String 对象,频繁拼接效率低
StringBuilder 拼接原理(append 复用自身)
直接使用 StringBuilder 的
append(),方法返回自身对象,始终在同一个缓冲区中操作
flowchart LR
subgraph stack[栈内存]
sb[Reference sb]
end
subgraph heap[堆内存]
BUF[StringBuilder 同一个对象]
end
sb --> BUF
BUF --> A1[append a]
BUF --> A2[append b]
BUF --> A3[append c]优势:不会产生中间 String 对象,效率远高于
+拼接
StringBuilder 小结
StringBuilder 可以大幅度提升字符串操作效率 今后若频繁进行字符串拼接操作,建议使用 StringBuilder
StringBuilder 与 String 互转
// StringBuilder → String:通过 toString()String s = sb.toString();
// String → StringBuilder:通过构造方法StringBuilder sb2 = new StringBuilder("hello");链式编程
StringBuilder 的
append()方法返回的是对象本身,因此可以连续调用
StringBuilder sb = new StringBuilder();sb.append("a").append("b").append("c").reverse().append("d");System.out.println(sb); // dcba第十一章 集合基础
1. 集合概述
什么是集合
集合是一种容器,用来装数据的,类似于数组
数组 vs 集合
| 对比项 | 数组 | 集合 |
|---|---|---|
| 长度 | 固定不变 | 可变 |
| 类型 | 基本数据类型 / 引用数据类型 | 只能存放引用数据类型(基本类型用包装类) |
| 使用场景 | 元素个数固定不变 | 元素个数经常发生改变 |
结论:开发中集合用的更多,因为业务中数据量往往是动态的
集合的体系结构
classDiagram
class Collection
class Map
Collection <|-- List
Collection <|-- Set
List <|-- ArrayList
List <|-- LinkedList
List <|-- Vector
Set <|-- HashSet
Set <|-- TreeSet
Set <|-- LinkedHashSet
Map <|-- HashMap
Map <|-- LinkedHashMap
Map <|-- TreeMapCollection 接口:单列集合,每次操作存储一个元素 Map 接口:双列集合,每次操作存储一对键值对(key-value)
2. ArrayList
ArrayList 长度可变原理
- 当创建 ArrayList 集合容器的时候,底层会存在一个长度为 10 的空数组
- 当数组存满后,再次添加新元素时,ArrayList 会扩容原数组 1.5 倍 大小的新数组
- 将原数组数据拷贝到新数组中
- 将新元素添加到新数组中
flowchart TB
A[创建 ArrayList 底层数组长度 10] --> B{数组是否存满?}
B -- 否 --> C[直接添加]
B -- 是 --> D[扩容原数组 1.5 倍新数组]
D --> E[拷贝原数据到新数组]
E --> F[添加新元素]
F --> B集合和数组的使用选择小结
- 数组:存储的元素个数固定不变
- 集合:存储的元素个数经常发生改变
3. 创建集合
创建集合的两种方式
方案 1(不推荐):不写泛型,添加数据时可以是任意类型,但取数据时容易出问题
ArrayList list = new ArrayList();list.add(1);list.add(12.3);list.add('a');list.add("abc");list.add(false);方案 2(推荐):写泛型,限制集合中只能存储指定类型的元素
ArrayList<String> list = new ArrayList<>();list.add(1); // ❌ 编译报错,类型不匹配list.add(12.3); // ❌list.add("abc"); // ✅建议使用方案 2,创建集合的时候加入泛型可以使数据严谨和规范
创建集合小结
- 集合在创建的时候如果不加泛型,添加数据的时候可以是任意类型
- 建议创建的时候加入泛型,保证数据的严谨和规范
- JDK7 版本开始,右侧
<>中可以省略类型
// JDK7 前:右侧需要写泛型ArrayList<String> list = new ArrayList<String>();
// JDK7+:右侧 <> 可省略(菱形语法)ArrayList<String> list = new ArrayList<>();4. ArrayList 常用成员方法
增删改查方法
| 方法名 | 说明 |
|---|---|
public boolean add(E e) | 将指定的元素添加到此集合的末尾 |
public void add(int index, E element) | 在此集合中的指定位置插入指定的元素 |
public E get(int index) | 返回指定索引处的元素 |
public int size() | 返回集合中的元素的个数 |
public E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
public boolean remove(Object o) | 删除指定的元素,返回删除是否成功 |
public E set(int index, E element) | 修改指定索引处的元素,返回被修改的元素 |
集合的遍历
ArrayList<String> list = new ArrayList<>();list.add("aaa");list.add("bbb");list.add("ccc");
// 方式 1:普通 forfor (int i = 0; i < list.size(); i++) { System.out.println(list.get(i));}
// 方式 2:增强 for(推荐)for (String s : list) { System.out.println(s);}5. 集合练习
练习 1:集合存储字符串并遍历
需求:创建一个存储字符串的集合,内部存储 3 个字符串元素,使用程序实现在控制台遍历该集合
ArrayList<String> list = new ArrayList<>();list.add("张三");list.add("李四");list.add("王五");
for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i));}练习 2:集合存储学生对象并遍历
需求:创建一个存储学生对象的集合,存储 3 个学生对象,使用程序实现在控制台遍历该集合 学生的姓名和年龄来自于键盘录入
public class Student { private String name; private int age;
public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } // 省略 getter / setter}ArrayList<Student> list = new ArrayList<>();Scanner sc = new Scanner(System.in);
for (int i = 0; i < 3; i++) { System.out.print("请输入姓名:"); String name = sc.next(); System.out.print("请输入年龄:"); int age = sc.nextInt();
Student stu = new Student(name, age); list.add(stu);}
for (Student stu : list) { System.out.println(stu.getName() + "," + stu.getAge());}集合存储对象内存图
以
addStudent(list)三个学生对象为例,逐步展示栈内存和堆内存的变化
flowchart LR
subgraph stack[栈内存]
M[方法 main ArrayList list 001]
A1[方法 addStudent name 张三 age 23 stu 00A]
A2[方法 addStudent name 李四 age 24 stu 00B]
A3[方法 addStudent name 王五 age 25 stu 00C]
end
subgraph heap[堆内存]
L[new ArrayList 001 内部存 00A 00B 00C]
O1[new Student 00A 张三 23]
O2[new Student 00B 李四 24]
O3[new Student 00C 王五 25]
end
M --> L
A1 -.add 后.-> L
A2 -.add 后.-> L
A3 -.add 后.-> L
L --> O1
L --> O2
L --> O3关键结论:集合中存储的是对象的地址值,而非对象本身 由于 ArrayList 底层是数组,所以数组中存的是对象的地址引用
练习 3:集合删除元素
需求:创建一个存储 String 的集合,内部存储
(test, 张三, 李四, test, test)字符串 删除所有的test字符串,删除后,将集合剩余元素打印在控制台
ArrayList<String> list = new ArrayList<>();list.add("test");list.add("张三");list.add("李四");list.add("test");list.add("test");
// 注意:不能一边遍历一边删除,会导致索引错乱// 方案 1:倒着遍历for (int i = list.size() - 1; i >= 0; i--) { if ("test".equals(list.get(i))) { list.remove(i); }}
// 方案 2:使用迭代器(推荐)Iterator<String> it = list.iterator();while (it.hasNext()) { if ("test".equals(it.next())) { it.remove(); // 使用迭代器自己的 remove }}练习 4:集合元素筛选
需求:定义一个方法,方法接收一个集合对象(泛型为
Student),方法内部将年龄低于 18 的学生对象找出,并存入新集合对象,方法返回新集合
public static ArrayList<Student> filterStudents(ArrayList<Student> list) { ArrayList<Student> result = new ArrayList<>(); for (Student s : list) { if (s.getAge() < 18) { result.add(s); } } return result;}注意:返回值必须是一个新的集合,不能修改原集合
集合小结
- 集合是一种长度可变的容器
- 开发中推荐使用方案 2(加泛型)创建集合,保证数据严谨
- 集合中存储的是对象的地址,不是对象本身
- 遍历集合常用普通 for(带索引)和增强 for
- 删除元素不能一边遍历一边删除,要倒着删或用迭代器
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 9 天
部分内容可能已过时
评论