[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rb3mx9IG-1677431488888)(./assets/%E7%8E%84%E5%AD%90Share%E4%B8%89%E7%89%88.jpg)]
玄子Share-BCSP助学手册之JAVA开发
前言:
此文为玄子,学习 BCSP 一二期后整理的文章,文中对知识点的解释仅为个人理解,源码均可直接复制运行
配套PPT,站点源码,等学习资料请加文章封面联系方式
针对课程的一些调整:
- 一期 15 章的 String 类,调整至手册第十三章
13.3 String 类
- 部分章节的知识点会有相对应的增改,以课本为准
- ……
文章目录
- 玄子Share-BCSP助学手册之JAVA开发
- 一、预科
- 二、JAVA 基础
- 三、流程控制语句
- 四、数组
- 五、面向对象
- 六、方法
- 七、封装
- 八、继承
- 九、多态
- 十、抽象类和接口
- 十一、异常
- 十二、集合框架
- 十三、实用类
- 十四、IO流
- 十五、多线程
- 十六、知识点补充
一、预科
1.1 JAVA 介绍
Java 是 Sun Microsystems 于1995年推出的高级编程语言
1.1.1 JAVA 之父
詹姆斯·高斯林(James Gosling)是一名软件专家,1955年5月19日出生于加拿大,Java编程语言的共同创始人之一,一般公认他为Java之父
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2bRRaOAo-1677431488891)(./assets/java.jpg)]
1.1.2 JAVA 的核心优势
跨平台是Java
语言的核心优势,赶上最初互联网的发展,并随着互联网的发展而发展,建立了强大的生态体系,目前已经覆盖 IT 各行业的第一大语言
,是计算机界的英语
1.1.3 JAVA 各版本的含义
JavaSE(Java Standard Edition):标准版,定位在个人计算机上的应用
JavaEE(Java Enterprise Edition):企业版,定位在服务器端的应用
JavaME(Java Micro Edition):微型版,定位在消费性电子产品的应用上
1.1.4 JAVA 运行机制
计算机高级语言的类型主要有编译型和解释型两种,而Java 语言是两种类型的结合
1.1.4 JVM、JRE 和 JDK
Java Virtual Machine (JVM) :用于执行字节码的”虚拟计算机”。不同的操作系统有不同版本 JVM,屏蔽了底层运行平台的差别,是实现跨平台的核心。
Java Runtime Environment (JRE) 包含:Java 虚拟机、库函数等。
Java Development Kit (JDK)包含:JRE,编译器和调试器等。
1.2 JAVA 开发环境搭建
1.2.1 下载 JDK
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eIBu3bBI-1677431488891)(./assets/202212221415811.png)]
下滑找到 Windows x64 安装程序,点击后方链接下载安装包。
1.2.2 安装 JDK
-
按照图中指引一直下一步就可以了
!!!中间可以更改安装位置,但不建议更改,为了方便后期配置环境变量。
1.2.3 配置环境变量
-
右键此电脑属性
-
高级系统设置
-
点击右下角环境变量
-
新建环境变量
-
变量名:
JAVA_HOME
变量值:java JDK 安装路径
默认为:
C:\Program Files\Java\jdk1.8.0_341
设置完成后点击确定
-
再次点击新建
变量名:
CLASSPATH
变量值:
.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar
!!!变量值是固定的,注意变量值开头为英文字符点
.
-
下滑找到
Path
双击变量值进入设置然后点击右上角新建,值为 JDK 安装的
bin
目录默认为:
C:\Program Files\Java\jdk1.8.0_341\bin
!!!请注意这个值和 JAVA_HOME 是不一样的,要进入到
bin
目录的路径后在复制 -
然后继续添加两条变量
变量固定分别为:
%JAVA_HOME%\bin
%JAVA_HOME%\jre\bin
-
这里直接点击编辑本文,在变量尾部一次添加完效果是一样的
变量值:
C:\Program Files\Java\jdk1.8.0_341\bin;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
1.2.4 检验环境变量
-
键盘按下
Win + R
输入cmd
后按回车 -
在窗体输入:
Java -version
!!!java 后面有一个空格
-
显示 java version “1.8.0_341” 即为环境变量配置成功
后面的
1.8.0_341
就是所安装 java 的 JDK 版本 -
恭喜你!到这里 JDK 的下载、安装、配置环境变量就已经全部完成了
1.3 编写第一个 JAVA 程序
1.3.1 编写 JAVA 代码
-
在桌面上右键新建文本文档
-
将新建的文本文档更名为
ChangeTheWorld
-
如果你新建的文本文档没有显示
.txt
后缀的话需要在文件资源管理器中设置显示 -
鼠标双击打开文本文档输入以下代码
public class ChangeTheWorld {
public static void main(String[] args) {
System.out.println("Change The World!");
}
}
class 后面的代码要和文件名一致
!!!全文都是在英文输入法下编写
1.3.2 执行 JAVA 程序
-
将文件名后缀修改为
.java
例如:ChangeTheWorld.java
-
将修改后的 Java 文件复制到任意磁盘根目录
-
点击文件地址栏输入
cmd
回车 -
分别输入
javac
和java
代码执行编译,下面显示的Change The World
即为我们编写的 Java 输出语句所输出的代码javac ChangeTheWorld.java
java ChangeTheWorld
javac 后面跟文件全名,需要带
.java
后缀java 后直接写文件名即可
-
到这里你已经可以独立编写,编译 Java 代码了,后面我们会在学习一些计算基础知识
1.4 电脑常用快捷键
熟练的使用电脑快捷键,可以让我们的工作效率事半功倍
按键 | 说明 |
---|---|
Ctrl + C | 复制 |
Ctrl + V | 粘贴 |
Ctrl + A | 全选 |
Ctrl + X | 剪切 |
Ctrl + Z | 撤销 |
Ctrl + Y | 撤回 |
Ctrl + S | 保存 |
Alt + F4 | 关闭窗体 |
Alt + Tab | 窗体切换 |
Windows + R | 运行 |
Windows + L | 快速锁屏 |
Windows + E | 资源管理器 |
Ctrl + Shift + ESC | 任务管理器 |
1.4.1 功能性快捷键
功能性快捷键在大多软件中都适用,如
按键 | 说明 |
---|---|
Ctrl + C | 复制 |
Ctrl + V | 粘贴 |
1.5 DOS 命令
1.5.1 打开 CMD 的方法
-
开始 > 系统 > 命令提示符
-
按下 Win + R 输入 cmd 打开控制台(推荐使用)
-
在任意的文件夹下面,按住 Shift + 鼠标右键点击,在此处打开命令行窗口
-
资源管理器的地址栏输入
cmd
1.5.2 常用 DOS 命令
命令 | 说明 | 备注 |
---|---|---|
C: | 选择盘符 | 盘符名称加冒号 |
dir | 查看当前目录下的所有文件 | |
cd /d C: | 盘符切换 | Change Directory |
cd 文件名\文件名 | 目录切换 | |
cd… | 返回上一级目录 | |
cls | 清理屏幕 | Clear Screen |
exit | 退出 | |
ipconfig | 查看电脑 IP 地址 | |
clac mspaint notepad | 打开本地程序 | 计算器 画图 记事本 |
ping 网址 | ping命令 | |
md 文件名 | 创建文件夹 | Make Directory |
cd> a.txt | 创建文件 | 注意文件后缀 |
del a.txt | 删除文件 | 注意文件后缀 |
rd 文件名 | 移除目录 | Remove Directory |
1.6 计算机语言发展史
1.6.1 一代语言
机器语言:
-
我们都知道计算机的基本计算方式都是基于二进制的方式。
-
二进制:010111001010110010110100
-
这种代码是直接输入给计算机使用的,不经过任何的转换!
十进制 | 二进制 |
---|---|
1 | 1 |
2 | 10 |
3 | 11 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
8 | 1000 |
16 | 10000 |
32 | 100000 |
1.6.2 二代语言
汇编语言
-
解决人类无法读懂机器语言的问题
-
指令代替二进制
目前应用
- 逆向工程
- 机器人
- 病毒
1.6.3 三代语言
-
高级语言
-
大体上分为:面向过程和面向对象两大类
-
C语言是典型的面向过程的语言。C++、JAVA是典型的面向对象的语言
高级语言
-
C
-
C++
-
JAVA
-
C#
-
Python
先有
C
语言,改良后为C++
面向对象语言,再有JAVA
,C#
是微软基于JAVA
研发的.NET
平台软件
1.7 安装 JAVA 开发工具
1.7.1 Intellij IDEA 开发工具
Intellij IDEA 是目前主流的 Java 开发工具(付费软件),安装教程这里不过多介绍
详细安装教程请加文章封面联系方式
1.7.2 初始化设置 IDEA 2022.3
工欲善其事比先利其器,Idea 有许多实用的插件和设置,可以提高我们的工作效率
-
汉化,分别点击左上角
File
>Settings
-
按照下图点击
Plugins
搜索Chinese
下载汉化包后点击右下角Apply
应用安装,重启软件即可生效 -
还有一些实用插件分享,从上到下分别是:代码规范,UI美化,汉化包,快捷键提示,彩虹括号,代码提示补全等
-
以及保存代码时自动格式化代码和方法间分割线
二、JAVA 基础
2.1 使用 IDEA 编写 JAVA 程序
2.1.1 Change The World
package CH01_JAVABase;
//Change The World!
public class XZ01_ChangeTheWorld {
public static void main(String[] args) {
System.out.println("Change The World!");
//Change The World!
}
}
public class XZ01_ChangeTheWorld {}
// public 关键字,表示访问权限
// XZ01_ChangeTheWorld 类名与文件名要完全一样
public static void main(String[] args) {}
// main( )方法四要素必不可少 public static void main
// main( )方法是 Java 程序执行的入口点
System.out.println("Change The World!");
// 从控制台输出信息
代码语句 | 说明 | 快捷语句 |
---|---|---|
public static void main(String[] args) { } | Main函数,程序主入口 | main/psvm |
System.out.println( ); | 输出语句 | sout |
2.2 注释
注释不会出现在字节码文件中,即Java 编译器编译时会跳过注释语句
2.2.1 单行注释
单行注释使用//
开头
//我是单行注释
2.2.2 多行注释
多行注释以/*
开头以*/
结尾。注意,多行注释不能嵌套使用
/*
我是多行注释
我是多行注释
*/
2.2.3 文档注释
文档注释以/**
开头以 */
结尾,注释中包含一些说明性的文字及一些 JavaDoc 标签(后期写项目时,可以生成项目的API文档)
/**
* XZ04_Annotate 类(我是文档注释)
* @author 玄子 (作者)
* @version 1.0 (版本)
*/
2.2.4 演示案例
package CH01_JAVABase;
//注释
/**
* XZ04_Annotate 类(我是文档注释)
* @author 玄子 (作者)
* @version 1.0 (版本)
*/
public class XZ04_Annotate {
//我是单行注释
public static void main(String[] args) {
System.out.println("Change The World!");
/*
System.out.println("Change The World!");
System.out.println("我是多行注释!");
*/
}
}
注释语法 | 注释名称 | 快捷键 |
---|---|---|
// | 单行注释 | Ctrl + / |
/* */ | 多行注释 | |
/** */ | 文档注释 |
文档注释参数 | 描述 |
---|---|
@author | 作者名 |
@version | 版本号 |
@since | 指明需要最早使用的jdk版本 |
@param | 参数名 |
@return | 返回值情况 |
@throws | 异常抛出情况 |
JavaDoc 命令是用来生成 API 文档的
2.2.5 JAVA API 文档
JDK 文档是 Oracle 公司提供的一整套文档资料
-
Java 各种技术的详细资料
-
JDK 中提供的各种类型的帮助说明
2.2.6 生成自己的 JAVA API 文档
JavaDoc能够从源代码中抽取类、属性、方法等的注释,形成一个配套的API帮助文档
- 使用IDEA工具生成
- 使用命令行生成
- 在项目地址上输入
cmd
- 然后输入以下代码
javadoc -encoding UTF-8 -charset UTF-8 文件名. java
- 生成 API 文档
- 在项目地址上输入
2.3 数据类型
Java 数据类型分为两大类:基本数据类型(primitive data type)和引用数据类型(reference data type)
2.3.1 整型(byte、short、int、long)
package CH01_JAVABase;
//八大数据类型
public class XZ02_DataType {
public static void main(String[] args) {
int num1 = 1;
byte num2 = 1;
short num3 = 1;
long num4 = 1L;
// long 的数值后面需要加大写字母 L
//整型
}
}
类型 | 占用存储空间 | 表数范围 |
---|---|---|
byte | 1 字节 | -27 ~ 27-1(-128~127) |
short | 2 字节 | -215 ~ 215-1 (-32768~32767) |
int | 4 字节 | -231 ~ 231-1 (-2147483648~2147483647) 约21 亿 |
long | 8 字节 | -263~ 263-1 |
2.3.2 浮点型(double、float)
package CH01_JAVABase;
//八大数据类型
public class XZ02_DataType {
public static void main(String[] args) {
double num5 = 1.1;
float num6 = 1.2F;
// float 的数值后面需要加大写字母 F
//浮点型
}
}
类型 | 占用存储空间 | 表数范围 |
---|---|---|
float | 4 字节 | -3.403E38~3.403E38 |
double | 8 字节 | -1.798E308~1.798E308 |
2.3.3 字符型(char)
package CH01_JAVABase;
//八大数据类型
public class XZ02_DataType {
public static void main(String[] args) {
char ch = 'a';
char ch = '玄';
//单字符
}
}
字符型在内存中占 2 个字节,在 Java 中使用单引号来表示字符常量。例如
'A'
是一个字符,它与"A"
是不同的,"A"
表示含有一个字符的字符串char 类型用来表示在 Unicode 编码表中的字符
Unicode 编码被设计用来处理各种语言的文字,它占 2 个字节,可允许有 65536 个字符
2.3.4 布尔型(boolean)
package CH01_JAVABase;
//八大数据类型
public class XZ02_DataType {
public static void main(String[] args) {
boolean is = false;
boolean is = true;
// 只有两个结果 true false
//布尔型
}
}
2.3.5 引用型(String)
package CH01_JAVABase;
//八大数据类型
public class XZ02_DataType {
public static void main(String[] args) {
String string = "Change The World!";
//引用型,不属于基本数据类型
}
}
2.4 数据类型转换
八种基本数据类型,除了 boolean 类型之外的七种类型是可以自动转化的
自动类型转换指的是容量小的数据类型可以自动转换为容量大的数据类型。如图下所示,的实线表示无数据丢失的自动类型转换,而虚线表示在转换时可能会有精度的损失
2.4.1 隐式类型转换(自动类型转换)
可以将整型常量直接赋值给 byte、short、char 等类型变量,而不需要进行强制类型转换,只要不超出其表数范围即可
package CH01_JAVABase;
//数据类型转换
public class XZ03_TypeConversion {
public static void main(String[] args) {
int a = 1;
double b = a;
System.out.println(b);
// b = 1.0
//隐式数据类型转换,自动转换
}
}
2.4.2 显示类型转换(强制类型转换)
package CH01_JAVABase;
//数据类型转换
public class XZ03_TypeConversion {
public static void main(String[] args) {
double c = 1.2;
int d = (int) c;
System.out.println(d);
// d = 1
//显式数据类型转换转换变量前加 (转换类型)
}
}
2.4.3 数据类型转换拓展
package CH01_JAVABase;
//数据类型转换
public class XZ03_TypeConversion {
public static void main(String[] args) {
char e = 'a';
int f = e + 1;
System.out.println((int) e);
// 97
System.out.println(f);
// 98
System.out.println((char) f);
// b
}
}
2.4.4 注意事项
- 不能对布尔值进行转换
- 不能把对象类型转换为不相干的类型
- 在把高容量转换到低容量的时候,强制转换
- 转换的时候可能存在内存溢出,或者精度问题
- 这里的数据类型转换就证明了 char 类型的值本质上还是数字
- ASCII编码表
2.5 变量
package CH01_JAVABase;
//变量
public class XZ05_Variable {
static String str = "XuanZi"; //类变量
//成员变量
int age; //默认值 0
String Sex = "男"; //默认值null
boolean IsNull; //默认值false
public static void main(String[] args) {
//局部变量
int value = 1;
String name = "玄子";
//创建类的对象调用方法
XZ05_Variable variable = new XZ05_Variable();
System.out.println(name);
//输出局部变量
variable.set();
//调用类方法
System.out.println(variable.IsNull);
//实例变量需要创建对象后才能调用
System.out.println(str);
//类变量可直接调用
}
public void set() {
String conn = "XuanZiShare"; //局部变量
System.out.println(age);
System.out.println(Sex);
System.out.println(conn);
}
}
2.5.1 变量(variable)
- 变量本质上就是代表一个”可操作的存储空间”,空间位置是确定的,但是里面放置什么值不确定
- Java 是一种强类型语言,每个变量都必须声明其数据类型。变量的数据类型决定了变量占据存储空间的大小
- 可通过变量名来访问“对应的存储空间”,从而操纵这个“存储空间”存储的值
2.5.2 注意事项
- 每个变量都有类型,类型可以是基本类型,也可以是引用类型
- 变量名必须是合法的标识符
- 变量声明是一条完整的语句,因此每一个声明都必须以分号结束
2.5.3 类变量具有默认值
类变量具有默认值,声明时可不对其赋值
变量类型 | 默认值 |
---|---|
整型(int,byte,short,long) | 0 |
单精度浮点型(float) | 0.0f |
双精度浮点型(double) | 0.0d |
字符型(char) | /u0000 |
布尔型(boolean) | false |
引用类型(array,String,class,……) | null |
2.5.4 变量的分类和作用域
变量有三种类型
- 局部变量
- 成员变量(实例变量)
- 静态变量(类变量)
类型 | 声明位置 | 从属于 | 生命周期(作用域) |
---|---|---|---|
局部变量 | 方法或语句块内部 | 方法/语句块 | 从声明位置开始,直到方法或语 句块执行完毕,局部变量消失 |
成员变量 | 类内部,方法外部 | 对象 | 对象创建,成员变量也跟着创建 对象消失,成员变量也跟着消失 |
静态变量 | 类内部,static 修饰 | 类 | 类被加载,静态变量就有效 类被卸载,静态变量消失 |
2.6 常量
package CH01_JAVABase;
//常量
public class XZ06_Constant {
// final 数据类型 常量名 = 值;
public static final double PI = 3.14;
// public static 修饰符,不存在先后顺序
public static void main(String[] args) {
System.out.println(PI);
}
}
2.6.1 常量(Constant)
- 初始化(initialization)后不能再改变值!不会变动的值
- 所谓常量可以理解成一种特殊的变量,它的值被设定后,在程序运行过程中不允许被改变
- 常量名一般使用大写字符
2.7 运算符
计算机的基本用途就是执行数学运算,Java 提供了一套丰富的运算符来操作变量
2.7.1 一元运算符
int num1 = 1;
double num2 = 2.5;
System.out.println(num1+num2);// 1
// mum1 + 1 上一句输出后才+1 2
// 1 + mum1 下一句输出前就+1 3
System.out.println(num2 % num1); //0.5
// + - * / %
// 加 减 乘 除 余
加、减 、乘、除。与正常数学运算用法一致,余(%)在 Java 中表示求余数 例如
2.5 % 1
的余数就是0.5
如果两个数都为
int
型的话,余数会舍去尾数,取整数
2.7.2 二元运算符
int num1 = 1;
System.out.println(num1++);
// ++ 写在变量后面等于 mum1 + 1 输出后才 +1 = 2
System.out.println(++num1);
// ++ 写在变量前面等于 1 + mum1 输出前就 +1 = 2 + 1 = 3
System.out.println(num1 + 1); // 4
// 二元运算符,是改变,变量实际值进行运算,值会随着运算而改变
// ++ --
// 自增 自减
2.7.3 赋值运算符
int num1 = 1;
System.out.println(num1);
// =
// 赋值
2.7.4 扩展运算符
int a = 10;
int b = 20;
System.out.println(a += b);
// a = a + b = 10 + 20 = 30
System.out.println(a); // 30
//和二元运算符一样,运算时,是改变自身实际值运算
// += -= *= /= %
// 加等 减等 乘等 除等 余等
2.7.5 关系运算符
int num1 = 1;
double num2 = 2.5;
System.out.println(num1 <= num2);
// 结果是布尔型 true 或 false
// > < >= <= != ==
// 大于 小于 大于等于 小于等于 不等于 等等于
2.7.6 逻辑运算符
int num1 = 1;
double num2 = 2.5;
System.out.println(num1 > num2 || num2 > num1);
//两个条件一个为真就返回true
//如果第一个条件就为假直接返回 false,不再判断第二个条件
System.out.println(num1 > num2 && num2 > num1);
//两个条件均为真才返回 true
System.out.println(!(num1 > num2 && num2 > num1));
//判断结果取反
// 结果是布尔型 true 或 false
// && || !
// 与 或 非
2.7.7 位辑运运算符
char A = 'A';
char B = 'B';
System.out.println("A:" + (int) A);
System.out.println("B:" + (int) B);
System.out.println(A ^ B);
// -------二进制---------
// A = 0011 1100
// B = 0000 1101
// --------判断--------
// A&B = 0000 1100 不同为0相同为1
// A|B = 0011 1101 有1即为1
// A^B = 0011 0001 相同为0不同为1
// ~B = 1111 0010 1为0 0为1
System.out.println(2 << 3);
// -------二进制---------
// 0000 0000 0
// 0000 0001 1
// 0000 0010 2
// 0000 0011 3
// 0000 0100 4
// 0000 1000 8
// 0001 0000 16
// & | ^ ~ << >>
// 与 或 非 异或(按位取反) 左移(*) 右移(/)
2.7.8 条件运算符
int score = 60;
String type = score >= 60 ? "及格" : "不及格";
System.out.println(type);
// ? :
// 布尔 ? 条件1 : 条件2
// 如果布尔结果为 true 那么结果为条件1,否则结果为条件2
2.7.9 字符串连接符
System.out.println("" + 10 + 20); // 1020
System.out.println(10 + 20 + ""); // 30
// String写在前后的区别
System.out.println("" + (10 + 20)); // 30
// ()加强运算优先级
2.7.10 算术方法
System.out.println("Math.pow(2, 3) = " + Math.pow(2, 3));
// 2的三次方 8.0
System.out.println("Math.pow(3, 2) = " + Math.pow(3, 2));
// 3的二次方 9.0
// Math.方法
2.7.11 常用运算符表
运算符种类 | 符号 | 描述 |
---|---|---|
算术运算符(一元) | +,-,*,/,% | 加,减,乘,除,余 |
算术运算符(二元) | ++,– | 自增,自增 |
赋值运算符 | = | 赋值 |
扩展运算符 | +=,-=,*=,/=,%= | 加等,减等,乘等,除等,余等 |
关系运算符 | >,<,>=,<=,==,!=,instanceof | 大于,小于,大于等于,小于等于,等等于,不等于,实例判断 |
逻辑运算符 | &&,||,!,^ | 与,或,非,按位 |
位辑运运算符 | &,|,^,~ , >>,<< | 与,或,非,异或(按位取反),左移(*),右移(/) |
条件运算符(三目) | ? : | 布尔 ? 条件1 : 条件2 如果布尔结果为 true 那么结果为条件1,否则结果为条件2 |
字符串连接符 | + | 拼接两个字符串 |
2.8 转义符
2.8.1 println() 与 print() 的区别
System.out.println("Change The World!");
// 打印完引号中的信息后会自动换行
System.out.println("Change The World!");
// 打印输出信息后不会自动换行
2.8.2 转义符 \n 与 \t
package CH01_JAVABase;
//转义符
public class XZ08_EscapeCharacter {
public static void main(String[] args) {
System.out.println("人生若只如初见,何事秋风悲画扇。");
System.out.println("============================");
System.out.print("人生若只如初见,");
//这里的输 print 加上ln同样表示换行
System.out.println("何事秋风悲画扇。");
System.out.println("============================");
System.out.println("人生若只如初见,\n何事秋风悲画扇。");
System.out.println("============================");
System.out.println("人生若只如初见,\t何事秋风悲画扇。");
//\n 换行
//\t 占位符
}
}
2.8.3 常用转义符表
转义字符 | 意义 | ASCII码值(十进制) |
---|---|---|
\a | 响铃(BEL) | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\ | 代表一个反斜线字符’’ | 092 |
’ | 代表一个单引号(撇号)字符 | 039 |
" | 代表一个双引号字符 | 034 |
? | 代表一个问号 | 063 |
\0 | 空字符(NULL) | 000 |
\ooo | 1到3位八进制数所代表的任意字符 | 三位八进制 |
\xhh | 十六进制所代表的任意字符 | 十六进制 |
2.9 命名规范与关键字
package CH01_JAVABase;
//命名规范
public class XZ09_NamingSpecification {
public static void main(String[] args) {
// Java 所有的组成部分都需要名字。
// 类名、变量名以及方法名都被称为标识符。
String name;
int num;
double value;
boolean is;
//尽量使用英语单词作为标识符
//常用命名法
String studentName = "玄子";
//驼峰命名法:以小写字母开头,第二个及以后单词首字母大写
String StudentName = "玄子";
//帕斯卡命名法:以大写字母开头,第二个及以后单词首字母大写
}
}
2.9.1 JAVA 常用关键字
——— | ——— | ——— | ——— | ——— |
---|---|---|---|---|
abstract | assert | boolean | break | byte |
case | catch | char | class | continue |
default | do | double | else | enum |
extends | final | finally | float | for |
if | implements | import | int | interface |
instanceof | long | native | new | package |
private | protected | public | return | short |
static | strictfp | super | switch | synchronized |
this | throw | throws | transient | try |
void | volatile | while |
2.9.2 识符命名规范
- 所有标识符应具有实际意义,尽量不要使用 a、b 这样的无意义命名
- 所有的标识符都应该以字母(A-Z或者a-z),美元符($)、或者下划线(_)开始
- 首字符之后可以是字母(A-Z或者a-z),美元符($)、下划线(_)或数字的任何字符组合
- 不能使用关键字作为变量名或方法名
- 识符是大小写敏感的
- 合法标识符举例:age、$salary、_value、_1_value
- 非法标识符举例:123abc、-salary、#abc
- 可以使用中文命名,但是一般不建议这样去使用,也不建议使用拼音,很Low
2.9.3 常用命名法
- 所有变量、方法、类名:见名知意,具有实际意义
- 类成员变量:驼峰命名法:studentName
- 局部变量:驼峰命名法:studentAge
- 常量:以大写字母命名,下划线拼接:MAX_VALUE
- 类名:帕斯卡命名法:StudentName
- 方法名:帕斯卡命名法:StudentAge( )
- 所有方法都带有( )
2.10 包机制
package CH01_JAVABase;
//包机制
public class XZ10_PackageMechanism {
public static void main(String[] args) {
// 为了更好地组织类,Java提供了包机制,用于区别类名的命名空间。
// 包语句的语法格式为:
// package pkg1[. pkg2[. pkg3...]];
// 一般利用公司域名倒置作为包名;com.XuanZiShare.www
// 为了能够使用某一个包的成员,我们需要在Java程序中明确导入该包。
// 使用“import”语句可完成此功能
// import package1[.package2...].(classname|*);
// *通配符 所有
}
}
三、流程控制语句
3.1 Scanner 用户交互
Scanner 类是在 jdk1.5 版本引入的,它在 java 的 util 工具包下,主要用于扫描用户从控制台输入的文本。当我们需要通过控制台输入数据时,只需要事先导入 java.util 包中的 Scanner 类,然后调用 Scanner 类,我们的程序就能获取我们在控制台所输入的数据了
3.1.1 导包
import java.util.Scanner;
方法 | 描述 |
---|---|
boolean hasNext() | 判断输入的数据是否为String类型 |
boolean hasNextInt() | 判断输入的数据是否为int类型 |
boolean hasNextDouble() | 判断输入的数据是否为double类型 |
boolean hasNextBoolean() | 判断输入的数据是否为boolean类型 |
在 IDEA 中可直接创建 Scanner 对象 IDEA 会自动帮我们导包
3.1.2 基本用法
package CH02_JAVAProcessControl;
//基础Scanner
import java.util.Scanner;
//导包
public class XZ01_UserInteraction {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// 创建Scanner对象
// input 是对象名称,在符合命名规范前提下,可随意命名
System.out.println("请输入:");
if (input.hasNext()) {
// 判断用户是否输入数据
String i = input.next();
// 声明变量接收用户输入数据,例如
// input.nextDouble();
// input.nextInt();
System.out.println(i);
// 输出接收用户输入数据的变量
}
input.close();
// 关闭Scanner对象
}
}
通过 Scanner 类的 next() 与 nextLine() 方法获取输入的字符串,在读取前我们一般需要使用 hasNext() 与 hasNextLine() 判断是否还有输入的数据
使用完Scanner后,我们一定要记得将它关闭,因为使用Scanner本质上是打开了一个 IO 流,如果不关闭的话,它将会一直占用系统资源。注意一旦你关闭后,就算在
input.close();
这行代码后你再重新创建 Scanner 对象也不能重新再打开一个扫描器了,如果继续使用程序会报错,所以一定要在用不到扫描器之后再关闭,即把input.close();
放到代码的最后
3.1.3 next() 与 nextLine() 的区别
package CH02_JAVAProcessControl;
//基础Scanner
import java.util.Scanner;
public class XZ01_UserInteraction2 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("next() 与 nextLine() 的区别");
String i2 = input.nextLine();
// String i2 = input.next();
System.out.println(i2);
// NextLine与Next的区别:
// NextLine会记录用户输入直到按下回车键的所有数据
// Next遇到空格就自动停止
input.close();
}
}
3.1.4 next() 用法总结
- 一定要读取到有效字符后才可以结束输入
- 对输入的有效字符之前所遇到的空白,会自动将其去除
- 只有输入的有效字符后才将其后面输入的空白作为结束符
- next()不能得到带有空格的字符串
3.1.5 nextLine() 用法总结
- 以回车符作为结束标识符,获取到的是回车符前输入的所有字符串(包括空格)
3.1.6 演示案例
package CH02_JAVAProcessControl;
//基础Scanner
import java.util.Scanner;
public class XZ01_UserInteraction3 {
public static void main(String[] args) {
//我们可以输入多个数字,并求其总和与平均数,每输入一个数字用回车确认,
//通过输入非数字来结束输入并输出执行结果:
Scanner input = new Scanner(System.in);
double sum = 0;
// 声明变量记录用户输入数据和
int count = 0;
// 声明变量记录用户输入数据次数
System.out.println("请输入数字(输入字母停止)");
while (input.hasNextDouble()) {
// 只有用户输入Double类型数据才会执行
sum += input.nextDouble();
// 记录和的变量,加上用户当前输入数据
count++;
// 用户输入数据次数+1
}
System.out.println(count + "个数的和为:" + sum);
input.close();
}
}
3.1.7 顺序结构
- JAVA的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行
- 顺序结构是最简单的算法结构
- 语句与语句之间,框与框之间是按从上到下的顺序进行的,它是由若干个依次执行的处理步骤组成的,它是任何一个算法都离不开的一种基本算法结构
3.2 If 选择结构
3.2.1 单层 if 选择结构
package CH02_JAVAProcessControl;
//单层 if 选择结构
public class XZ02_SelectStructure {
public static void main(String[] args) {
int i = 60;
if (i >= 60) {
System.out.println("及格");
//当结果为true执行
} else {
System.out.println("不及格");
//当结果为false执行
}
}
}
3.2.2 多重 if 选择结构
package CH02_JAVAProcessControl;
//多重 if 选择结构
import java.util.Scanner;
public class XZ02_SelectStructure2 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// 判断用户输入数据,对数据进行分区
System.out.println("请输入成绩");
double score = input.nextDouble();
if (score > 100) {
System.out.println("数据非法");
} else if (score <= 100 && score >= 90) {
System.out.println("A级");
} else if (score >= 80) {
System.out.println("B级");
} else if (score >= 70) {
System.out.println("C级");
} else if (score >= 60) {
System.out.println("D级");
} else {
System.out.println("不及格");
}
input.close();
}
}
3.2.3 嵌套 if 选择结构
package CH02_JAVAProcessControl;
//嵌套if
import java.util.Scanner;
public class XZ02_SelectStructure3 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// 判断用户输入数据,对数据进行分区
System.out.println("请输入成绩");
if (input.hasNextDouble()) {
double score = input.nextDouble();
// if判断用户输入数据是否为double类型
// 然后在进行分级判断
if (score > 100) {
System.out.println("数据非法");
} else if (score <= 100 && score >= 90) {
System.out.println("A级");
} else if (score >= 80) {
System.out.println("B级");
} else if (score >= 70) {
System.out.println("C级");
} else if (score >= 60) {
System.out.println("D级");
} else {
System.out.println("不及格");
}
}
input.close();
}
}
3.2.4 if 语句执行条件
- 如果第一条 if 语句执行结果就为true则下方的所有if语句都不会在执行
- 也就是如果 if 能进入下一句判断则,他一定不满足上一个 if 的条件
3.3 Switch 选择结构
3.3.1 switch 选择结构
package CH02_JAVAProcessControl;
//switch判断整数
import java.util.Scanner;
public class XZ02_SelectStructure4 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入数字");
int var = input.nextInt();
switch (var) {
case 0:
System.out.println("值1");
break;
case 1:
System.out.println("值2");
break;
case 2:
System.out.println("值3");
break;
default:
System.out.println("默认值");
}
input.close();
}
}
3.3.2 选择结构
-
多选择结构还有一个实现方式就是switch case 语句
-
switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支
-
如果需要每个case执行完后跳出,在每个 case 后不要忘记写
break;
3.3.3 switch 选择结构进阶
package CH02_JAVAProcessControl;
//switch判断字符串
import java.util.Scanner;
public class XZ02_SelectStructure5 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入值");
String var = input.next();
switch (var) {
case "玄子":
System.out.println("玄子");
break;
case "XuanZiShaer":
System.out.println("XuanZiShaer");
break;
default:
System.out.println("默认值");
}
input.close();
}
}
3.3.4 switch 语句中的变量类型
- byte、short、int 或者 char
- 从 Java SE 7 开始 switch 支持字符串 String 类型了
- 同时 case 标签必须为字符串常量或字面量
3.3.5 switch 选择结构案例
package CH02_JAVAProcessControl;
// 输入一个日期判断这个日期已经过了多少天
import java.util.Scanner;
public class XZ02_SelectStructure6 {
public static void main(String[] args) {
// 普通闰年:公历年份是4的倍数,且不是100的倍数的,为闰年。
// 能被4整除,且不能被100整除
// 世纪闰年:公历年份是整百数的,必须是400的倍数才是闰年)。
// 能被100整除且被400整除
Scanner input = new Scanner(System.in);
System.out.println("请输入日期年:");
int year = input.nextInt();
System.out.println("请输入日期月:");
int month = input.nextInt();
System.out.println("请输入日期日:");
int day = input.nextInt();
switch (month - 1) {
case 11:
day += 30;
case 10:
day += 31;
case 9:
day += 30;
case 8:
day += 31;
case 7:
day += 31;
case 6:
day += 30;
case 5:
day += 31;
case 4:
day += 30;
case 3:
day += 31;
case 2:
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
System.out.println("闰年");
day += 29;
} else {
System.out.println("平年");
day += 28;
}
case 1:
day += 31;
break;
}
System.out.println(day);
input.close();
}
}
3.3.6 比较switch和多重if选择结构
相同点
- 都是用来处理多分支条件的结构
不同点
-
switch选择结构
- 只能处理等值条件判断的情况
-
多重if选择结构
- 没有switch选择结构的限制,特别适合某个变量的取值范围处于某个连续区间的情况
3.4 While 循环结构
3.4.1 while 循环
package CH02_JAVAProcessControl;
//while输出100以内的和
public class XZ03_CirculateStructure {
public static void main(String[] args) {
int sum = 0;
int i = 0;
while (i <= 100) {
sum += i;
i++;
}
System.out.println(sum);
}
}
3.4.2 while 循环结构
- 只要布尔表达式为 true,循环就会一直执行下去
- 大多数情况是会让循环停止下来的,我们需要一个让表达式失效的方式来结束循环
- 少部分情况需要循环一直执行,比如服务器的请求响应监听等
- 循环条件一直为true就会造成无限循环死循环,我们正常的业务编程中应该尽量避免死循环。会影响程序性能或者造成程序卡死奔溃
3.5 Do…While 循环结构
3.5.1 do…while 循环
package CH02_JAVAProcessControl;
//do while 和while的区别
public class XZ03_CirculateStructure2 {
public static void main(String[] args) {
int i = 0;
while (i < 0) {
i++;
System.out.println(i);
}
System.out.println("=====================");
do {
i++;
System.out.println(i);
} while (i < 0);
}
}
3.5.2 do…while 循环结构
- 对于while语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件,也至少执行一次
- do…while 循环和 while 循环相似,不同的是 do…while 循环至少会执行一次
3.5.3 while 和 do…while 的区别
- while 先判断后执行
- dowhile 是先执行后判断
- Do…while 总是保证循环体会被至少执行一次
3.6 For 循环结构
3.6.1 for 循环
package CH02_JAVAProcessControl;
//for循环输出100以内的和
public class XZ03_CirculateStructure3 {
public static void main(String[] args) {
int a = 0;
//初始化 //条件判断 //迭代
for (int i = 0; i <= 100; i++) {
a += i;
}
System.out.println(a);
}
}
3.6.2 for 循环结构
- 虽然所有循环结构都可以用 while 或者 do…while 表示,但 Java 提供了另一种语句 for 循环,使一些循环结构变得更加简单
- for 循环语句是支持迭代的一种通用结构,是最有效、最灵活的循环结构
- for 循环执行的次数是在执行前就确定的
3.6.3 for 循环执行步骤
- 最先执行初始化步骤。可以声明一种类型,但可初始化一个或多个循环控制变量,也可以是空语句
- 然后,检测布尔表达式的值。如果为true,循环体被执行。如果为false,循环终止,开始执行循环体后面的语句
- 执行一次循环后,更新循环控制变量(迭代因子控制循环变量的增减)
- 再次检测布尔表达式。循环执行上面的过程
3.7 Foreach 循环结构
3.7.1 增强 for
package CH02_JAVAProcessControl;
//增强for
public class XZ03_CirculateStructure7 {
public static void main(String[] args) {
//遍历数组中的值
int[] a = new int[]{10, 20, 30, 40, 50, 60};
for (int x : a) {
System.out.println(x);
}
}
}
3.7.2 增强 for 结构
- Java5 引入了一种主要用于数组或集合的增强型 for 循环
- 声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等
- 表达式:表达式是要访问的数组名,或者是返回值为数组的方法
3.8 Break 和 Continue
package CH02_JAVAProcessControl;
//break和continue的区别
public class XZ03_CirculateStructure1_break_continue {
public static void main(String[] args) {
int i = 0;
while (i < 100) {
i++;
System.out.print(i + " ");
if (i == 30) {
break;
}
}
System.out.println();
System.out.println("========================");
int j = 0;
while (j < 100) {
j++;
if (j % 10 == 0) {
System.out.println();
continue;
}
System.out.print(j);
}
}
}
3.8.1 break 和 continue 的区别
- break 在任何循环语句的主体部分,均可用break控制循环的流程。break用于强行退出循环,不执行循环中剩余的语句。(break语句也在switch语句中使用)
- continue 语句用在循环语句体中,用于终止某次循环过程,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判定
3.9 go to 关键字
package CH02_JAVAProcessControl;
public class XZ04_GoToKeyWord {
public static void main(String[] args) {
// 打印101-159之间所有的质数
// 质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
int count = 0;
outer:
for (int i = 101; i < 150; i++) {
for (int j = 2; j < i / 2; j++) {
if (i % j == 0) {
continue outer;
}
}
System.out.print(i + " ");
}
}
}
3.9.1 go to 结构
- 关于 go to 关键字 go to 关键字很早就在程序设计语言中出现
- 尽管 go to 仍是 Java 的一个保留字,但并未在语言中得到正式使用;Java 没有go to
- 然而,在 break 和 continue 这两个关键字的身上,我们仍然能看出一些 go to 的影子像是带标签的 break 和continue
- “标签”是指后面跟一个冒号的标识符,例如:label:
- 对 Java 来说唯一用到标签的地方是在循环语句之前。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另个循环,由于 break 和 continue 关键字通常只中断当前循环,但若随同标签使用,它们就会中断到存在标签的地方
3.10 循环结构综合案例
3.10.1 二进制转换十进制
package LearnJava.进制转换;
import java.util.Scanner;
public class 二进制转换十进制 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
//先判断有几位数
//输入数*输入位数减去1的平方
//转换后数值相加
//输出最终结果
System.out.println("请输入二进制数字:");
int erjinzhi = input.nextInt();
int shijinzhi = 0, p = 0;
while (erjinzhi != 0) {
shijinzhi += ((erjinzhi % 10) * Math.pow(2, p));
erjinzhi = erjinzhi / 10;
p++;
}
System.out.println(shijinzhi);
}
}
3.10.2 十进制转换二进制
package LearnJava.进制转换;
import java.util.Scanner;
public class 十进制转换二进制 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入十进制数字");
int n = input.nextInt();
int t = 0;
//用来记录位数
int bin = 0;
//用来记录最后的二进制数
int r = 0;
//用来存储余数
while (n != 0) {
r = n % 2;
n = n / 2;
bin += r * Math.pow(10, t);
t++;
}
System.out.println(bin);
}
}
3.10.3 打印 100 以内的奇数与偶数和
package CH02_JAVAProcessControl;
//for循环输出100以内的奇数与偶数和
public class XZ03_CirculateStructure4 {
public static void main(String[] args) {
int ji = 0;
int oh = 0;
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
oh += i;
} else {
ji += i;
}
}
System.out.println(ji);
System.out.println(oh);
System.out.println(ji + oh);
}
}
3.10.4 打印 1-1000 之间能被 5 整除的数,并且每行输出 3 个
package CH02_JAVAProcessControl;
//用while或for循环输出1-1000之间能被5整除的数,并且每行输出3个
public class XZ03_CirculateStructure5 {
public static void main(String[] args) {
for (int i = 1; i < 1000; i++) {
if (i % 5 == 0) {
System.out.print(i + "\t");
}
if (i % (3 * 5) == 0) {
System.out.println();
}
}
}
}
3.10.5 打印正反 99 乘法表
package CH02_JAVAProcessControl;
//打印正反99乘法表
public class XZ03_CirculateStructure6 {
public static void main(String[] args) {
// 1.我们先打印第一列,这个大家应该都会
// 2.我们把固定的1再用一个循环包起来
// 3.去掉重复项,i<=j
// 4.调整样式
for (int j = 1; j <= 9; j++) {
for (int i = 1; i <= j; i++) {
System.out.print(j + "*" + i + "=" + (j * i) + "\t");
}
System.out.println();
}
System.out.println("===========================");
for (int j = 9; j >= 0; j--) {
for (int i = 1; i <= j; i++) {
System.out.print(j + "*" + i + "=" + (j * i) + "\t");
}
System.out.println();
}
}
}
3.10.6 打印等腰三角形
package CH02_JAVAProcessControl;
//打印三角形
public class XZ03_CirculateStructure8 {
public static void main(String[] args) {
// 空白与实体之间的关系 2*i-1
for (int i = 0; i <= 5; i++) {
for (int j = 5; j >= i; j--) {
System.out.print(" ");
}
for (int j = 1; j <= 2 * i - 1; j++) {
System.out.print("*");
}
System.out.println();
}
System.out.println("===========================");
for (int i = 1; i <= 5; i++) {
for (int j = 5; j >= i; j--) {
System.out.print(" ");
}
for (int j = 1; j <= i; j++) {
System.out.print("*");
}
for (int j = 1; j < i; j++) {
System.out.print("*");
}
System.out.println();
}
}
}
四、数组
4.1 数组的基本要素
名称 | 说明 |
---|---|
标识符 | 也称为数组名,用于区分不同的数组 |
数组类型 | 一个数组中所有元素具有统一的数据类型 |
数组元素 | 数组中存放的数据 |
数组下标 | 数组元素的编号,通过这些编号访问数组元素 |
4.1.1 数组的定义
package CH04_JAVAArrays;
//数组的定义
public class XZ01_DefinitionOfArray {
public static void main(String[] args) {
int[] nums;
// 声明数组
nums = new int[10];
// 定义数组空间
nums[0] = 1;
nums[1] = 2;
nums[2] = 3;
nums[3] = 4;
nums[4] = 5;
nums[5] = 6;
nums[6] = 7;
nums[7] = 8;
nums[8] = 9;
nums[9] = 10;
// 对数组进行赋值
// nums[10] = 11;
// 数组素引超出范围
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
System.out.println("和为:" + sum);
}
}
4.1.2 注意事项
- 数组是相同类型数据的有序集合
- 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成
- 其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们
4.2 数组状态
package CH04_JAVAArrays;
//数组状态
public class XZ02_ArrayState {
public static void main(String[] args) {
int[] nums = new int[10];
nums[0] = 1;
// 动态状态
int[] nums2 = {10, 20, 30, 40, 50};
// 静态状态
System.out.println(nums[0]);
System.out.println(nums[1]);
System.out.println(nums2[0]);
}
}
4.2.1 数组的默认初始化
- 数组是引用类型,它的元素相当于类的实例变量
- 因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化
4.3 数组下标越界异常
package CH04_JAVAArrays;
//数组下标越界
public class XZ03_ArraySubscriptOutOfBounds {
public static void main(String[] args) {
int[] nums = new int[10];
System.out.println(nums[10]);
//打印数组下标超过数组存储就会报错: 数组下标越界
}
}
- 数组是相同数据类型(数据类型可以为任意类型)的有序集合
- 数组也是对象。数组元素相当于对象的成员变量
- 数组长度的确定的,不可变的。如果越界,则报错:ArraylndexOutofBounds
4.4 数组的基本特征
- 数组长度是确定的。数组一旦被创建,它的大小就是不可以改变的。其元素必须是相同类型,不允许出现混合类型
- 数组中的元素可以是任何数据类型,包括基本类型和引用类型
- 数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量
- 数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的
4.5 数组基础案例
4.5.1 遍历数组
package CH04_JAVAArrays;
//数组基础案例
public class XZ04_ArrayBasicCase {
public static void main(String[] args) {
int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
System.out.println("=========遍历数组============");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + "\t");
}
System.out.println();
System.out.println("==========遍历数组============");
for (int num : nums) {
System.out.print(num + "\t");
}
System.out.println();
System.out.println("==========计算和============");
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
System.out.print("和为:" + sum);
System.out.println();
System.out.println("==========计算最大数============");
int max = nums[0];
for (int i = 0; i < nums.length; i++) {
if (nums[i] > max) {
max = nums[i];
}
}
System.out.print("最大数为:" + max);
System.out.println();
System.out.println("==========反转数组============");
for (int i = 0; i < reverse(nums).length; i++) {
System.out.print(reverse(nums)[i] + "\t");
}
}
public static int[] reverse(int[] nums) {
// 反转数组
int[] result = new int[nums.length];
for (int i = 0, j = result.length - 1; i < nums.length; i++, j--) {
result[i] = nums[j];
}
return result;
}
}
4.5.2 数组插入
package XuanZi.CH08.数组;
//数组插入
import java.util.Arrays;
public class XuanZi06 {
public static void main(String[] args) {
int[] lao = {18, 17, 55, 19, 51, 45};
//老数组
int num = 52;
//插入数
int[] xin = new int[lao.length + 1];
//新数组长度等于老数组长度加一
//新数组的i位成语老数组的i位
System.arraycopy(lao, 0, xin, 0, lao.length);
xin[lao.length] = num;
//新数组最后一位等于插入数
Arrays.sort(xin);
for (int i = 0; i < xin.length; i++) {
System.out.println(xin[i]);
//排序输出
}
}
}
4.5.3 数组合并
package XuanZi.CH08.数组;
public class XUanZi07 {
public static void main(String[] args) {
int[] a = new int[]{10, 20, 30};
int[] b = new int[]{40, 50, 60};
int[] xin = new int[a.length + b.length];
int c = 0;
System.out.print("第一个数组中的元素:");
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]);
if (i < a.length - 1) {
System.out.print(",");
}
}
System.out.println();
System.out.print("第二个数组中的元素:");
for (int i = 0; i < a.length; i++) {
System.out.print(b[i]);
if (i < b.length - 1) {
System.out.print(",");
}
}
System.out.println();
for (int i = 0; i < xin.length; i++) {
if (i < a.length) {
xin[i] = a[i];
} else {
xin[i] = b[c];
c++;
}
}
System.out.print("两个数组合并后:");
for (int i = 0; i < xin.length; i++) {
System.out.print(xin[i]);
if (i < xin.length - 1) {
System.out.print(",");
}
}
System.out.println();
System.out.print("逆序后:");
for (int i = 0; i < xin.length; i++) {
System.out.print((xin[xin.length - i - 1]));
if (i < xin.length - 1) {
System.out.print(",");
}
}
}
}
4.6 多维数组
package CH04_JAVAArrays;
//多维数组
public class XZ05_multidimensionalArray {
public static void main(String[] args) {
int[] nums = {1, 2, 3, 4, 5};
int[][] ages = {{1, 2}, {2, 3}, {3, 4}, {4, 5}};
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + "\t");
}
System.out.println();
System.out.println("==========打印多维数组======");
for (int i = 0; i < ages.length; i++) {
for (int j = 0; j < ages[i].length; j++) {
System.out.print(ages[i][j] + "\t");
}
}
}
}
多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组
4.7 Arrays 类
package CH04_JAVAArrays;
//Arrays类
import java.util.Arrays;
public class XZ06_ArrayClass {
public static void main(String[] args) {
int[] nums = {2, 4, 6, 7, 5};
Arrays.sort(nums);
//数组排序
System.out.println(Arrays.toString(nums));
//打印数组
Arrays.fill(nums, 2, 4, 0);
// 填充数组 起始下标 填充值
System.out.println(Arrays.toString(nums));
}
}
4.7.1 Arrays 类
- 数组的工具类
java.util.Arrays
- 由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作
- Arrays类中的方法都是static修饰的静态方法,在使用的时候可以直接使用类名进行调用,而
不用
使用对象来调用(注意:是不用
而不是不能
)
4.7.2 常用功能
- 给数组赋值:通过
fill
方法 - 对数组排序:通过
sort
方法,按升序 - 比较数组:通过
equals
方法比较数组中元素值是否相等 - 查找数组元素:通过
binarySearch
方法能对排序好的数组进行二分查找法操作
4.8 冒泡排序
package CH04_JAVAArrays;
import java.util.Arrays;
//冒泡排序
public class XZ07_bubbleSort {
public static void main(String[] args) {
// 比较数组中,两个相邻的元素,如果第一个数比第二个数大
// 我们就交换他们的位置
// 每一次比较,都会产生出一个最大,或者最小的数字
// 下一轮则可以少一次排序
// 依次循环,直到结束
int[] a = {1, 4, 5, 6, 72, 2, 2, 2, 25, 6, 7};
int[] sort = sort(a);
//调用完我们自己写的排序方法以后,返回一个排序后的数组
System.out.println(Arrays.toString(sort));
}
public static int[] sort(int[] array) {
//临时变量
int temp = 0;
//外层循环,判断我们这个要走多少次;
for (int i = 0; i < array.length - 1; i++) {
boolean flag = false;//减少没有意义的比较
//内层循环,比价判断两个数,如果第一个数,比第二个数大,则交换位置
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j + 1] > array[j]) {
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
flag = true;
}
}
if (!flag) {
break;
}
}
return array;
}
}
4.8.1 冒泡排序
- 冒泡排序无疑是最为出名的排序算法之一,总共有八大排序
- 冒泡的代码还是相当简单的,两层循环,外层冒泡轮数,里层依次比较,江湖中人人尽皆知
- 我们看到嵌套循环,应该立马就可以得出这个算法的时间复杂度为O(n2)
4.8.2 冒泡排序口诀
- 外层循环 n-1,控制比较轮数
- 内层循环 n-1-i,控制每一轮比较次数
- 两两比较做交换,判断大小交换位置
五、面向对象
5.1 类与对象
5.1.1 类与对象的定义
-
类是现实世界或思维世界中的实体在计算机中的反映,它将数据以及这些数据上的操作封装在一起
-
对象是具有类,类型的变量。类和对象是面向对象编程技术中的最基本的概念
5.1.2 类与对象的关系
- 类是对象的类型,对象是类的实例
- 类是抽象的,不占用内存,而对象是具体的,占用存储空间
- 类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板
- 定义类就是通过抽取同类对象的共性而自定义的一种数据类型
5.1.3 如何使用类
- 定义类:使用关键字
class
- 创建类的对象:使用关键字
new
- 使用类的属性和方法:使用
.
操作符
package CH05_JAVAObjectOriented;
//类与对象的创建
public class XZ01_Student {
// 学生类
String name;
// 默认值 null
int age;
// 默认值 0
public void study() {
System.out.println(this.name + "在学习");
// this 代表当前类的属性
}
}
package CH05_JAVAObjectOriented;
//一个项目应该只存在一个 Main 方法
public class XZ01_Main {
public static void main(String[] args) {
//类:抽象的,实例化
//类实例化后会返回一个自己的对象!
XZ01_Student xiaoMing = new XZ01_Student();
// 使用new关键字创建对象
System.out.println(xiaoMing.name);
System.out.println(xiaoMing.age);
xiaoMing.study();
XZ01_Student xiaoHong = new XZ01_Student();
System.out.println("------------------------");
xiaoHong.name = "小红";
// 对属性进行赋值
xiaoHong.age = 16;
System.out.println(xiaoHong.name);
System.out.println(xiaoHong.age);
xiaoHong.study();
//xiaoHong,xiaoHong就是一个Student类的具体实例!
}
}
5.2 面向对象的定义
面向对象编程:(Object-Oriented Programming);简称OOP
面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装) 数据
5.3 面向过程与面向对象
面向过程(Procedure Oriented)
-
按自顶向下的步骤依次执行的过程式解决方法,每个步骤可定义为一个模块
-
优点:性能高,流程和步骤清晰,容易理解
-
不足:复用率低,扩展性差,维护难度高
面向对象(Object Oriented)
-
将构成问题的事物拆解成各个对象,建立对象的目的是为了描述每个事物在整个解决问题步骤中的行为
-
优点:易扩展、代码复用率高、利于后期扩展和维护,提升开发效率
-
不足:一定程度上增加系统开销
5.4 面向对象三大特征
5.4.1 封装(Encapsulation):面向对象三大特征之一
将属性私有通过get/set
方法操作属性
5.4.2 继承(Inheritance):面向对象的三大特性之一
继承是代码重用的一种方式, 将子类共有的属性和行为放到父类中
5.4.3 多态(Polymorphic):面向对象的三大特征之一
同一个操作用于不同的对象时,产生不同的执行结果,即父类的引用指向了子类的对象
六、方法
6.1 类的方法
-
类的方法用来实现对象的行为,方法的本意是功能模块,是用来“做一件事情”、“实现一种功能”
-
它们在一起执行一个功能
-
方法是解决一类问题的步骤的有序组合
-
方法包含于类或对象中
-
方法在程序中被创建,在其他地方被引用设计方法的原则方法的本意是功能块,就是实现某个功能的语句块的集合
-
我们设计方法的时候,最好保持方法的原子性
-
一个方法只完成1个功能,样利于后期代码的扩展
6.2 方法的定义元素
Java的方法类似于其它语言的函数,是一段用来完成特定功能的代码片段
一个方法包括:一个方法头和一个方法体
public 返回值类型 方法名() {
//方法的主体
}
6.2.1 方法头
修饰符
- 修饰符是可选的,告诉编译器如何调用该方法
- 定义了该方法的访问类型
返回值类型
- 方法可能会返回值
- return ValueType 是方法返回值的数据类型
- 有些方法执行所需的操作,但没有返回值
- return ValueType 就是关键字 void
方法名
- 是方法的实际名称
- 方法名和参数表共同构成方法签名
参数类型
- 参数像是一个占位符
- 当方法被调用时,传递值给参数
- 这个值被称为实参或变量
- 参数列表是指方法的参数类型、顺序和参数的个数
- 参数是可选的,方法可以不包含任何参数
- **形式参数:**在方法被调用时用于接收外界输入的数据
- **实参:**调用方法时实际传给方法的数据
6.2.2 方法体
方法体
- 方法体包含具体的语句,定义该方法的功能
- 如方法需要返回值,则使用
return
关键字返回值
6.3 方法的返回值
6.3.1 有返回值
如果方法具有返回值,方法中必须使用关键字return
返回该值,返回值类型为该返回值的类型
public class Student{
String name = "玄子";
public String Student(){
return name;
// 跳出方法、返回结果
}
}
6.3.2 没返回值
如果方法没有返回值,返回值类型为void
public class Student{
String name = "玄子";
public void Student(){
System.out.println("我的名字是:" + name);
}
}
一个类中可以定义多个方法,方法之间是平行关系,不能在方法中再定义另一个方法
6.4方法调用
在程序中通过使用方法名执行方法的语句,这一过程就是方法调用,方法只有被调用才能生效,无需关心方法内部实现
对象名.方法名();
6.4.1 演示案例
创建类,定义类的属性和方法
package CH05_OOP;
//类的方法
public class XZ01_Student {
// 学生类
String name;
// 姓名
int age;
// 年龄
public void study() {
System.out.println(this.name + "在学习");
// 定义学习方法
}
public int showAge() {
return age;
// 将年龄作为返回值返回
}
}
}
在方法中创建类的对象,赋值后调用
public static void main(String[] args) {
XZ01_Student student = new XZ01_Student();
// 创建类的对象
student.name = "玄子";
student.age = 12;
// 对实例化后的对象赋值
student.study();
System.out.println(student.showAge());
// 调用方法
// showAge()方法为返回值,对其返回值进行输出
}
6.5 变量的作用域
变量声明的位置决定变量作用域,变量作用域确定可在程序中按变量名访问该变量的区域
package CH05_OOP;
//类与对象的创建
public class XZ01_Student {
String name;
int age;
// 成员变量,声明在类中
static String abc;
// 被 static 修饰的变量 静态变量
// 可在别的类中直接通过对象名调用
public void study() {
char nansex = '男';
// 局部变量,声明在方法中
System.out.println(nansex);
// 局部变量只能在当前方法中调用
System.out.println(name + "在学习");
// 但成员变量可在全类中调用
}
public int showAge() {
char nvsex ='女';
// 局部变量,声明在方法中
System.out.println(nvsex);
return age;
}
}
6.5.1 变量的类型
- 局部变量
- 成员变量(实例变量)
- 静态变量(类变量)
类型 | 声明位置 | 从属于 | 生命周期(作用域) |
---|---|---|---|
局部变量 | 方法或语句块内部 | 方法/语句块 | 从声明位置开始,直到方法或语 句块执行完毕,局部变量消失 |
成员变量 | 类内部,方法外部 | 对象 | 对象创建,成员变量也跟着创建 对象消失,成员变量也跟着消失 |
静态变量 | 类内部,static 修饰 | 类 | 类被加载,静态变量就有效 类被卸载,静态变量消失 |
6.5.2 成员变量和局部变量的区别
作用域不同
-
局部变量的作用域仅限于定义它的方法
-
成员变量的作用域在整个类内部都是可见的
初始值不同
-
Java会给成员变量一个初始值
-
Java不会给局部变量赋予初始值
在同一个方法中,不允许有同名局部变量
在不同的方法中,可以有同名局部变量
在同一个类中,成员变量和局部变量同名时
局部变量具有更高的优先级
6.6 方法的重载
package CH03_JAVAMethod;
//方法的重载
public class XZ02_OverloadingOfMethod {
public static void main(String[] args) {
// 声明变量x和y接收用户输入变量
int result = add(10, 20, 30);
int result2 = add(10, 20);
double result3 = add(10, 20, 30.6, 40);
// 方法名相同,根据传递参数数量,类型不同自动判断
System.out.println("和为:" + result);
System.out.println("和为:" + result2);
System.out.println("和为:" + result3);
// 输出返回值
}
public static int add(int x, int y) {
// 修饰符 返回值类型 方法名(参数类型 参数名){
int result = 0;
// 声明变量返回结果
result = x + y;
// 方法体
return result;
// return 返回值;
}
public static int add(int x, int y, int z) {
// 修饰符 返回值类型 方法名(参数类型 参数名){
int result = 0;
// 声明变量返回结果
result = x + y + z;
// 方法体
return result;
// return 返回值;
}
public static double add(double x, double y, double z, double n) {
// 修饰符 返回值类型 方法名(参数类型 参数名){
double result = 0;
// 声明变量返回结果
result = x + y + z + n;
// 方法体
return result;
// return 返回值;
}
}
方法重载是指
同一个类中
包含了两个或两个以上的方法,它们的方法名相同,方法参数的个数或参数类型不同
类的成员方法和构造方法都可以进行重载
6.6.1 方法的重载规则
- 必须在同一类中
- 方法名相同
- 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)
- 返回值类型随意
6.7 命令行传参
package CH03_JAVAMethod;
//传参
public class XZ03_ChuanshenOfMethod {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "]: " + args[i]);
}
}
}
命令行传参有时候你希望运行一个程序时候再传递给它消息。这要靠传递命令行参数 给main( ) 函数实现
6.7.1 实现步骤
-
通过cmd窗体编译 java 文件传递参数
-
编译文件
javac XZ03_ChuanshenOfMethod.java
-
cd…/ 回退到 src 目录下
-
书写全路径
java CH03_JAVAMethod/XZ03_ChuanshenOfMethod
-
加上传递参数
java CH03_JAVAMethod.XZ03_ChuanshenOfMethod XuanZi XuanZiShaer
!!! 注释可能无法编译,导致编译失败
6.8 可变参数
package CH03_JAVAMethod;
//可变参数
public class XZ04_VariableParameterOfMethod {
public static void main(String[] args) {
printMax(312, 22.2, 3213, 32131);
}
public static void printMax(int a, double... numbers) {
if (numbers.length == 0) {
System.out.println("No argument passed");
return;
}
double result = numbers[0];
//排序!
for (int i = 1; i < numbers.length; i++) {
if (numbers[i] > result) {
result = numbers[i];
}
}
System.out.println("The max value is " + result);
}
}
6.8.1 可变参数
- JDK1.5 开始,Java 支持传递同类型的可变参数给一个方法
- 在方法声明中,在指定参数类型后加一个省略号(…)
- 一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明
6.9 递归
package CH03_JAVAMethod;
//递归
public class XZ05_RecursionOfMethod {
public static void main(String[] args) {
System.out.println(f(25));
}
public static long f(long n) {
if (n == 1) {
return 1;
} else {
return n * f(n - 1);
}
}
}
6.9.1 递归
- A方法调用B方法,我们很容易理解
- 递归就是:A方法调用A方法!就是自己调用自己调用
- 递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。
- 递归结构包括两个部分:
- 递归头:什么时候不调用自身方法。如果没有头,将陷入死循环
- 递归体:什么时候需要调用自身方法
6.10 构造器
类中的构造器也称为构造方法,对类进行初始化定义
6.10.1 构造器有以下特点
- 方法名和类名相同
- 没有返回值类型
- 一旦自定义构造方法,系统将不再提供默认无参构造方法
package CH05_JAVAObjectOriented;
public class XZ02_Constructors {
//一个类即使什么都不写,它也会存在一个方法
//显示的定义构造器
String name;
public XZ02_Constructors(String name) {
//有参构造:一旦定义了有参构造,无参就必须显示定义
//只要定义了有参构造就也定义个无参构造
this.name = name;
}
public XZ02_Constructors() {
this.name = "玄子";
}
}
package CH05_JAVAObjectOriented;
public class XZ02_Main {
public static void main(String[] args) {
XZ02_Constructors constructors = new XZ02_Constructors("玉玉诏");
System.out.println(constructors.name);
// 这里看不懂可尝试 Debug 一下
}
}
6.10.2 快捷生成
快捷键: Alt + Instant
笔记本用户根据自己机型考虑加上 Shift
即同时按下 Alt + Shift + Instant
6.11 This 关键字
this
关键字是对一个对象的默认引用
-
调用成员变量
-
调用成员方法
-
调用已定义的构造方法
this 关键字是在对象内部指代自身的引用,所以它
只能调用实例变量、实例方法和构造方法
,不能调用类变量和类方法,也不能调用局部变量
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
七、封装
封装(Encapsulation):面向对象三大特征之一
7.1定义
-
将类的某些信息隐藏在类内部,不允许外部程序直接访问
-
而是通过该类提供的方法来实现对隐藏信息的操作和访问
将属性私有通过
get/set
方法操作属性
7.1.1 封装的好处
- 隐藏类的实现细节
- 方便加入控制语句
- 方便修改实现
- 只能通过规定方法访问数据
7.1.2 快捷生成
快捷键: Alt + Instant
笔记本用户根据自己机型考虑加上 Shift
即同时按下 Alt + Shift + Instant
选择
Getter 或 Setter
7.1.3 演示案例
package CH05_JAVAObjectOriented;
//封装
public class XZ03_Encapsulation {
private String name;
private int age;
private char sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 为了避免赋值错误,需对属性设置限制,在set()方法中进行验证
if (age >= 100) {
System.out.println(age + ":为非法数据");
this.age = 18;
} else {
// this代表当前对象
this.age = age;
}
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
package CH05_JAVAObjectOriented;
public class XZ03_Main {
public static void main(String[] args) {
XZ03_Encapsulation encapsulation = new XZ03_Encapsulation();
encapsulation.setName("玄子");
encapsulation.setAge(12);
encapsulation.setSex('男');
System.out.println(encapsulation.getName());
System.out.println(encapsulation.getAge());
System.out.println(encapsulation.getSex());
System.out.println("------------------");
XZ03_Encapsulation encapsulation2 = new XZ03_Encapsulation();
encapsulation2.setAge(112);
System.out.println(encapsulation2.getAge());
}
}
7.2 访问修饰符
7.2.1 类的访问修饰符
修饰符 \ 作用域 | 同一包中 | 非同一包中 |
---|---|---|
public | 可以使用 | 可以使用 |
默认修饰符 | 可以使用 | 不可以使用 |
7.2.2 类成员的访问修饰符(属性、方法)
修饰符 \ 作用域 | 同一类中 | 同一包中 | 子类中 | 外部包 |
---|---|---|---|---|
private | 可以使用 | 不可以使用 | 不可以使用 | 不可以使用 |
默认修饰符 | 可以使用 | 可以使用 | 不可以使用 | 不可以使用 |
protected | 可以使用 | 可以使用 | 可以使用 | 不可以使用 |
public | 可以使用 | 可以使用 | 可以使用 | 可以使用 |
7.3 Static 关键字
public class Student {
public static String name;
}
public class Main {
public static void main(String[] args) {
Student.name ="玄子";
//通过类名直接调用,不需再消耗资源反复创建对象
}
}
-
使用static修饰的属性称为静态变量或类变量
-
没有使用static修饰的属性称为实例变量
-
程序中的类信息只会加载一次
-
每次用new 来创建对象的时候都会分配一个新的空间来保存相关的非静态字段信息
-
静态成员属于类级别的,为所有的对象所共享,不单独属于某一个对象, 所以调用静态成员要用类名来调
-
在静态方法的定义中只能调用静态的方法和静态的字段或属性
static代码块在JVM初始化阶段执行,只会执行一次一般情况下, 使用static代码块对static变量进行初始化
方法里,不可以定义static变量,即:
类变量不能是局部变量
7.4 Final常量
public class Student {
public static String name;
public final int Age = 18;
}
public class Main {
public static void main(String[] args) {
Student student = New Student();
//student.age=20;
}
}
- 常量名一般由大写字母组成
- 声明常量时一定要赋初值
- 见名知意,便于阅读,易于代码修改和维护
7.5 使用 static 修饰类
static 非private修饰 | 非static private修饰 | |
---|---|---|
属性 | 类属性 类变量 | 实例属性 实例变量 |
方法 | 类方法 | 实例方法 |
调用方式 | 类名.属性 类名.方法() 对象.属性 对象.方法() | 对象.属性 对象.方法() |
归属 | 类 | 单个对象 |
在静态方法中,不能直接访问实例变量和实例方法
在实例方法中,可以直接调用类中定义的静态变量和静态方法
八、继承
继承(Inheritance):面向对象的三大特性之一
8.1.1 定义
-
一个类获取现有类的所有属性和行为的机制
-
创建基于现有类的新类,可以重用现有类的属性和方法
-
可以在新创建的子类中添加新属性和方法
8.1.2 作用
-
有效的解决了代码的重用问题,使代码拓展更加灵活
-
从始至终完整的体现了一个应用系统,逻辑更加清晰
-
增加软件的可扩展性,以适应不同的业务需求
8.1.3 何时使用继承
- 继承是类和类之间的一种关系,除此之外,类和类之间的关系还有依赖、组合、聚合等
- 继承关系的俩个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字
extends
来表示 - 子类和父类之间,子类和父类之间, 应符合
is-a关系
- 继承是代码重用的一种方式, 将子类共有的属性和行为放到父类中
public class XZ04_Student extends XZ04_Extend {
// extends 后跟继承父级
// 继承 XZ04_Extend 类的所有方法
}
8.1.4 理解继承
不能被继承的父类成员
- private 成员
- 子类与父类不在同包,使用默认访问权限的成员
- 构造方法
访问修饰符 protected
- 可以修饰属性和方法
- 本类、同包、子类可以访问
访问修饰符总结
访问修饰符 | 本类 | 同包 | 子类 | 其他 |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
package CH05_JAVAObjectOriented;
//继承父类
public class XZ04_Extend /* extends Object*/ {
public String name = "玄子";
// 所有的 Java 类都直接或间接地继承了Object类
private int money;
public void say() {
System.out.println("我是父级:XZ04_Extend");
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
package CH05_JAVAObjectOriented;
//继承子类
public class XZ04_Student extends XZ04_Extend {
// extends 父级:XZ04_Extend 的所有方法
// Ctrl + H 查看继承结构
public String name = "XuanZi";
public void test(String name) {
System.out.println(name);
// XuanZiShare
System.out.println(this.name);
// XuanZi
System.out.println(super.name);
// 玄子
}
}
package CH05_JAVAObjectOriented;
public class XZ04_Main {
public static void main(String[] args) {
XZ04_Student student =new XZ04_Student();
student.say();
student.setMoney(1000);
System.out.println(student.getMoney());
System.out.println("------------------");
student.test("XuanZiShare");
}
}
8.2 Object 类
Object 类是所有 Java 类的祖先,所有的 Java 类都直接或间接地继承了 Object 类
-
位于 java.lang 包中
-
在定义一个类时,如果没有使用 extends 关键字,即没有显式地继承某个类,那么这个类直接继承 Object 类
public class XZ04_Extend /* extends Object*/ {
// 默认继承Object类
}
Ctrl + H 查看继承结构树
8.2.1 常用方法
方法 | 说明 |
---|---|
toString() | 返回当前对象本身的有关信息,返回字符串对象 |
equals() | 比较两个对象是否是同一个对象。若是,返回true |
clone() | 生成当前对象的一个副本,并返回 |
hashCode() | 返回该对象的哈希代码值 |
getClass() | 获取当前对象所属的类信息,返回Class对象 |
java.lang.String 类重写了 Object 类中的 equals() 方法,用于比较两个字符串的值是否相等
8.3 Super 关键字
子类访问父类成员super
代表父类对象
- super 调用父类的构造方法,必须在构造方法的第一个
- super 必须只能出现在子类的方法或者构造方法中
- super 和 this 不能同时调用构造方法
8.3.1 访问父类的成员
-
super 只能出现在子类的方法和构造方法中
-
super 调用构造方法时,只能是第一句
-
super 不能访问父类的 private 成员
8.3.2 Super 和 This 的区别
代表的对象不同
- this:本身调用者这个对象
- super:代表父类对象的应用
前提
- this:没继承也可以使用
- super:只能在继承条件才可以使用
构造方法
- this(); 本类的构造
- super(); 父类的构造
8.4 方法的重写
方法的重写或方法的覆盖Override
在子类中,根据需求对从父类继承的方法体进行重新编写,以实现子类需求
8.4.1 方法重写的规则
-
需要先建立父子类关系
-
重写方法和被重写方法必须具有相同的方法名
-
重写方法和被重写方法必须具有相同的参数列表
-
重写方法返回值类型必须和被重写方法的返回值类型相同或为其子类
-
重写方法不能缩小被重写方法的访问权限
8.4.2 方法重载与方法重写
重载 \ 重写 | 位置 | 方法名 | 参数表 | 返回值 | 访问修饰符 |
---|---|---|---|---|---|
方法重载 | 同类 | 相同 | 不相同 | 无关 | 无关 |
方法重写 | 子类 | 相同 | 相同 | 相同或是其子类 | 不能比父类更严格 |
package CH05_JAVAObjectOriented;
//父类
public class XZ05_Extend_B {
public void test() {
System.out.println("B执行");
}
}
package CH05_JAVAObjectOriented;
//子类
public class XZ05_Extend_A extends XZ05_Extend_B {
@Override
public void test() {
// super.test(); 调用父类方法
System.out.println("A执行");
}
}
package CH05_JAVAObjectOriented;
public class XZ05_Extend_Main {
public static void main(String[] args) {
XZ05_Extend_A a = new XZ05_Extend_A();
a.test();
XZ05_Extend_B b = new XZ05_Extend_A();
b.test();
}
}
8.5 多级继承
-
一个类可以继承自某一个类,成为这个类的子类
-
同时,也可以在自身的基础上创建新的类,即成为它子类的父类
-
Java 中只支持单继承,即每个类只能有一个直接父类
8.5.1 继承关系中的构造方法
-
类的构造方法执行场景
-
创建该类的对象时,又称类的实例化
-
创建该类的子类的对象时,又称子类的实例化
-
-
Java 虚拟机按照先父类后子类的顺序执行一系列的构造方法
父类属性 > 父类构造方法 > 子类属性 > 子类构造方法 >
8.5.2 子类继承父类时构造方法的调用规则
-
如果类的构造方法中,没有通过 super 关键字显式调用父类的有参构造方法
-
也没有通过 this 关键字显式调用自身的其他构造方法,则系统会默认先调用父类的无参构造方法
-
在这种情况下,是否写
super( );
语句,效果是一样的 -
如果子类的构造方法中,通过 super 关键字显式地调用了父类的有参构造方法,则将执行父类相应的构造方法,而不执行父类无参构造方法
-
如果子类的构造方法中,通过 this 关键字显式地调用了自身的其他构造方法,在相应的构造方法中遵循以上两条规则
如果存在多级继承关系,则在创建一个子类对象时,以上规则会多次向更高一级父类应用,一直到执行顶级父类 Object 类的无参构造方法为止
九、多态
多态(Polymorphic):面向对象的三大特征之一
9.1 多态的定义
同一个对象拥有多种形态,即:同一个操作用于不同的对象时,产生不同的执行结果
9.2 多态的优缺点
9.2.1 优点
- 可替换性:多态对已存在的代码具有可替换性
- 简化性:多态简化了应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出
- 可扩充性:多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性
- 继承性:以及其他特性的运行和操作。实际上新加的子类更容易获得多态功能
- 灵活性:它在应用中体现了灵活多样的操作,提高了使用效率
- 接口性:多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的
9.2.2 缺点
- 父类引用无法访问子类所特有的方法
9.3 如何实现多态
9.3.1 实现多态的三要素
-
继承关系的父类和子类(继承是多态的基础,没有继承就没有多态)
-
子类重写父类方法(多态下调用子类重写后的方法)
-
父类的引用指向子类的对象(向上转型)
9.4 多态的类型
9.4.1 向上转型——子类到父类的转换:自动类型转换
<父类型 > < 引用变量名 > = new < 子类型 >();
- 将一个父类的引用指向一个子类对象称为向上转型,系统会自动进行类型转换
- 通过父类引用变量调用的方法是子类覆盖或继承的子类方法,不是父类的方法
- 通过父类引用变量无法调用子类特有的方法
9.4.2 向下转型——父类到子类的转换:强制类型转换
<< 子类型 > < 引用变量名 > = (< 子类型 >)< 父类型的引用变量 >;
-
将一个指向子类对象的父类引用赋给一个子类的引用,即将父类类型转换为子类类型,称为向下转型
-
向下转型必须进行强制类型转换
-
将父类类型转换为它的某个子类类型后,才能调用其子类特有的属性
9.5 instanceof 运算符
用于判断一个对象是否属于一个类或者实现了一个接口
从父类到子类的向下转型,可以实现多态,即执行不同子类中定义的特定方法。但事实上,父类对象的引用可能指向某一个子类对象,如果在向下转型时,不是转换为真实的子类类型,就会出现转换异常,为了避免这种异常的发生就要,使用instanceof
运算符
作用:避免引发类型转换异常,提高代码的健壮性
应用场合:向下转型之前,先使用instanceof进行类型判断
对象的类型必须与instanceof参数后所指定的类或接口在继承树上具有上下级关系;否则,会出现编译错误
9.6 面向对象总结
面向对象的三大特性
-
封装(Encapsulation)
-
继承(Inheritance)
-
多态(Polymorphic)
封装是隐藏对象的属性和实现细节
- 将类的成员属性声明为私有的,同时提供公有的方法实现对该成员属性的存取操作
继承是软件可重用性的一种表现
- 新类可以在不增加自身代码的情况下,通过从现有的类中继承其属性和方法充实自身内容
多态是具有表现多种形态的能力的特征
- 在程序设计的术语中,意味着一个特定类型的变量可以引用不同类型的对象,自动地调用引用的对象的方法
- 即根据作用到的不同对象类型,响应不同的操作
十、抽象类和接口
10.1 抽象类
[访问修饰符] abstract class <类名> {}
// 中括号包裹的表示可有可无
抽象类不能被实例化
10.1.1 抽象方法
[访问修饰符] abstract class 类名 {
[ 访问修饰符 ] abstract < 返回类型 > < 方法名 >([ 参数列表 ]);
// 只有方法声明,无具体实现
}
有抽象方法的类必然是抽象类,但抽象类中的方法并不一定都是抽象方法
抽象类中使用抽象方法优化代码
-
abstract
关键字也可用于方法——抽象方法 -
抽象方法没有方法体
-
抽象方法必须在抽象类里
-
抽象方法必须在子类中被实现,除非子类是抽象类
10.1.2 抽象类的优势
提高可重用性
- 抽象类可以看作是类的一个模版,定义了子类的行为,可以为子类提供默认实现,无需子类中重复实现这些方法
代码松耦合,更易于维护
- 子类可以分别实现抽象父类中定义的抽象方法,将方法定义和方法实现的分离
方便实现多态
- 抽象类作为继承关系下的抽象层,不能被实例化,通常定义抽象类类型变量,其具体引用是实现抽象类的子类对象
10.1.3 抽象类的应用场合
-
抽象类用来列举一个类所需要的共性行为
-
抽象类不明确提供具体实现方法
-
抽象类必须由其子类实现它的抽象方法(除非子类也具有抽象性)
-
父类提供一系列规定,约束子类的行为
-
抽象类和抽象方法实现多态性
10.1.4 抽象类与抽象方法
抽象方法只有方法声明,没有方法实现
-
有抽象方法的类必须声明为抽象类
-
子类必须重写所有的抽象方法才能实例化;否则,子类也必须声明成抽象类
-
抽象类中可以没有、有一个或多个抽象方法,甚至可以定义全部方法都是抽象方法
-
抽象类可以有构造方法,其构造方法可以被本类的其他构造方法调用,不是由 private 修饰构造方法,可以被其子类的构造方法调用
abstract 可以用来修饰类和方法,不能用来修饰属性和构造方法
10.2 Final 用法
- 使用 final 修饰的类不可被继承
public final class Dog extends Animal {
// 不能被继承
}
- 使用 final 修饰的方法不可被重写
public class Dog {
public final void eat() {
System.out.println("狗吃骨头");
// 不能被重写
}
}
- 使用 final 修饰的常量不可被修改
public class Dog {
final String name =“小狗";
public void setName(String name) {
this.name=name; //错误,不可再赋值
}
}
10.2.1 Final关键字的用法
-
final可以用来修饰类、方法和属性,不能修饰构造方法
-
Java提供的很多类都是final类,不能重写
- 如:String类、Math类
-
Object类有一些final方法,只能被子类继承而不能被重写
- 如:getClass( )、notify( )、wait( )
-
Object类的hashCode( )、toString( )、equals(Object obj)方法不是final方法,可以被重写
10.3 常见错误
10.3.1 错误一
public class Student {
String name;
public Student(String name) {
this.name = name;
}
public static void main(String[] args) {
final Student stu = new Student("李明");
stu.name = "李明航";
stu = new Student("王亮");
// 使用final修饰的引用型变量,不可以再指向其他对象
}
}
使用 final 修饰引用型变量,变量的值是固定不变的,而变量所指向的对象的属性值是可变的
10.3.2 错误二
class Value {
int v;
}
public class Test {
public void changeValue(final int i, final Value value) {
i = 8;
// 使用final修饰的方法参数,在整个方法中不能改变参数值
value.v = 8;
}
}
使用 final 修饰的方法参数,这些参数的值是固定不变的
10.3.3 错误三
public static abstract void print();
抽象方法只有声明无具体实现,static 方法可通过类名直接访问,但无法修饰一个没有实现的方法
private abstract void print();
抽象方法需在子类中重写,但 private 方法不能被子类继承,自然无法进行重写
public final abstract void print();
抽象方法需要在子类中重写,但 final 修饰的方法表示该方法不能被子类重写,前后是相互矛盾的
10.4 接口
10.4.1 什么是接口
生活中的接口就是一套规范
- 如:主板上的PCI插槽有统一的标准,规定了尺寸、排线
Java 中的接口
-
是一种规范和标准
- 可以约束类的行为,使得实现接口的类(或结构)在形式上保持一致
-
是一些方法特征的集合
-
可看作是一种特殊的
抽象类
-
但采用与抽象类完全不同的语法
-
抽象类利于代码复用,接口利于代码的扩展和维护
10.4.2 定义接口
接口是一个不能实例化的类型
[访问修饰符] interface 接口名 {
// 如果是 public,则在整个项目中可见如果省略,则只在该包中可见
// interface 定义接口的关键字
}
接口中的变量都是全局静态常量
-
自动使用public static final修饰
-
必须在定义时指定初始值
10.4.3 实现接口
类实现接口
class 类名 implements 接口名 {
// implements 实现接口使用的关键字
}
-
实现类必须实现接口的所有方法
-
实现类可以实现多个接口
JDK1.8 版本之前,接口中只能定义抽象方法,自 JDK1.8 版本开始,接口还允许定义静态方法和默认方法
向后兼容
- 允许开发者在已有接口里添加新的方法时不需改动已经实施该接口的所有实现类
10.4.4 定义复杂的接口
接口的多继承
[ 访问修饰符 ] interface 接口名 extends 父接口 1, 父接口 2,……{
// 通过extends实现接口的继承关系
// 多个父接口之间用“,”分隔
}
一个接口可以继承多个接口,但接口不能继承类
类实现多个接口
[ 访问修饰符 ] class 类名 extends 父类名 implements 接口 1, 接口 2,……{
// 通过implements实现多个接口
}
- 一个普通类只能继承一个父类,但能同时实现多个接口
extends
关键字必须位于implements
关键字之前- 类
必须实现
所有接口(接口1、接口2……)的全部抽象方法
,否则必须定义为抽象类
10.4.5 接口是一种能力
接口有比抽象类更好的特性
-
可以被多继承
-
设计和实现完全分离
-
更自然的使用多态
-
更容易搭建程序框架
-
更容易更换实现
10.5 面向对象设计原则
为了让代码更具灵活性,更能适应变化,需遵循的原则
-
摘取代码中变化的部分,形成接口
-
多用组合,少用继承
-
面向接口编程,不依赖于具体实现
-
针对扩展开放,针对改变关闭
- 开闭原则
面向接口编程可以实现接口和实现的分离,能够在客户端未知的情况下修改实现代码
十一、异常
11.1 什么是异常
是指在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序
11.1.2 程序中的异常处理
Java编程语言使用异常处理机制为程序提供了错误处理的能力
Java中,所有的异常都定义为类,除了内置的异常类之外,Java也可以自定义异常类,Java的异常处理机制也允许自行抛出异常
11.1.4 Java 中如何进行异常处理
Java的异常处理是通过5个关键字实现的
- try:执行可能产生异常的代码
- catch:捕获异常
- finally:无论是否发生异常,代码总能执行
- throw:手动抛出异常
- throws:声明方法可能要,抛出的各种异常
11.1.5 常用的异常处理结构
try {
//有可能出现异常的语句
}[catch(异常类型 异常对象) {
//异常处理语句
}] [finally {
//一定会运行到的语句
}]
catch 和 finally 可选,但不能同时省略
11.1.6 try-catch-finally
在 try-catch 块后加入 finally 块,不论是否发生异常都执行
11.1.7 多重 catch 块
try {
//有可能出现异常的语句
}[catch(异常类型1 异常对象) {
//异常处理语句
}catch(异常类型2 异常对象) {
//异常处理语句
}……]
[finally {
//一定会运行到的语句
}]
引发多种类型的异常
-
排列catch 语句的顺序:先子类后父类
-
发生异常时按顺序逐个匹配
-
只执行第一个与异常类型匹配的catch语句
11.1.8 多重异常处理示例
从上到下的顺序检测每个catch语句。当匹配到某条catch语句后,后续其他catch语句块将不再执行
Exception为参数的catch语句必须放在最后的位置;否则,后面以其子类异常作为参数的catch语句将得不到被执行的机会
11.2 异常的分类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q0tXlk5U-1677431488893)(./assets/image-20230212022813823-1677427166715-109.png)]
11.2.1 运行时异常
是可以在程序中避免的异常,当程序进行时发生异常,会输出异常堆栈信息并终止程序运行,可以使用try-catch语句捕获
11.2.2 常见的异常类型
异常类型 | 说明 |
---|---|
ArithmeticException | 当出现算术错误时,抛出此异常 如:一个整数“除以零”时,抛出此异常 |
ArrayIndexOutOfBoundsException | 非法索引访问数组时抛出的异常 如索引为负或大于等于数组长度 |
ClassCastException | 当试图将对象强制转换为非本对象类型的子类时,抛出该异常 |
IllegalArgumentException | 表明向方法传递了一个不合法或不正确的参数 |
InputMismatchException | 欲得到的数据类型与实际输入的类型不匹配 |
NullPointerException | 当应用程序试图在需要对象的地方使用null时,抛出该异常 |
NumberFormatException | 当试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常 如:把“ABC”转成数字 |
11.2.3 Checked异常
-
是指运行时异常以外的异常
-
是用户错误或问题引起的异常
-
程序员无法预见
-
编译器会提示
-
如果不进行捕获,则会出现编译错误
-
常见的异常类型
-
FileNotFoundException异常
-
SQLException异常
-
对异常类子类命名时会使用XXXError
或XXXException形式
11.2.4 异常处理的完整流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gJpijfJP-1677431488893)(./assets/image-20230212023313284-1677427166715-110.png)]
-
对于异常处理,不同项目的要求并不相同
-
由项目开发标准决定异常是统一使用Exception类型接收处理还是分开处理
-
如果开发要求严格,会对每一种异常分别进行处理,详细记录异常信息,产生一定工作量
11.3 声明异常
使用关键字throws声明某个方法可能抛出的各种异常
public void 方法名() throws 异常类型[,异常类型] {
//方法体 声明异常,多个异常用逗号隔开
}
实际开发中,main()方法不建议声明异常,因为如果程序出现了错误,会导致程序中断执行
11.4 抛出异常
-
除了系统自动抛出异常外,有些问题需要程序员自行抛出异常
-
根据程序逻辑自定义的异常类,在Java异常体系中并未提供,不能抛出
-
根据业务需要自行选择异常抛出时机,或自定义异常处理逻辑
throw new 异常名([参数列表]);
throw抛出的只能是Throwable类或其子类的对象
11.5 自定义异常
当Java异常体系中提供的异常类型不能满足程序的需要时,可以自定义异常类
步骤
-
定义异常类,继承Exception类或者RuntimeException类
-
编写异常类的构造方法,并继承父类的实现
-
实例化自定义异常对象,并使用throw关键字抛出
11.5.1 自定义异常的应用场景
-
项目中因业务逻辑错误需要抛出异常,但Java中不存在这类异常例如,年龄异常、性别异常等
-
项目开发一般是由团队成员共同完成,为统一对外异常展示的方式,可以使用自定义异常
11.6 日志记录框架
使用日志框架以文件形式记录异常信息
日志(log)
-
主要用来记录系统运行中一些重要操作信息
-
便于监视系统运行情况,帮助用户提前发现和避开可能出现的问题,或者出现问题后根据日志找到原因
日志分类
- SQL日志、异常日志、业务日志
日志的主要用途
-
问题追踪
-
状态监控
-
安全审计
11.11.1 日志框架log4j2
一款非常优秀的日志框架
-
控制日志的输出级别
-
控制日志信息输送的目的地是控制台、文件等
-
控制每一条日志的输出格式
11.11.2 使用log4j2记录日志
操作步骤
-
编写配置文件
- 文件后缀可为.xml、.json或者.jsn
-
定义日志记录器Logger
- 获取日志记录器的方式
-
记录日志
- Logger类可以替代System.out或者System.err,供开发者记录日志信息
11.11.3 Logger类常用方法
方法 | 描述 |
---|---|
publicvoiddebug(Objectmsg) publicvoiddebug(Objectmsg,Throwablet) | 记录debug级别日志 |
publicvoidinfo(Objectmsg) publicvoidinfo(Objectmsg,Throwablet) | 记录info级别日志 |
publicvoidwarn(Objectmsg) publicvoidwarn(Objectmsg,Throwablet) | 记录warn级别日志 |
publicvoiderror(Objectmsg) publicvoiderror(Objectmsg,Throwablet) | 记录error级别日志 |
publicvoidfatal(Objectmsg) publicvoidfatal(Objectmsg,Throwablet) | 记录fatal级别日志 |
需要手工创建
一般用log4j2.xml命名
11.11.4 日志记录器的日志级别
-
all:最低等级,用于打开所有日志记录
-
trace:用于程序追踪输出
-
debug:指出细粒度信息事件,对高度应用程序是非常有帮助的
-
info:在粗粒度级别上指明消息,强调应用程序的运行过程
-
warn:表示警告信息,即可能会出现的潜在错误
-
error:指出虽然发生错误事件,但仍然不影响系统的继续运行
-
fatal:指出将会严重的错误事件将会导致应用程序退出
-
OFF:最高等级的,用于关闭所有日志记录
程序会输出高于或等于所设置级别的日志
设置的日志等级越高,输出的日志就越少
十二、集合框架
12.1 集合框架概述
12.1.1 为什么使用集合框架
数组的缺陷
-
数组长度固定不变
-
不便存储具有映射关系的数据
-
数据变更效率低下
如果并不知道程序运行时会需要多少对象,或者需要更复杂方式存储对象,可以使用Java集合框架
12.1.2 Java 集合框架
Java 集合框架提供了一套性能优良、使用方便的接口和类,位于java.util包中,集合中的元素全部是对象,即Object类的实例,不同的集合类有不同的功能和特点,适合不同的场合
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L2xt4y2k-1677431488894)(./assets/%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6-1677427066337-93.jpg)]
Collection 接口存储一组不唯一,无序的对象
- List:存储一组不唯一,有序(插入顺序)的对象
- Set:存储一组唯一,无序的对象
Map接口存储一组键值对象,提供key到value的映射
12.1.3 Java 集合框架常用接口说明
名称 | 描述 |
---|---|
Collection | ● 单个值集合的根接口,最基本的集合接口 ● 一个 Collection 代表一组 Object (对象)。Java不提供实现类,只提供子接口( List 接口和 Set 接口) ● Collection 接口存储一组可重复的无序对象 |
Set | ● 继承 Collection 接口,存储一组不可重复的无序对象 |
List | ● 继承 Collection 接口,存储一组可重复的有序对象 ● 元素顺序以元素插入的次序来放置元素,不会重新排序 ● 通过索引访问数组,索引从0开始 ● List 接口常用的实现类有 ArrayList 类和 LinkedList 类 |
Map | ● 存储成对数据的根接口,可存储一组" key(键)-value(值对)"对象 ● 提供 key(键) 到 value(值) 的映射 ● Map 中的 key(键) 是无序的,不重复的 ● Map 中的 value(值) 是无序的,可重复的 ● 可以通过 key(键) 找到对应的 value(值) |
Iterator | ● 集合迭代器,可以遍历集合元素的接口 |
Collections | ● 与 Collection 是不同的概念,它提供了对象合对象进行基本操作的通用接口方法 ● 包含各种有关集合操作的静态方法 ● 工具类,不可实例化 |
12.1.4 Collection 接口
Collection 接口是 List、Set 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操 作List 集合
12.1.5 Collection 接口常用方法
方法 | 描述 |
---|---|
boolean add(Object o) | 用于向集合中添加一个元素 如果集合不允许重复且已包含了指定元素,则返回false |
boolean addAll(Collection c) | 将集合c里所有元素添加到指定集合里。添加成功返回true |
void clear() | 清除集合中所有元素,将集合长度变为为0 |
boolean contains(Object o) | 判断集合中是否包含指定元素 |
boolean containsAll(Collection c) | 判断集合中是否包含集合c里所有的元素 |
boolean remove(Object o) | 删除集合中的指定元素o。当集合中包含了一个或多个元素o时,这些元素将被删除。删除成功,返回true |
int size() | 返回集合里元素的个数 |
boolean retainAll(Collection c) | 从集合中删除集合c里不包含的元素(相当于把调用该方法的集合变成该集合和集合c的交集) 如果该操作改变了调用该方法的集合,则返回true |
boolean removeAll(Collection c) | 从集合中删除集合c里包含的所有元素 如果删除了一个或一个以上的元素,则返回true |
boolean isEmpty() | 如果此集合中不包含任何元素,则返回true |
Object[] toArray() | 该方法把集合转换成一个数组,所有的集合元素变成对应的数组元素 |
Collection list=new ArrayList();
//使用 ArrayList 对象
// ArrayList 是 List 的实现类
Collection set=new HashSet();
//使用 HashSet 对象
// HashSet 是 Set 的实现类
集合中存储元素为Object类型对象,属于引用数据类型,默认调用Object类toString()方法执行输出操作,输出集合元素时,调用String类重写的toString()方法
12.1.6 遍历集合
当时用 System.out.println() 方法输出集合时,将以[ele1,ele2,…]的形式输出,这是因为所有的 Collection 实现类都重写了 toString() 方法,该方法可以一次性输出集合里的每一个元素,如果想依次访问集合元素,并对其进行操作,则需要遍历集合元素
- 使用 Iterator 接口遍历集合元素(Iterator 对象也被称为迭代器)
Iterator接口定义的方法
方法 | 描述 |
---|---|
boolean hasNext() | 是否存在下一个迭代元素,存在则返回true |
Object next() | 返回迭代的下一个元素 |
void remove() | 删除集合里上一次next()方法返回的元素 |
Iterator 接口对元素进行遍历时,是把集合元素的值传给了迭代器,所以修改迭代器中存储的值,对集合元素本身没有任何影响
- 使用 foreach 循环遍历集合元素
for(数据类型 迭代变量名 : 迭代对象){
//引用迭代变量名的语句
}
foreach 循环中的迭代变量也不是集合元素本身,系统只是把集合元素付给了迭代变量,因此,在 foreach 循环中修改迭代变量的值也没有任何实际意义
12.1.7 案例
package CH07_Collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class MyIterator {
public static void main(String[] args) {
Collection list = new ArrayList();
list.add("玄子");
list.add("XuanZiShaer");
list.add("玉玉诏");
list.add("QQ:3336392096");
System.out.println("------println-------");
System.out.println(list);
System.out.println("------Iterator------");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("-----ForEach--------");
for (Object o : list) {
System.out.println(o);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-khf7XxPS-1677431488894)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230216081931-1677427066337-95.png)]
12.1.8 两种遍历方式的区别
-
foreach:在遍历集合时不能对集合进行操作
-
Iterator:在遍历集合时可以对集合进行操作,他是将自己的数据传给了 iterator 迭代器
12.2 List 接口
- 继承Collection接口
- 存储一组可重复的有序对象
- 元素顺序以元素插入的次序来放置元素,不会重新排序
- 通过索引访问数组元素,索引从0开始
12.2.1 继承关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mMjgI9Uv-1677431488894)(./assets/image-20230216085402344-1677427066337-94.png)]
12.2.2 List 拓展方法
方法 | 描述 |
---|---|
void add(int index,Object element) | 将元素(element)插入到List的指定位置(index)处 |
boolean addAll(int index,Collection c) | 将集合c所包含的所有元素都插入List集合指定位置(index)处 |
Object get(int index) | 返回集合index索引处的元素 |
int indexOf(Object o) | 返回对象o在List集合中第一次出现的位置索引 |
int lastIndexOf(Object o) | 返回对象o在List集合中最后一次出现的位置索引 |
Object remove(int index) | 从集合中删除指定位置的元素 |
boolean remove(Object o) | 从集合中删除指定对象 |
Object set(int index,Object element) | 将index索引处的元素替换成element对象,返回新元素 |
List subList(int fromIndex,int toIndex) | 返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素组成的子集合 |
List 接口常用的子类有 ArrayList 类 和 LinkedList 类
List list = new ArrayList();
List list2 = new LinkedList();
使用 List 接口的扩展方法,需要通过 List 接口的实现类实例化对象调用
12.2.3 ArrayList 实现类
可以使用索引访问 List 集合元素
可以使用 for 循环遍历 List 集合
List 集合中存放的都是 Object 类对象
-
add(Object o)方法的参数是Object类对象
-
在通过get(int i)方法获取元素时必须进行强制类型转换
删除集合元素的方法
-
按索引删除
-
按对象删除
调用set(int index,Object o)方法改变List集合指定索引的元素时,指定的索引必须是List集合的有效索引
set(int index,Object o)方法不会改变List集合的长度
package CH07_Collection.List;
import java.util.ArrayList;
import java.util.List;
public class MyArrayList {
public static void main(String[] args) {
List list = new ArrayList();
list.add("XuanZi");
list.add("玄子");
list.add(123);
list.add(true);
list.add('男');
list.add(new Student("玄子", 18));
// 添加元素
System.out.println(list);
// 打印集合元素
list.remove("XuanZi");
// 移除元素
System.out.println(list);
boolean iscont = list.contains("XuanZi");
// 是否含有元素
System.out.println(iscont);
int index = list.indexOf("玄子");
// 查找元素下标
System.out.println(index);
list.set(1, "456");
// 替换元素 下标 元素
System.out.println(list);
Object obj = list.get(2);
// 获取元素 下标
System.out.println(obj);
System.out.println(list.subList(2, 4));
// 截取元素 起始下标(包含) 结束下标(不包含)
System.out.println(list);
for (Object o : list) {
System.out.println(o);
}
// foreach 打印集合元素
list.clear();
// 清空元素
System.out.println(list);
}
}
12.2.3 LinkedList 实现类
- 具有双向链表结构,更加方便实现添加和删除操作
- 但是
LinkedList
类随机访问元素的速度则相对较慢 - 具有List接口扩展的方法
12.2.4 LinkedList 拓展操作链表的方法
方法 | 描述 |
---|---|
void addFirst(Object o) | 在链表的首部添加元素 |
void addLast(Object o) | 在链表的末尾添加元素 |
Object getFirst() | 返回链表中第一个元素 |
Object getLast() | 返回链表中最后一个元素 |
Object removeFirst() | 删除并返回链表中的第一个元素 |
Object removeLast() | 删除并返回链表中的最后一个元素 |
LinkedList 特有的方法 必须是LinkedList 类型的集合才可以使用,如果是父类,则无法调用
LinkedList list=new LinkedList();
// 通过 LinkedList 类实现
list.addFirst("第一位");
12.2.5 ArrayList 类和 LinkedList 类对比
ArrayList 类和 LinkedList 类的共同点
-
可以容纳所有类型的元素对象,包括null
-
元素值可以重复
-
元素按顺序存储
ArrayList 类特点
-
底层是数组
-
优点:基于数组实现,读取操作效率高
-
缺点:不适合频繁进行插入和删除操作,因为每次执行该类操作都需要频繁移动其中的元素
LinkedList 类特点
-
由双向链表实现,任意一个节点都可以方便地访问它的前驱节点和后继节点
-
优点:增加、删除操作只需修改链表节点指针,不需进行频繁的移动
-
缺点:遍历效率较低
12.3 Set接口
- 和 List 接口一样,也是 Collection 的子接口
- 集合里的多个对象之间没有明显的顺序
- 不允许包含重复的元素
- 与 Collection 接口基本一样,没有提供额外的方法
- 只是行为上略有不同
12.3.1 继承关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jro0jyKJ-1677431488895)(./assets/image-20230216085227374-1677427066337-97.png)]
12.3.2 HashSet 类
- 是 Set 接口的典型实现
- 可以实现对无序不重复数据的存储
- 具有很好的存取和查找性能
特征
-
不允许存储重复的元素
-
没有索引,没有包含索引的方法,不能使用索引遍历
-
无序集合,存储元素和取出元素的顺序可能不一致
执行添加操作时,会将新添加的对象依次和集合中现有的元素进行比较
通过执行集合元素的hascode()方法和equals()方法进行判断
如果集合中不存在所添加的元素,则添加成功;否则,添加失败
12.3.3 Set 集合与 List 集合的区别
- 不能使用索引进行遍历
- 不允许存储重复的元素
- 存储无序
12.3.4 案例
package CH07_Collection.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class MyHashSet {
public static void main(String[] args) {
Set set = new HashSet();
set.add("玄子");
set.add("玄子1");
set.add("玄子2");
set.add("玄子3");
set.remove("玄子");
System.out.println(set.size());
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println(set.contains("玄子"));
set.clear();
System.out.println(set.isEmpty());
}
}
12.4 Map 接口
Map接口专门处理键值映射数据的存储
-
根据键(key)实现对值(value)的操作
-
Key:不要求有序,不允许重复
-
Value:不要求有序,但允许重复
与Collection接口不存在继承关系
12.4.1. 继承关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KAvwXhMF-1677431488895)(./assets/image-20230216091119798-1677427066337-96.png)]
12.4.2 Map 接口的常用方法
方法 | 描述 |
---|---|
Object put(Object key,Object value) | 以“键-值对”的方式进行存储 |
Object get(Object key) | 返回指定键所对应的值。如果不存在指定的键,返回null |
int size() | 返回元素个数 |
boolean remove(Object key) | 删除指定的键映射的“键-值对” |
Set keyset() | 返回键的集合 |
Collection values() | 返回值的集合 |
boolean containsKey(Object key) | 如果存在指定的键映射的“键-值对”,则返回true |
Set entrySet() | 返回“键-值对”集合 |
boolean isEmpty() | 若不存在“键-值对”元素,则返回true |
void clear() | 删除该Map对象中的所有“键-值对” |
Map 接口提供了大量的实现类,典型实现类如 HashMap 类、HashTable 类、TreeMap 类等,其中 HashMap 类是最常用的 Map 接口实现类
12.4.3 HashMap 类
package CH07_Collection.Map;
import java.util.*;
public class MyMap {
public static void main(String[] args) {
Map map = new HashMap();
// 创建Map集合
map.put("一", "玄子");
map.put("二", "玉玉诏");
map.put("三", "XuanZi");
map.put("四", "XuanZiShaer");
// 给集合里添加数据
System.out.println(map.get("二"));
// 根据提供的key值返回value值
System.out.println(map.size());
// 元素个数
System.out.println(map.remove("三"));
// 根据提供的kye值删除元素
System.out.println(map.containsKey("四"));
// 判断是否存在映射的键-值对
map.clear();
// 清除集合所有元素
System.out.println(map.isEmpty());
}
}
12.4.4 Map 的遍历
package CH07_Collection.Map;
import java.util.*;
public class MyMap {
public static void main(String[] args) {
Map map = new HashMap();
// 创建Map集合
map.put("一", "玄子");
map.put("二", "玉玉诏");
map.put("三", "XuanZi");
map.put("四", "XuanZiShaer");
// 给集合里添加数据
System.out.println("-----遍历Map-----");
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key);
System.out.println(map.get(key));
}
System.out.println("------------");
for (Object o : set) {
System.out.println(o + map.get(o).toString());
}
System.out.println("------------");
Set set1 = map.entrySet();
for (Object o : set1) {
System.out.println(o);
}
System.out.println("------------");
Collection collection = map.values();
for (Object o : collection) {
System.out.println(o.toString());
}
System.out.println("------------");
}
}
Set集合中每个元素都是一个Map.Entry对象,进行键和值的分离,由于是Object类型,在获取对象之前还需要进行必要的类型转换
12.4.5 Map.Entry 接口
用于保存“键-值对”元素
运用Map.Entry接口遍历集合
-
通过entrySet()方法获取所有“键-值”对元素的集合
-
遍历集合中每个元素,进行键和值的分离
方法 | 描述 |
---|---|
Object getKey() | 取得此“键-值对”对应的key值 |
Object getValue() | 取得此“键-值对”相对应的value值 |
int hashCode() | 返回该“键-值对”的哈希码值 |
Object setValue(Object value) | 用指定的值替换该“键-值对”的value值 |
12.4.6 Hashtable 类
与HashMap类具有相似的特征,也可以存储“键-值对”元素,是一个古老的Map接口实现类
12.4.7 Hashtable 类和 HashMap 类之间存在的区别
Hashtable | HashMap |
---|---|
继承自 Dictionary 类 | Java1.2引进的 Map interface 的一个实现 |
比 HashMap 要古老 | 是 Hashtable 的轻量级实现 |
线程安全 | 线程不安全 |
不允许有 null 的键和值 | 允许有 null 的键和值 |
效率稍低 | 效率稍高 |
实际开发中,HashMap类使用更多
12.5 泛型集合
使用集合存储数据时容易出现的问题
-
对象存储不易控制
-
类型转换容易出错
约束录入集合的元素类型,大大提高数据安全性,从集合中取出数据无需进行类型转换,让代码更加简洁,程序更加健壮,JDK1.5使用泛型改写了集合框架中的所有接口和类
集合类型 <要规范的类型> 集合名=new 集合类型 <要规范的类型>();
public class Test03 {
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>();
//集合内只可以存储 Integer 对象
list.add(1);
list.add(2);
list.add(3);
//因为已经设置了泛型,所以不需要再转换类型
int num= list.get(1);
}
}
十三、实用类
13.1Java API
Java应用程序编程接口(Java Application Programming Interface)
-
是运行库的集合
-
预先定义了一些接口和类
-
还特指API的说明文档,也称API帮助文档
13.1.1 常用包
包 | 描述 |
---|---|
java.lang | 编写Java程序时最广泛使用的包,自动导入所有的程序中,包含了Java程序的基础类和接口。包装类、String类等常用的类都包含在此包中, 还提供了用于管理类的动态加载、外部进程创建、主机环境查询和安全策略实施等系统操作的类 |
java.util | 包含系统辅助类,特别是Collection、List和Map等集合类 |
java.time | 包含对日期时间进行处理的类,如创建日期对象,格式化日期等 |
java.io | 包含与输入/输出有关的类,如文件操作等类 |
java.net | 包含与网络有关的类,如Socket、ServerSocket等类 |
java.sql | 包含与数据库相关的类,如Connection、Statement等类 |
13.2 枚举
是由一组固定的常量组成的类型,JDK1.5引入,继承自java.lang.Enum类
13.2.1 作用
-
使代码更易于维护,有助于确保为变量指定合法的、期望的值
-
简化代码的编写
-
使代码更加清晰,允许描述性的名称表示数据,使用时直观方便
访问修饰符 enum 枚举名 {
// enum 表示是枚举类型
常量1[,常量2...[;] ]
// 可以定义多个常量,表示不同的枚举值
// 逗号作为分隔符
[其他成员]
// 可以定义其他成员,须置于枚举常量后
}
获取枚举中指定内容
枚举名.常量名
通常,使用枚举表示一组有限的值,实现对输入的值进行约束
13.2.2 枚举方法
每一个枚举类型成员都是Enum类的对象,可以调用Enum类的方法,实现枚举的遍历、比较等操作
常用方法
方法 | 描述 |
---|---|
T[] values() | 以数组形式返回枚举类型的所有成员 |
T valueOf() | 将普通字符串转换为枚举实例 |
int compareTo() | 比较两个枚举成员在定义时的顺序,结果为负整数、零或正整数,表示当前对象小于、等于或大于指定对象 |
int ordinal() | 获取枚举成员的索引位置 |
13.2.3 枚举的好处
- 可以使代码易于维护,有助于确保为变量指定合法的、期望的值
- 使用枚举赋值,只需要输入 枚举名和
.
,就可以显示所有枚举值 - 枚举使代码更加清晰,允许用描述性的数据,使用时更直观方便
13.2.4 演示案例 一
enum Week {
MON,TUE,WED,THU,FRI,SAT,SUN;
}
public class EnumTest {
public static void main(String[] args) {
System.out.println("*********foreach遍历枚举元素*********");
for(Week w:Week.values()) {
System.out.println(w);
}
System.out.println("*********获取枚举的个数*********");
System.out.println("一周有"+Week.values().length+"天。");
System.out.println("*********使用索引遍历枚举元素*********");
for(int i = 0; i< Week.values().length; i++) {
System.out.println("索引"+ Week.values()[i].ordinal()+",值:" + Week.values()[i]+"。");
}
System.out.println("*********枚举元素比较*********");
System.out.println((Week.valueOf("MON")) .equals(Week.MON) );
System.out.println( Week.FRI.compareTo(Week.TUE));
}
}
13.2.5 演示案例 二
package CH_08.Enum;
public enum MyEnum {
XuanZi,玉玉诏,玄子,XuanZiShare;
}
package CH_08.Enum;
public class Student {
MyEnum name;
}
package CH_08.Enum;
public class XuanZi {
public static void main(String[] args) {
Student student = new Student();
student.name = MyEnum.玉玉诏;
switch (student.name) {
case 玄子:
System.out.println(student.name);
break;
case XuanZi:
System.out.println(student.name);
break;
case 玉玉诏:
System.out.println(student.name);
break;
case XuanZiShare:
System.out.println(student.name);
break;
default:
System.out.println(student.name);
break;
}
System.out.println("----遍历枚举-----");
for (MyEnum value : MyEnum.values()) {
System.out.println(value);
}
}
}
13.3 String 类
13.3.1 使用 String 对象存储字符串
String类位于java.lang包中,具有丰富的方法
String str1=“BDQN”;
String s = new String();
String str2=new String(“BDQN”);
13.3.2 字符串长度
通过String类的length( )方法可以获取到字符串中的字符个数,即字符串的长度
String str = "玄子";
System.out.println("字符串长度:" + str.length());
// 2
13.3.3 比较字符串
对比两个字符串对象的内容是否相同的,需要使用String类的equals( )方法
String str = "玄子";
String name = "玉玉诏";
System.out.println("比较字符串:" + str.equals(name));
// false
== 与 equals 的区别在章节 16.2
equals( )方法对大小写敏感,若忽略大小写可使用equalsIgnoreCase( )方法
System.out.println("A".equals("a"));
// equals 大小写敏感
System.out.println("A".toLowerCase().equals("a"));
// toLowerCase 转小写
System.out.println("a".toUpperCase().equals("A"));
// toUpperCase 转大写
System.out.println("A".equalsIgnoreCase("a"));
// 使用 equalsIgnoreCase 忽略大小写比较
13.3.4 连接字符串
使用+
运算符将字符串和变量连接在一起,+
运算符会将其他数据类型的值自动转换为String类型
System.out.println(str + name + 123);
// 字符串拼接
String类的concat()方法,将一个字符串连接到另一个字符串的末尾
System.out.println(str.concat(name).concat("123"));
// 字符串拼接 concat
13.3.5 提取字符串
方法 | 说明 |
---|---|
public int indexOf(int ch) | 搜索第一个出现的字符ch(或字符串value) |
public int indexOf(String value) | |
public int lastIndexOf(int ch) | 搜索最后一个出现的字符ch(或字符串value) |
public int lastIndexOf(String value) |
返回出现第一个匹配的位置,如果没有找到字符或字符串,则返回
-1
String value = "你好,你不好,你好好,你好不好,你不好不好";
System.out.println(value.indexOf("好"));
// 第一次 出现 好 的下标 从0开始
System.out.println(value.lastIndexOf("好"));
// 最后一次 出现 好 的下标 从0开始
方法 | 说明 |
---|---|
public String substring(int index) | 提取从位置索引开始的字符串部分 |
public String substring(int beginindex,int endindex) | 提取beginindex和endindex之间的字符串部分 |
public String trim() | 返回一个前后不含任何空格的调用字符串的副本 |
beginindex: 字符串的位置从0开始算
endindex: 字符串的位置从1开始算
String value = "你好,你不好,你好好,你好不好,你不好不好";
System.out.println(value.substring(3));
// 从下标为 3 的位置开始截取
System.out.println(value.substring(3, 6));
// 截取不到最后一位
String value = " 你好,你不好,你好好,你好不好,你不好不好 ";
System.out.println(value);
System.out.println(value.trim());
// 去除字符串前后空格
13.3.6 拆分字符串
String类提供了split()方法,将一个字符串分割为子字符串,结果作为字符串数组返回
String[] values = value.split(",");
// 按照 , 分割字符串
for (int i = 0; i < values.length; i++) {
System.out.println(values[i]);
}
// split 拆分后返回一个数组
13.3.7 StringBuffer 类
StringBuffer:String增强版
对字符串频繁修改(如字符串连接)时,使用StringBuffer类可以大大提高程序执行效率
创建StringBuffer 对象
StringBuffer sb1 = new StringBuffer();
//创建一个 StringBuffer 类的空对象
StringBuffer sb2 = new StringBuffer("玄子");
//创建一个带有内容的StringBuffer对象
StringBuffer 的使用
System.out.println(sb2.toString());
// 转普通字符串
System.out.println(sb2.append(123));
// 可追加 数字
System.out.println(sb2.append("玉玉诏"));
// 可追加 普通字符串
System.out.println(sb2.insert(1, "玉玉诏"));
// 从下标为 1 的位置插入新字符串
13.4 包装类
基本数据类型是不支持面向对象机制的,不具备对象的特征,为程序开发带来了一定方便的同时,也会受到一些制约
将基本数据类型封装到一个类中,即将基本类型包装成一个类类型
-
Java为每一种基本类型都提供了一个包装类
-
保存于java.lang包中
13.4.1 用途
-
作为和基本数据类型对应的类型,方便对象的操作
-
包含每种基本数据类型的相关属性
- 如最大值、最小值等,以及相关的操作方法
13.4.2 包装类关系图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zrxb0yF8-1677431488895)(./assets/image-20230216215350033-1677426702022-89.png)]
所有的数字类型都继承了 Number 类,Number 类是一个抽象类
13.4.3 包装类和基本数据类的对应关系
类型 | 长度 | 默认值 | 包装类 |
---|---|---|---|
byte | 8位 | 0 | java.lang.Byte |
short | 16位 | 0 | java.lang.Short |
int | 32位 | 0 | java.lang.Integer |
long | 64位 | 0 | java.lang.Long |
float | 32位 | 0.0 | java.lang.Float |
double | 64位 | 0.0 | java.lang.Double |
char | 16位 | \u0000~\uFFFF | java.lang.Character |
boolean | 1位 | false、true | java.lang.Boolean |
不同应用场景中,基本数据类型和包装类型间要进行相互转换以完成特定操作
13.4.4 基本数据类型向包装类转换
包装类的构造方法
public Type (type value)
// Type 表示包装类
// (type value) type 基本数据类型
public Type (String value)
包装类对象 对象名=new 包装类对象(基本数据)
Integer integer=new Integer(32);
Integer integer=new Integer("32");
调用包装类的 valueOf() 方法也可实现从基本数据类型到包装类型的转换,可自行查阅API帮助文档学习
13.4.5 包装类和基本数据类型的转换
public type typeValue( );
// type 表示基本数据类型
基本数据类型 变量名=对象名.基本数据类型Value();
Integer integer=new Integer(32);
//定义一个Integer包装类对象
int num=integer.intValue();
//转换为 基本数据类型 int
13.4.6 装箱拆箱
自动装箱(Autoboxing)
- 把基本类型变量直接转换为对应的包装类对象,或者转换为Object类型对象
自动拆箱(AutoUnboxing)
- 与装箱相反,将包装类对象转换成对应的基本类型变量
package CH_08.Integer;
public class MyInteger {
public static void main(String[] args) {
Integer inObj = 5;
Object boolObj = true;
// 装箱
System.out.println(inObj + "," + boolObj);
int it = inObj;
// 拆箱
System.out.println(it);
if (boolObj instanceof Boolean) {
boolean b = (Boolean) boolObj;
System.out.println(b);
// 先将Object类对象强制转换为Boolean类型
// 再赋值给boolean类型变量
}
}
}
13.4.7 总结
自动装箱拆箱大大简化了基本类型和包装类型相互转换的过程
编码时,基本数据类型和包装类型要相互匹配
-
Integer对象只能自动拆箱成int变量,不能试图拆箱成boolean类型变量
-
int变量也只能自动装箱成Integer对象,而不能是Boolean对象
即使可以赋值给Object对象,也只是利用了Java中向上自动转型特性
基本数据类型和包装类对象间的相互转换工作会增加系统的额外负担,影响代码的运行性能
包装类对象只有在基本数据类型需要用对象表示时才使用,但它并不能取代基本数据类型
13.5日期类
JDK1.8中,所有的日期/时间基础类都包含在java.time包中
是不可变的线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求
13.5.1 常用类
类 | 描述 | 示例 |
---|---|---|
LocalDate | 表示日期(年/月/日),不包含时间 | 2020/11/24 |
LocalTime | 表示时间(时/分/秒/毫秒),不包含日期 | 15:32:12 |
LocalDateTime | 表示日期和时间(年/月/日/时/分/秒/毫秒) | 2020/11/24 15:32 |
-
都是不可变的对象
-
提供简单的日期或时间
-
不包含与时区相关的信息
13.5.2 日期类的通用方法
方法 | 描述 |
---|---|
日期类 now() | 根据当前时间创建对象,返回日期对象 |
日期类 of(int year, Month month, int dayOfMonth) | 根据指定日期/时间创建日期类对象 |
Month getMonth() | 获取月份,返回Month,这是一个表示月份的枚举 |
int getDayOfMonth() | 返回当前日期对象的月份天数 |
int getYear() | 返回当前日期对象的年份 |
日期类 plusDays() | 在指定日期上添加天数 |
日期类 plusWeeks() | 在指定日期上添加周数 |
日期类 plusMonths() | 在指定日期上添加月数 |
日期类 plusYears() | 在指定日期上添加年数 |
LocalDateTime localDate = LocalDateTime.now();
//获取当前时间
13.5.3 日期格式化
使用DateTimeFormatter类对日期格式化,使之按指定格式输出
DateTimeFormatter类位于java.time.format包
常用方法
方法 | 描述 |
---|---|
ofPattern() | 传入格式字符串,规范化日期输出格式 |
format() | 将日期格式化为字符串 |
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日" );
// 指定日期格式
//两种格式化方式
String str1 = localDate.format( formatter );
// formatter DateTimeFormatter类对象
String str2 = formatter.format( localDate );
// localDate LocalDate 类对象
//结果:2023年02月16日
//另一种格式化方式
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date dt = new Date();
System.out.println(format.format(dt));
//结果:2023-02-16
13.5.4 演示案例 一
package CH_08.LocalDateTime;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class MyLocalDateTime {
public static void main(String[] args) {
LocalDateTime date = LocalDateTime.now();
System.out.println(date);
System.out.println(date.getMonth());
System.out.println(date.getDayOfMonth());
System.out.println(date.getYear());
System.out.println(date.getDayOfWeek());
System.out.println(date.getHour());
System.out.println(date.getMinute());
System.out.println(date.getSecond());
System.out.println(date.getMonthValue());
System.out.println(date.getDayOfYear());
LocalDateTime date2 = LocalDateTime.of(2, 2, 2, 2, 2, 2, 2);
System.out.println(date2);
System.out.println(date2.plusDays(1));
System.out.println(date2.plusHours(1));
System.out.println(date2.plusMinutes(1));
System.out.println(date2.plusYears(1));
System.out.println(date2.plusMonths(1));
System.out.println("--------------------");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date dt = new Date();
System.out.println(format.format(dt));
System.out.println("--------------------");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date1 = LocalDate.now();
System.out.println(formatter.format(date1));
System.out.println(date1.format(formatter));
}
}
13.5.5 演示案例 二
package CH_08.LocalDateTime;
import java.time.LocalDate;
import java.util.Scanner;
public class Birth {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入您今年的生日(格式:YYYY-MM-DD):");
// String birht = input.nextLine();
String birht = "2022-12-02";
String[] br = birht.split("-");
LocalDate date3 = LocalDate.parse(birht);
LocalDate date = LocalDate.of(Integer.parseInt(br[0]), Integer.parseInt(br[1]), Integer.parseInt(br[2]));
System.out.println("生日:"+date);
LocalDate date2 = LocalDate.now();
System.out.println("今年:"+date2);
System.out.println("过了多少天:"+date.getDayOfYear());
System.out.println("当月几号:"+date.getDayOfMonth());
System.out.println("周几:"+date.getDayOfWeek());
}
}
13.6 Random 类
通过一个种子以任意或非系统方式生成随机数
位于java.util包
13.6.1 构造方法
方法 | 描述 |
---|---|
Random() | 创建一个随机数生成器 |
Random(long seed) | 使用单个long种子创建一个随机数生成器 |
13.6.2 nextInt() 重载方法
方法 | 描述 |
---|---|
int nextInt(); | 返回下一个伪随机数,它是此随机数生成器序列中均匀分布的int值 |
int nextInt(int n); | 取自此随机数生成器序列的、在0(包括)和指定值n(不包括)之间均匀分布的int值 |
13.6.3 演示案例
package CH_08.Random;
import java.util.Random;
public class MyRandom {
public static void main(String[] args) {
//创建一个Random对象,即随机数生成器
Random rand = new Random();
//随机生成10个随机整数,并显示
for (int i = 0; i < 10; i++) {
int num = rand.nextInt(10);
System.out.println("第" + (i + 1) + "个随机数是:" + num);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RFAJM8KO-1677431488896)(./assets/image-20230216222148704-1677426702023-90.png)]
package CH_08.Random;
// 设置random随机数取值范围
public class MyRandom {
public static void main(String[] args) {
// 10-19 之间的值
int a = (int) (Math.random() * 10 + 10);
// (乘以差+1) +保底
}
}
十四、IO流
14.1 File 类
是java.io包下代表操作与平台无关的文件和目录的类,实现对文件或目录的新建、删除、重命名等操作
File file = new File( String pathname );
// 创建File类对象时,必须设置文件路径
// pathname 文件路径
绝对路径:以根目录开头的完整路径
相对路径:相对于当前目录文件的路径
.
表示当前目录
..
表示上级目录
Windows文件路径名中,分隔符可以使用正斜杠
/
,也可以使用反斜杠\
。但必须写成\\
,其中第一个\
表示转义符
14.1.1 File类的常用方法
方法名 | 描述 |
---|---|
boolean createNewFile() | 创建新文件 |
boolean delete() | 删除文件 |
boolean exists() | 判断文件是否存在 |
Boolean isFile() | 判断是否是文件 |
boolean isDirectory() | 判断是否是目录 |
long length() | 返回文件长度,单位为字节 若文件不存在,则返回0L |
String getPath() | 返回此对象文件名所对应的路径 |
String getAbsolutePath() | 返回此对象表示的文件的绝对路径名 |
在实际开发中,如需完成对File类的更多操作,可随时查阅API帮助文档
package CH09_IO.IO;
import java.io.File;
import java.io.IOException;
public class MyIO {
public static void main(String[] args) {
File file = new File("XuanZi.txt");
if (file.exists()) {
if (file.isFile()) {
System.out.println("文件名:" + file.getName() + ",文件长度:" + file.length() + "字节。");
System.out.println("文件路径是:" + file.getPath());
System.out.println("文件绝对路径是:" + file.getAbsolutePath());
}
if (file.isDirectory()) {
System.out.println("此文件是目录");
}
} else {
System.out.println("此文件不存在!");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JjtdID8R-1677431488896)(./assets/image-20230216232759203-1677032462242-1-1677426567965-72.png)]
package CH09_IO.IO;
import java.io.File;
import java.io.IOException;
public class MyFile {
public static void main(String[] args) {
File file = new File("resources/XuanZi.txt");
// 指定文件路径
File dirs = file.getParentFile();
// 获取上级文件路径
if (!dirs.exists()) {
// 判断上级文件夹是否存在
dirs.mkdirs();
// 创建上级文件夹
}
try {
file.createNewFile();
// 创建文件
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public void deleteFile(File file) {
if (file.exists()) {
file.delete();
//删除文件
System.out.println("文件删除成功!");
}
}
14.2 IO 流概述
IO流是指一连串流动的字符,以先进先出方式发送信息的通道
-
I:input,指读入操作
-
O:output,指写出操作
Java把所有流类型都封装到java.io包中,以实现输入/输出操作
14.2.1 Java 流的分类
-
按流向划分:输入流和输出流
-
按处理单元划分:字节流和字符流
-
按流的角色划分:节点流和处理流
14.2.2 按流向划分:输入流和输出流
输入流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10WKBFPZ-1677431488896)(./assets/image-20230217004345541-1677426567965-71.png)]
只能从中读取数据,而不能写入数据的流,实现程序从数据源中读数据
输出流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LXhHDiab-1677431488897)(./assets/image-20230217004349220-1677426567965-73.png)]
只能向其写入数据,而不能从中读数据的流,实现程序向目标数据源中写数据
-
输入流主要由InputStream和Reader作为基类
-
输出流则主要由OutputStream和Writer作为基类
-
都是抽象类,无法直接实例化对象
按照流的流向进行分类时,输入流完成数据读入操作,输出流完成数据写出操作,这里的
出
和入
一定是从程序运行所在内存的角度来论述
14.2.3 按处理单元划分:字节流和字符流
字节流
-
以8位字节为操作数据单元的流,可操作二进制数据
-
可细分为字节输入流、字节输出流
字符流
-
以16位字符为操作数据单元的流,可操作文本数据
-
可细分为字符输入流、字符输出流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UKFRXAyk-1677431488897)(./assets/image-20230217004450803-1677426567965-78.png)]
区别
-
操作的数据单元不同
-
使用方法几乎相同
14.2.4 按流的角色划分:节点流和处理流
节点流(包装流)
-
可以直接向一个特定的存储介质(如磁盘、文件)读写数据的流
-
使用节点流进行读写数据操作时,程序直接连接到数据源
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EwZa5wTg-1677431488897)(./assets/image-20230216234219675-1677426567965-79.png)]
处理流
-
对已存在的流进行连接和封装,通过封装后的流实现数据读写操作的流
-
使用处理流进行读写操作时,程序并不会直接连接到实际的数据源
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z7gPHV4h-1677431488897)(./assets/image-20230216234211113-1677426567965-74.png)]
使用处理流包装节点流,通过处理流执行输入和输出功能,让节点流与文件或磁盘等存储设置交互,可隐藏底层节点流的差异
14.3 字节流
具有输入和输出操作
主要操作byte类型数据
基类
-
字节输出流类: OutputStream
-
字节输入流类:InputStream
14.3.1 字节输出流的基类:OutputStream
是抽象类,必须使用该类的子类进行实例化对象
常用方法
方法 | 描述 |
---|---|
void close() | 关闭输出流 |
void flush() | 刷新缓冲区 |
void write(byte[] b) | 将每个byte数组写入数据流 |
void write(byte[] b,int off,int len) | 将每一个指定范围的byte数组写入数据流 |
void write(int b) | 将一个字节数据写入数据流 |
如果需要操作文件,则使用FileOutputStream实例化
14.3.2 字节输出流FileOutputStream类
使用OutputStream类的FileOutputStream子类向文本文件写入数据
常用构造方法
方法 | 描述 |
---|---|
FileOutputStream( File file) | 创建向指定File对象写数据的文件输出流 file:指定目标文件的对象 |
FileOutputStream( String name) | 创建向指定路径的文件写数据的文件输出流 name:指定目标文件的路径字符串 |
FileOutputStream( String name, boolean append) | 创建一个向指定路径的文件写入数据的文件输出流 name:指定目标文件的路径字符串 append:表示是否在文件末尾追加数据。如果为true,则表示可以在文件末尾追加数据 |
14.3.3 创建文件输出流对象的常用方式
//方式一:使用File对象构造对象
File file = new File("D:\\mydoc\\test.txt");
OutputStream fos = new FileOutputStream(file);
//方式二:使用文件路径构造对象
OutputStream fos = new FileOutputStream("D:\\doc\\test.txt");
//方式三:使用文件路径构造对象,且可向文件末尾追加数据
OutputStream fos = new FileOutputStream("D:\\doc\\test.txt",true);
使用FileOutputStream类的构造方法创建对象时
如果相应的文件不存在,就会自动新建一个文件
如果参数file或name表示的文件路径是一个目录,则会抛出FileNotFoundException异常
14.3.4实现步骤
- 导入相关的类
- 构造文件输出流FileOutputStream对象
- 把数据写入文本文件
- 关闭文件输出流对象
package CH09_IO.IO;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class MyFileOutputStream {
public static void main(String[] args) throws IOException {
OutputStream fos = new FileOutputStream("resources/XuanZi.txt", true);
//准备一个字符串
String str = "I love Java!";
//将字符串转换为byte数组
byte[] words = str.getBytes();
fos.write(words, 0, words.length);
System.out.println("文件已写入成功!");
if (fos != null) {
fos.close();
}
}
}
创建输出流对象、执行写操作、关闭输出流时都可能会有IOException异常发生
因此,除处理FileNotFoundException异常外,还需处理IOException异常
14.3.5 字节输入流的基类:InputStream
从文件中读数据,与OutputStream一样,也是抽象类
常用方法
方法 | 描述 |
---|---|
int read() | 读取一个字节数据 |
int read(byte[] b) | 将数据读取到字节数组中 |
int read(byte[] b,int off,int len) | 从输入流中读取最多len长度的字节,保存到字节数组中,保存的位置从off开始 |
void close() | 关闭输入流 |
int available() | 返回输入流读取的字节数 |
14.3.6 字节输入流 FileInputStream 类
使用InputStream类的FileInputStream子类实现文本文件内容的读取
常用构造方法
方法 | 描述 |
---|---|
FileInputStream(File file) | 用于创建从指定File对象读取数据的文件输入流 file:指定目标文件数据源对象 |
FileInputStream( String name) | 用于创建从指定路径的文件读取数据的文件输入流 name:指定目标文件数据源路径字符串 |
14.3.7 创建文件输入流对象的常用方式
//方式一:使用File对象构造对象
File file = new File("D:\\doc\\test.txt");
InputStream fis = new FileInputStream(file);
//方式二:使用文件路径构造对象
InputStream fis = new FileInputStream("D:\\doc\\test.txt");
从文件读取到计算机内存中的过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qpET5v9P-1677431488898)(./assets/image-20230217005442035-1677426567965-75.png)]
read()方法
-
每次读取一个字节(0~255的整数)
-
每次读取多个字节
读取文件中全部数据
while( (data=fis.read()) != -1 ) {
// 判断是否到文件尾
System.out.print( (char) data);
// 强制类型转换
}
按字节读取并显示数据时需进行强制类型转换
使用read()读取文件中的数据时,当返回结果为-1时,即输入流已经读到末尾
在创建输入流对象、读取数据、关闭流时必须进行异常处理
14.3.8 实现步骤
- 构造文件输入流FileInputStream对象
- 读取文本文件中数据
- 关闭文件输入流对象
package CH09_IO.IO;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MyFileInputStream {
public static void main(String[] args) throws IOException {
File file = new File("resources/XuanZi.txt");
FileInputStream fis = new FileInputStream(file);
System.out.println("可读取的字节数:" + fis.available());
System.out.println("----------");
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
String s = new String(bytes, 0, len);
System.out.println(s);
}
fis.close();
}
}
文本文件保存时,采用GBK编码方式,每个中文占2个字节
read()方法一次读取1个字节(半个中文字符)
当数组长度不足时,可能导致中文乱码
14.4 字符流
一个字符占用内存的两个字节
-
当输入和输出是文本文件时,尽量使用字符流
-
使用Reader类和Writer类操作字符
基类
-
字符输出流类: Writer
-
字符输入流类:Reader
使用字符流读写文本更合适
14.4.1 字符输出流的基类:Writer
抽象类
常用方法
方法 | 描述 |
---|---|
void write(String str) | 将str字符串中包含的字符输出到输出流中 |
void write(String str, int off, int len) | 将字符串中从off位置开始,长度为len的字符输出到输出流中 |
void close() | 关闭输出流 |
void flush() | 刷新输出流 |
14.4.2 字符输出流FileWriter类
- 导入相关的类
- 创建FileWriter对象
- 使用write()方法向文本文件写数据
- 清空流
- 关闭流
package CH09_IO.IO;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class MyFileWrite {
public static void main(String[] args) throws IOException {
File file = new File("resources/XuanZi.txt");
FileWriter fw = new FileWriter(file,true);
fw.write("我能写字符串了");
fw.flush();
// 刷新
fw.close();
}
}
14.4.3 字符输入流基类:Reader
是抽象类
常用方法
方法 | 描述 |
---|---|
int read() | 从输入流中读取单个字符,返回所读取的字符数据 |
int read(char[] c) | 从输入流中最多读取c.length个字符数据,并将其存储在字符数组c中,返回实际读取的字符数 |
int read(char[] c, int off, int len) | 从输入流中最多读取len个字符的数据,并将其存储在字符数组c中 存入数组c中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数 |
14.4.4 字符输入流FileReader类
- 导入相关的类
- 创建FileReader对象
- 循环使用read()方法读取文件中数据
- 关闭流
package CH09_IO.IO;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class MyFileReader {
public static void main(String[] args) throws IOException {
File file = new File("resources/XuanZi.txt");
FileReader fr = new FileReader(file);
System.out.println("-----------");
char[] c = new char[1024];
int len = 0;
while ((len = fr.read(c)) != -1) {
String str = new String(c, 0, len);
System.out.println(str);
}
fr.close();
}
}
14.5 缓冲流
java.io包提供了缓冲流,高读写文件数据的执行效率
Java缓冲流自身并不具有IO功能,只是在别的流上增加缓冲,以提高程序性能
分类
-
字节缓冲流
-
字符缓冲流
-
BufferedWriter类
-
BufferedReader类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BGVUHFms-1677431488898)(./assets/image-20230218193712672-1677426567965-76.png)]
14.5.1 BufferedWriter类
是Writer类的子类,带有缓冲区
默认情况下,只有缓冲区满的时候,才会把缓冲区的数据真正写到目的地,能减少物理写数据的次数,提高输入/输出操作的执行效率
常用的构造方法
方法 | 描述 |
---|---|
BufferedWriter(Writer out) | 创建一个缓冲字符输出流 |
使用FileWriter类与BufferedWriter类,可提高字符流写文本文件的效率
14.5.2 实现步骤
- 导入相关的类
- 构造FileWriter对象和BufferedWriter对象
- 调用write()方法写数据
- 清空、关闭流对象
package CH09_IO.IO;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class MyBufferedWriter {
public static void main(String[] args) throws IOException {
File file = new File("resources/XuanZi.txt");
FileWriter fw = new FileWriter(file, true);
BufferedWriter bw = new BufferedWriter(fw);
bw.write("玄子啊啊啊");
bw.flush();
bw.close();
}
}
14.5.3 BufferedReader类
Reader类的子类
带有缓冲区,提高文件读取的效率
-
把一批数据读到缓冲区
-
从缓冲区内获取数据
常用构造方法
方法 | 描述 |
---|---|
BufferedReader(Reader in) | 创建一个缓冲字符输入流 |
readLine()方法
- 按行读取文件中数据
14.5.4 实现步骤
- 导入相关的类
- 构造FileReader对象和BufferedReader对象
- 调用readLine()方法读取数据
- 清空、关闭流对象
package CH09_IO.IO;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class MyBufferedReader {
public static void main(String[] args) throws IOException {
File file = new File("resources/XuanZi.txt");
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
// br.readLine();
String str = "";
while ((str = br.readLine()) != null) {
System.out.println(str);
}
br.close();
}
}
14.6 数据操作流
对二进制文件读写操作
DataOutputStream类
-
OutputStream类的子类
-
与FileOutputStream类结合使用写二进制文件
使用DataOutputStream类写二进制文件的实现步骤与FileOutputStream类写文本文件相似
DataInputStream类
-
InputStream类的子类
-
与FileInputStream类结合使用读取二进制文件
使用DataInputStream类读取二进制文件的实现步骤与FileInputStream类读取文本文件相似
14.6.1DataOutputStream类
按照与平台无关的方式向流中写入基本数据类型的数据,如int、float、long、double和boolean等
使用writeUTF()方法能写入采用utf-8字符编码的字符串
FileOutputStream out1 = new FileOutputStream("D:\\doc\\test.txt");
BufferedOutputStream out2 = new BufferedOutputStream(out1);
DataOutputStream out = new DataOutputStream(out2);
out.writeByte(1);
out.writeLong(2);
out.writeChar('c');
out.writeUTF("hello");
14.6.2 DataInputStream类
按照与平台无关的方式从流中读取基本数据类型的数据,如int、float、long、double和boolean等
使用readUTF( )方法能读取采用utf-8字符编码的字符串
FileInputStream in1 = new FileInputStream("D:\\doc\\test.txt");
BufferedInputStream in2 = new BufferedInputStream(in1);
DataInputStream in = new DataInputStream(in2);
System.out.println(in.readByte());
System.out.println(in.readLong());
System.out.println(in.readChar());
System.out.println(in.readUTF());
14.6.3 实现步骤
写二进制文件
- 导入相关的类
- 构造数据输出流对象
- 调用write()方法写二进制文件
- 关闭数据输出流
读二进制文件
- 导入相关的类
- 构造数据输入流对象
- 调用read ()方法读取二进制文件
- 关闭数据输入流
package CH09_IO.IO;
import java.io.*;
public class MyData {
public static void main(String[] args) throws IOException {
DataOutputStream dos = null;
DataInputStream dis = null;
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//创建输入流文件
fis = new FileInputStream("D:\\doc\\User.class");
dis = new DataInputStream(fis);
//创建输出流文件
fos = new FileOutputStream("D:\\doc\\newUser.class");
dos = new DataOutputStream(fos);
int temp;
while (((temp = dis.read()) != -1)) {
fos.write(temp);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
//省略异常处理代码
dis.close();
fis.close();
fos.close();
dos.close();
}
}
}
14.7 序列化与反序列化
为每个对象属性一一编写读写代码,过程很繁琐且非常容易出错
使用序列化和反序列化,方便数据传输和存储
-
序列化是将对象的状态存储到特定的存储介质中的过程
-
反序列化是将特定的存储介质中数据重新构建对象的过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DAjlUSz4-1677431488898)(./assets/image-20230218194926286-1677426567965-77.png)]
对象输出流( ObjectOutputStream ):实现序列化
对象输入流( ObjectInputStream):实现反序列化
14.7.1 使用ObjectOutputStream类实现序列化
序列化的对象所属类须为可序列化的类
一个类实现java.io.Serializable接口,该类的对象是可序列化的
public interface Serializable{}
JDK1.8类库中,有些类实现了java.io.Serializable接口
- 如:String类、包装类和日期时间类等
常用方法
方法 | 描述 | 类型 |
---|---|---|
ObjectOutputStream(OutputStream out) | 创建对象输出流对象 | 构造方法 |
final void writeObject(Object obj) | 将指定对象写入流 | 实例方法 |
创建一个Person类,并标记该类的对象是可序列化的
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
private String sex;
public void print() {
System.out.println("姓名:"+this.name+",年龄:"+this.age+",性别:"+this.sex+"。");
}
}
14.7.2 解决序列化和反序列化的版本不一致问题
引入serialVersionUID常量
-
serialVersionUID常量为long类型
-
JVM在编译时自动生成serialVersionUID常量,也可显式定义
在Person类中显式定义serialVersionUID常量
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID=1L;
// 序列化和反序列化时serialVersionUID常量值需一致
private String name;
private int age;
private String sex;
public void print() {
System.out.println("姓名:"+this.name+",年龄:"+this.age+",性别:"+this.sex+"。");
}
}
14.7.3 实现步骤
- 导入相关的类
- 创建可序列化的类,要求实现Serializable接口
- 创建对象输出流(ObjectOutputStream)
- 输出可序列化对象
- 关闭对象输出流
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("D:\\doc\\persons.bat"));
// 创建流
Person person = new Person("杰米", 25, "男");
Person person2 = new Person("Lisa", 30, "女");
ArrayList<Person> list = new ArrayList<Person>();
list.add(person);
list.add(person2);
oos.writeObject(list);
// 集合对象序列化
System.out.println("序列化成功!");
}catch(IOException ex){
ex.printStackTrace();
}finally {
if(oos!=null){
try{
oos.close();
}catch(IOException ex){
ex.printStackTrace();
}
}
}
14.7.4 使用ObjectInputStream类实现反序列化
使用对象输出流ObjectInputStream可以还原序列化的对象
常用方法
方法 | 描述 | 类型 |
---|---|---|
ObjectInputStream(InputStream in) | 创建对象输入流对象 | 构造方法 |
final Object readObject() | 从指定位置读取对象 | 实例方法 |
final Object readObject()
返回一个Object类型的对象,如果确定该Object对象的真实类型,则可以将该对象强制转换成其真实类型
14.7.5 实现步骤
- 导入相关的类
- 创建一个对象输入流(ObjectInputStream)
- 输出反序列化后对象信息
- 关闭对象输入流
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("D:\\doc\\persons.bat"));
ArrayList<Person> list = (ArrayList<Person>)ois.readObject();
// 反序列化,进行强制类型转换
for(Person person:list) {
person.print();
}
//输出转换反序列化后的对象信息
}catch(ClassNotFoundException ex) {
ex.printStackTrace();
} catch(FileNotFoundException ex) {
ex.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (ois != null)
ois.close();
}catch (IOException ex) {
ex.printStackTrace();
}
}
反序列化时,readObject()方法返回的是Object对象需强制类型转换为Person对象
如果使用序列化方式向文件中写入多个对象,那么反序列化恢复对象时,也是按照写入的顺序读取
如果一个可序列化的类有多个父类(包括直接或间接父类),则这些父类要么是可序列化的,要么有无参数的构造;否则,将会抛出异常
14.7.6 限制序列化
出于安全考虑,对于一些比较敏感的信息(如用户密码),应限制被序列化
使用transient关键字修改不需要序列化的对象属性
希望Person类对象中的年龄信息不被序列化
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private transient int age;
// 被限制序列化
private String sex;
public void print() {
System.out.println("姓名:"+this.name+",年龄:"+this.age+",性别:"+this.sex+"。");
}
}
14.8 IO 总结
14.8.1 Java常用IO流
分类 | 字节输出流 | 字节输入流 | 字符输出流 | 字符输入流 |
---|---|---|---|---|
基类 | OutputStream | InputStream | Writer | Reader |
文件流 | FileOutputSteam | FileInputStream | FileWriter | FileReader |
缓冲流 | BufferedOutputStream | BufferedInputStream | BufferedWriter | BufferedReader |
对象流 | ObjectOutputStream | ObjectInputStream | - | - |
数据操作流 | DataOutputStream | DataInputStream | - | - |
-
所有的基类都是抽象类,无法直接创建实例,需要借助其实现类
-
所有输出流实现写数据,所有输入流实现读数据
-
输入和输出是相对程序而言
-
所有的文件流直接与存储介质关联,需指定物理节点
在操作文本文件时,应使用字符流
字节流可以处理二进制数据,它的功能比字符流更强大
如果是二进制文本,应考虑使用字节流
十五、多线程
15.1 多线程概述
15.1.1 进程和线程
进程
- 应用程序的执行实例
- 有独立的内存空间和系统资源
线程
- CPU调度和分派的基本单位
- 进程中执行运算的最小单位,可完成一个独立的顺序控制流程
15.1.2 多线程运行机制
如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程
多个线程交替占用CPU资源,而非真正的并行执行
多线程好处
-
充分利用CPU的资源
-
简化编程模型
-
良好的用户体验
进程与线程的关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8A8iEY3z-1677431488899)(./assets/image-20230214134539630-1677425557526-1.png)]
并行执行
-
通常表示同一个时刻有多条指令代码在处理器上同时运行
-
往往需要多个处理器支持
并发运行
-
表示在一个处理器中,操作系统为了提高程序的运行效率,将CPU的执行时间分成多个时间片,分配给同一进程的不同线程
-
多个线程分享CPU时间,交替执行
宏观并行,微观串行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dlo6I7ZU-1677431488899)(./assets/image-20230214022709196-1677425557527-3.png)]
15.1.3 多线程的优势
- 系统资源利用率高
- 用户交互性高
- 极少数情况下开发更简单
15.2 多线程编程
15.2.1 Thread 类常用方法
Thread 类提供了大量的方法来控制和操作多线程
方法 | 描述 | 类型 |
---|---|---|
Thread() | 创建 Thread 对象 | 构造方法 |
Thread(Runnable target) | 创建 Thread 对象,target 为 run() 方法被调用的对象 | 构造方法 |
Thread(Runnable target,String name) | 创建 Thread 对象,target 为run() 方法被调用的对象,name 为新线程的名称 | 构造方法 |
void run() | 执行任务操作的方法 | 实例方法 |
void start() | 使该线程开始执行,JVM 将调用该线程的 run() 方法 | 实例方法 |
void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) | 静态方法 |
Thread currentThread() | 返回当前线程对象的引用 | 静态方法 |
15.2.2 主线程
Java程序启动时,一个线程立即随之启动,通常称之为程序的主线程
- main()方法即为主线程入口
- 主线程是产生其他子线程的线程
- 主线程必须最后完成执行,因为它执行各种关闭动作
15.2.3 使用 Thread 类的方法获取主线程信息
package CH10_Thread.xz01_CurrentThread;
public class CurrentThread {
public static void main(String[] args) {
Thread t = Thread.currentThread();
// 获得主线程对象
System.out.println("当前线程:" + t.getName());
// 查看当前主线程 main
t.setName("MyThread");
// setName() 设置线程名
System.out.println("当前线程:" + t.getName());
// getName() 获取线程名
}
}
15.2.4 实现多线程
Java中创建线程的两种方式
-
继承
java.lang.Thread
类 -
实现
java.lang.Runnable
接口
使用线程的步骤
- 定义线程
- 创建线程对象
- 启动线程
15.2.5 继承 Thread 类创建线程
- 自定义线程类必须继承自 Thread 类
- 重写 run() 方法,编写线程执行体
- 创建线程对象,调用 start() 方法启动线程
线程从它的 run() 方法开始执行,即 Thread 类的 run() 方法是线程运行的起点
package CH10_Thread.xz02_MyThread;
public class MyThread extends Thread {
// 继承自Thread类
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程:"+ i);
}
}
// 重写Thread类中run()方法
}
package CH10_Thread.xz02_MyThread;
public class XuanZi {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
// 启动线程
}
}
15.2.6 线程休眠
让线程暂时睡眠指定时长,线程进入阻塞状态,睡眠时间过后线程会再进入可运行状态
public static void sleep(long millis)
// millis为休眠时长,以毫秒为单位
package CH10_Thread.xz03_Sleep;
public class MyThread extends Thread {
// 继承自Thread类
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程:" + i);
try {
MyThread.sleep(1000);
//线程休眠1秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
调用 sleep() 方法需处理 InterruptedException 异常
package CH10_Thread.xz03_Sleep;
public class XuanZi {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
myThread.start();
// 已启动的线程对象不能重复调用start()方法
// 否则会抛出IllegalThreadStateException异常
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9kYvTY0k-1677431488899)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214143527-1677425557527-2.png)]
package CH10_Thread.xz04_InterruptedException;
public class XuanZi {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
myThread.interrupt();
// 打断子线程
// 如果调用sleep()方法控制线程休眠时间的线程
// 被其他线程中断,则会产生InterruptedException异常
for (int i = 0; i < 5; i++) {
System.out.println("主线程" + i);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HRiwtlOV-1677431488900)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214150331-1677425557527-5.png)]
15.2.7 继承 Thread 类创建多线程
多个线程交替执行,不是真正的“并行”,线程每次执行时长由分配的CPU时间片长度决定
package CH10_Thread.xz05_Threads;
public class XuanZi {
public static void main(String[] args) {
MyThread myThread = new MyThread("线程A:");
MyThread myThread1 = new MyThread("线程B:");
myThread.start();
myThread1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程:" + i);
}
}
}
package CH10_Thread.xz05_Threads;
public class MyThread extends Thread {
public MyThread(String name) {
super.setName(name);
// 构造传参
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(super.getName() + i);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vZmYYmeB-1677431488900)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214151502-1677425557527-4.png)]
调用 start() 方法后,每个线程独立完成各自的操作,相互间没有影响,并行执行
15.2.8 启动线程不可以直接调用 run() 方法
线程对象调用 start() 方法是启动线程,run()方法是实例方法,在实际应用中切不要混淆
package CH10_Thread.xz06_run;
public class XuanZi {
public static void main(String[] args) {
MyThread myThread = new MyThread("线程A:");
myThread.run();
// 调用run 方法
for (int i = 0; i < 1000; i++) {
System.out.println("主线程:" + i);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KFpmuKVA-1677431488900)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214151857-1677425557527-6.png)]
用 run() 替换 start() ,属于单线程执行模式
15.2.9 实现Runnable接口创建线程
Runnable 接口位于 java.lang 包,只提供一个抽象方法 run() 的声明
实现步骤
-
定义 MyRunnable 类实现 Runnable 接口
-
实现 run() 方法,编写线程执行体
-
创建线程对象,调用 start() 方法启动线程
package CH10_Thread.xz07_MyRunnable;
public class XuanZi {
public static void main(String[] args) {
Runnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
//创建线程对象
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程:" + i);
}
}
}
package CH10_Thread.xz07_MyRunnable;
public class MyRunnable implements Runnable {
//实现Runnable接口
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("子线程:" + i);
}
//Runnable接口的run()方法
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KRoKJayI-1677431488900)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214153358-1677425557527-7.png)]
15.2.10 比较两种创建线程的方式
继承 Thread 类
-
编写简单,可直接操作线程
-
适用于单继承
实现 Runnable 接口
-
避免单继承局限性
-
便于共享资源
推荐使用实现 Runnable 接口方式创建线程
15.3 线程的生命周期
通常,线程的生命周期有五种状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iinwtMho-1677431488901)(./assets/%E6%90%9C%E7%8B%97%E6%88%AA%E5%9B%BE20230214125554-1677425557527-8.png)]
处于运行状态的线程会让出CPU控制权
-
线程运行完毕
-
有比当前线程优先级更高的线程抢占了CPU
-
线程休眠
-
线程因等待某个资源而处于阻塞状态
15.4 线程调度
指按照特定机制为多个线程分配CPU的使用权,每个线程执行时都具有一定的优先级
15.4.1 常用的线程操作方法
方法 | 说明 |
---|---|
int getPriority() | 返回线程的优先级 |
void setPrority(int new Priority) | 更改线程的优先级 |
boolean isAlive() | 测试线程是否处于活动状态 |
void join() | 进程中的其它线程必须等待该线程终止后才能执行 |
void interrupt() | 中断线程 |
void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
15.4.2 线程优先级
线程优先级由1~10表示,1最低,默认优先级为5,优先级高的线程获得CPU资源的概率较大
使用Thread类的静态常量设置线程的优先级
-
MAX_PRIORITY:值是10,表示优先级最高
-
MIN_PRIORITY:值是1,表示优先级最低
-
NORM_PRIORITY:值是5,表示普通优先级
尽管为线程设定了不同的优先级,但实际上并不能精确控制这些线程的执行先后顺序
在不同的计算机或同一计算机不同时刻中运行本程序,都会得到不同的执行序列
15.4.3 线程的强制运行
使当前线程暂停执行,等待其他线程结束后再继续执行本线程
join()方法的重载方法
public final void join()
public final void join(long mills)
public final void join(long mills, int nanos)
// 阻塞主线程,子线程强制执行
-
millis:以毫秒为单位的等待时长
-
nanos:要等待的附加纳秒时长
-
需处理 InterruptedException 异常
15.8.4 线程的礼让
暂停当前线程,允许其他具有相同优先级的线程获得运行机会,该线程处于就绪状态,不转为阻塞状态
yield()方法定义
public static void yield()
只是提供一种可能,但是不能保证一定会实现礼让
执行Thread.yield()方法后,多个线程间交替执行较为频繁,可以提高程序的并发性
15.4.5 比较 sleep() 方法和 yield() 方法
共同点
-
Thread 类的静态方法
-
会使当前处于运行状态的线程放弃CPU使用权,将运行机会让给其他线程
不同点
-
sleep() 方法会给其他线程运行机会,不考虑其他线程的优先级,因此较低优先级线程可能会获得运行机会
-
yield() 方法只会将运行机会让给相同优先级或者更高优先级的线程
-
调用 sleep() 方法需处理 InterruptedException 异常,而调用 yield() 方法无此要求
15.5 线程同步
当两个或多个线程需要访问同一资源时,需要以某种顺序来确保该资源某一时刻只能被一个线程使用,相当于将线程中需要一次性完成不允许中断的操作加上一把锁,以解决冲突
使用 synchronized 关键字,为当前的线程声明一把锁
实现方式
-
同步代码块
-
同步方法
15.5.1 同步代码块
使用 synchronized 关键字修饰的代码块
synchronized(syncObject) {
// syncObject:需同步的对象,通常为this
// 需要同步的代码
}
一般情况下,只有获得锁的线程可以操作共享数据,执行完同步代码块中所有代码后,才会释放锁,使其他线程获得锁
15.5.2 同步方法
使用 synchronized 修饰的方法控制对类成员变量的访问
访问修饰符 synchronized 返回类型 方法名(参数列表){……}
synchronized 访问修饰符 返回类型 方法名(参数列表){……}
15.5.3 线程同步的特征
-
不同的线程在执行以同一个对象作为锁标记的同步代码块或同步方法时,因为要获得这个对象的锁而相互牵制
-
多个并发线程访问同一资源的同步代码块或同步方法时
-
同一时刻只能有一个线程进入 synchronized(this)同步代码块
-
当一个线程访问一个 synchronized(this)同步代码块时,其他 synchronized(this)同步代码块同样被锁定
-
当一个线程访问一个 synchronized(this)同步代码块时,其他线程可以访问该资源的非 synchronized(this)同步代码
-
-
如果多个线程访问的不是同一共享资源,无需同步
使用同步代码块和同步方法完成线程同步,二者的实现结果没有区别
不同点
-
同步方法便于阅读理解
-
同步代码块更精确地限制访问区域,会更高效
15.5.4 演示案例
package CH_10.Synchronized;
public class XuanZi {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable, "张三");
Thread thread2 = new Thread(runnable, "李四");
thread1.start();
thread2.start();
}
}
package CH_10.Synchronized;
public class MyRunnable implements Runnable {
int num = 0;
int count = 1000;
@Override
public void run() {
while (count > 1) {
// qiang();
synchronized (this) {
num++;
count--;
System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票" + "剩余" + count + "张票");
}
}
}
public synchronized void qiang() {
num++;
count--;
System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票" + "剩余" + count + "张票");
}
}
15.5.5 线程安全的类型
如果程序所在的进程中,有多个线程同时运行,每次运行结果和单线程时运行结果是一样的,且其他变量的值也和预期相同,则当前程序是线程安全的
查看 ArrayList 类的 add() 方法定义
public boolean add(E e) {
ensureCapacityInternal(size + 1);
// 集合扩容,确保能新增数据
elementData[size++] = e;
// 在新增位置存放数据
return true;
}
ArrayList 类的 add() 方法为非同步方法
当多个线程向同一个 ArrayList 对象添加数据时,可能出现数据不一致问题
ArrayList 为非线程安全的类型
方法是否同步 | 效率比较 | 适合场景 | |
---|---|---|---|
线程安全 | 是 | 低 | 多线程并发共享资源 |
非线程安全 | 否 | 高 | 单线程 |
为达到安全性和效率的平衡,可以根据实际场景选择合适的类型
15.6 常见类型对比
15.6.1 Hashtable && HashMap
Hashtable
-
继承关系
- 实现了 Map 接口,Hashtable 继承 Dictionary 类
-
线程安全,效率较低
-
键和值都不允许为 null
HashMap
-
继承关系
- 实现了 Map 接口,继承 AbstractMap 类
-
非线程安全,效率较高
-
键和值都允许为null
15.6.2 StringBuffer && StringBuilder
前者线程安全,后者非线程安全
在单线程环境下,StringBuilder 执行效率更高
十六、知识点补充
16.1 a++ 与 ++a 的区别
package CH12_Expand.XZ01;
public class Test01 {
public static void main(String[] args) {
int a = 8;
int b = 8;
int c = 8;
System.out.println(a++);
// 8 , 这里相当于 先输出a 然后执行++
System.out.println(a);
System.out.println("---------------");
// 9
System.out.println(++b);
// 9 , 这里相当于 先运算++ 然后输出b
System.out.println(b);
// 9
System.out.println("---------------");
System.out.println(c);
// 8
c = c + 1;
System.out.println(c);
// 9
c += 1;
System.out.println(c);
// 10
c++;
System.out.println(c);
// 11
}
}
16.2 equals 与 == 的区别
package CH12_Expand.XZ01;
public class Test02 {
public static void main(String[] args) {
String name1 = "张三";
String name2 = "张三";
String name3 = new String("张三");
String name4 = name2;
System.out.println(name1 == name2);
// == 判断的是两个字符串的内存地址是否相同
// 只是去声明变量不会,创建新的内存地址
// name1 和 name2 指向的是同一个内存地址
System.out.println(name1.equals(name2));
// equals 判断的是两个字符串的值是否相等
System.out.println(name1 == name3);
// 使用 new 关键字创建新的对象
// 就会产生新的内存地址
System.out.println(name1.equals(name3));
// 但是他们的变量值还是相等的
System.out.println(name1 == name4);
System.out.println(name4 == name3);
}
}
16.3 逻辑 && 与位逻辑 & 的区别
package CH12_Expand.XZ01;
public class Test03 {
public static void main(String[] args) {
int i = 5;
int j = 10;
if (i++ > 5 && ++j > 10) {
System.out.println("A!");
} else {
System.out.println("B!");
}
System.out.println(i);
// 第一个i的条件不满足立即停止
System.out.println(j);
// j 没有 + 1
System.out.println("----------------------");
int q = 5;
int p = 10;
if (q++ > 5 & ++p > 10) {
System.out.println("A!");
} else {
System.out.println("B!");
}
System.out.println(q);
// 即使第一个q的条件不满足
// 也会把第二个p的条件比较完
// 再进行判断
System.out.println(p);
// p 加上了 + 1
// && 是按顺序一个一个地比较,当比较对象出现false时就停止比较
// 而 & 是将所有对象全部比较之后(出现false时不会立即停止)最后再输出结果
}
}
16.4 使用递归计算阶乘,并输出结果末尾有几位零
package CH12_Expand.XZ01;
import java.util.Scanner;
public class Factorial {
public static void main(String[] args) {
// 100的阶乘的末尾有多少个0? 例如N=10,N的阶乘=3628800,末尾有两个0
// 一个数 N 的阶乘末尾有多少个 0 取决于从 1 到 n 的各个数的因子中 2 和 5 的个数, 而 2 的个数是远远多余 5 的个数的
// 因此求出 5 的个数即可. 题解中给出的求解因子 5 的个数的方法是用 n 不断除以 5, 直到结果为 0, 然后把中间得到的结果累加
// 例如, 100/5 = 20, 20/5 = 4, 4/5 = 0, 则 1 到 100 中因子 5 的个数为 (20 + 4 + 0) = 24 个
// 即 100 的阶乘末尾有 24 个 0. 其实不断除以 5, 是因为每间隔 5 个数有一个数可以被 5 整除
// 然后在这些可被 5 整除的数中, 每间隔 5 个数又有一个可以被 25 整除, 故要再除一次, ... 直到结果为 0, 表示没有能继续被 5 整除的数了
Scanner input = new Scanner(System.in);
System.out.print("输入需要阶乘的数字(输出阶乘结果后有几个零:)");
int num = input.nextInt();
// 自定义变量 num 接收需要阶乘的变量
System.out.println("利用递归计算" + num + "的阶乘为" + recursion(num));
//调用 recursion 方法 计算 num 阶乘结果
int count = 0;
// 定义变量存储 阶乘结果有几个零
for (int i = 0; i < num; i++) {
num = num / 5;
// 求出因字数 5 的个数
count = count + num;
// 将因字数相加
}
System.out.println(count);
// 输出计算结果 n! 中有几个因子5 即为 N! 结果末尾有几个零
}
public static int recursion(int num) {
//利用递归计算阶乘
int sum = 1;
if (num < 0) {
throw new IllegalArgumentException("必须为正整数!");
}
//抛出不合理参数异常
if (num == 1) {
return 1;
//根据条件,跳出循环
} else {
sum = num * recursion(num - 1);
//运用递归计算
return sum;
}
}
}
玄子Share-BCSP助学手册之JAVA开发 2.26