学习java中整理的笔记方便记忆和自己查看
- java基础知识
- 1 Java概述
- 2 入门程序HelloWorld
- 3 IDEA安装使用
- 4 Java基础语法
- 5. 数组
- 6 方法
- 7 类和对象
- 8 继承
- 9 多态
- 10 访问控制权限修饰符
- 11 static关键字
- 12 final关键字
- 13 抽象类
- 14 接口
- 15 内部类
- 16 内存分配
- 17 面向对象三大特征
- 18 String类
- 19 正则表达式
- 20 Object类
- 21 包装类
- 22 异常
- 23 File类
- 24 Lambda表达式
- 25 IO流
- 25 多线程
- 26 网络编程
- 27 集合
- 28 单元测试
- 29 反射
- 30 注解
- 31 动态代理
java基础知识
1 Java概述
1.1 Java语言发展史
- 语言:人与人交流沟通的表达方式
- 计算机语言:人与计算机之间进行信息交流沟通的一种特殊语言
- Java语言是美国Sun公司(Stanford University Network)在1995年推出的计算机语言
- Java之父:詹姆斯·高斯林(James Gosling)
- 2009年,Sun公司被甲骨文公司收购,所以我们现在访问oracle官网即可:https://www.oracle.com
1.2 Java开发环境
- JVM(Java Virtual Machine),Java虚拟机 加载.class并运行.class
- JRE(Java Runtime Environment),运行环境,包含了JVM和Java的核心类库(Java
API)JRE=JVM+Java系统类库 - JDK(Java Development Kit)称为Java开发工具,包含了JRE和开发工具 JDK = JRE+编译、运行等命令工具
- 编译运行过程:
编译期:.java源文件,经过编译,生成.class字节码文件
运行期:JVM加载.class并运行.class(0和1)
总结:
- 其特点为跨平台、一次编程到处使用,我们只需安装JDK即可,它包含了java的运行环境JRE和虚拟机JVM。
- 说明:
运行java程序的最小环境为JRE
开发java程序的最小环境为JDK
1.3 JDK的下载和安装
1.3.1 下载
通过官方网站下载JDK,官方网站: http://www.oracle.com
找到下面页面进行选择下载:
注意:有Windows、Linux、macOS系统版本不同的JDK,根据自己使用的操作系统,下载对应版本的JDK。
1.3.2 安装
下载后安装JDK,安装过程中点击下一步即可。但默认的安装路径是在C:\Program Files\Java\jdkxxx下,为方便统一管理建议修改安装路径,将与开发相关的软件都安装到一个目录下。例如我们选择安装目录为 C:\Program Files (x86)\Java\jdk1.8.0_301。
注意:建议安装路径不要包含中文或者空格等特殊字符(使用纯英文目录)。
下面变量设置参数如下:
- 变量名:JAVA_HOME
- 变量值:D:\Java\jdk1.8.0_301 // 要根据自己的实际路径配置
- 变量名:Path
- 变量值:%JAVA_HOME%\bin
- 变量名:CLASSPATH
- 变量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
注意:如果发现环境变量中某一项已经存在,则说明你安装的JDK版本已经给你配置了,需要配置没有出现的环境变量
安装完成后,需要我们手动配置Java的环境变量,此电脑右键->点击属性->往下滑找到高级系统设置->点击高级->点击环境变量
在系统环境变量下点击**新建(W)…**输入变量名和变量值确定。
在系统环境变量下找到Path选中后点击编辑,在右侧点击新建,输入%JAVA_HOME%\bin确定即可。
新建一个系统变量:
- 变量名:CLASSPATH
- 变量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
验证环境变量是否配置成功,Win+R输入cmd输入javac和java -version
配置成功。
注意:路径不能出错,建议到对应的安装路径复制。
2 入门程序HelloWorld
2.1 常用DOS命令
在接触集成开发环境之前,我们需要使用命令行窗口对java程序进行编译和运行,所以需要知道一些常用DOS命令。
1、打开命令行窗口的方式:Win+R打开运行窗口,输入cmd,回车。
2、常用命令及其作用
操作 | 说明 |
---|---|
盘符名称: | 盘符切换。E:回车,表示切换到E盘。 |
dir | 查看当前路径下的内容。 |
cd 目录 | 进入单级目录。cd java |
cd … | 回退到上一级目录。 |
cd 目录1\目录2… | 进入多级目录。cd java\JavaSE |
cd \ | 回退到盘符目录。 |
cls | 清屏。 |
exit | 退出命令提示符窗口。 |
java -jar jar包名.jar | 运行jar包 |
2.2 HelloWorld案例
HelloWorld案例是指在计算机屏幕上输出“HelloWorld”。
2.2.1 Java程序开发运行流程
开发Java程序,需要三个步骤:编写程序,编译程序,运行程序。
2.2.2 HelloWorld案例的编写
1、新建文本文档文件,修改名称为HelloWorld.java。
2、用记事本打开HelloWorld.java文件,输写程序内容。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("HelloWorld");
}
}
2.2.3 HelloWorld案例的编译和运行
保存文件,打开命令行窗口,将目录切换至java文件所在目录,在这里直接建议打开HelloWorld.java的文件夹,在地址栏输入cmd,编译java文件生成class文件,运行class文件,感受Java的编写程序->编译->运行。
编译:javac 文件名.java
范例:javac HelloWorld.java
执行:java 类名
范例:java HelloWorld
3 IDEA安装使用
3.1 下载
进入 IDEA 官方下载页面,(官网地址为 https://www.jetbrains.com/idea/),点击 DOWNLOAD。
3.2 安装
打开后选择安装路径,首先会显示欢迎界面,next下一步,选择安装路径建议选择其它盘,到配置安装选项在Create Desktop Shortcut中选择64-bit launcher,意思是将64位启动程序添加到桌面,然后一直下一步即可。
安装完成后启动,第一次打开会弹出一个激活框,选中Evaluate for free-免费评估,再点击Evaluate评估,可以免费试用30天,安装完成。
3.3 idea常用快捷键
记得这些快捷键可以提高敲代码的速度。
快捷键 | 功能 |
---|---|
Alt + Enter | 神键,可以根据光标所在问题,提供快速修复大部分问题选择,光标放在的位置不同提示的结果也不同 |
Ctrl + Z | 撤销 |
Ctrl + Shift + Z | 取消撤销 |
Alt + Shift + 上下箭头 | 移动当前代码行 |
Ctrl + D | 复制光标所在行的内容,插入光标位置下面 |
Ctrl + Y | 删除光标所在行 |
Ctrl + Alt + L | 格式化代码,可以对当前类或者文件和整个包目录使用 |
Ctrl + Alt + V | 自动补全前面的代码或者加.var |
Ctrl + F | 在当前文件进行文本查找 |
Ctrl + R | 在当前文件进行文本替换 |
Ctrl + O | 选择可重写的方法 |
Ctrl + / | 注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号// |
Ctrl + Shift + / | 代码块注释 /**/ |
Shift + Enter | 光标跳到下一行并回车,在敲代码中不用按end来Enter |
Ctrl + Enter | 向下回车,光标不动 |
Ctrl + Shift + Enter | 回车并格式化这行代码,并自动补分号; |
Alt + Insert | 代码自动生成,如生成构造方法对象的 set / get 方法,toString() 等 |
Ctrl + Alt + O | 优化导入的类,可以对当前文件和整个包目录使用 |
Ctrl + Shift + F | 根据输入内容查找整个项目 或 指定目录内文件 |
Ctrl + Shift + R | 根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件 |
Ctrl + Shift + Enter | 自动结束代码,行末自动添加分号 |
Ctrl + Alt + T | 快速写出try-catch、if等代码 |
Shift + F6 | 统一修改名字(类名,方法名,属性) |
Ctrl+Shift+U | 选中单词,然后按Ctrl+Shift+U,可以在大小写之间来回切换 |
4 Java基础语法
一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。
- 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
- 类:类是一个模板,它描述一类对象的行为和状态。
- 方法:方法就是行为,一个类可以有很多方法。逻辑运算、数据修改以及所有动作都是在方法中完成的。
- 实例变量:每个对象都有独特的实例变量,对象的状态由这些实例变量的值决定。
4.1 注释
提高代码阅读性,解释代码的作用
单行注释
//单行注释
多行注释
/*多行注释*/
4.2 数据类型
4.2.1 计算机存储单元
计算机底层都是一些数字电路(理解成开关),用开表示0、关表示1,这些01的形式就是二进制。
数据在计算机底层都是采用二进制存储的,l在计算机中认为一个开关表示的0|1称为1位(b),每8位称为一个字节(B), 所以1B=8b
字节是计算机中数据的最小单位。
我们知道计算机是可以用来存储数据的,但是无论是内存还是硬盘,计算机存储设备的最小信息单元叫“位(bit)”,我们又称之为“比特位”,通常用小写的字母”b”表示。而计算机中最基本的存储单元叫“字节(byte)”,
通常用大写字母”B”表示,字节是由连续的8个位组成。
除了字节外还有一些常用的存储单位,其换算单位如下:
1B(字节) = 8bit
1KB = 1024B
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
4.2.2 变量
变量:在程序运行过程中,其值可以发生改变的量。
从本质上讲,变量是内存中的一小块区域,其值可以在一定范围内变化。
声明格式:
//数据类型 变量名;
int a; //声明了一个整型的变量,名为a
int b,c,d; //声明了三个整型的变量,名为b,c,d
//int a; //编译错误,变量不能同名
初始化/第一次赋值:
int a = 250; //声明整型变量a并赋值为250
int b; //声明整型变量b
b = 250; //给变量b赋值为250
b = 360; //修改变量b的值为360
使用:
int a = 5;
int b = a + 10; //取出a的值5,加10后,再赋值给变量b
System.out.println(b); //输出变量b的值15
System.out.println("b"); //输出b,双引号中的原样输出
a = a+10; //在a本身基础之上增10
System.out.println(a); //输出变量a的值15
使用之前必须声明并初始化:
int a = 5;
int b = a + 10; //取出a的值5,加10后,再赋值给变量b
System.out.println(b); //输出变量b的值15
System.out.println("b"); //输出b,双引号中的原样输出
a = a+10; //在a本身基础之上增10
System.out.println(a); //输出变量a的值15
4.3 Java中的基本数据类型
八种基本数据类型
数据类型 | 关键字 | 内存占用(字节) | 取值范围 |
---|---|---|---|
整数 | byte | 1 | 负的2的7次方 ~ 2的7次方-1(-128~127) |
short | 2 | 负的2的15次方 ~ 2的15次方-1(-32768~32767) | |
int | 4 | 负的2的31次方 ~ 2的31次方-1 | |
long | 8 | 负的2的63次方 ~ 2的63次方-1 | |
浮点数 | float | 4 | 1.401298e-45 ~ 3.402823e+38 |
double | 8 | 4.9000000e-324 ~ 1.797693e+308 | |
字符 | char | 2 | 0-65535 |
布尔 | boolean | 1 | true,false |
注意: float类型:数据后必须加F或者f表示。
- int:整型,4个字节,-21个多亿到21个多亿
- 整数直接量默认为int类型,但不能超出范围,若超范围则发生编译错误
- 两个整数相除,结果还是整数,小数位无条件舍弃(不会四舍五入)
- 运算时若超范围,则发生溢出(溢出不是错误,但是需要避免)
//1)int:整型,4个字节,-21个多亿到21个多亿
int a = 25; //25为整数直接量,默认为int型
//int b = 10000000000; //编译错误,100亿默认为int类型,但超出范围了
//int c = 25.678; //编译错误,整型变量中不能装小数
System.out.println(5/2); //2
System.out.println(2/5); //0
System.out.println(5/2.0); //2.5
int d = 2147483647; //int的最大值
d = d+1;
System.out.println(d); //-2147483648(int的最小值),发生溢出了
- long:长整型,8个字节,很大很大很大
- 长整型直接量需在数字后加L或l
- 运算时若有可能溢出,建议在第1个数字后加L
//2)long:长整型,8个字节,很大很大很大
long a = 25L; //25L为长整型直接量
//long b = 10000000000; //编译错误,100亿默认为int类型,但超出int范围了
long c = 10000000000L;
//运算时若有可能溢出,建议在第1个数字后加L
long d = 1000000000*2*10L;
System.out.println(d); //200亿
long e = 1000000000*3*10L;
System.out.println(e); //不是300亿
long f = 1000000000L*3*10;
System.out.println(f); //300亿
- double:浮点型,8个字节,很大很大很大
- 浮点数直接量默认为double型,若表示float需在数字后加F或f
- double和float型数据参与运算时,有可能会发生舍入误差,精确场合不能使用
//3)double:浮点型,8个字节,很大很大很大
double a = 3.14; //3.14为浮点型直接量,默认double型
float b = 3.14F; //3.14F为float型直接量
double c = 12340000000000000000000000.0;
System.out.println(c); //1.234E25,科学计数法表示,相当于1.234*(10的25次幂)
double d=3.0,e=2.9;
System.out.println(d-e); //0.10000000000000009,有可能发生舍入误差
- boolean:布尔型,1个字节
只能存储true或false
//4)boolean:布尔型,1个字节
boolean b1 = true; //true为布尔型直接量
boolean b2 = false; //false为布尔型直接量
//boolean b3 = 25; //编译错误,布尔型只能取值为true或false
- char:字符型,2个字节
- 采用Unicode字符集编码格式,一个字符对应一个码,
表现的形式是字符char,但本质上是码int(0到65535之间)
ASCII码:‘a’—97 ‘A’—65 ‘0’—48- 字符型直接量必须放在单引号中,有且仅有一个字符
- 特殊符号需通过\来转义
//5)char:字符型,2个字节
char c1 = '女'; //字符女
char c2 = 'f'; //字符f
char c3 = '6'; //字符6
char c4 = '*'; //字符*
//char c5 = 女; //编译错误,字符型直接量必须放在单引号中
//char c6 = ''; //编译错误,必须有字符
//char c7 = '女性'; //编译错误,只能有1个字符
char c8 = 65; //0到65535之间----一般不这么用
System.out.println(c8); //println()输出时会依据c8的数据类型来显示
//若c8为char型,则显示为字符
//若c8为int型,则显示为数字
char c9 = '\\';
System.out.println(c9); //\
4.4 关键字、标志符
4.4.1 关键字
- Java自己保留的一些单词,作为特殊功能的,例如:public、class、byte、short、int、long、double…
- 我们不能用来作为类名或者是变量名称,否则报错。
abstract | assert | boolean | break | byte |
---|---|---|---|---|
case | catch | char | class | const |
continue | default | do | double | else |
enum | extends | final | finally | float |
for | goto | if | implements | import |
instanceof | int | interface | long | native |
new | package | private | protected | public |
return | strictfp | short | static | super |
switch | synchronized | this | throw | throws |
transient | try | void | volatile | while |
4.4.1 标志符
- 标志符就是由一些字符、符号组合起来的名称,用于给类,方法,变量等起名字的规矩。
- 基本要求:由数字、字母、下划线(_)和美元符($)等组成
- 强制要求:不能以数字开头、不能是关键字、区分大小写
4.5 基本命名规定
4.5.1 变量名称
- 只能包含字母、数字、_和$符,不能以数字开头
- 严格区分大小写
- 不能使用关键字
- 满足标识符规则,建议全英文、有意义、首字母小写,满足“小驼峰命名法”。
4.5.2 类名称
满足标识符规则,建议全英文、有意义、首字母大写,满足“大驼峰命名法”,例如:HelloWorld.java。
class TestOne{
public static void main(String[] args) throws IOException {
int a1,a_5$,_3c,$t;
//int a*b; //编译错误,不能包含*等特殊符号
//int 1a; //编译错误,不能以数字开头
int aa = 5;
//System.out.println(aA); //编译错误,严格区分大小写
//int class; //编译错误,不能使用关键字
int 年龄; //允许,但不建议
int nianLing; //必须杜绝,既不直观也不专业
int age; //建议---英文的见名知意
int score,myScore,myJavaScore; //建议"小驼峰命名法"
}
}
4.6 类型间的转换
基本类型由小到大依次为:
4.6.1 自动/隐式类型转换:小类型到大类型
把一个表示数据范围小的数值或者变量赋值给另一个表示数据范围大的变量。这种转换方式是自动的,直接书写即可。
int a = 5;
long b = a; //自动/隐式类型转换
int c = (int)b; //强制类型转换
long d = 5; //自动类型转换
double e = 5; //自动类型转换
注意事项:
整数直接量可以直接赋值给byte,short,char,但不能超出范围。
byte,short,char型数据参与运算时,系统一律自动将其转换为int再运算 。
4.6.2 强制类型转换:大类型到小类型
类型范围大的数据或者变量,不能直接赋值给类型范围小的变量,会报错,把一个表示数据范围大的数值或者变量赋值给另一个表示数据范围小的变量必须进行强制类型转换。
强制类型转换格式:目标数据类型 变量名 = (目标数据类型)值或者变量;
long f = 10000000000L;
int g = (int)f; //强制类型转换
System.out.println(g); //1410065408,强转有可能发生溢出
double h = 25.987;
int i = (int)h; //强转类型转换
System.out.println(i); //25,强转有可能丢失精度
注意:boolean类型不能与其他基本数据类型相互转换。
4.7 运算符
4.7.1 算术运算符
符号 | 作用 | 说明 |
---|---|---|
+ | 加 | 加法 - 相加运算符两侧的值 |
- | 减 | 减法 - 左操作数减去右操作数 |
* | 乘 | 乘法 - 相乘操作符两侧的值 |
/ | 除 | 除法 - 左操作数除以右操作数 |
% | 取余 | 获取的是两个数据做除法的余数 |
++ | 自增: 操作数的值增加1 | B++ 或 ++B 等于 21 |
– | 自减: 操作数的值减少1 | B-- 或 --B 等于 19 |
//%的演示
System.out.println(8%2); //0,商4余0----整除
System.out.println(5%2); //1,商2余1
System.out.println(2%8); //2,商0余2
//++单独使用:
int a=5,b=5;
a++; //相当于a=a+1
++b; //相当于b=b+1
System.out.println(a); //6
System.out.println(b); //6
//++被使用:
int a=5,b=5;
int c = a++; //1)保存a++的值5 2)a自增1变为6 3)将第1步保存的值5赋值给c--底层运算过程
//---粗暴记法:a++的值为5,c就是5
int d = ++b; //1)保存++b的值6 2)b自增1变为6 3)将第1步保存的值6赋值给d--底层运算过程
//---粗暴记法:++b的值为6,d就是6
System.out.println(a); //6
System.out.println(b); //6
System.out.println(c); //5
System.out.println(d); //6
//--单独使用:
int a=5,b=5;
a--; //相当于a=a-1
--b; //相当于b=b-1
System.out.println(a); //4
System.out.println(b); //4
//--被使用:
int a=5,b=5;
int c = a--; //a--的值为5,所以c的值为5
int d = --b; //--b的值为4,所以d的值为4
System.out.println(a); //4
System.out.println(b); //4
System.out.println(c); //5
System.out.println(d); //4
4.7.2 关系运算符
符号 | 说明 |
---|---|
== | a==b,判断a和b的值是否相等,成立为true,不成立为false |
!= | a!=b,判断a和b的值是否不相等,成立为true,不成立为false |
> | a>b,判断a是否大于b,成立为true,不成立为false |
>= | a>=b,判断a是否大于等于b,成立为true,不成立为false |
< | a<b,判断a是否小于b,成立为true,不成立为false |
<= | a<=b,判断a是否小于等于b,成立为true,不成立为false |
int a=5,b=10,c=5;
boolean b1 = a>b;
System.out.println(b1); //false
System.out.println(c<b); //true
System.out.println(a>=c); //true
System.out.println(a<=b); //true
System.out.println(a==c); //true
System.out.println(a!=c); //false
System.out.println(a+c>b); //false
System.out.println(a%2==0); //false
System.out.println(c++>5); //false-------c自增1变为6
System.out.println(c++>5); //true--------c自增1变为7
注意事项:
- 关系运算符的结果都是boolean类型,要么是true,要么是false。
4.7.3 逻辑运算符
符号 | 作用 | 说明 |
---|---|---|
& | 逻辑与 | a&b,a和b都是true,结果为true,否则为false |
| | 逻辑或 | a|b,a和b都是false,结果为false,否则为true |
^ | 逻辑异或 | a^b,a和b结果不同为true,相同为false |
! | 逻辑非 | !a,结果和a的结果正好相反 |
//定义变量
int i = 10;
int j = 20;
int k = 30;
//& “与”,并且的关系,只要表达式中有一个值为false,结果即为false
System.out.println((i > j) & (i > k)); //false & false,输出false
System.out.println((i < j) & (i > k)); //true & false,输出false
System.out.println((i > j) & (i < k)); //false & true,输出false
System.out.println((i < j) & (i < k)); //true & true,输出true
System.out.println("--------");
//| “或”,或者的关系,只要表达式中有一个值为true,结果即为true
System.out.println((i > j) | (i > k)); //false | false,输出false
System.out.println((i < j) | (i > k)); //true | false,输出true
System.out.println((i > j) | (i < k)); //false | true,输出true
System.out.println((i < j) | (i < k)); //true | true,输出true
System.out.println("--------");
//^ “异或”,相同为false,不同为true
System.out.println((i > j) ^ (i > k)); //false ^ false,输出false
System.out.println((i < j) ^ (i > k)); //true ^ false,输出true
System.out.println((i > j) ^ (i < k)); //false ^ true,输出true
System.out.println((i < j) ^ (i < k)); //true ^ true,输出false
System.out.println("--------");
//! “非”,取反
System.out.println((i > j)); //false
System.out.println(!(i > j)); //!false,,输出true
4.7.4 赋值运算符
符号 | 作用 | 说明 |
---|---|---|
= | 赋值 | a=10,将10赋值给变量a |
+= | 加后赋值 | a+=b,将a+b的值给a |
-= | 减后赋值 | a-=b,将a-b的值给a |
*= | 乘后赋值 | a*=b,将a×b的值给a |
/= | 除后赋值 | a/=b,将a÷b的商给a |
%= | 取余后赋值 | a%=b,将a÷b的余数给a |
注意:扩展的赋值运算符隐含了强制类型转换。
short s = 10;
s = s + 10; // 此行代码报出,因为运算中s提升为int类型,运算结果int赋值给short可能损失精度
s += 10; // 此行代码没有问题,隐含了强制类型转换,相当于 s = (short) (s + 10);
4.7.5 短路逻辑运算符
符号 | 作用 | 说明 |
---|---|---|
&& | 短路与 | 作用和&相同,但是有短路效果 |
|| | 短路或 | 作用和|相同,但是有短路效果 |
在逻辑与运算中,只要有一个表达式的值为false,那么结果就可以判定为false了,没有必要将所有表达式的值都计算出来,短路与操作就有这样的效果,可以提高效率。同理在逻辑或运算中,一旦发现值为true,右边的表达式将不再参与运算。
-
逻辑与&,无论左边真假,右边都要执行。
-
短路与&&,如果左边为真,右边执行;如果左边为假,右边不执行。
-
逻辑或|,无论左边真假,右边都要执行。
-
短路或||,如果左边为假,右边执行;如果左边为真,右边不执行。
int x = 3;
int y = 4;
System.out.println((x++ > 4) & (y++ > 5)); // 两个表达都会运算
System.out.println(x); // 4
System.out.println(y); // 5
System.out.println((x++ > 4) && (y++ > 5)); // 左边已经可以确定结果为false,右边不参与运算
System.out.println(x); // 4
System.out.println(y); // 4
4.7.6 字符的“+”操作
char类型参与算术运算,使用的是计算机底层对应的十进制数值。需要我们记住三个字符对应的数值:
‘a’ – 97 a-z是连续的,所以’b’对应的数值是98,'c’是99,依次递加
‘A’ – 65 A-Z是连续的,所以’B’对应的数值是66,'C’是67,依次递加
‘0’ – 48 0-9是连续的,所以’1’对应的数值是49,'2’是50,依次递加
// 可以通过使用字符与整数做算术运算,得出字符对应的数值是多少
char ch1 = 'a';
System.out.println(ch1 + 1); // 输出98,97 + 1 = 98
char ch2 = 'A';
System.out.println(ch2 + 1); // 输出66,65 + 1 = 66
char ch3 = '0';
System.out.println(ch3 + 1); // 输出49,48 + 1 = 49
4.7.7 字符串的“+”操作
当“+”操作中出现字符串时,这个”+”是字符串连接符,而不是算术运算。
System.out.println("a"+ 666); // 输出:a666
在”+”操作中,如果出现了字符串,就是连接运算符,否则就是算术运算。当连续进行“+”操作时,从左到右逐个执行。
System.out.println(1 + 99 + "年s"); // 输出:100年s
System.out.println(1 + 2 + "ssss" + 3 + 4); // 输出:3ssss34
// 可以使用小括号改变运算的优先级
System.out.println(1 + 2 + "ssss" + (3 + 4)); // 输出:3ssss7
4.7.8 三元运算符
三元运算符语法格式:
关系表达式 ? 表达式1 : 表达式2;
解释:问号前面的位置是判断的条件,判断结果为boolean型,为true时调用表达式1,为false时调用表达式2。其逻辑为:如果条件表达式成立或者满足则执行表达式1,否则执行第二个。
int a = 10;
int b = 20;
int c = a > b ? a : b; // 判断 a>b 是否为真,如果为真取a的值,如果为假,取b的值
4.8 流程控制语句
- 顺序结构
- 分支结构(if, switch)
- 循环结构(for, while, do…while)
4.8.1 顺序结构
顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。
4.8.2 分支结构(if, switch)
1. if语句:
格式:
if (条件表达式1) {
语句体1;
} else if (条件表达式2) {
语句体2;
} else if (条件表达式3) {
语句体3;
}
. . .
else {
语句体n+1;
}
public class IfDemo {
public static void main(String[] args) {
System.out.println("开始");
//定义两个变量
int a = 10;
int b = 20;
//需求:判断a和b的值是否相等,如果相等,就在控制台输出:a等于b
if(a == b) {
System.out.println("a等于b");
}
//需求:判断a和c的值是否相等,如果相等,就在控制台输出:a等于c
int c = 10;
if(a == c) {
System.out.println("a等于c");
}
System.out.println("结束");
}
}
2. switch语句:
switch…case结构:多条路
优点:效率高、结构清晰
缺点:只能对整数判断相等
break:跳出switch
注意:如果switch中得case,没有对应break的话,则会出现case穿透的现象。
格式:
switch (表达式) {
case 1:
语句体1;
break;
case 2:
语句体2;
break;
…
default:
语句体n+1;
break;
}
public class SwitchDemo {
public static void main(String[] args) {
//键盘录入月份数据,使用变量接收
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个月份:");
int month = sc.nextInt();
//case穿透
switch(month) {
case 1:
case 2:
case 12:
System.out.println("冬季");
break;
case 3:
case 4:
case 5:
System.out.println("春季");
break;
case 6:
case 7:
case 8:
System.out.println("夏季");
break;
case 9:
case 10:
case 11:
System.out.println("秋季");
break;
default:
System.out.println("你输入的月份有误");
}
}
}
4.8.3 循环结构(for, while, do…while)
1. for循环语句
for循环格式:
for (初始化语句;条件判断语句;条件控制语句) {
循环体语句;
}
示例1:
//输出3次Hello World
for (int i = 0; i < 3; i++) {
System.out.println("Hello World");
}
示例2:
//需求:求1-5之间的数据和,并把求和结果在控制台输出
public class ForTest02 {
public static void main(String[] args) {
//求和的最终结果必须保存起来,需要定义一个变量,用于保存求和的结果,初始值为0
int sum = 0;
//从1开始到5结束的数据,使用循环结构完成
for(int i=1; i<=5; i++) {
//将反复进行的事情写入循环结构内部
// 此处反复进行的事情是将数据 i 加到用于保存最终求和的变量 sum 中
sum += i;
/*
sum += i; sum = sum + i;
第一次:sum = sum + i = 0 + 1 = 1;
第二次:sum = sum + i = 1 + 2 = 3;
第三次:sum = sum + i = 3 + 3 = 6;
第四次:sum = sum + i = 6 + 4 = 10;
第五次:sum = sum + i = 10 + 5 = 15;
*/
}
//当循环执行完毕时,将最终数据打印出来
System.out.println("1-5之间的数据和是:" + sum);
}
}
示例3:
//需求:求1-100之间的偶数和,并把求和结果在控制台输出
public class ForTest03 {
public static void main(String[] args) {
//求和的最终结果必须保存起来,需要定义一个变量,用于保存求和的结果,初始值为0
int sum = 0;
//对1-100的数据求和与1-5的数据求和几乎完全一样,仅仅是结束条件不同
for(int i=1; i<=100; i++) {
//对1-100的偶数求和,需要对求和操作添加限制条件,判断是否是偶数
if(i%2 == 0) {
sum += i;
}
}
//当循环执行完毕时,将最终数据打印出来
System.out.println("1-100之间的偶数和是:" + sum);
}
}
示例4:
/*
* 需求:在控制台输出所有的“水仙花数”
* 解释:什么是水仙花数?
* 水仙花数,指的是一个三位数,个位、十位、百位的数字立方和等于原数
* 例如`153 3*3*3 + 5*5*5 + 1*1*1 = 153`
* 思路:
* 1. 获取所有的三位数,准备进行筛选,最小的三位数为100,最大的三位数为999,使用for循环获取
* 2. 获取每一个三位数的个位,十位,百位,做if语句判断是否是水仙花数
*/
public class ForTest04 {
public static void main(String[] args) {
//输出所有的水仙花数必然要使用到循环,遍历所有的三位数,三位数从100开始,到999结束
for(int i=100; i<1000; i++) {
//在计算之前获取三位数中每个位上的值
int ge = i%10;
int shi = i/10%10;
int bai = i/10/10%10;
//判定条件是将三位数中的每个数值取出来,计算立方和后与原始数字比较是否相等
if(ge*ge*ge + shi*shi*shi + bai*bai*bai == i) {
//输出满足条件的数字就是水仙花数
System.out.println(i);
}
}
}
}
2. while循环语句
while循环完整格式:
初始化语句;
while (条件判断语句) {
循环体语句;
条件控制语句;
}
示例1:
public class WhileDemo {
public static void main(String[] args) {
//需求:在控制台输出5次"HelloWorld"
//for循环实现
for(int i=1; i<=5; i++) {
System.out.println("HelloWorld");
}
System.out.println("--------");
//while循环实现
int j = 1;
while(j<=5) {
System.out.println("HelloWorld");
j++;
}
}
}
示例2:
//猜字小游戏
public class Guessing {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int num = (int)(Math.random()*1000+1); //1到1000之内的随机数
System.out.println(num); //作弊
//300(大),200(小),250(对)
System.out.println("猜吧!");
int guess = scan.nextInt(); //1.
while(guess!=num){ //2.
if(guess>num){
System.out.println("太大了");
}else{
System.out.println("太小了");
}
System.out.println("猜吧!");
guess = scan.nextInt(); //3.
}
System.out.println("恭喜你猜对了!");
}
}
3. do…while循环语句
格式:
初始化语句;
do {
循环体语句;
条件控制语句;
}while(条件判断语句);
public class DoWhileDemo {
public static void main(String[] args) {
//需求:在控制台输出5次"HelloWorld"
//for循环实现
for(int i=1; i<=5; i++) {
System.out.println("HelloWorld");
}
System.out.println("--------");
//do...while循环实现
int j = 1;
do {
System.out.println("HelloWorld");
j++;
}while(j<=5);
}
}
4. 三种循环的区别
- 三种循环的区别
- for循环和while循环先判断条件是否成立,然后决定是否执行循环体(先判断后执行)
- do…while循环先执行一次循环体,然后判断条件是否成立,是否继续执行循环体(先执行后判断)
- for循环和while的区别
- 条件控制语句所控制的自增变量,因为归属for循环的语法结构中,在for循环结束后,就不能再次被访问到了
- 条件控制语句所控制的自增变量,对于while循环来说不归属其语法结构中,在while循环结束后,该变量还可以继续使用
- 死循环(无限循环)的三种格式
- for( ; ; ){}
- while(true){}
- do {} while(true);
5. 跳转控制语句
- 跳转控制语句(break)
- 跳出循环,结束循环
- 跳转控制语句(continue)
- 跳过本次循环,继续下次循环
for(int i=1;i<=9;i++){
if(i==4){ //在某种特定条件下,提前结束循环
break;
}
System.out.println(i+"*9="+i*9);
}
for(int i=1;i<=9;i++){
if(i%3==0){
continue; //跳过循环体中剩余语句而进入下一次循环
}
System.out.println(i+"*9="+i*9);
}
注意: continue只能在循环中进行使用!
6. 循环嵌套
- 循环中套循环,常常多行多列时使用,一般外层控制行,内层控制列
- 执行过程:外层循环走一次,内层循环走所有次
- 建议:嵌套层数越少越好,能用一层就不用两层,能用两层就不用三层
- break只能跳出当前一层循环
public static void main(String[] args) {
//外循环控制小时的范围,内循环控制分钟的范围
for (int hour = 0; hour < 24; hour++) {
for (int minute = 0; minute < 60; minute++) {
System.out.println(hour + "时" + minute + "分");
}
System.out.println("--------");
}
}
结论:
外循环执行一次,内循环执行一圈
5. 数组
- 数组就是存储数据长度固定的容器,存储多个数据的数据类型要一致
- 是一种数据类型(引用类型)
- 相同数据类型元素的集合
5.1 数组定义格式
5.1.1 第一种方式
数据类型[] 数组名
int[] arr;
double[] arr;
char[] arr;
5.1.2 第二种方式
数据类型 数组名[]
int arr[];
double arr[];
char arr[];
5.2 数组动态初始化
数组动态初始化就是只给定数组的长度,由系统给出默认初始化值。
动态初始化格式:
数据类型[] 数组名 = new 数据类型[数组长度];
int[] arr = new int[3];
5.3静态初始化格式
完整格式:
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,...};
简化版格式:
数据类型[] 数组名 = {元素1,元素2,...};
5.4 访问数组元素格式
数组名[索引];
public class ArrayDemo {
public static void main(String[] args) {
int[] arr = new int[3];
//输出数组名
System.out.println(arr); //[I@880ec60
//输出数组中的元素
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
}
}
5.5 索引越界异常
出现原因
public class ArrayDemo {
public static void main(String[] args) {
int[] arr = new int[3];
System.out.println(arr[3]);
}
}
数组长度为3,索引范围是0~2,但是我们却访问了一个3的索引。
程序运行后,将会抛出ArrayIndexOutOfBoundsException 数组越界异常。在开发中,数组的越界异常是不能出现的,一旦出现了,就必须要修改我们编写的代码。
解决方案:将错误的索引修改为正确的索引范围即可!
5.6 空指针异常
public class ArrayDemo {
public static void main(String[] args) {
int[] arr = new int[3];
//把null赋值给数组
arr = null;
System.out.println(arr[0]);
}
}
arr = null 这行代码,意味着变量arr将不会再保存数组的内存地址,也就不允许再操作数组了,因此运行的时候会抛出 NullPointerException 空指针异常。在开发中,数组的越界异常是不能出现的,一旦出现了,就必须要修改我们编写的代码。
解决方案:给数组一个真正的堆内存空间引用即可!
5.7 数组遍历
数组遍历:就是将数组中的每个元素分别获取出来,就是遍历。遍历也是数组操作中的基石。
public class ArrayTest01 {
public static void main(String[] args) {
//定义数组
int[] arr = {11, 22, 33, 44, 55};
//使用通用的遍历格式
for(int x = 0; x < arr.length; x++) {
System.out.println(arr[x]);
}
}
}
5.8 数组最值
最大值获取:从数组的所有元素中找出最大值,最小值同理。
public class ArrayTest02 {
public static void main(String[] args) {
//定义数组
int[] arr = {12, 45, 98, 73, 60};
//定义一个变量,用于保存最大值
//取数组中第一个数据作为变量的初始值
int max = arr[0];
//与数组中剩余的数据逐个比对,每次比对将最大值保存到变量中
for(int x=1; x<arr.length; x++) {
if(arr[x] > max) {
max = arr[x];
}
}
//循环结束后打印变量的值
System.out.println("max:" + max);
}
}
5.9 循环控制语句
循环控制语句主要有两个:break,continue
break
在循环过程中,碰到break整个循环就直接结束了
注意:break只能出现在循环中或者switch中
continue
如果在循环过程中碰到了continue,则跳过本次循环 , 继续下次循环
6 方法
方法:函数、过程
- 封装一段特定的业务逻辑功能
- 尽可能的独立,一个方法只干一件事
- 方法可以被反复多次调用
- 减少代码重复,有利于代码复用,有利于代码维护
6.1 定义方法
格式:
修饰词 返回值类型 方法名(参数列表) {
方法体--------------具体的业务逻辑功能实现
}
//无参无返回值
public static void say(){
System.out.println("大家好,我叫WKJ,今年38岁了");
}
//有参无返回值
public static void sayHi(String name){ //---------形参
System.out.println("大家好,我叫"+name+",今年38岁了");
}
//有参无返回值
public static void sayHello(String name,int age){
if(age>=35){ //在某种特定条件下,提前结束方法
return; //结束方法
}
System.out.println("大家好,我叫"+name+",今年"+age+"岁了");
}
//无参有返回值
public static double getNum(){
//在有返回值的方法中:
//--必须得通过return来返回一个值,并且这个值的类型必须与返回值类型匹配
//return "abc"; //编译错误,返回的值必须与返回值类型匹配
return 8.88; //1)结束方法的执行 2)返回一个结果给调用方
}
//有参有返回值
public static int plus(int num1,int num2){
int num = num1+num2;
return num; //返回的是num里面的那个数
//return num1+num2; //返回的是num1与num2的和
}
//无参有返回值
public static int[] testArray(){
int[] arr = new int[10];
for(int i=0;i<arr.length;i++){
arr[i] = (int)(Math.random()*100);
}
return arr;
}
6.2 调用方法
- 无返回值:方法名(有参传参);
- 有返回值:数据类型 变量 = 方法名(有参传参);
//say();
//sayHi(); //编译错误,有参则必须传参
//sayHi(250); //编译错误,参数类型必须匹配
sayHi("zhangsan"); //String name="zhangsan" //-------实参
sayHi("lisi"); //String name="lisi" //---------------实参
sayHi("wangwu"); //String name="wangwu" //-----------实参
sayHello("zhangsan",25); //实参
sayHello("WKJ",38); //实参
double a = getNum(); //getNum()的值就是8.88
System.out.println(a); //8.88----模拟对返回值的后续操作
int b = plus(5,6);
System.out.println(b); //11----模拟对返回值的后续操作
int m=5,n=6;
int c = plus(m,n); //传递的是m和n里面的那个数
System.out.println(c); //11----模拟对返回值的后续操作
int[] d = testArray();
System.out.println(d.length); //10---模拟对返回值的后续操作
for(int i=0;i<d.length;i++){ //输出每个元素的值---模拟对返回值的后续操作
System.out.println(d[i]);
}
6.3 return:
-
return 值;
1)结束方法的执行
2)返回结果给调用方
----------用在有返回值方法中 -
return; 结束方法的执行
-----------------用在无返回值的方法中
6.4 形参和实参
- 形参:方法定义中的参数
等同于变量定义格式,例如:int number
- 实参:方法调用中的参数
等同于使用变量或常量,例如: 10 number
6.5 方法重载
方法重载指同一个类中定义的多个方法之间的关系,满足下列条件的多个方法相互构成重载
- 多个方法在同一个类中
- 多个方法具有相同的方法名
- 多个方法的参数不相同,类型不同或者数量不同
public class MethodTest {
public static void main(String[] args) {
//调用方法
System.out.println(compare(10, 20));
System.out.println(compare((byte) 10, (byte) 20));
System.out.println(compare((short) 10, (short) 20));
System.out.println(compare(10L, 20L));
}
//int
public static boolean compare(int a, int b) {
System.out.println("int");
return a == b;
}
//byte
public static boolean compare(byte a, byte b) {
System.out.println("byte");
return a == b;
}
//short
public static boolean compare(short a, short b) {
System.out.println("short");
return a == b;
}
//long
public static boolean compare(long a, long b) {
System.out.println("long");
return a == b;
}
}
7 类和对象
类:对现实事物的一种描述
对象:对象是某类事物的一个个体
类中可以包含:
对象的属性/特征-----------------------在类中通过成员变量来体现
对象的行为/动作-----------------------在类中通过成员方法来体现
一个类可以创建多个对象
类和对象的关系
- 类:类是对现实生活中一类具有共同属性和行为的事物的抽象
- 对象:是能够看得到摸的着的真实存在的实体
- 简单理解:类是对事物的一种描述,对象则为具体存在的事物
public class 类名 {
// 成员变量
变量1的数据类型 变量1;
变量2的数据类型 变量2;
…
// 成员方法
方法1;
方法2;
}
7.1 对象的使用
- 创建对象的格式:
- 类名 对象名 = new 类名();
- 调用成员的格式:
- 对象名.成员变量
- 对象名.成员方法();
public class PhoneDemo {
public static void main(String[] args) {
//创建对象
Phone p = new Phone();
//使用成员变量
System.out.println(p.brand);
System.out.println(p.price);
p.brand = "小米";
p.price = 2999;
System.out.println(p.brand);
System.out.println(p.price);
//使用成员方法
p.call();
p.sendMessage();
}
}
学生对象练习:
//成员变量
String name;
int age;
//成员方法
public void study() {
System.out.println("好好学习,天天向上");
}
public void doHomework() {
System.out.println("键盘敲烂,月薪过万");
}
}
/*
学生测试类
*/
public class StudentDemo {
public static void main(String[] args) {
//创建对象
Student s = new Student();
//使用对象
System.out.println(s.name + "," + s.age);
s.name = "林青霞";
s.age = 30;
System.out.println(s.name + "," + s.age);
s.study();
s.doHomework();
}
}
7.2 成员变量和局部变量的区别
- 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)
- 内存中位置不同:成员变量(堆内存)局部变量(栈内存)
- 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,醉着方法的调用完毕而消失)
- 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)
7.3 封装思想
-
封装概述
是面向对象三大特征之一(封装,继承,多态)
是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的
-
封装原则
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
成员变量private,提供对应的getXxx()/setXxx()方法
-
封装好处
通过方法来控制成员变量的操作,提高了代码的安全性
把代码用方法进行封装,提高了代码的复用性
7.4 构造方法
构造方法是一种特殊的方法
-
作用:创建对象 Student stu = new Student();
-
格式:
public class 类名{
修饰符 类名( 参数 ) {
}
}
- 功能:主要是完成对象数据的初始化
class Student {
private String name;
private int age;
//构造方法
public Student() {
System.out.println("无参构造方法");
}
public void show() {
System.out.println(name + "," + age);
}
}
/*
测试类
*/
public class StudentDemo {
public static void main(String[] args) {
//创建对象
Student s = new Student();
s.show();
}
}
7.5 构造方法的注意事项
- 构造方法的创建
如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法
- 构造方法的重载
如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法
- 推荐的使用方式
无论是否使用,都手工书写无参数构造方法
- 重要功能!
可以使用带参构造,为成员变量进行初始化
7.6 private关键字
private是一个修饰符,可以用来修饰成员(成员变量,成员方法)
被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
- 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
- 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
7.7 this关键字
this代表当前调用方法的引用,哪个对象调用的方法,this就代表哪一个对象。
this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
- 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
- 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
作用:
- this.属性名,访问成员变量
- this.方法名,调用方法访问该类里的另一个方法或实例变量
- this() 调用构造方法
注意:成员变量与局部变量同名时,若想访问成员变量则this不能省略
public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void show() {
System.out.println(name + "," + age);
}
}
8 继承
8.1 继承概念
继承可以理解为就是让两个类(事物)产生从属关系,有了从属关系子类就肯定会具有父类的特征(父类中的非私有成员),这样我们用类去描述一些事物的时候就可以更方便
超类/父类:共有的属性和行为
派生类/子类:特有的属性和行为
实现继承的格式 继承通过extends实现
格式:
class 子类 extends 父类 { }
public class Fu {
public void show() {
System.out.println("show方法被调用");
}
}
public class Zi extends Fu {
public void method() {
System.out.println("method方法被调用");
}
}
public class Demo {
public static void main(String[] args) {
//创建对象,调用方法
Fu f = new Fu();
f.show();
Zi z = new Zi();
z.method();
z.show();
}
}
8.2 继承特点
继承鼓励类的重用
继承可以多层继承
Java中类只支持单继承,不支持多继承(接口支持多继承)
父类中private,default修饰的不能被继承
构造方法不能被继承,只能调用
具有传递性
8.3 继承的作用
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
8.4 继承带来的好处与弊端
继承的好处
继承可以让类与类之间产生关系,子父类关系,产生子父类后,子类则可以使用父类中非私有的成员。
继承好处 提高了代码的复用性(多个类相同的成员可以放到同一个类中) 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
继承弊端
继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削 弱了子类的独立性,违背了`高内聚,低耦合`原则
继承的应用场景: 使用继承,需要考虑类与类之间是否存在is..a的关系,不能盲目使用继承 is..a的关系:谁是谁的一种,例如:老师和学生是人的一种,
那人就是父类,学生和老师就是子类
8.5 super关键字
super:指代当前对象的超类对象
super的用法:
super.成员变量名---------------------访问超类的成员变量
super.方法名()-------------------------调用超类的方法
super()-----------------------------------调用超类的构造方法
//super的演示
public class SuperDemo {
public static void main(String[] args) {
Boo o = new Boo();
}
}
class Coo{
Coo(int a){
}
}
class Doo extends Coo{
Doo(){
super(5); //调用超类的有参构造
}
/*
//如下代码为默认的:
Doo(){
super();
}
*/
}
class Aoo{
Aoo(){
System.out.println("超类构造");
}
}
class Boo extends Aoo{
Boo(){
//super(); //默认的,调用超类的无参构造
System.out.println("派生类构造");
}
}
8.6 this&super关键字
this:代表本类对象的引用
super:代表父类存储空间的标识(可以理解为父类对象引用)
this和super的使用分别
成员变量:
this.成员变量 - 访问本类成员变量
super.成员变量 - 访问父类成员变量
成员方法:
this.成员方法 - 访问本类成员方法
super.成员方法 - 访问父类成员方法
构造方法:
this(…) - 访问本类构造方法
super(…) - 访问父类构造方法
8.7 向上造型
- 超类型的引用指向派生类的对象
- 规定:能点出来什么,看引用的类型
public class UploadDemo {
public static void main(String[] args) {
Aoo o1 = new Aoo();
o1.a = 1;
o1.show();
//o1.b = 2; //编译错误
//o1.test(); //编译错误,超类不能访问派生类的
Boo o2 = new Boo();
o2.b = 1;
o2.test();
o2.a = 2; //正确
o2.show(); //正确,派生类可以访问超类的
Aoo o3 = new Boo(); //向上造型
o3.a = 1;
o3.show();
//o3.b = 2; //编译错误
//o3.test(); //编译错误,能点出来什么,看引用的类型
}
}
class Aoo{
int a;
void show(){
}
}
class Boo extends Aoo{
int b;
void test(){
}
}
8.8 方法的重写
1. 发生在父子类中,方法名相同,参数列表相同
2. 重写方法被调用时,看对象的类型-------这是规定,记住就行了
3. 重写遵循"两同两小一大"原则:-----------了解,一般都是一模一样的
1. 两同:
方法名相同
参数列表相同
2. 两小:
1. 派生类方法的返回值类型小于或等于超类方法的
1) void和基本类型时,必须相等
2) 引用类型时,小于或等于
2. 派生类方法抛出的异常小于或等于超类方法的
3. 一大:
派生类方法的访问权限大于或等于超类方法的
重写与重载的区别:-----------常见面试题
- 重写(override):发生在父子类中,方法名相同,参数列表相同
- 重载(overload):发生在同一类中,方法名相同,参数列表不同
9 多态
同类型的对象,执行同一个行为,会表现出不同的行为特征。
9.1 常见形式
父类类型 对象名称 = new 子类构造器;
接口 对象名称 = new 实现类构造器;
9.2 多态中成员访问特点
方法调用:编译看左边,运行看右边。
变量调用:编译看左边,运行也看左边。(**多态侧重行为多态**)
9.3 多态的前提
要有继承或实现关系 要有方法的重写 要有父类引用指向子类对象
9.4 多态的优势
- 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
- 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
9.5 向上造型/自动类型转换:
- 超类型的引用指向派生类的对象
- 能点出来什么,看引用的类型
- 能造型成为的数据类型有:超类+所实现的接口
9.6 强制类型转换
成功的条件只有如下两种:
- 引用所指向的对象,就是该类型
- 引用所指向的对象,实现了该接口或继承了该类
9.7 可能出现的异常
强转时若不符合如上条件,则发生ClassCastException类型转换异常
建议在强转之前先通过instanceof判断引用的对象是否是该类型
public class MultiTypeDemo {
public static void main(String[] args) {
Aoo o = new Boo(); //向上造型
Boo o1 = (Boo)o; //引用o指向的对象就是Boo
Inter o2 = (Inter)o; //引用o指向的对象实现了Inter接口
//Coo o3 = (Coo)o; //运行时发生ClassCastException类型转换异常
if(o instanceof Coo){ //false
Coo o4 = (Coo)o;
}else{
System.out.println("o不是Coo类型");
}
}
}
interface Inter{
}
class Aoo{
}
class Boo extends Aoo implements Inter{
}
class Coo extends Aoo{
}
10 访问控制权限修饰符
修饰符 | 同一个类中 | 同一个包中子类无关类 | 不同包的子类 | 不同包的无关类 |
---|---|---|---|---|
private | √ | |||
default(默认) | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
说明:
- 类的访问权限只能是public或默认的
- 类中成员的访问权限如上4种都可以
11 static关键字
11.1 static作用
- static是静态的意思,可以修饰成员变量和成员方法。
- static修饰成员变量表示该成员变量只在内存中只存储一份,可以被共享访问、修改,存储在方法区。
11.2 静态成员变量
静态成员变量(有static修饰,属于类、加载一次,可以被共享访问,存储在方法区)
没有static修饰的属于对象的,存储在堆中,有几个对象就有几份。
访问格式:
- 类名.静态成员变量(推荐)
- 对象.静态成员变量(不推荐)。
public class StaticDemo {
public static void main(String[] args) {
Loo o1 = new Loo();
o1.show();
Loo o2 = new Loo();
o2.show();
Loo o3 = new Loo();
o3.show();
System.out.println(Loo.b); //常常通过类名点来访问
}
}
//演示静态变量
class Loo{
int a;
static int b;
Loo(){
a++;
b++;
}
void show(){
System.out.println("a="+a+",b="+b);
}
}
11.3 静态成员方法
静态成员方法(有static修饰,属于类),建议用类名访问,也可以用对象访问
访问格式:
类名.静态成员方法
访问特点:
静态成员方法只能访问静态成员
注意事项
- 静态方法中不能直接使用非静态的成员
- 静态方法中不能使用this
使用场景:
- 表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法。
- 如果该方法是以执行一个共用功能为目的,则可以申明成静态方法。
public class StaticDemo {
public static void main(String[] args) {
Moo.test();
}
}
//演示静态方法
class Moo{
int a; //实例变量(对象点来访问)
static int b; //静态变量(类名点来访问)
void show(){ //有隐式this
System.out.println(this.a);
System.out.println(Moo.b);
}
static void test(){ //没有隐式this
//静态方法中没有隐式this传递
//没有this就意味着没有对象
//而实例变量a必须通过对象点来访问
//所以如下代码发生编译错误
//System.out.println(a); //编译错误
System.out.println(Moo.b);
}
}
//演示静态方法在何时用
class Noo{
int a; //对象的属性a
//在show()中用到了对象的属性a,意味着show()方法与对象有关,所以不能设计为静态方法
void show(){
System.out.println(a);
}
//在plus()中没有用到对象的属性,意味着plus()方法与对象无关,所以可以设计为静态方法
static int plus(int num1,int num2){
int num = num1+num2;
return num;
}
}
11.4 静态代码块
格式:static{}
特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
作用:
- 如果要在启动系统时对数据进行初始化。
- 建议使用静态代码块完成数据的初始化操作,代码优雅。
public class StaticDemo {
public static void main(String[] args) {
Poo o4 = new Poo();
Poo o5 = new Poo();
}
}
//演示静态块
class Poo{
static{
System.out.println("静态块");
}
Poo(){
System.out.println("构造方法");
}
}
12 final关键字
fianl关键字的作用 final代表最终的意思,可以修饰成员方法,成员变量,类 final修饰类、方法、变量的效果
- final修饰变量:表明该变量是一个常量,不能再次赋值
- final修饰方法:该方法不能被重写
- fianl修饰类:该类不能被继承(不能有子类,但是可以有父类)
注意:
- final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
- final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。
package ooday05_vis;
//演示final修饰变量
class Eoo{
final int a = 5;
void test(){
//a = 55; //编译错误,final的变量不能被改变
}
}
//演示final修饰方法
class Foo{
final void show(){}
}
class Goo extends Foo{
//void show(){} //编译错误,final的方法不能被重写
}
//演示final修饰类
final class Hoo{}
//class Ioo extends Hoo{} //编译错误,final的类不能被继承
class Joo{}
final class Koo extends Joo{} //正确,不能当老爸,但能当儿子
12.1 static final常量
- 必须声明同时初始化
- 类名点来访问,不能被改变
- 建议:常量名所有字母都大写,多个单词用_分隔
- 编译器在编译时会将常量直接替换为具体的数,效率高
- 何时用:数据永远不变,并且经常使用
public class StaticFinalDemo {
public static void main(String[] args) {
System.out.println(Aoo.PI); //常常通过类名点来访问
//Aoo.PI = 3.1415926; //编译错误,常量不能被改变
//1)加载Boo.class到方法区中
//2)静态变量num一并存储到方法区中
//3)到方法区中获取num的值并输出
System.out.println(Boo.num);
//编译器在编译时会将常量直接替换为具体的值,效率高
//相当于System.out.println(5);
System.out.println(Boo.COUNT);
}
}
class Boo{
public static int num = 5; //静态变量
public static final int COUNT = 5; //常量
}
class Aoo{
public static final double PI = 3.14159;
//public static final int NUM; //编译错误,常量必须声明同时初始化
}
13 抽象类
在Java中abstract是抽象的意思,如果一个类中的某个方法的具体实现不能确定,就可以申明成abstract修饰的抽象方法(不能写方法体了),这个类必须用abstract修饰,被称为抽象类。
一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
抽象类的意义:
1. 封装共有的属性和行为---------------代码复用
2. 为所有派生类提供统一的类型------向上造型
3. 可以包含抽象方法,为所有派生类提供统一的入口(能点出来),
4. 派生类的行为不同,但入口是一致的,同时相当于定义了一个标准(强制重写)
抽象方法:
- 由abstract修饰
- 只有方法的定义,没有具体的实现(连{}都没有)
final和abstract是什么关系?
- 互斥关系
- abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
- 抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。
14 接口
14.1 定义接口
格式:
public interface 接口名{
//定义抽象方法
void method();
}
14.2 实现接口
在要实现接口的类名后面加上implements 接口名。
public class 类名 implements 接口名{}
14.3 接口的特点
- 是一种引用数据类型
- 由interface定义
- 只能包含常量和抽象方法
- 接口不能被实例化(new对象)
- 接口是需要被实现/继承的,实现类/派生类:----必须重写所有抽象方法
- 一个类可以实现多个接口,用逗号分隔,若又继承又实现时,应先继承后实现
- 接口可以继承接口
//接口的演示
public class InterfaceDemo {
public static void main(String[] args) {
//Inter5 o1 = new Inter5(); //编译错误,接口不能被实例化
Inter5 o2 = new Doo(); //向上造型(可以造型为它所实现的接口)
Inter4 o3 = new Doo(); //向上造型
}
}
//演示接口继承接口
interface Inter4{
void show();
}
interface Inter5 extends Inter4{
void test();
}
class Doo implements Inter5{
public void test(){}
public void show(){}
}
//演示接口多实现
interface Inter2{
void show();
}
interface Inter3{
void test();
}
abstract class Boo{
abstract void say();
}
class Coo extends Boo implements Inter2,Inter3{
public void show(){}
public void test(){}
public void say(){}
}
//演示接口的实现
interface Inter1{
void show(); //访问权限默认是public
void test();
}
class Aoo implements Inter1{
public void show(){} //重写接口中的抽象方法,访问权限必须是public
public void test(){}
}
//演示接口的语法
interface Inter{
public static final int NUM = 5; //接口中成员的访问权限只能是public的
public abstract void show();
int COUNT = 6; //默认public static final
void say(); //默认public abstract
//int number; //编译错误,常量必须声明同时初始化
//void test(){} //编译错误,抽象方法不能有方法体
}
14.4 接口的成员特点
成员变量
只能是常量 默认修饰符:public static final
构造方法
没有,因为接口主要是扩展功能的,而没有具体存在
成员方法
只能是抽象方法
默认修饰符:public abstract
14.5 类和接口的关系
类与类的关系
继承关系,只能单继承,但是可以多层继承
类与接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
接口与接口的关系
继承关系,可以单继承,也可以多继承
14.6抽象类和接口的区别
成员区别
抽象类
变量,常量;有构造方法;有抽象方法,也有非抽象方法
接口
常量;无构造方法,抽象方法
关系区别
类与类
继承,单继承
类与接口
实现,可以单实现,也可以多实现
接口与接口
继承,单继承,多继承
设计理念区别
抽象类
对类抽象,包括属性、行为
接口
对行为抽象,主要是行为
15 内部类
15.1 成员内部类
- 成员内部类:应用率低
- 类中套类,外面的称为外部类,里面的称为内部类
- 内部类通常只服务于外部类,对外不具备可见性
- 内部类对象通常在外部类中创建
- 内部类中可以直接访问外部类的成员(包括私有的)
- 内部类中有个隐式的引用指向了创建它的外部类对象:外部类名.this----API时会用
public class InnerClassDemo {
public static void main(String[] args) {
Mama m = new Mama();
//Baby b = new Baby(); //编译错误,内部类对外不具备可见性
}
}
class Mama{ //外部类
private String name;
void create(){
Baby b = new Baby(); //正确,内部类对象通常在外部类中创建
}
class Baby{ //内部类
void showName(){
System.out.println(name); //省略写法
System.out.println(Mama.this.name); //完整写法
//System.out.println(this.name); //编译错误,this指代当前Baby对象
}
}
}
15.2 匿名内部类
匿名内部类本质是一个对象,它是某个(接口)的子类(实现类对象)
本质:是一个继承了该类或者实现了该接口的子类匿名对象
匿名内部类的特点
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
面试题:问:内部类有独立的.class吗? 答:有
interface Inter{
void method();
}
class Test{
public static void main(String[] args){
new Inter(){
@Override
public void method(){
System.out.println("我是匿名内部类");
}
}.method(); // 直接调用方法
}
}
16 内存分配
16.1 内存概述
内存是计算机中的重要原件,临时存储区域,作用是运行程序。
我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的。
必须放进内存中才能运行,运行完毕后会清空内存。
Java虚拟机要运行程序,必须要对内存进行空间的分配和管理。
16.2 Java中的内存分配
区域名称 | 作用 |
---|---|
寄存器 | 给CPU使用,和我们开发无关。 |
本地方法栈 | JVM在使用操作系统功能的时候使用,和我们开发无关。 |
方法区 | 存储可以运行的class文件。 |
堆内存 | 存储对象或者数组,new来创建的,都存储在堆内存。 |
方法栈 | 方法运行时使用的内存,比如main方法运行,进入方法栈中执行。 |
16.3 内存管理
由JVM来管理的
堆:
- 存储new出来的对象(包括实例变量)
- 垃圾:没有任何引用所指向的对象
垃圾回收器(GC)不定时到内存中清扫垃圾,回收过程是透明的(看不到的),不一定一发现垃圾就立刻回收,通过调用System.gc()可以建议JVM尽快调度GC回收 - 实例变量的生命周期: 创建(new)对象时存储在堆中,对象被回收时一并被回收
- 内存泄漏:不再使用的对象没有被及时的回收,严重的泄漏会导致系统的崩溃
建议:不再使用的对象应及时将引用设置为null
栈:
- 存储正在调用的方法中的局部变量(包括方法的参数)
- 调用方法时,会在栈中为该方法分配一块对应的栈帧,栈帧中存储局部变量(包括方法的参数),方法调用结束时,栈帧被自动清除,局部变量一并被清除
- 局部变量的生命周期: 调用方法时存储在栈中,方法调用结束时与栈帧一并被清除
方法区:
- 存储.class字节码文件(包括静态变量、所有方法)
- 方法只有一份,通过this来区分具体的访问对象
17 面向对象三大特征
面对对象编程:
- 面向就是拿或找的意思。
- 对象就是东西的意思。
- 面向对象编程就是就是拿或找东西过来编程。
封装:
- 类:封装的是对象的属性和行为
- 方法:封装的是具体的业务逻辑功能实现
- 访问控制修饰符:封装的是具体的访问权限,以保护数据的安全
继承:
- 作用:代码复用
- 超类:所有派生类所共有的属性和行为
接口:部分派生类所共有的属性和行为
派生类:派生类所特有的属性和行为 - 单一继承、多接口实现,具有传递性
多态:
- 行为多态:所有抽象方法都是多态的(通过方法的重写实现的)
对象多态:所有对象都是多态的(通过向上造型来实现) - 向上造型、强制类型转换、instanceof判断
18 String类
18.1 概述
- java.lang.String使用final修饰,不能被继承
- java中的String在内存中采用Unicode编码方式,任何一个字符占用两个字节的编码
- 字符串底层封装的是一个字符数组
- 字符串一旦创建,对象内容永远无法改变,但字符串引用可以重新赋值------不变对象
18.2 String常用构造方法:
方法名 | 说明 |
---|---|
public String() | 创建一个空白字符串对象,不含有任何内容 |
public String(char[] chs) | 根据字符数组内容,来创建字符串对象 |
public String(byte[] bys) | 根据字符数组内容,来创建字符串对象 |
String s=“abc” | 赋值的方式来创建字符串对象,内容为abc |
方法名 | 说明 |
---|---|
public boolean equals(Object anObject) | 比较字符串的内容,严格区分大小写(用户名和密码) |
public char charAt(int index) | 返回指定索引处的char值 |
public int length() | 返回此字符串的长度 |
/*
使用字面量来创建字符串对象时,JVM会检查常量池中是否有该对象:
1)若没有,则会创建该字符串对象并存入常量池中
2)若有,则直接将常量池中的对象返回(不会再创建新的字符串对象)
*/
/*
String s1 = "123abc"; //常量池还没有,因此创建该字符串对象,并存入常量池
String s2 = "123abc"; //常量池中已经有了,直接重用对象
String s3 = "123abc"; //常量池中已经有了,直接重用对象
//引用类型==,比较地址是否相同
System.out.println(s1==s2); //true
System.out.println(s1==s3); //true
System.out.println(s2==s3); //true
s1 = s1+"!"; //创建新的字符串对象并将地址赋值给s1
System.out.println(s1==s2); //false,因为s1为新对象的地址,与s2不同了
*/
String s1 = "123abc"; //堆中创建一个123abc对象,常量池中存储这个对象的引用
//编译器在编译时,若发现是两个字面量连接,
//则直接运算好并将结果保存起来,如下代码相当于String s2="123abc";
String s2 = "123"+"abc"; //复用常量池中的123abc对象
System.out.println(s1==s2); //true
String s3 = "123";
//因为s3不是字面量,所以并不会直接运算结果
String s4 = s3+"abc"; //会在堆中创建新的123abc对象,而不会重用常量池中的对象
System.out.println(s1==s4); //false
String面试题
/*
String s = new String("hello");
问:如上语句创建了几个对象?
答:2个
第一个:字面量"hello"
---java会创建一个String对象表示字面量"hello",并将其存入常量池
第二个:new String()
---new String()时会再创建一个字符串对象,并引用hello字符串的内容
*/
String s = new String("hello");
String s1 = "hello";
System.out.println("s:"+s); //hello
System.out.println("s1:"+s1); //hello
System.out.println(s==s1); //false,==比较的是地址是否相同
//字符串实际开发中比较相等一般都是比较字符串的内容
//因此我们需要使用equals()方法来比较两个字符串的内容
System.out.println(s.equals(s1)); //equals()比较的是内容是否相同
18.3 String常用方法
- length():获取字符串的长度(字符个数)
String str = "我爱Java!";
int len = str.length(); //获取str的长度
System.out.println(len); //7
- trim():去除当前字符串两边的空白字符
String str = " hello world ";
System.out.println(str); // hello world
str = str.trim(); //去除当前字符串两边的空白字符
System.out.println(str); //hello world
- toUpperCase()和toLowerCase():将当前字符串中的英文部分转为全大写/全小写
String str = "我爱Java!";
String upper = str.toUpperCase(); //将str中英文部分转为全大写
System.out.println(upper); //我爱JAVA!
String lower = str.toLowerCase(); //将str中英文部分转为全小写
System.out.println(lower); //我爱java!
- startsWith(String str)和endsWith(String str):判断当前字符串是否是以给定的字符串开始/结尾的
String str = "thinking in java";
boolean starts = str.startsWith("think"); //判断str是否是以think开头的
System.out.println("starts:"+starts); //true
boolean ends = str.endsWith(".png"); //判断str是否是以.png结尾的
System.out.println("ends:"+ends); //false
- charAt():返回当前字符串指定位置上的字符
// 0123456789012345
String str = "thinking in java";
char c = str.charAt(9); //获取位置9所对应的字符
System.out.println(c); //i
- indexOf()和lastIndexOf():检索给定字符串在当前字符串中的开始位置
// 111111
// 0123456789012345
String str = "thinking in java";
int index = str.indexOf("in"); //检索in在字符串str中的开始位置
System.out.println(index); //2
index = str.indexOf("in",3); //从下标为3的位置开始找in第一次出现的位置
System.out.println(index); //5
index = str.indexOf("IN"); //当前字符串不包含IN,所以返回-1
System.out.println(index); //-1
index = str.lastIndexOf("in"); //找in最后一次出现的位置
System.out.println(index); //9
- substring():截取当前字符串中指定范围内的字符串
// 1
// 01234567890
String str = "www.eee.cn";
String name = str.substring(4,8); //截到下标4到7范围的字符串
System.out.println(name); //eee
name = str.substring(4); //从下标4开始一直截到末尾
System.out.println(name); //eee.cn
- String的静态方法valueOf():将其它数据类型转换为String
int a = 123;
String s1 = String.valueOf(a); //将int型变量a转换为String类型并赋值给s1
System.out.println(s1); //123---字符串类型
double d = 123.456;
String s2 = String.valueOf(d); //将double型变量d转换为String类型并赋值给s2
System.out.println(s2); //123.456----字符串类型
String s3 = a+""; //任何内容和字符串连接的结果都是字符串,效率低(下节课讲)
System.out.println(s3); //123---字符串类型
- boolean contains(CharSequence s) :当且仅当此字符串包含指定的char值序列时,才返回true。
if ("hdsdajs?dhajkhd".contains("?")) {
//有
System.out.println("有");
} else {
System.out.println("没有");
}
18.4 StringBuilder类
StringBuilder构造器
名称 | 说明 |
---|---|
public StringBuilder() | 创建一个空白的可变的字符串对象,不包含任何内容 |
public StringBuilder(String str) | 创建一个指定字符串内容的可变字符串对象 |
常用方法:
名称 | 说明 |
---|---|
public StringBuilder append(任意类型) | 添加数据并返回StringBuilder对象本身 |
public StringBuilder reverse() | 将对象的内容反转 |
public int length() | 返回对象内容长度 |
public String toString() | 通过toString()就可以实现把StringBuilder转换为String |
replace() | 替换部分内容 |
delete() | 删除部分内容 |
insert() | 插入内容 |
String str = "好好学习java";
//复制str中的内容到builder中-----好好学习java
StringBuilder builder = new StringBuilder(str);
//append():追加内容
builder.append(",为了找个好工作!");
System.out.println(builder); //好好学习java,为了找个好工作!
//replace():替换部分内容
builder.replace(9,16,"就是为了改变世界"); //替换下标9到15的
System.out.println(builder); //好好学习java,就是为了改变世界!
//delete():删除部分内容
builder.delete(0,8); //删除下标0到7的
System.out.println(builder); //,就是为了改变世界!
//insert():插入操作
builder.insert(0,"活着"); //从下标0的位置插入
System.out.println(builder); //活着,就是为了改变世界!
//reverse():翻转
builder.reverse(); //翻转内容
System.out.println(builder); //!界世变改了为是就,着活
import java.util.ArrayList;
import java.util.List;
public class ArrayList01 {
public static void main(String[] args) {
StringBuilder sb=new StringBuilder("hello");
System.out.println(sb);//hellow
StringBuilder sb1= sb.append("world");
System.out.println(sb1==sb);//true append返回对象本身
sb.reverse();//反转
System.out.println(sb);//dlrowwolleh
}
}
面试题:
StringBuilder s1=new StringBuilder("java");
StringBuilder s2=new StringBuilder("java");
s1==s2;//felse
s1.equles(s2);//felse
补充:
StringBuilder和StringBuffer:
- StringBuffer:是线程安全的,同步处理的,性能稍慢
- StringBuilder:非线程安全的,并发处理的,性能稍快
18.4 常量池
- java对字符串有一个优化措施:字符串常量池(堆中)
- java推荐我们使用字面量/直接量的方式来创建字符串,并且会缓存所有以字面量形式创建的字符串对象到常量池中,当使用相同字面量再创
- 建对象时将复用常量池中的对象以减少内存开销,从而避免内存中堆积大量内容相同的字符串对象
18.5 StringUtils工具类
StringUtils工具包中的isBlank函数
isBlank( )函数位于 org.apache.commons.lang.StringUtils工具包中,该函数的功能是判断传入的变量是否为空(通常为String类型)
在判断一个String变量是否为空时,通常分为以下三种情况:
(1)变量是否为null
(2)变量是否为“”
(3)变量是否为空字符串“ ”
而 isBlank( )函数能够一次性判断以上三种情况,返回值都是为true。
19 正则表达式
正则表达式是用来描述字符串内容格式,使用它通常用来匹配一个字符串的内容是否符合要求
正则表达式的语法:
[]:表示一个字符,该字符可以是[]中指定的内容
例如:
[abc]:这个字符可以是a或b或c
[a-z]:表示任意一个小写字母
[a-zA-Z]:表示任意一个字母
[a-zA-Z0-9_]:表示任意一个数字字母下划线
[^abc]:该字符只要不是a或b或c
预定义字符:
.:表示任意一个字符,没有范围限制
\d:表示任意一个数字,等同于[0-9]
\w:表示任意一个单词字符,等同于[a-zA-Z0-9_]
\s:表示任意一个空白字符
\D:表示不是数字
\W:不是单词字符
\S:不是空白字符
量词:
?:表示前面的内容出现0-1次
例如: [abc]? 可以匹配:a 或 b 或 c 或什么也不写
+:表示前面的内容最少出现1次
例如: [abc]+ 可以匹配:b或aaaaaaaaaa...或abcabcbabcbabcbabcbabbabab....
但是不能匹配:什么都不写 或 abcfdfsbbaqbb34bbwer...
*:表示前面的内容出现任意次(0-多次)---匹配内容与+一致,只是可以一次都不写
例如: [abc]* 可以匹配:b或aaaaaaaaaa...或abcabcbabcbabcbabcbabbabab....或什么也不写
但是不能匹配:abcfdfsbbaqbb34bbwer...
{n}:表示前面的内容出现n次
例如: [abc]{3} 可以匹配:aaa 或 bbb 或 aab 或abc 或bbc
但是不能匹配: aaaa 或 aad
{n,m}:表示前面的内容出现最少n次最多m次
例如: [abc]{3,5} 可以匹配:aaa 或 abcab 或者 abcc
但是不能匹配:aaaaaa 或 aabbd
{n,}:表示前面的内容出现n次以上(含n次)
例如: [abc]{3,} 可以匹配:aaa 或 aaaaa.... 或 abcbabbcbabcbabcba....
但是不能匹配:aa 或 abbdaw...
()用于分组,是将括号内的内容看做是一个整体
例如: (abc){3} 表示abc整体出现3次. 可以匹配abcabcabc
但是不能匹配aaa 或abcabc
(abc|def){3}表示abc或def整体出现3次.
可以匹配: abcabcabc 或 defdefdef 或 abcdefabc
但是不能匹配abcdef 或abcdfbdef
String支持与正则表达式相关的方法
方法1: matches():使用给定的正则表达式验证当前字符串的格式是否符合要求
/*
邮箱的正则表达式:
[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\.[a-zA-Z]+)+
*/
String email = "wangkj@aaa.cn";
String regex = "[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.[a-zA-Z]+)+";
boolean match = email.matches(regex);
if(match){
System.out.println("是正确的邮箱");
}else{
System.out.println("不是正确的邮箱");
}
方法2: split():将当前字符串按照满足正则表达式的部分进行拆分
String line = "abc123def456ghi";
String[] data = line.split("[0-9]+"); //按数字拆分
System.out.println(Arrays.toString(data)); //将data数组按照字符串的格式输出
line = "123,456,789,482";
data = line.split(","); //按逗号拆分
System.out.println(Arrays.toString(data));
line = "123.456.789.482"; //练习+下课--------11:35继续
data = line.split("\\."); //按点拆分
System.out.println(Arrays.toString(data));
//最开始就是可拆分项(.),那么数组中的第1个元素为一个空字符串------""
//如果连续两个(两个以上)可拆分项,它们中间也会拆出一个空字符串-----""
//如果末尾连续多个可拆分项,那么拆出的空字符串被忽略
line = ".123.456..789.482.......";
data = line.split("\\.");
System.out.println(Arrays.toString(data));
方法3: replaceAll():将当前字符串中满足正则表达式的部分替换为给定的字符串
String line = "abc123def456ghi";
line = line.replaceAll("[0-9]+","#NUMBER#"); //将数字部分替换为#NUMBER#
System.out.println(line);
20 Object类
20.1 Object类的作用
- 是所有类的鼻祖,所有类都直接或间接继承了Object,万物皆对象,为了多态
20.2 Object类的常用方法
方法名称 | 类型 | 描述 |
---|---|---|
public Object() | 构造 | 构造器 |
public boolean equals(Object obj) | 普通 | 对象比较 |
public String toString() | 普通 | 对象打印时调用 |
- Object的oString方法存在的意义:
父类toString()方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息
Object的oString方法作用:
让子类重写,以便返回子类对象的内容。
- Object的equals方法存在的意义:
父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则。
Object的equals方法作用:
- 默认是与另一个对象比较地址是否一样
- 让子类重写,以便比较2个子类对象的内容是否相同
import java.util.Objects;
/** 测试常常被派生类重写的Object中的相关方法 */
public class Point {
private int x;
private int y;
@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
public class ObjectDemo {
public static void main(String[] args) {
//演示重写toString()
Point p = new Point(1,2);
//输出引用对象时默认调用toString()
//相当于System.out.println(p.toString());
System.out.println(p);
//字符串连接时将默认调用对象的toString()
//相当于String str = "这是个点:"+p.toString();
String str = "这是个点:"+p;
System.out.println(str);
//演示重写equals()
//Object类另一个常常被派生类重写的方法:equals()
Point p1 = new Point(1,2);
Point p2 = new Point(1,2);
System.out.println(p1==p2); //false,==比较的地址
System.out.println(p1.equals(p2)); //true,因为重写equals()中比较的是x和y
}
}
20.3 ==操作符与equals方法
面试题
1. ==
基本类型比较值:只要两个变量的值相等,即为true。
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回true
用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
2. equals
所有类都继承了Object,也就获得了equals()方法。还可以重写
只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象
特例:当用equals()方法进行比较时,对类File、String、Date及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;(原因:在这些类中重写了Object类的equals()方法。)
21 包装类
21.1 包装类的作用
- Java为了实现一切皆对象,为8种基本类型提供了对应的引用类型。
- 后面的集合和泛型其实也只能支持包装类型,不支持基本数据类型。
- java定义了8个包装类,目的是为了解决基本类型不能直接参与面向对象开发的问题,使得基本类型可以通过包装类的实例以对象的方式存在
自动装箱:基本类型的数据和变量可以直接赋值给包装类型的变量。
自动装箱:基本类型的数据和变量可以直接赋值给包装类型的变量。
基本数据类型 | 引用数据类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
//演示自动拆装箱
//触发了自动装箱特性
Integer i1 = 5; //会被编译为: Integer i1 = Integer.valueOf(5);
//触发了自动拆箱特性
int ii = i1; //会被编译为: int ii = i1.intValue();
//演示包装类的实际操作:
//1)可以通过包装类来得到基本类型的取值范围:
int max = Integer.MAX_VALUE; //获取int的最大值
int min = Integer.MIN_VALUE; //获取int的最小值
System.out.println("int的最大值为:"+max);
System.out.println("int的最小值为:"+min);
long lMax = Long.MAX_VALUE; //获取long的最大值
long lMin = Long.MIN_VALUE; //获取long的最小值
System.out.println("long的最大值为:"+lMax);
System.out.println("long的最小值为:"+lMin);
//2)包装类可以将字符串转换为对应的基本类型
// 前提是该字符串正确表达了基本类型的值
// 若不能正确表达,则发生NumberFormatException数字转换异常
String str = "123";
int num = Integer.parseInt(str); //将字符串str转换为int类型
System.out.println(num); //123
str = "123.456";
double dou = Double.parseDouble(str); //将字符串str转换为double类型
System.out.println(dou); //123.456
21.2 包装类的特有功能
- 可以把基本类型的数据转换成字符串类型(用处不大)
//1)可以把基本类型的数据转换成字符串类型
public class Test1 {
public static void main(String[] args) {
int a=1;
double b=2.0;
System.out.println(Integer.toString(a));
System.out.println(Double.toString(b));
}
}
- 可以把字符串类型的数值转换成真实的数据类型(真的很有用)
//2)包装类可以将字符串转换为对应的基本类型
// 前提是该字符串正确表达了基本类型的值
// 若不能正确表达,则发生NumberFormatException数字转换异常
String str = "123";
int num = Integer.parseInt(str); //将字符串str转换为int类型
System.out.println(num); //123
str = "123.456";
double dou = Double.parseDouble(str); //将字符串str转换为double类型
System.out.println(dou); //123.456
21.3 转义序列
Java的转义序列:
转义序列 | 描述 |
---|---|
\t | 在文中该处插入一个tab键 |
\b | 在文中该处插入一个后退键 |
\n | 在文中该处换行 |
\r | 在文中该处插入回车 |
\f | 在文中该处插入换页符 |
’ | 在文中该处插入单引号 |
" | 在文中该处插入双引号 |
\ | 在文中该处插入反斜杠 |
public class Test {
public static void main(String[] args) {
System.out.println("访问\"CSDN!\"");
}
}
22 异常
- 异常是代码在编译或者执行的过程中可能出现的错误。
- 应该避免异常的出现,同时处理可能出现的异常,让代码更稳健。
22.1 异常分类
分为编译时异常、运行时异常。
- 编译时异常:没有继承RuntimeExcpetion的异常,编译阶段就会出错,编译期必须处理的,否则程序不能通过编译。
- 运行时异常:继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。
22.2 异常体系
java的异常处理机制
java中所有的异常的超类为Throwable,其下派生了两个子类:Error和Exception
Error:表示系统错误,通常是不能在程序运行期间被解决的错误。
Exception:异常类,表示程序级别的错误,通常是由于逻辑等导致的问题,可以在程序运行期间被解决。
- RuntimeException及其子类:运行时异常,编译阶段不会报错。 (空指针异常,数组索引越界异常)
- 除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译。 (日期格式化异常)。
22.3 运行时异常
继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。
常见运行时异常:
- 空指针异常 : NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。
- 数学操作异常:ArithmeticException
- 数字转换异常: NumberFormatException
- 数组索引越界异常: ArrayIndexOutOfBoundsException
- 类型转换异常:ClassCastException
- 字符串下标越界异常:StringIndexOutOfBoundsException
22.4 编译时异常
没有继承RuntimeExcpetion的异常,编译阶段就会出错,编译期必须处理的,否则程序不能通过编译。
常见编译时异常:
IO流异常:java.io.IOExeption
没有找到指定的类异常:java.lang.ClassNotFoundException
系统找不到指定的文件异常:java.io.FileNotFoundException
访问关系数据库类产生的异常:java.sql.SQLException
22.5JVM默认处理异常流程
- 默认会在出现异常的代码那里自动的创建一个异常对象
- 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机
- 虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据
- 直接从当前执行的异常点干掉当前程序
- 后续代码没有机会执行了,因为程序已经死亡
22.6 throws异常处理方式
当一个方法中使用throw抛出一个非RuntimeException的异常时,就要在该方法上使用throws声明这个异常的抛出。此时调用该方法的代码就必须处理这个异常,否则编译不通过。
当我们调用一个含有throws声明异常抛出的方法时,编译器要求我们必须处理这个异常,否则编译不通过。 处理手段有两种:
- 使用try-catch捕获并处理这个异常
- 在当前方法(本案例就是main方法)上继续使用throws声明该异常的抛出给调用者解决。 具体选取那种取决于异常处理的责任问题。
throws 格式:
方法 throws 异常1 ,异常2 ,异常3 ..{
}
方法 throws Exception{//抛出一切异常
}
/**
* throw关键字,用于主动对外抛出一个异常
*/
public class ThrowDemo {
public static void main(String[] args){
System.out.println("程序开始了...");
try {
Person p = new Person();
/*
当我们调用一个含有throws声明异常抛出的方法时,编译器要求
我们必须添加处理异常的手段,否则编译不通过.而处理手段有两种
1:使用try-catch捕获并处理异常
2:在当前方法上继续使用throws声明该异常的抛出
具体用哪种取决于异常处理的责任问题
*/
p.setAge(100000);//典型的符合语法,但是不符合业务逻辑要求
System.out.println("此人年龄:"+p.getAge()+"岁");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("程序结束了...");
}
}
/**
* 测试异常的抛出
*/
public class Person {
private int age;
public int getAge() {
return age;
}
/**
* 当一个方法使用throws声明异常抛出时,调用此方法的代码片段就必须处理这个异常
*/
public void setAge(int age) throws Exception {
if(age<0||age>100){
//使用throw对外抛出一个异常
// throw new RuntimeException("年龄不合法!");
//除了RuntimeException之外,抛出什么异常就要在方法上声明throws什么异常
throw new Exception("年龄不合法!");
}
this.age = age;
}
}
22.7 throw关键字
throw用来对外主动抛出一个异常,通常下面两种情况我们主动对外抛出异常:
- 1:当程序遇到一个满足语法,但是不满足业务要求时,可以抛出一个异常告知调用者。
- 2:程序执行遇到一个异常,但是该异常不应当在当前代码片段被解决时可以抛出给调用者。
public class ThrowDemo {
public static void main(String[] args) {
Person p = new Person();
p.setAge(10000);//符合语法,但是不符合业务逻辑要求。
System.out.println("此人年龄:"+p.getAge());
}
}
/**
* 测试异常的抛出
*/
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) throws Exception {
if(age<0||age>100){
//使用throw对外抛出一个异常
throw new RuntimeException("年龄不合法!");
}
this.age = age;
}
}
22.8 try-catch异常处理方式
try-catch格式:
try{
// 监视可能出现异常的代码!
}catch(异常类型1 变量){
// 处理异常
}catch(异常类型2 变量){
// 处理异常
}...
try{
// 可能出现异常的代码!
}catch (Exception e){//可以捕获处理一切异常类型
e.printStackTrace(); // 直接打印异常栈信息}
}
/**
* java的异常处理机制
* java中所有的异常的超类为Throwable,其下派生了两个子类型:Error和Exception
* Error表示系统错误,通常是不能在程序运行期间被解决的错误。
* Exception表示程序级别的错误,通常是由于逻辑等导致的问题,可以在程序运行期间被解决。
*
* 异常处理机制中的try-catch
* 语法:
* try{
* 可能会出现的异常的代码片段
* }catch(xxxxException e){
* try语句块中出现xxxxException后的解决办法
* }
*
*/
public class TryCatchDemo {
public static void main(String[] args) {
System.out.println("程序开始了...");
try {
String line = "";
String s = null;
String a = "a";
/*
JVM执行到这里时,如果发生了异常就会实例化一个对应的异常实例,并将程序执行
过程设置进去,然后将异常抛出。
*/
System.out.println(s.length());
System.out.println(line.charAt(5));
System.out.println(Integer.parseInt(a));
}
/*catch (NullPointerException e) {
System.out.println("空指针异常出现");
}catch (StringIndexOutOfBoundsException e) {
System.out.println("出现下标越界异常");
}*/
//当多个异常的解决办法相同时,可以合并到一个catch来捕获并处理
catch (StringIndexOutOfBoundsException | NullPointerException e) {
System.out.println("两种异常统一解决的办法");
//可以在最后一个catch处捕获Eeception,避免因未能捕获的异常导致程序中断
} catch (Exception e) {
System.out.println("");
} finally {
System.out.println("---");
}
System.out.println("程序结束了...");
}
}
finally:
/**
* 异常处理机制中的finally块
* finally块就是异常处理机制的最后一块,它可以跟在try之后或者最后一个catch之后。
* finally可以保证只要程序执行到try语句块中,无论try中是否出现异常,finally最终都
* 会必定执行。
* 通常我们将释放资源这类操作方法finally中去报运行,例如IO操作后最终的close()调用
*
*/
public class FinallyDemo {
public static void main(String[] args) {
System.out.println("程序开始了");
try {
String line = null;
// String line = "abc";
System.out.println(line.length());
//如果出错try中的以下的语句将不会执行
System.out.println("!!!!!!!!!!!!");
} catch (Exception e) {
System.out.println("出错了");
}finally {
System.out.println("finally中的代码执行了");
}
System.out.println("程序结束了");//出错正常执行
}
}
22.9 try-catch自动关闭
格式:
try (
…
) {
…
}
} catch (Exception e) {
e.printStackTrace();
}
在()中的资源会自动关闭。
try (
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
) {
User user = (User) ois.readObject();
userList.add(user);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
22.10 自定义异常
1、自定义编译时异常
定义一个异常类继承Exception.
重写构造器。
在出现异常的地方用throw new 自定义对象抛出,
作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!
2、自定义运行时异常
定义一个异常类继承RuntimeException.
重写构造器。
在出现异常的地方用throw new 自定义对象抛出!
作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!!
22.11 含有throws的方法被子类重写时的规则
/**
* 子类重写超类含有throws声明异常抛出的方法时对throws的几种特殊的重写规则
*/
public class ThrowsDemo {
public void dosome()throws IOException, AWTException {}
}
class SubClass extends ThrowsDemo{
// public void dosome()throws IOException, AWTException {}
//可以不再抛出任何异常
// public void dosome(){}
//可以仅抛出部分异常
// public void dosome()throws IOException {}
//可以抛出超类方法抛出异常的子类型异常
// public void dosome()throws FileNotFoundException {}
//不允许抛出额外异常(超类方法中没有的,并且没有继承关系的异常)
// public void dosome()throws SQLException {}
//不可以抛出超类方法抛出异常的超类型异常
// public void dosome()throws Exception {}
}
23 File类
File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路径)
使用File可以做到:
- 访问其表示的文件或目录的属性信息,例如:名字,大小,修改时间等等
- 创建和删除文件或目录
- 访问一个目录中的子项
但是File不能访问文件数据。
方法名称 | 说明 |
---|---|
public File(String pathname) | 根据文件路径创建文件对象 |
public File(String parent, String child) | 从父路径名字字符串和子路径名字符串创建对象 |
public File(File parent, , String child) | 根据父路径和子路径名字符串创建文件对象 |
public class FileDemo {
public static void main(String[] args) {
//使用File访问当前项目目录下的demo.txt文件
/*
创建File时要指定路径,而路径通常使用相对路径。
相对路径的好处在于有良好的跨平台性。
"./"是相对路径中使用最多的,表示"当前目录",而当前目录是哪里
取决于程序运行环境而定,在idea中运行java程序时,这里指定的
当前目录就是当前程序所在的项目目录。
*/
// File file = new File("c:/xxx/xxx/xx/xxx.txt");
File file = new File("./demo.txt");
//获取名字
String name = file.getName();
System.out.println(name);
//获取文件大小(单位是字节)
long len = file.length();
System.out.println(len+"字节");
//是否可读可写
boolean cr = file.canRead();
boolean cw = file.canWrite();
System.out.println("是否可读:"+cr);
System.out.println("是否可写:"+cw);
//是否隐藏
boolean ih = file.isHidden();
System.out.println("是否隐藏:"+ih);
}
}
23.1 判断功能
方法名称 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为文件夹 |
public boolean isFile() | 测试此抽象路径名表示的File是否为文件 |
public boolean exists() | 测试此抽象路径名表示的File是否存在 |
public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
public String getPath() | 将此抽象路径名转换为路径名字符串 |
public String getName() | 返回由此抽象路径名表示的文件或文件夹的名称 |
public long lastModified() | 返回文件最后修改的时间毫秒值 |
23.2 File类创建和删除
方法名称 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean delete() | 删除由此抽象路径名表示的文件或空文件夹 |
public boolean mkdir() | 只能创建一级文件夹 |
public boolean mkdirs() | 可以创建多级文件夹 |
createNewFile():创建一个新的空的文件
/**
* 使用File创建一个新文件
*/
public class CreateNewFileDemo {
public static void main(String[] args) throws IOException {
//在当前目录下新建一个文件:test.txt
File file = new File("./test.txt");
//boolean exists()判断当前File表示的位置是否已经实际存在该文件或目录
if(file.exists()){
System.out.println("该文件已存在!");
}else{
file.createNewFile();//将File表示的文件创建出来
System.out.println("文件已创建!");
}
}
}
delete:删除由此抽象路径名表示的文件或空文件夹
- delete方法默认只能删除文件和空文件夹。
- delete方法直接删除不走回收站。
/**
* 使用File删除一个文件
*/
public class DeleteFileDemo {
public static void main(String[] args) {
//将当前目录下的test.txt文件删除
/*
相对路径中"./"可以忽略不写,默认就是从当前目录开始的。
*/
File file = new File("test.txt");
if(file.exists()){
file.delete();
System.out.println("文件已删除!");
}else{
System.out.println("文件不存在!");
}
}
}
/**
* 删除一个目录
*/
public class DeleteDirDemo {
public static void main(String[] args) {
//将当前目录下的demo目录删除
File dir = new File("demo");
// File dir = new File("a");
if(dir.exists()){
dir.delete();//delete方法删除目录时只能删除空目录
System.out.println("目录已删除!");
}else{
System.out.println("目录不存在!");
}
}
}
mkDir():创建当前File表示的目录
mkDirs():创建当前File表示的目录,同时将所有不存在的父目录一同创建
/**
* 使用File创建目录
*/
public class MkDirDemo {
public static void main(String[] args) {
//在当前目录下新建一个目录:demo
// File dir = new File("demo");
File dir = new File("./a/b/c/d/e/f");
if(dir.exists()){
System.out.println("该目录已存在!");
}else{
// dir.mkdir();//创建目录时要求所在的目录必须存在
dir.mkdirs();//创建目录时会将路径上所有不存在的目录一同创建
System.out.println("目录已创建!");
}
}
}
23.3 File类的遍历
方法名称 | 说明 |
---|---|
public String[] list() | 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。 |
public File[] listFiles()(常用) | 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点) |
/**
* 访问一个目录中的所有子项
*/
public class ListFilesDemo1 {
public static void main(String[] args) {
//获取当前目录中的所有子项
File dir = new File(".");
/*
boolean isFile()
判断当前File表示的是否为一个文件
boolean isDirectory()
判断当前File表示的是否为一个目录
*/
if(dir.isDirectory()){
/*
File[] listFiles()
将当前目录中的所有子项返回。返回的数组中每个File实例表示其中的一个子项
*/
File[] subs = dir.listFiles();
System.out.println("当前目录包含"+subs.length+"个子项");
for(int i=0;i<subs.length;i++){
File sub = subs[i];
System.out.println(sub.getName());
}
}
}
}
获取目录中符合特定条件的子项
/**
* 重载的listFiles方法,允许我们传入一个文件过滤器从而可以有条件的获取一个目录
* 中的子项。
*/
public class ListFilesDemo2 {
public static void main(String[] args) {
/*
需求:获取当前目录中所有名字以"."开始的子项
*/
File dir = new File(".");
if(dir.isDirectory()){
// FileFilter filter = new FileFilter(){//匿名内部类创建过滤器
// public boolean accept(File file) {
// String name = file.getName();
// boolean starts = name.startsWith(".");//名字是否以"."开始
// System.out.println("过滤器过滤:"+name+",是否符合要求:"+starts);
// return starts;
// }
// };
// File[] subs = dir.listFiles(filter);//方法内部会调用accept方法
File[] subs = dir.listFiles(new FileFilter(){
public boolean accept(File file) {
return file.getName().startsWith(".");
}
});
System.out.println(subs.length);
}
}
}
listFiles方法注意事项:
- 当调用者不存在时,返回null
- 当调用者是一个文件时,返回null
- 当调用者是一个空文件夹时,返回一个长度为0的数组
- 当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
- 当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容
- 当调用者是一个需要权限才能进入的文件夹时,返回null
24 Lambda表达式
DK8之后,java支持了lambda表达式这个特性.
- lambda可以用更精简的代码创建匿名内部类.但是该匿名内部类实现的接口只能有一个抽象方法,否则无法使用!
- lambda表达式是编译器认可的,最终会将其改为内部类编译到class文件中
/**
* JDK8之后java支持了lambda表达式这个特性
* lambda表达式可以用更精简的语法创建匿名内部类,但是实现的接口只能有一个抽象
* 方法,否则无法使用。
* lambda表达式是编译器认可的,最终会被改为内部类形式编译到class文件中。
*
* 语法:
* (参数列表)->{
* 方法体
* }
*/
public class LambdaDemo {
public static void main(String[] args) {
//匿名内部类形式创建FileFilter
FileFilter filter = new FileFilter() {
public boolean accept(File file) {
return file.getName().startsWith(".");
}
};
FileFilter filter2 = (File file)->{
return file.getName().startsWith(".");
};
//lambda表达式中参数的类型可以忽略不写
FileFilter filter3 = (file)->{
return file.getName().startsWith(".");
};
/*
lambda表达式方法体中若只有一句代码,则{}可以省略
如果这句话有return关键字,那么return也要一并省略!
*/
FileFilter filter4 = (file)->file.getName().startsWith(".");
}
}
25 IO流
字节流:操作所有类型的文件,比如音频视频图片等。
字符流:只能操作纯文本文件,比如包括java文件,txt文件等。
绿色为抽象类,蓝色实现类
25.1 字节流
25.1.1 文件字节输入流FileInputStream
作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去。
构造器 | 说明 |
---|---|
public FileInputStream(File file) | 创建字节输入流管道与源文件对象接通 |
public FileInputStream(String pathname) | 创建字节输入流管道与源文件路径接通 |
方法名称 | 说明 |
---|---|
public int read0 | 每次读取一个字节返回,如果字节已经没有可读的返回-1 |
public int read(byte] buffer) | 每次读取一个字节数组返回,如果字节已经没有可读的返回-1 |
/*
目标:字节输入流的使用。
*/
public class FileInputStreamDemo01 {
public static void main(String[] args) throws Exception {
// 1、创建一个文件字节输入流管道与源文件接通。
// InputStream is = new FileInputStream(new File("file-io-app\\src\\data.txt"));
// 简化写法
InputStream is = new FileInputStream("data.txt");
// 2、读取一个字节返回 (每次读取一滴水)
// int b1 = is.read();
// System.out.println((char)b1);
//
// int b2 = is.read();
// System.out.println((char)b2);
//
// int b3 = is.read();
// System.out.println((char)b3);
//
// int b4 = is.read(); // 读取完毕返回-1
// System.out.println(b4);
// 3、使用循环改进
// 定义一个变量记录每次读取的字节 a b 3 爱
// o o o [ooo]
int b;
while (( b = is.read() ) != -1){
System.out.print((char) b);
}
}
}
/**
目标:使用文件字节输入流每次读取一个字节数组的数据。
*/
public class FileInputStreamDemo02 {
public static void main(String[] args) throws Exception {
// 1、创建一个文件字节输入流管道与源文件接通
InputStream is = new FileInputStream("data02.txt");
// 2、定义一个字节数组,用于读取字节数组
// byte[] buffer = new byte[3]; // 3B
// int len = is.read(buffer);
// System.out.println("读取了几个字节:" + len);
// String rs = new String(buffer);
// System.out.println(rs);
//
// int len1 = is.read(buffer);
// System.out.println("读取了几个字节:" + len1);
// String rs1 = new String(buffer);
// System.out.println(rs1);
// // buffer = [a b c]
//
// // buffer = [a b c] ==> [c d c]
// int len2 = is.read(buffer);
// System.out.println("读取了几个字节:" + len2);
// // 读取多少倒出多少
// String rs2 = new String(buffer,0 ,len2);
// System.out.println(rs2);
//
// int len3 = is.read(buffer);
// System.out.println(len3); // 读取完毕返回-1
// 3、改进使用循环,每次读取一个字节数组
byte[] buffer = new byte[3];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1) {
// 读取多少倒出多少
System.out.print(new String(buffer, 0 , len));
}
}
}
/**
目标:使用文件字节输入流一次读完文件的全部字节。可以解决乱码问题。
*/
public class FileInputStreamDemo03 {
public static void main(String[] args) throws Exception {
// 1、创建一个文件字节输入流管道与源文件接通
File f = new File("data03.txt");
InputStream is = new FileInputStream(f);
// 2、定义一个字节数组与文件的大小刚刚一样大。
// byte[] buffer = new byte[(int) f.length()];
// int len = is.read(buffer);
// System.out.println("读取了多少个字节:" + len);
// System.out.println("文件大小:" + f.length());
// System.out.println(new String(buffer));
// 读取全部字节数组
byte[] buffer = is.readAllBytes();//直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回
System.out.println(new String(buffer));
}
}
25.1.2 文件字节输出流FileOutputStream
作用:以内存为基准,把内存中的数据以字节的形式写出到磁盘文件中去的流。
构造器 | 说明 |
---|---|
public FileOutputStream(File file) | 创建字节输出流管道与源文件对象接通 |
public FileOutputStream(File file, boolean append) | 创建字节输出流管道与源文件对象接通,可追加数据 |
public FileOutputStream(String filepath) | 创建字节输出流管道与源文件路径接通 |
public FileOutputStream(String filepath, boolean append) | 创建字节输出流管道与源文件路径接通,可追加数据 |
方法名称 | 说明 |
---|---|
public void write(int a) | 写一个字节出去 |
public void write(byte[] buffer) | 写一个字节数组出去 |
public void write(byte[] buffer , int pos , int len) | 写一个字节数组的一部分出去。 |
/*
目标:字节输出流的使用。
*/
public class OutputStreamDemo04 {
public static void main(String[] args) throws Exception {
// 1、创建一个文件字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("file-io-app/src/out04.txt" , true); // 追加数据管道
// OutputStream os = new FileOutputStream("file-io-app/src/out04.txt"); // 先清空之前的数据,写新数据进入
// 2、写数据出去
// a.public void write(int a):写一个字节出去
os.write('a');
os.write(98);
os.write("\r\n".getBytes()); // 换行
// os.write('徐'); // [ooo]
// b.public void write(byte[] buffer):写一个字节数组出去。
byte[] buffer = {'a' , 97, 98, 99};
os.write(buffer);
os.write("\r\n".getBytes()); // 换行
byte[] buffer2 = "我是中国人".getBytes();
// byte[] buffer2 = "我是中国人".getBytes("GBK");
os.write(buffer2);
os.write("\r\n".getBytes()); // 换行
// c. public void write(byte[] buffer , int pos , int len):写一个字节数组的一部分出去。
byte[] buffer3 = {'a',97, 98, 99};
os.write(buffer3, 0 , 3);
os.write("\r\n".getBytes()); // 换行
// os.flush(); // 写数据必须,刷新数据 可以继续使用流
os.close(); // 释放资源,包含了刷新的!关闭后流不可以使用了
}
}
文件的复制:
/**
* 文件的复制
*/
public class CopyDemo {
public static void main(String[] args) throws IOException {
//创建文件输入流读取原文件
FileInputStream fis = new FileInputStream("image.jpg");
//创建文件输出流写入复制文件
FileOutputStream fos = new FileOutputStream("image_cp.jpg");
int d;//保存每次读取到的字节
/*
原文件数据:
11000011 10101010 00001111 11001100 00110011 ...
^^^^^^^^
d = fis.read();
d:00000000 00000000 00000000 10101010
fos.write(d);
复制文件的数据:
11000011 10101010
*/
long start = System.currentTimeMillis();//获取当前系统时间的毫秒值(UTC时间)
while((d = fis.read()) != -1) {
fos.write(d);
}
long end = System.currentTimeMillis();//获取当前系统时间的毫秒值(UTC时间)
System.out.println("复制完毕!耗时:"+(end-start)+"ms");
fis.close();
fos.close();
}
}
25.1.3 流的关闭与刷新
方法 | 说明 |
---|---|
flush0 | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
25.2 字符流
25.2.1 文件字符输入流:FileReader
构造器 | 说明 |
---|---|
public EileReader(File file) | 创建字符输入流管道与源文件对象接通 |
public FileReader(String pathname) | 创建字符输入流管道与源文件路径接通 |
方法名称 | 说明 |
---|---|
public int read0 | 每次读取一个字符返回,如果字符已经没有可读的返回-1 |
public int read(char[] buffer) | 每次读取一个字符数组,返回读取的字符个数,如果字符已经没有可读的返回-1 |
/*
目标:字符输入流的使用。
*/
public class FileReaderDemo01 {
public static void main(String[] args) throws Exception {
// 目标:每次读取一个字符。
// 1、创建一个字符输入流管道与源文件接通
Reader fr = new FileReader("data06.txt");
// 2、读取一个字符返回,没有可读的字符了返回-1
// int code = fr.read();
// System.out.print((char)code);
//
// int code1 = fr.read();
// System.out.print((char)code1);
// 3、使用循环读取字符
int code;
while ((code = fr.read()) != -1){
System.out.print((char) code);
}
}
}
/*
目标:字符输入流的使用-按照字符数组读取。
*/
public class FileReaderDemo02 {
public static void main(String[] args) throws Exception {
// 1、创建一个文件字符输入流与源文件接通
Reader fr = new FileReader("data07.txt");
// 2、用循环,每次读取一个字符数组的数据。 1024 + 1024 + 8
char[] buffer = new char[1024]; // 1K字符
int len;
while ((len = fr.read(buffer)) != -1) {
String rs = new String(buffer, 0, len);
System.out.print(rs);
}
}
}
25.2.2 文件字符输出流:FileWriter
作用:以内存为基准,把内存中的数据以字符的形式写出到磁盘文件中去的流。
构造器 | 说明 |
---|---|
public Eilelritec(File file) | 创建字符输出流管道与源文件对象接通 |
public Eileiitec(File file,boolean append) | 创建字符输出流管道与源文件对象接通,可追加数据 |
public Eilewritec(String filepath) | 创建字符输出流管道与源文件路径接通 |
public EileMrite(String filepath,boolsan append) | 创建字符输出流管道与源文件路径接通,可追加数据 |
方法名称 | 说明 |
---|---|
public void write(int c) | 写一个字符出去 |
public void write(String c) | 写一个字符串出去 |
public void write(char[] buffer) | 写一个字符数组出去 |
public void write(String c ,int pos ,int len) | 写字符串的一部分出去 |
public void write(char[] buffer ,int pos ,int len) | 写字符数组的一部分出去 |
/*
目标:字符输出流的使用。
*/
public class FileWriterDemo03 {
public static void main(String[] args) throws Exception {
// 1、创建一个字符输出流管道与目标文件接通
// Writer fw = new FileWriter("file-io-app/src/out08.txt"); // 覆盖管道,每次启动都会清空文件之前的数据
Writer fw = new FileWriter("out08.txt", true); // 覆盖管道,每次启动都会清空文件之前的数据
// a.public void write(int c):写一个字符出去
fw.write(98);
fw.write('a');
fw.write('徐'); // 不会出问题了
fw.write("\r\n"); // 换行
// b.public void write(String c)写一个字符串出去
fw.write("abc我是中国人");
fw.write("\r\n"); // 换行
// c.public void write(char[] buffer):写一个字符数组出去
char[] chars = "abc我是中国人".toCharArray();
fw.write(chars);
fw.write("\r\n"); // 换行
// d.public void write(String c ,int pos ,int len):写字符串的一部分出去
fw.write("abc我是中国人", 0, 5);
fw.write("\r\n"); // 换行
// e.public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
fw.write(chars, 3, 5);
fw.write("\r\n"); // 换行
// fw.flush();// 刷新后流可以继续使用
fw.close(); // 关闭包含刷线,关闭后流不能使用
}
}
25.3 字节缓冲流
- 缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。
- 作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能
25.3.1 字节缓冲输入流: BufferedInputStream
构造器 | 说明 |
---|---|
public BufferedInputStream(InputStream is) | 可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能 |
public BufferedOutputStream(OutputStream os) | 可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能 |
/**
* java将流分为节点流与处理流两类
* 节点流:也称为低级流,是真实连接程序与另一端的"管道",负责实际读写数据的流。
* 读写一定是建立在节点流的基础上进行的。
* 节点流好比家里的"自来水管"。连接我们的家庭与自来水厂,负责搬运水。
* 处理流:也称为高级流,不能独立存在,必须连接在其他流上,目的是当数据经过当前流时
* 对其进行某种加工处理,简化我们对数据的同等操作。
* 高级流好比家里常见的对水做加工的设备,比如"净水器","热水器"。
* 有了它们我们就不必再自己对水进行加工了。
* 实际开发中我们经常会串联一组高级流最终连接到低级流上,在读写操作时以流水线式的加工
* 完成复杂IO操作。这个过程也称为"流的连接"。
*
* 缓冲流,是一对高级流,作用是加快读写效率。
* java.io.BufferedInputStream和java.io.BufferedOutputStream
*
*/
public class CopyDemo3 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("ppt.pptx");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("ppt_cp.pptx");
BufferedOutputStream bos = new BufferedOutputStream(fos);
int d;
long start = System.currentTimeMillis();
while((d = bis.read())!=-1){//使用缓冲流读取字节
bos.write(d);//使用缓冲流写出字节
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-start)+"ms");
bis.close();//关闭流时只需要关闭高级流即可,它会自动关闭它连接的流
bos.close();
}
}
25.3.2 字节缓冲输出流:BufferedOutputStream
构造器 | 说明 |
---|---|
public BufferedOutputStream(OutputStream os) | 可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能 |
/**
* 缓冲输出流写出数据的缓冲区问题
*/
public class BOS_FlushDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("bos.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
String line = "奥里给!";
byte[] data = line.getBytes(StandardCharsets.UTF_8);
bos.write(data);
System.out.println("写出完毕!");
/*
缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。
注:该方法实际上实在字节输出流的超类OutputStream上定义的,并非只有缓冲
输出流有这个方法。但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现
该方法的目的仅仅是为了在流连接过程中传递flush动作给缓冲输出流。
*/
bos.flush();//冲
bos.close();
}
}
25.4 字符缓冲流
- 缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。
- 作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能
25.4.1 字符缓冲输入流:BufferedReader
作用:提高字符输入流读取数据的性能,除此之外多了按照行读取数据的功能。
构造器 | 说明 |
---|---|
public BufferedReader(Reader r) | 可以把低级的字符输入流包装成一个高级的缓冲字符输入流管道,从而提高字符输入流读数据的性能 |
方法 | 说明 |
---|---|
public String readLine() | 读取一行数据返回,如果读取没有完毕,无行可读返回null |
/**
目标:学会使用缓冲字符输入流提高字符输入流的性能,新增了按照行读取的方法(经典代码)
*/
public class BufferedReaderDemo1 {
public static void main(String[] args) {
try (
// 1、创建一个文件字符输入流与源文件接通。
Reader fr = new FileReader("data01.txt");
// a、把低级的字符输入流包装成高级的缓冲字符输入流。
BufferedReader br = new BufferedReader(fr);
){
// 2、用循环,每次读取一个字符数组的数据。 1024 + 1024 + 8
// char[] buffer = new char[1024]; // 1K字符
// int len;
// while ((len = br.read(buffer)) != -1) {
// String rs = new String(buffer, 0, len);
// System.out.print(rs);
// }
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
25.4.2 字符缓冲输出流:BufferedWriter
作用:提高字符输出流写取数据的性能,除此之外多了换行功能
构造器 | 说明 |
---|---|
public BufferedWriter(Writer w) | 可以把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能 |
方法 | 说明 |
---|---|
public void newLine() | 换行操作 |
/**
目标:缓冲字符输出流的使用,学会它多出来的一个功能:newLine();
*/
public class BufferedWriterDemo2 {
public static void main(String[] args) throws Exception {
// 1、创建一个字符输出流管道与目标文件接通
Writer fw = new FileWriter("out02.txt"); // 覆盖管道,每次启动都会清空文件之前的数据
//Writer fw = new FileWriter("io-app2/src/out02.txt", true); // 追加数据
BufferedWriter bw = new BufferedWriter(fw);
// a.public void write(int c):写一个字符出去
bw.write(98);
bw.write('a');
bw.write('徐'); // 不会出问题了
bw.newLine(); // bw.write("\r\n"); // 换行
// b.public void write(String c)写一个字符串出去
bw.write("abc我是中国人");
bw.newLine(); // bw.write("\r\n"); // 换行
// c.public void write(char[] buffer):写一个字符数组出去
char[] chars = "abc我是中国人".toCharArray();
bw.write(chars);
bw.newLine(); // bw.write("\r\n"); // 换行
// d.public void write(String c ,int pos ,int len):写字符串的一部分出去
bw.write("abc我是中国人", 0, 5);
bw.newLine(); // bw.write("\r\n"); // 换行
// e.public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
bw.write(chars, 3, 5);
bw.newLine(); // bw.write("\r\n"); // 换行
// fw.flush();// 刷新后流可以继续使用
bw.close(); // 关闭包含刷线,关闭后流不能使用
}
}
25.5 转换流
字符输入转换流InputStreamReader作用:
- 可以解决字符流读取不同编码乱码的问题
- public InputStreamReader(InputStream is,String
charset):可以指定编码把原始字节流转换成字符流,如此字符流中的字符不乱码。
25.5.1 字符输入转换流:InputStreamReader
作用:可以把原始的字节流按照指定编码转换成字符输入流。
构造器 | 说明 |
---|---|
public InputStreamReader(InputStream is) | 可以把原始的字节流按照代码默认编码转换成字符输入流。几乎不用,与默认的FileReader一样。 |
public InputStreamReader(InputStream is ,String charset) | 可以把原始的字节流按照指定编码转换成字符输入流,这样字符流中的字符就不乱码了(重点) |
/**
目标:字符输入转换流InputStreamReader的使用。
*/
public class InputStreamReaderDemo01 {
public static void main(String[] args) throws Exception {
// 代码UTF-8 文件 GBK "D:\\resources\\data.txt"
// 1、提取GBK文件的原始字节流。 abc 我
// ooo oo
InputStream is = new FileInputStream("D:\\resources\\data.txt");
// 2、把原始字节流转换成字符输入流
// Reader isr = new InputStreamReader(is); // 默认以UTF-8的方式转换成字符流。 还是会乱码的 跟直接使用FileReader是一样的
Reader isr = new InputStreamReader(is , "GBK"); // 以指定的GBK编码转换成字符输入流 完美的解决了乱码问题
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
}
}
25.5.1 字符输出转换流:OutputStreamWriter
作用:可以把字节输出流按照指定编码转换成字符输出流。
构造器 | 说明 |
---|---|
public OutputStreamWriter(OutputStream os) | 可以把原始的字节输出流按照代码默认编码转换成字符输出流。几乎不用。 |
public OutputStreamWriter(OutputStream os,String charset) | 可以把原始的字节输出流按照指定编码转换成字符输出流(重点) |
/**
目标:字符输出转换OutputStreamWriter流的使用。
*/
public class OutputStreamWriterDemo02 {
public static void main(String[] args) throws Exception {
// 1、定义一个字节输出流
OutputStream os = new FileOutputStream("io-app2/src/out03.txt");
// 2、把原始的字节输出流转换成字符输出流
// Writer osw = new OutputStreamWriter(os); // 以默认的UTF-8写字符出去 跟直接写FileWriter一样
Writer osw = new OutputStreamWriter(os , "GBK"); // 指定GBK的方式写字符出去
// 3、把低级的字符输出流包装成高级的缓冲字符输出流。
BufferedWriter bw = new BufferedWriter(osw);
bw.write("我爱中国1~~");
bw.write("我爱中国2~~");
bw.write("我爱中国3~~");
bw.close();
}
}
25.6 对象流
25.6.1 对象字节输出流:ObjectOutputStream
作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。
使用到的流是对象字节输出流:ObjectOutputStream
构造器 | 说明 |
---|---|
public ObjectOutputStream(OutputStream out) | 把低级字节输出流包装成高级的对象字节输出流 |
ObjectOutputStream序列化方法
方法名称 | 说明 |
---|---|
public final void writeObject(Object obj) | 把对象写出去到对象序列化流的文件中去 |
/*
目标:学会对象序列化,使用 ObjectOutputStream 把内存中的对象存入到磁盘文件中。
*/
public class ObjectOutputStreamDemo1 {
public static void main(String[] args) throws Exception {
// 1、创建学生对象
Student s = new Student("陈磊", "chenlei","1314520", 21);
// 2、对象序列化:使用对象字节输出流包装字节输出流管道
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io-app2/src/obj.txt"));
// 3、直接调用序列化方法
oos.writeObject(s);
// 4、释放资源
oos.close();
System.out.println("序列化完成了~~");
}
}
/**
对象如果要序列化,必须实现Serializable序列化接口。
*/
public class Student implements Serializable {
// 申明序列化的版本号码
// 序列化的版本号与反序列化的版本号必须一致才不会出错!
private static final long serialVersionUID = 1;
private String name;
private String loginName;
// transient修饰的成员变量不参与序列化了
private transient String passWord;
private int age ;
//省略get和set方法、toString和构造方法。
}
25.6.2 对象字节输入流:ObjectInputStream
作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化。
使用到的流是对象字节输入流:ObjectOutputStream
构造器 | 说明 |
---|---|
public ObjectInputStream(InputStream out) | 把低级字节输如流包装成高级的对象字节输入流 |
ObjectInputStream序列化方法
方法名称 | 说明 |
---|---|
public Object readObject() | 把存储到磁盘文件中去的对象数据恢复成内存中的对象返回 |
/**
目标:学会进行对象反序列化:使用对象字节输入流把文件中的对象数据恢复成内存中的Java对象。
*/
public class ObjectInputStreamDemo2 {
public static void main(String[] args) throws Exception {
// 1、创建对象字节输入流管道包装低级的字节输入流管道
ObjectInputStream is = new ObjectInputStream(new FileInputStream("io-app2/src/obj.txt"));
// 2、调用对象字节输入流的反序列化方法
Student s = (Student) is.readObject();
System.out.println(s);
}
}
25.7 打印流
作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指:PrintStream,PrintWriter两个类。
可以实现打印什么数据就是什么数据,例如打印整数97写出去就是97,打印boolean的true,写出去就是true。
1. PrintStream
构造器 | 说明 |
---|---|
public PrintWriter(OutputStream os) | 打印流直接通向字节输出流管道 |
public PrintStream(File f) | 打印流直接通向文件对象 |
public PrintStream(String filepath) | 打印流直接通向文件路径 |
方法 | 说明 |
---|---|
public void print(Xxx xx) | 打印任意类型的数据出去 |
2. PrintWriter
构造器 | 说明 |
---|---|
public PrintStream(OutputStream os) | 打印流直接通向字节输出流管道 |
public PrintWriter (Writer w) | 打印流直接通向字符输出流管道 |
public PrintWriter (File f) | 打印流直接通向文件对象 |
public PrintWriter (String filepath) | 打印流直接通向文件路径 |
方法 | 说明 |
---|---|
public void print(Xxx xx) | 打印任意类型的数据出去 |
PrintStream和PrintWriter的区别:
- 打印数据功能上是一模一样的,都是使用方便,性能高效(核心优势)
- PrintStream继承自字节输出流OutputStream,支持写字节数据的方法。
- PrintWriter继承自字符输出流Writer,支持写字符数据出去。
/**
目标:学会使用打印流 高效 方便写数据到文件。
*/
public class PrintDemo1 {
public static void main(String[] args) throws Exception {
// 1、创建一个打印流对象
// PrintStream ps = new PrintStream(new FileOutputStream("io-app2/src/ps.txt"));
// PrintStream ps = new PrintStream(new FileOutputStream("io-app2/src/ps.txt" , true)); // 追加数据,在低级管道后面加True
// PrintStream ps = new PrintStream("io-app2/src/ps.txt" );
PrintWriter ps = new PrintWriter("io-app2/src/ps.txt"); // 打印功能上与PrintStream的使用没有区别
ps.println(97);
ps.println('a');
ps.println(23.3);
ps.println(true);
ps.println("我是打印流输出的,我是啥就打印啥");
ps.close();
}
}
25 多线程
- 线程(thread)是一个程序内部的一条执行路径。
- 我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。
- 程序中如果只有一条执行路径,那么这个程序就是单线程的程序。
- 多线程是指从软硬件上实现多条执行流程的技术。
25.1 多线程的创建
Thread的构造器
构造器 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target ,String name ) | 封装Runnable对象成为线程对象,并指定线程名称 |
1. 继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动后还是执行run方法的)
优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于扩展。
注意:
- 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
- 只有调用start方法才是启动一个新的线程执行。
/**
目标:多线程的创建方式一:继承Thread类实现。
*/
public class ThreadDemo1 {
public static void main(String[] args) {
// 3、new一个新线程对象
Thread t = new MyThread();
// 4、调用start方法启动线程(执行的还是run方法)
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
/**
1、定义一个线程类继承Thread类
*/
class MyThread extends Thread{
/**
2、重写run方法,里面是定义线程以后要干啥
*/
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行输出:" + i);
}
}
}
2. 实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
- 创建MyRunnable任务对象
- 把MyRunnable任务对象交给Thread处理。
- 调用线程对象的start()方法启动线程
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。
/**
目标:学会线程的创建方式二,理解它的优缺点。
*/
public class ThreadDemo2 {
public static void main(String[] args) {
// 3、创建一个任务对象
Runnable target = new MyRunnable();
// 4、把任务对象交给Thread处理
Thread t = new Thread(target);
// Thread t = new Thread(target, "1号");
// 5、启动线程
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
/**
1、定义一个线程任务类 实现Runnable接口
*/
class MyRunnable implements Runnable {
/**
2、重写run方法,定义线程的执行任务的
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程执行输出:" + i);
}
}
}
3. JDK 5.0新增:实现Callable接口
前2种线程创建方式都存在一个问题:
- 他们重写的run方法均不能直接返回结果。
- 不适合需要返回线程执行结果的业务场景。
怎么解决这个问题呢?
- JDK 5.0提供了Callable和FutureTask来实现。
- 这种方式的优点是:可以得到线程执行的结果。
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。
利用Callable、FutureTask接口实现。
/**
目标:学会线程的创建方式三:实现Callable接口,结合FutureTask完成。
*/
public class ThreadDemo3 {
public static void main(String[] args) {
// 3、创建Callable任务对象
Callable<String> call = new MyCallable(100);
// 4、把Callable任务对象 交给 FutureTask 对象
// FutureTask对象的作用1: 是Runnable的对象(实现了Runnable接口),可以交给Thread了
// FutureTask对象的作用2: 可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果
FutureTask<String> f1 = new FutureTask<>(call);
// 5、交给线程处理
Thread t1 = new Thread(f1);
// 6、启动线程
t1.start();
Callable<String> call2 = new MyCallable(200);
FutureTask<String> f2 = new FutureTask<>(call2);
Thread t2 = new Thread(f2);
t2.start();
try {
// 如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果。
String rs1 = f1.get();
System.out.println("第一个结果:" + rs1);
} catch (Exception e) {
e.printStackTrace();
}
try {
// 如果f2任务没有执行完毕,这里的代码会等待,直到线程2跑完才提取结果。
String rs2 = f2.get();
System.out.println("第二个结果:" + rs2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
1、定义一个任务类 实现Callable接口 应该申明线程任务执行完毕后的结果的数据类型
*/
class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n) {
this.n = n;
}
/**
2、重写call方法(任务方法)
*/
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n ; i++) {
sum += i;
}
return "子线程执行的结果是:" + sum;
}
}
4. 3三种方式的区别
方法 | 优点 | 缺点 |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 扩展性较差,不能再继承其他的类,不能返回线程执行的结果 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。 | 编程相对复杂,不能返回线程执行的结果 |
实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果 | 编程相对复杂 |
5. 匿名内部类形式的线程创建
/**
* 使用匿名内部类完成线程的两种创建
*/
public class ThreadDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
for(int i=0;i<1000;i++){
System.out.println("你是谁啊?");
}
}
};
// Runnable r2 = new Runnable() {
// public void run() {
// for(int i=0;i<1000;i++){
// System.out.println("我是查水表的!");
// }
// }
// };
//Runnable可以使用lambda表达式创建
Runnable r2 = ()->{
for(int i=0;i<1000;i++){
System.out.println("我是查水表的!");
}
};
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
25.2 线程同步
25.2.1 同步代码块
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
作用:把出现线程安全问题的核心代码给上锁。
锁对象的规范要求
- 规范上:建议使用共享资源作为锁对象。
- 对于实例方法建议使用this作为锁对象。
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象。
格式:
synchronized(同步锁对象) {
操作共享资源的代码(核心代码)
}
/**
* 同步块
* 同步块可以更准确的锁定需要同步执行的代码片段,有效的缩小排队范围可以在保证安全的前提下
* 尽可能的提高并发效率。
* 语法:
* synchronized(同步监视器对象){
* 需要多个线程同步执行的代码片段
* }
*
* 同步执行:多个线程执行时有先后顺序
*/
public class SyncDemo2 {
public static void main(String[] args) {
Shop shop = new Shop();
Thread t1 = new Thread(){
public void run(){
shop.buy();
}
};
Thread t2 = new Thread(){
public void run(){
shop.buy();
}
};
t1.start();
t2.start();
}
}
class Shop {
/*
在方法上使用synchronized时,同步监视器对象就是当前方法的所属对象,即:this
*/
// public synchronized void buy(){
public void buy() {
try {
Thread t = Thread.currentThread();//获取运行buy方法的线程
System.out.println(t.getName()+":"+"正在挑衣服...");
Thread.sleep(5000);
/*
同步块使用时需要指定一个同步监视器对象,即:上锁的对象
该对象从语法的角度来讲可以是任意引用类型的实例,但是必须同时满足多个需要同步(排队)
执行该代码片段的线程看到的是同一个对象才行!
*/
// synchronized (new Object()) {//无效的
synchronized (this) {
System.out.println(t.getName() + ":正在试衣服...");
Thread.sleep(5000);
}
System.out.println(t.getName()+":结账离开");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
}
}
25.2.2 同步方法
作用:把出现线程安全问题的核心方法给上锁。
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
格式:
修饰符 synchronized 返回值类型 方法名称(形参列表) {
操作共享资源的代码
}
package thread;
/**
* 静态方法上如果使用synchronized,那么该方法一定是同步的。
*
* 静态方法上指定的锁对象为当前类的类对象,即Class类的实例。
* 在JVM中每个被加载的类都有且只有一个Class的实例与之对应,它称为一个类的类对象。
*
*/
public class SyncDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
Boo.dosome();
}
};
Thread t2 = new Thread(){
public void run(){
Boo.dosome();
}
};
t1.start();
t2.start();
}
}
class Boo{
public synchronized static void dosome(){
// public static void dosome(){
//静态方法中使用同步块时,也可以指定类对象,方式为:类名.class
try {
Thread t = Thread.currentThread();
System.out.println(t.getName() + ":正在执行dosome方法...");
Thread.sleep(5000);
System.out.println(t.getName() + ":执行dosome方法完毕!");
} catch (InterruptedException e) {
}
}
}
25.2.3 Lock锁
- 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。
- Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
- Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。
方法名称 | 说明 |
---|---|
public ReentrantLock() | 获得Lock锁的实现类对象 |
Lock的API:
方法名称 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
public class sellticks implements Runnable{
private static int tickets=100;
private Lock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
if(tickets>0) {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
}
public class sellsticks_test {
public static void main(String[] args) {
sellticks st = new sellticks();
Thread th1 = new Thread(st,"窗口一");
Thread th2 = new Thread(st,"窗口二");
Thread th3 = new Thread(st,"窗口三");
th1.start();
th2.start();
th3.start();
}
}
25.3 有关方法
方法名称 | 说明 |
---|---|
string getName() | 获取当前线程的名称,默认线程名称是Thread-索引 |
void setName(String name) | 设量线程名称 |
public static Thread curretThread(): | 返回对当前正在执行的线程对象的引用 |
public static void sleep( long time) | 让线程休眠指定的时间,单位为毫秒。 |
public void run ( ) | 线程任务方法 |
public void start() | 线程启动方法 |
public static void sleep(long time) | 让当前线程休眠指定的时间后再继续执行,单位为毫秒 |
/**
* 获取线程相关信息的一组方法
*/
public class ThreadInfoDemo {
public static void main(String[] args) {
Thread main = Thread.currentThread();//获取主线程
String name = main.getName();//获取线程的名字
System.out.println("名字:"+name);
long id = main.getId();//获取该线程的唯一标识
System.out.println("id:"+id);
int priority = main.getPriority();//获取该线程的优先级
System.out.println("优先级:"+priority);
boolean isAlive = main.isAlive();//该线程是否活着
System.out.println("是否活着:"+isAlive);
boolean isDaemon = main.isDaemon();//是否为守护线程
System.out.println("是否为守护线程:"+isDaemon);
boolean isInterrupted = main.isInterrupted();//是否被中断了
System.out.println("是否被中断了:"+isInterrupted);
}
}
sleep阻塞
public class SleepDemo {
public static void main(String[] args) {
System.out.println("程序开始了!");
try {
Thread.sleep(5000);//主线程阻塞5秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序结束了!");
}
}
25.4 多线程并发安全问题
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪.
临界资源:操作该资源的全过程同时只能被单个线程完成。
/**
* 多线程并发安全问题
* 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现
* 混乱,严重时可能导致系统瘫痪。
* 临界资源:同时只能被单一线程访问操作过程的资源。
*/
public class SyncDemo {
public static void main(String[] args) {
Table table = new Table();
Thread t1 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
int bean = table.getBean();
/*
static void yield()
线程提供的这个静态方法作用是让执行该方法的线程
主动放弃本次时间片。
这里使用它的目的是模拟执行到这里CPU没有时间了,发生
线程切换,来看并发安全问题的产生。
*/
Thread.yield();
System.out.println(getName()+":"+bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int beans = 20;//桌子上有20个豆子
public int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了!");
}
Thread.yield();
return beans--;
}
}
25.5 守护线程
守护线程也称为:后台线程
- 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异.
- 守护线程的结束时机上有一点与普通线程不同,即:进程的结束.
- 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线程.
/**
* 守护线程
* 守护线程是通过普通线程调用setDaemon(true)设置而转变的。因此守护线程创建上
* 与普通线程无异。
* 但是结束时机上有一点不同:进程结束。
* 当一个java进程中的所有普通线程都结束时,该进程就会结束,此时会强制杀死所有正在
* 运行的守护线程。
*/
public class DaemonThreadDemo {
public static void main(String[] args) {
Thread rose = new Thread(){
public void run(){
for(int i=0;i<5;i++){
System.out.println("rose:let me go!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("rose:啊啊啊啊啊啊AAAAAAAaaaaa....");
System.out.println("噗通");
}
};
Thread jack = new Thread(){
public void run(){
while(true){
System.out.println("jack:you jump!i jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
};
rose.start();
jack.setDaemon(true);//设置守护线程必须在线程启动前进行
jack.start();
}
}
注意:
通常当我们不关心某个线程的任务什么时候停下来,它可以一直运行,但是程序主要的工作都结束时它应当跟着结束时,这样的任务就适合放在守护线程上执行.比如GC就是在守护线程上运行的.
25.6 互斥锁
当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的.
使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的。
/**
* 互斥锁
* 当使用synchronized锁定多个不同的代码片段,并且指定的同步监视器对象相同时,
* 这些代码片段之间就是互斥的,即:多个线程不能同时访问这些方法。
*/
public class SyncDemo4 {
public static void main(String[] args) {
Foo foo = new Foo();
Thread t1 = new Thread(){
public void run(){
foo.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
foo.methodB();
}
};
t1.start();
t2.start();
}
}
class Foo{
public synchronized void methodA(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+":正在执行A方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行A方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodB(){
Thread t = Thread.currentThread();
try {
System.out.println(t.getName()+":正在执行B方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":执行B方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
25.7 死锁
死锁的产生:
两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。这个现象就是死锁。
/**
* 死锁
* 死锁的产生:
* 两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。
* 这个现象就是死锁。
*/
public class DeadLockDemo {
//定义两个锁对象,"筷子"和"勺"
public static Object chopsticks = new Object();
public static Object spoon = new Object();
public static void main(String[] args) {
Thread np = new Thread(){
public void run(){
System.out.println("北方人开始吃饭.");
System.out.println("北方人去拿筷子...");
synchronized (chopsticks){
System.out.println("北方人拿起了筷子开始吃饭...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("北方人吃完了饭,去拿勺...");
synchronized (spoon){
System.out.println("北方人拿起了勺子开始喝汤...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("北方人喝完了汤");
}
System.out.println("北方人放下了勺");
}
System.out.println("北方人放下了筷子,吃饭完毕!");
}
};
Thread sp = new Thread(){
public void run(){
System.out.println("南方人开始吃饭.");
System.out.println("南方人去拿勺...");
synchronized (spoon){
System.out.println("南方人拿起了勺开始喝汤...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("南方人喝完了汤,去拿筷子...");
synchronized (chopsticks){
System.out.println("南方人拿起了筷子开始吃饭...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("南方人吃完了饭");
}
System.out.println("南方人放下了筷子");
}
System.out.println("南方人放下了勺,吃饭完毕!");
}
};
np.start();
sp.start();
}
}
25.8 解决死锁
/**
* 解决死锁:
* 1:尽量避免在持有一个锁的同时去等待持有另一个锁(避免synchronized嵌套)
* 2:当无法避免synchronized嵌套时,就必须保证多个线程锁对象的持有顺序必须一致。
* 即:A线程在持有锁1的过程中去持有锁2时,B线程也要以这样的持有顺序进行。
*/
public class DeadLockDemo2 {
//筷子
private static Object chopsticks = new Object();
//勺
private static Object spoon = new Object();
public static void main(String[] args) {
//北方人
Thread np = new Thread(){
public void run(){
try {
System.out.println("北方人:开始吃饭");
System.out.println("北方人去拿筷子...");
synchronized (chopsticks) {
System.out.println("北方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);
}
System.out.println("北方人吃完了饭,放下了筷子");
System.out.println("北方人去拿勺子...");
synchronized (spoon){
System.out.println("北方人拿起了勺子,开始喝汤...");
Thread.sleep(5000);
}
System.out.println("北方人喝完了汤,北方人放下了勺子");
System.out.println("吃饭完毕。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//南方人
Thread sp = new Thread(){
public void run(){
try {
System.out.println("南方人:开始吃饭");
System.out.println("南方人去拿勺...");
synchronized (spoon) {
System.out.println("南方人拿起了勺,开始喝汤...");
Thread.sleep(5000);
}
System.out.println("南方人喝完了汤,放下勺子...");
System.out.println("南方人去拿筷子...");
synchronized (chopsticks){
System.out.println("南方人拿起了筷子,开始吃饭...");
Thread.sleep(5000);
}
System.out.println("南方人吃完了饭,南方人放下了筷子");
System.out.println("吃饭完毕。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
np.start();
sp.start();
}
}
26 网络编程
26.1 Socket
构造器 | 说明 |
---|---|
public Socket(String host , int port) | 创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口。 |
Socket构造方法
方法 | 说明 |
---|---|
OutputStream getOutputStream() | 获得字节输出流对象 |
InputStream getInputStream() | 获得字节输入流对象 |
26.2 实现客户端
客户端实现步骤
- 创建客户端的Socket对象,请求与服务端的连接。
- 使用socket对象调用getOutputStream()方法得到字节输出流。
- 使用字节输出流完成数据的发送。
- 释放资源:关闭socket管道。
/**
目标:完成Socket网络编程入门案例的客户端开发,实现1发1收。
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
// 4、发送消息
ps.println("我是TCP的客户端,我已经与你对接,并发出邀请:约吗?");
ps.flush();
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
26.3 实现服务端
ServerSocket(服务端)
构造器 | 说明 |
---|---|
public ServerSocket(int port) | 注册服务端端口 |
方法 | 说明 |
---|---|
public Socket accept() | 等待接收客户端的Socket通信连接,连接成功返回Socket对象与客户端建立端到端通信 |
/**
目标:开发Socket网络编程入门代码的服务端,实现接收消息
*/
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
if ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
26.4 实现多发多收消息
客户端:
/**
目标:实现多发和多收
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端:
/**
目标:开发Socket网络编程入门代码的服务端,实现接收消息
*/
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
while (true) {
// 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
26.5 同时接受多个客户端消息
客户端:
/**
目标:实现服务端可以同时处理多个客户端的消息。
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端:
/**
目标:实现服务端可以同时处理多个客户端的消息。
*/
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 3、开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
ServerReaderThread类:
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
26.6 使用线程池优化
线程池的优势在哪里?
- 服务端可以复用线程处理多个客户端,可以避免系统瘫痪。
- 适合客户端通信时长较短的场景。
客户端:
/**
拓展:使用线程池优化:实现通信。
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 6666);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端:
/**
目标:使用线程池优化:实现通信。
*/
public class ServerDemo2 {
// 使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(300,
1500, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2)
, Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(6666);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 任务对象负责读取消息。
Runnable target = new ServerReaderRunnable(socket);
pool.execute(target);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
ServerReaderRunnable类:
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
26.7 即时通信
- 即时通信,是指一个客户端的消息发出去,其他客户端可以接收到
- 即时通信需要进行端口转发的设计思想。
- 服务端需要把在线的Socket管道存储起来
- 一旦收到一个消息要推送给其他管道
客户端:
/**
拓展:即时通信
客户端:发消息的同时,随时有人发消息过来。
服务端:接收消息后,推送给其他所有的在线socket
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 6868);
// 马上为客户端分配一个独立的线程负责读取它收到的消息
new ClientReaderThread(socket).start();
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端:
/**
目标: 即时通信
*/
public class ServerDemo2 {
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(6868);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 把当前客户端管道Socket加入到在线集合中去
onLineSockets.add(socket);
// 3、开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
ClientReaderThread类:
public class ClientReaderThread extends Thread{
private Socket socket;
public ClientReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "收到了: " + msg);
}
} catch (Exception e) {
System.out.println("服务端把你踢出去了~~");
}
}
}
ServerReaderThread类:
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
// 把这个消息发给当前所有在线socket
sendMsgToAll(msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
// 从在线集合中抹掉本客户端socket
ServerDemo2.onLineSockets.remove(socket);
}
}
private void sendMsgToAll(String msg) {
try {
// 遍历全部的在线 socket给他们发消息
for (Socket onLineSocket : ServerDemo2.onLineSockets) {
// 除了自己的socket,其他socket我都发!!
if(onLineSocket != socket){
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println(msg);
ps.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
27 集合
27.1 集合概述
与数组的区别:
大小:
- 数组定义后类型确定,长度固定
- 集合类型可以不固定,大小是可变的。
类型:
- 数组可以存储基本类型和引用类型的数据。
- 集合只能存储引用数据类型的数据。
场景:
- 数组适合做数据个数和类型确定的场景。
- 集合适合做数据个数不确定,且要做增删元素的场景。
27.2 集合体系特点
紫色为接口,蓝色实现类
Collection集合特点:
List系列集合:添加的元素是有序、可重复、有索引。
- ArrayList、LinekdList :有序、可重复、有索引。
Set系列集合:添加的元素是无序、不重复、无索引。
- HashSet: 无序、不重复、无索引;LinkedHashSet: 有序、不重复、无索引。
- TreeSet:按照大小默认升序排序、不重复、无索引。
/**
目标:明确Collection集合体系的特点
*/
public class CollectionDemo1 {
public static void main(String[] args) {
// 有序 可重复 有索引
Collection list = new ArrayList();
list.add("Java");
list.add("Java");
list.add("Mybatis");
list.add(23);
list.add(23);
list.add(false);
list.add(false);
System.out.println(list);
// 无序 不重复 无索引
Collection list1 = new HashSet();
list1.add("Java");
list1.add("Java");
list1.add("Mybatis");
list1.add(23);
list1.add(23);
list1.add(false);
list1.add(false);
System.out.println(list1);
System.out.println("-----------------------------");
// Collection<String> list2 = new ArrayList<String>();
Collection<String> list2 = new ArrayList<>(); // JDK 7开始之后后面类型申明可以不写
list2.add("Java");
// list2.add(23);
list2.add("唐三");
// 集合和泛型不支持基本数据类型,只能支持引用数据类型
// Collection<int> list3 = new ArrayList<>();
Collection<Integer> list3 = new ArrayList<>();
list3.add(23);
list3.add(233);
list3.add(2333);
Collection<Double> list4 = new ArrayList<>();
list4.add(23.4);
list4.add(233.0);
list4.add(233.3);
}
}
27.3 Collection的常用方法
方法名称 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
booolean addAll(Collection c) | 将给定集合中所有元素添加到当前集合中,添加后当前集合发生了改变则返回true |
boolean containsAll(Collection c) | 判断当前集合是否包含给定集合中所有元素,全部包含则返回true |
/**
目标:Collection集合的常用API.
Collection是集合的祖宗类,它的功能是全部集合都可以继承使用的,所以要学习它。
Collection API如下:
- public boolean add(E e): 把给定的对象添加到当前集合中 。
- public void clear() :清空集合中所有的元素。
- public boolean remove(E e): 把给定的对象在当前集合中删除。
- public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。
- public boolean isEmpty(): 判断当前集合是否为空。
- public int size(): 返回集合中元素的个数。
- public Object[] toArray(): 把集合中的元素,存储到数组中。
小结:
记住以上API。
*/
public class CollectionDemo {
public static void main(String[] args) {
// HashSet:添加的元素是无序,不重复,无索引。
Collection<String> c = new ArrayList<>();
// 1.添加元素, 添加成功返回true。
c.add("Java");
c.add("HTML");
System.out.println(c.add("HTML"));
c.add("MySQL");
c.add("Java");
System.out.println(c.add("唐三"));
System.out.println(c); // [Java, HTML, HTML, MySQL, Java, 唐三]
// 2.清空集合的元素。
// c.clear();
// System.out.println(c);
// 3.判断集合是否为空 是空返回true,反之。
// System.out.println(c.isEmpty());
// 4.获取集合的大小。
System.out.println(c.size());
// 5.判断集合中是否包含某个元素。
System.out.println(c.contains("Java")); // true
System.out.println(c.contains("java")); // false
System.out.println(c.contains("唐三")); // true
// 6.删除某个元素:如果有多个重复元素默认删除前面的第一个!
System.out.println(c.remove("java")); // false
System.out.println(c);
System.out.println(c.remove("Java")); // true
System.out.println(c);
// 7.把集合转换成数组 [HTML, HTML, MySQL, Java, 唐三]
Object[] arrs = c.toArray();
System.out.println("数组1:" + Arrays.toString(arrs));
String[] array = c.toArray(new String[c.size()]);
System.out.println("数组2:"+Arrays.toString(array));
//8.数组转换为集合
System.out.println("array: "+Arrays.toString(array));
List<String> list = Arrays.asList(array);
System.out.println("list: "+list);
//9.containsAll(Collection c)判断当前集合是否包含给定集合中的所有元素,全部包含则返回true。
Collection c3 = new ArrayList();
c3.add("唐三");
c3.add("Java");
boolean contains = c.containsAll(c3);
System.out.println("包含所有:"+contains);
System.out.println("c:"+c);
System.out.println("c3:"+c3);
System.out.println("----------------------");
Collection<String> c1 = new ArrayList<>();
c1.add("java1");
c1.add("java2");
Collection<String> c2 = new ArrayList<>();
c2.add("赵敏");
c2.add("殷素素");
// addAll把c2集合的元素全部倒入到c1中去。
c1.addAll(c2);
System.out.println(c1);
System.out.println(c2);
}
}
27.4 集合的遍历方式
27.4.1 方式一:迭代器
Collection集合获取迭代器:
方法名称 | 说明 |
---|---|
Iterator iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引 |
Iterator中的常用方法:
方法名称 | 说明 |
---|---|
boolean hasNext() | 询问当前位置是否有元素存在,存在返回true ,不存在返回false |
E next() | 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界 |
/**
目标:Collection集合的遍历方式。
什么是遍历? 为什么开发中要遍历?
遍历就是一个一个的把容器中的元素访问一遍。
开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。
Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。
Collection集合的遍历方式有三种:
(1)迭代器。
(2)foreach(增强for循环)。
(3)JDK 1.8开始之后的新技术Lambda表达式(了解)
a.迭代器遍历集合。
-- 方法:
public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的
boolean hasNext():判断是否有下一个元素,有返回true ,反之。
E next():获取下一个元素值!
--流程:
1.先获取当前集合的迭代器
Iterator<String> it = lists.iterator();
2.定义一个while循环,问一次取一次。
通过it.hasNext()询问是否有下一个元素,有就通过
it.next()取出下一个元素。
小结:
记住代码。
*/
public class CollectionDemo01 {
public static void main(String[] args) {
ArrayList<String> lists = new ArrayList<>();
lists.add("萧炎");
lists.add("美杜莎");
lists.add("小医仙");
lists.add("药尘");
lists.add("萧薰儿");
lists.add("云韵");
System.out.println(lists);
// 1、得到当前集合的迭代器对象。
Iterator<String> it = lists.iterator();
// String ele = it.next();
// System.out.println(ele);
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next()); // NoSuchElementException 出现无此元素异常的错误
// 2、定义while循环
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
System.out.println("-----------------------------");
}
}
27.4.2 方式二:foreach/增强for循环
/**
目标:Collection集合的遍历方式。
什么是遍历? 为什么开发中要遍历?
遍历就是一个一个的把容器中的元素访问一遍。
开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。
Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。
Collection集合的遍历方式有三种:
(1)迭代器。
(2)foreach(增强for循环)。
(3)JDK 1.8开始之后的新技术Lambda表达式。
b.foreach(增强for循环)遍历集合。
foreach是一种遍历形式,可以遍历集合或者数组。
foreach遍历集合实际上是迭代器遍历集合的简化写法。
foreach遍历的关键是记住格式:
for(被遍历集合或者数组中元素的类型 变量名称 : 被遍历集合或者数组){
}
*/
public class CollectionDemo02 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小昭");
lists.add("殷素素");
lists.add("周芷若");
System.out.println(lists);
// [赵敏, 小昭, 殷素素, 周芷若]
// ele
for (String ele : lists) {
System.out.println(ele);
}
System.out.println("------------------");
double[] scores = {100, 99.5 , 59.5};
for (double score : scores) {
System.out.println(score);
// if(score == 59.5){
// score = 100.0; // 修改无意义,不会影响数组的元素值。
// }
}
System.out.println(Arrays.toString(scores));
}
}
27.4.3 方式三:lambda表达式
/**
目标:Collection集合的遍历方式。
什么是遍历? 为什么开发中要遍历?
遍历就是一个一个的把容器中的元素访问一遍。
开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。
Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。
Collection集合的遍历方式有三种:
(1)迭代器。
(2)foreach(增强for循环)。
(3)JDK 1.8开始之后的新技术Lambda表达式。
c.JDK 1.8开始之后的新技术Lambda表达式。
*/
public class CollectionDemo03 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小昭");
lists.add("殷素素");
lists.add("周芷若");
System.out.println(lists);
// [赵敏, 小昭, 殷素素, 周芷若]
// s
lists.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
// lists.forEach(s -> {
// System.out.println(s);
// });
// lists.forEach(s -> System.out.println(s) );
lists.forEach(System.out::println );
}
}
27.5 集合存储自定义类型的对象
注意 :集合中存储的是元素对象的地址。
public class TestDemo {
public static void main(String[] args) {
// 1、定义一个电影类
// 2、定义一个集合对象存储3部电影对象
Collection<Movie> movies = new ArrayList<>();
movies.add(new Movie("《你好,李焕英》", 9.5, "张小斐,贾玲,沈腾,陈赫"));
movies.add(new Movie("《唐人街探案》", 8.5, "王宝强,刘昊然,美女"));
movies.add(new Movie("《刺杀小说家》",8.6, "雷佳音,杨幂"));
System.out.println(movies);
// 3、遍历集合容器中的每个电影对象
for (Movie movie : movies) {
System.out.println("片名:" + movie.getName());
System.out.println("得分:" + movie.getScore());
System.out.println("主演:" + movie.getActor());
}
}
}
电影类:
/*
电影类
*/
public class Movie {
private String name;
private double score;
private String actor;
public Movie() {
}
public Movie(String name, double score, String actor) {
this.name = name;
this.score = score;
this.actor = actor;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
@Override
public String toString() {
return "Movie{" +
"name='" + name + '\'' +
", score=" + score +
", actor='" + actor + '\'' +
'}';
}
}
27.6 List集合
27.6.1 List集合特点、特有API
List系列集合特点
- ArrayList、LinekdList :有序,可重复,有索引。
- 有序:存储和取出的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
List集合特有方法
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
List subList(int start,int end) | 获取指定范围内的子集 |
/**
目标:ArrayList集合。
Collection集合体系的特点:
Set系列集合: 添加的元素,是无序,不重复,无索引的。
-- HashSet:添加的元素,是无序,不重复,无索引的。
-- LinkedHashSet:添加的元素,是有序,不重复,无索引的。
List系列集合:添加的元素,是有序,可重复,有索引的。
-- LinkedList: 添加的元素,是有序,可重复,有索引的。
-- ArrayList: 添加的元素,是有序,可重复,有索引的。
-- Vector 是线程安全的,速度慢,工作中很少使用。
1、List集合继承了Collection集合的全部功能,"同时因为List系列集合有索引",
2、因为List集合多了索引,所以多了很多按照索引操作元素的功能:
3、ArrayList实现类集合底层基于数组存储数据的,查询快,增删慢!
- public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
- public E get(int index):返回集合中指定位置的元素。
- public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
- public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回更新前的元素值。
小结:
ArrayList集合的底层是基于数组存储数据。查询快,增删慢!(相对的)
*/
public class ListDemo01 {
public static void main(String[] args) {
// 1.创建一个ArrayList集合对象:
// List:有序,可重复,有索引的。
ArrayList<String> list = new ArrayList<>(); // 一行经典代码!
list.add("Java");
list.add("Java");
list.add("HTML");
list.add("HTML");
list.add("MySQL");
list.add("MySQL");
// 2.在某个索引位置插入元素。
list.add(2, "唐三");
System.out.println(list);
// 3.根据索引删除元素,返回被删除元素
System.out.println(list.remove(1));
System.out.println(list);
// 4.根据索引获取元素:public E get(int index):返回集合中指定位置的元素。
System.out.println(list.get(1));
// 5.修改索引位置处的元素: public E set(int index, E element)
System.out.println(list.set(2, "小舞"));
System.out.println(list);
}
}
/**
* List subList(int start,int end)
* 获取指定范围内的子集
*/
public class ListDemo3 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
System.out.println(list);
List<Integer> integers = list.subList(3, 8);
System.out.println(integers);
//将子集每个元素扩大十倍
for (int i = 0; i < integers.size(); i++) {
integers.set(i, integers.get(i)*10);
}
System.out.println(integers);//对子集操作就是对原集合对应的操作
System.out.println(list);
//删除集合中的2-8这些元素
list.subList(2,9).clear();
System.out.println(list);
}
}
注意:对子集操作就是对原集合操作
27.6.2 List集合的遍历方式小结
List遍历方式:
- for循环。(独有的,因为List有索引)。
- 迭代器。
- foreach。
- JDK 1.8新技术。
/**
拓展:List系列集合的遍历方式有:4种。
List系列集合多了索引,所以多了一种按照索引遍历集合的for循环。
List遍历方式:
(1)for循环。(独有的,因为List有索引)。
(2)迭代器。
(3)foreach。
(4)JDK 1.8新技术。
*/
public class ListDemo02 {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
lists.add("唐三");
lists.add("小舞");
lists.add("小白");
/** (1)for循环。 */
System.out.println("-----------------------");
for (int i = 0; i < lists.size(); i++) {
String ele = lists.get(i);
System.out.println(ele);
}
/** (2)迭代器。 */
System.out.println("-----------------------");
Iterator<String> it = lists.iterator();
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
/** (3)foreach */
System.out.println("-----------------------");
for (String ele : lists) {
System.out.println(ele);
}
/** (4)JDK 1.8开始之后的Lambda表达式 */
System.out.println("-----------------------");
lists.forEach(s -> {
System.out.println(s);
});
}
}
27.6.4 LinkedList集合
LinkedList也是List的实现类:底层是基于双链表的,增删比较快,查询慢!!
LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的
所以LinkedList除了拥有List集合的全部功能还多了很多操作首尾元素的特殊功能:
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
/**
目标:LinkedList集合。
LinkedList也是List的实现类:底层是基于双链表的,增删比较快,查询慢!!
LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的
所以LinkedList除了拥有List集合的全部功能还多了很多操作首尾元素的特殊功能:
- public void addFirst(E e):将指定元素插入此列表的开头。
- public void addLast(E e):将指定元素添加到此列表的结尾。
- public E getFirst():返回此列表的第一个元素。
- public E getLast():返回此列表的最后一个元素。
- public E removeFirst():移除并返回此列表的第一个元素。
- public E removeLast():移除并返回此列表的最后一个元素。
- public E pop():从此列表所表示的堆栈处弹出一个元素。
- public void push(E e):将元素推入此列表所表示的堆栈。
小结:
LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的。
所以提供了很多操作首尾元素的特殊API可的实以做栈和队列现。
如果查询多而增删少用ArrayList集合。(用的最多的)
如果查询少而增删首尾较多用LinkedList集合。
*/
public class ListDemo03 {
public static void main(String[] args) {
// LinkedList可以完成队列结构,和栈结构 (双链表)
// 1、做一个队列:
LinkedList<String> queue = new LinkedList<>();
// 入队
queue.addLast("1号");
queue.addLast("2号");
queue.addLast("3号");
System.out.println(queue);
// 出队
// System.out.println(queue.getFirst());
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue);
// 2、做一个栈
LinkedList<String> stack = new LinkedList<>();
// 入栈 压栈 (push)
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack);
// 出栈 弹栈 pop
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack);
}
}
27.6.5 集合的并发修改异常问题
/**
目标:研究集合遍历并删除元素可能出现的:并发修改异常问题。
*/
public class Test {
public static void main(String[] args) {
// 1、准备数据
ArrayList<String> list = new ArrayList<>();
list.add("唐三");
list.add("小舞");
list.add("戴沐白");
list.add("马红俊");
list.add("宁荣荣");
list.add("朱竹青");
list.add("奥斯卡");
System.out.println(list);
// it
// 需求:删除全部的Java信息。
// a、迭代器遍历删除
Iterator<String> it = list.iterator();
// while (it.hasNext()){
// String ele = it.next();
// if("Java".equals(ele)){
// // 删除Java
// // list.remove(ele); // 集合删除会出毛病
// it.remove(); // 删除迭代器所在位置的元素值(没毛病)
// }
// }
// System.out.println(list);
// b、foreach遍历删除 (会出现问题,这种无法解决的,foreach不能边遍历边删除,会出bug)
// for (String s : list) {
// if("Java".equals(s)){
// list.remove(s);
// }
// }
// c、lambda表达式(会出现问题,这种无法解决的,Lambda遍历不能边遍历边删除,会出bug)
// list.forEach(s -> {
// if("Java".equals(s)){
// list.remove(s);
// }
// });
// d、for循环(边遍历边删除集合没毛病,但是必须从后面开始遍历删除才不会出现漏掉应该删除的元素)
for (int i = list.size() - 1; i >= 0 ; i--) {
String ele = list.get(i);
if("Java".equals(ele)){
list.remove(ele);
}
}
System.out.println(list);
}
}
27.7 泛型
1. 泛型概述
- 泛型就是一个标签:<数据类型>
- 泛型可以在编译阶段约束只能操作某种数据类型。
注意:JDK 1.7开始之后后面的泛型申明可以省略不写
小结:
泛型就是一个标签。
泛型可以在编译阶段约束只能操作某种数据类型。
泛型只能支持引用数据类型。
/**
目标:泛型的概述。
什么是泛型?
泛型就是一个标签:<数据类型>
泛型可以在编译阶段约束只能操作某种数据类型。
注意:
JDK 1.7开始之后后面的泛型申明可以省略不写
小结:
泛型就是一个标签。
泛型可以在编译阶段约束只能操作某种数据类型。
泛型只能支持引用数据类型。
*/
public class GenericityDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Java2");
// list.add(23);
List<String> list1 = new ArrayList();
list1.add("Java");
// list1.add(23.3);
// list1.add(false);
list1.add("Spring");
// for (Object o : list1) {
// String ele = (String) o;
// System.out.println(ele);
// }
for (String s : list1) {
System.out.println(s);
}
System.out.println("---------------------");
// 存储任意类型的元素
List<Object> list2 = new ArrayList<>();
list2.add(23);
list2.add(23.3);
list2.add("Java");
// List<int> list3 = new ArrayList<>();
List<Integer> list3 = new ArrayList<>();
}
}
2. 自定义泛型类
泛型类的概述
- 定义类时同时定义了泛型的类就是泛型类。
- 泛型类的格式:
修饰符 class 类名<泛型变量>{ }
此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。
作用:编译阶段可以指定数据类型,类似于集合的作用。
public class Test {
public static void main(String[] args) {
// 需求:模拟ArrayList定义一个MyArrayList ,关注泛型设计
MyArrayList<String> list = new MyArrayList<>();
list.add("Java");
list.add("Java");
list.add("MySQL");
list.remove("MySQL");
System.out.println(list);
MyArrayList<Integer> list2 = new MyArrayList<>();
list2.add(23);
list2.add(24);
list2.add(25);
list2.remove(25);
System.out.println(list2);
}
}
public class MyArrayList<E> {
private ArrayList lists = new ArrayList();
public void add(E e){
lists.add(e);
}
public void remove(E e){
lists.remove(e);
}
@Override
public String toString() {
return lists.toString();
}
}
3. 自定义泛型方法
泛型方法的概述:
- 定义方法时同时定义了泛型的方法就是泛型方法。
- 泛型方法的格式:
修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
/**
目标:自定义泛型方法。
什么是泛型方法?
定义了泛型的方法就是泛型方法。
泛型方法的定义格式:
修饰符 <泛型变量> 返回值类型 方法名称(形参列表){
}
注意:方法定义了是什么泛型变量,后面就只能用什么泛型变量。
泛型类的核心思想:是把出现泛型变量的地方全部替换成传输的真实数据类型。
需求:给你任何一个类型的数组,都能返回它的内容。Arrays.toString(数组)的功能!
小结:
泛型方法可以让方法更灵活的接收数据,可以做通用技术!
*/
public class GenericDemo {
public static void main(String[] args) {
String[] names = {"唐三", "小舞", "小白"};
printArray(names);
Integer[] ages = {10, 20, 30};
printArray(ages);
Integer[] ages2 = getArr(ages);
String[] names2 = getArr(names);
}
public static <T> T[] getArr(T[] arr){
return arr;
}
public static <T> void printArray(T[] arr){
if(arr != null){
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < arr.length; i++) {
sb.append(arr[i]).append(i == arr.length - 1 ? "" : ", ");
}
sb.append("]");
System.out.println(sb);
}else {
System.out.println(arr);
}
}
}
4. 自定义泛型接口
泛型接口的概述
- 使用了泛型定义的接口就是泛型接口。
- 泛型接口的格式:
修饰符 interface 接口名称<泛型变量>{}
作用:泛型接口可以约束实现类,实现类可以在实现接口的时候传入自己操作的数据类型这样重写的方法都将是针对于该类型的操作。
public class StudentData implements Data<Student>{
@Override
public void add(Student student) {
}
@Override
public void delete(int id) {
}
@Override
public void update(Student student) {
}
@Override
public Student queryById(int id) {
return null;
}
}
5. 泛型通配符、上下限
27.8 Set系列
1. Set系列集系概述
Set系列集合特点:
- 无序:存取顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
Set集合实现类特点:
- HashSet : 无序、不重复、无索引。
- LinkedHashSet:有序、不重复、无索引。
- TreeSet:排序、不重复、无索引。
public class SetDemo1 {
public static void main(String[] args) {
// 看看Set系列集合的特点: HashSet LinkedHashSet TreeSet
//
Set<String> sets = new HashSet<>(); // 一行经典代码 无序不重复,无索引
// Set<String> sets = new LinkedHashSet<>(); // 有序 不重复 无索引
sets.add("MySQL");
sets.add("MySQL");
sets.add("Java");
sets.add("Java");
sets.add("HTML");
sets.add("HTML");
sets.add("SpringBoot");
sets.add("SpringBoot");
System.out.println(sets);
}
}
2. 哈希表HashSet
哈希值
是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
Object类的API
public int hashCode():返回对象的哈希值
public class SetDemo2 {
public static void main(String[] args) {
// 目标:学会获取对象的哈希值,并确认一下
String name = "douluodalu";
System.out.println(name.hashCode());
System.out.println(name.hashCode());
String name1 = "douluodalu";
System.out.println(name1.hashCode());
System.out.println(name1.hashCode());
}
}
3. 实现类:LinkedHashSet
public class SetDemo4 {
public static void main(String[] args) {
// 看看Set系列集合的特点: HashSet LinkedHashSet TreeSet
Set<String> sets = new LinkedHashSet<>(); // 有序 不重复 无索引
sets.add("MySQL");
sets.add("MySQL");
sets.add("Java");
sets.add("Java");
sets.add("HTML");
sets.add("HTML");
sets.add("SpringBoot");
sets.add("SpringBoot");
System.out.println(sets);
}
}
3. 实现类:TreeSet
TreeSet集合概述和特点
- 不重复、无索引、可排序
- 可排序:按照元素的大小默认升序(有小到大)排序。
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet集合自定义排序规则方式
- 2种方式。
- 类实现Comparable接口,重写比较规则。
- 集合自定义Comparator比较器对象,重写比较规则。
/**
目标:观察TreeSet对于有值特性的数据如何排序。
学会对自定义类型的对象进行指定规则排序
*/
public class SetDemo5 {
public static void main(String[] args) {
Set<Integer> sets = new TreeSet<>(); // 不重复 无索引 可排序
sets.add(23);
sets.add(24);
sets.add(12);
sets.add(8);
System.out.println(sets);
Set<String> sets1 = new TreeSet<>(); // 不重复 无索引 可排序
sets1.add("Java");
sets1.add("Java");
sets1.add("angela");
sets1.add("唐三");
sets1.add("Java");
sets1.add("About");
sets1.add("Python");
sets1.add("UI");
sets1.add("UI");
System.out.println(sets1);
System.out.println("------------------------------");
// 方式二:集合自带比较器对象进行规则定制
//
// Set<Apple> apples = new TreeSet<>(new Comparator<Apple>() {
// @Override
// public int compare(Apple o1, Apple o2) {
// // return o1.getWeight() - o2.getWeight(); // 升序
// // return o2.getWeight() - o1.getWeight(); // 降序
// // 注意:浮点型建议直接使用Double.compare进行比较
// // return Double.compare(o1.getPrice() , o2.getPrice()); // 升序
// return Double.compare(o2.getPrice() , o1.getPrice()); // 降序
// }
// });
Set<Apple> apples = new TreeSet<>(( o1, o2) -> Double.compare(o2.getPrice() , o1.getPrice()) );
apples.add(new Apple("红富士", "红色", 9.9, 500));
apples.add(new Apple("青苹果", "绿色", 15.9, 300));
apples.add(new Apple("绿苹果", "青色", 29.9, 400));
apples.add(new Apple("黄苹果", "黄色", 9.8, 500));
System.out.println(apples);
}
}
Apple类:
public class Apple implements Comparable<Apple>{
private String name;
private String color;
private double price;
private int weight;
public Apple() {
}
public Apple(String name, String color, double price, int weight) {
this.name = name;
this.color = color;
this.price = price;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", price=" + price +
", weight=" + weight +
'}';
}
/**
方式一:类自定义比较规则
o1.compareTo(o2)
* @param o
* @return
*/
@Override
public int compareTo(Apple o) {
// 按照重量进行比较的
return this.weight - o.weight ; // 去重重量重复的元素
// return this.weight - o.weight >= 0 ? 1 : -1; // 保留重量重复的元素
}
}
27.9 Collection体系的特点、使用场景总结
- 如果希望元素可以重复,又有索引,索引查询要快?
用ArrayList集合,基于数组的。(用的最多) - 如果希望元素可以重复,又有索引,增删首尾操作快?
用LinkedList集合,基于链表的。 - 如果希望增删改查都快,但是元素不重复、无序、无索引。
用HashSet集合,基于哈希表的。 - 如果希望增删改查都快,但是元素不重复、有序、无索引。
用LinkedHashSet集合,基于哈希表和双链表。 - 如果要对对象进行排序。
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
27.10 集合工具类Collections
Collections集合工具类
- java.utils.Collections:是集合工具类
- 作用:Collections并不属于集合,是用来操作集合的工具类。
Collections常用的API
方法名称 | 说明 |
---|---|
public static boolean addAll(Collection<? super T> c, T… elements) | 给集合对象批量添加元素 |
public static void shuffle(List<?> list) | 打乱List集合元素的顺序 |
public static void reverse(List<?> list) | 反转集合中的元素 |
Collections排序相关API
使用范围:只能对于List集合的排序。
排序方式一:
方法名称 | 说明 |
---|---|
public static void sort(List list) | 将集合中元素按照默认规则排序 |
/**
目标:Collections工具类的使用。
java.utils.Collections:是集合工具类
Collections并不属于集合,是用来操作集合的工具类。
Collections有几个常用的API:
- public static <T> boolean addAll(Collection<? super T> c, T... elements)
给集合对象批量添加元素!
- public static void shuffle(List<?> list) :打乱集合顺序。
- public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
- public static <T> void sort(List<T> list,Comparator<? super T> c):将集合中元素按照指定规则排序。
*/
public class CollectionsDemo01 {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
//names.add("楚留香");
//names.add("胡铁花");
//names.add("张无忌");
//names.add("陆小凤");
Collections.addAll(names, "楚留香","胡铁花", "张无忌","陆小凤");
System.out.println(names);
// 2、public static void shuffle(List<?> list) :打乱集合顺序。
Collections.shuffle(names);
System.out.println(names);
// 3、 public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。 (排值特性的元素)
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 12, 23, 2, 4);
System.out.println(list);
Collections.sort(list);
System.out.println(list);
}
}
/*
反转
*/
public class listDemo01 {
public static void main(String[] args) {
// 1.创建一个ArrayList集合对象:
// List:有序,可重复,有索引的。
ArrayList<String> list = new ArrayList<>(); // 一行经典代码!
list.add("萧炎");
list.add("美杜莎");
list.add("云韵");
list.add("纳兰嫣然");
list.add("药尘");
list.add("雅妃");
System.out.println("反转前:"+list);
System.out.println("-----");
//反转
for (int i = 0; i < list.size()/2; i++) {
// //获取正数位置上的元素
// String e = list.get(i);
// //将正数位置上的元素放到倒数位置上并接收呗替换的倒数位置元素
// e = list.set(list.size() - 1 - i, e);
// //将倒数位置的元素放到正数位置上
// list.set(i, e);
list.set(list.size() - 1 - i, list.set(i, list.get(list.size() - 1 - i)));
}
System.out.println(list);
System.out.println("--------");
Collections.reverse(list);
System.out.println(list);
}
}
排序方式二:
方法名称 | 说明 |
---|---|
public static void sort(List list,Comparator<? super T> c) | 将集合中元素按照指定规则排序 |
/**
目标:引用数据类型的排序。
字符串按照首字符的编号升序排序!
自定义类型的比较方法API:Collections
- public static <T> void sort(List<T> list):
将集合中元素按照默认规则排序。
对于自定义的引用类型的排序人家根本不知道怎么排,直接报错!
- public static <T> void sort(List<T> list,Comparator<? super T> c):
将集合中元素按照指定规则排序,自带比较器
*/
public class CollectionsDemo02 {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>(); // 可以重复!
apples.add(new Apple("红富士", "红色", 9.9, 500));
apples.add(new Apple("青苹果", "绿色", 15.9, 300));
apples.add(new Apple("绿苹果", "青色", 29.9, 400));
apples.add(new Apple("黄苹果", "黄色", 9.8, 500));
// Collections.sort(apples); // 方法一:可以的,Apple类已经重写了比较规则
// System.out.println(apples);
// 方式二:sort方法自带比较器对象
// Collections.sort(apples, new Comparator<Apple>() {
// @Override
// public int compare(Apple o1, Apple o2) {
// return Double.compare(o1.getPrice() , o2.getPrice()); // 按照价格排序!!
// }
// });
Collections.sort(apples, ( o1, o2) -> Double.compare(o1.getPrice() , o2.getPrice()) );
System.out.println(apples);
}
}
Apple 类
public class Apple implements Comparable<Apple>{
private String name;
private String color;
private double price;
private int weight;
public Apple() {
}
public Apple(String name, String color, double price, int weight) {
this.name = name;
this.color = color;
this.price = price;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", price=" + price +
", weight=" + weight +
'}';
}
/**
方式一:类自定义比较规则
o1.compareTo(o2)
* @param o
* @return
*/
@Override
public int compareTo(Apple o) {
// 按照重量进行比较的
return this.weight - o.weight ; // 去重重量重复的元素
// return this.weight - o.weight >= 0 ? 1 : -1; // 保留重量重复的元素
}
}
27.9 Map集合体系
1. Map集合的概述
- Map集合概述和使用
- Map集合是一种双列集合,每个元素包含两个数据。
- Map集合的每个元素的格式:key=value(键值对元素)。
- Map集合也被称为“键值对集合”。
- Map集合整体格式:
- Collection集合的格式: [元素1,元素2,元素3…]
- Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , …}
2. Map集合体系特点
紫色为接口,蓝色实现类
说明:
- 使用最多的Map集合是HashMap。
Map集合体系特点
- Map集合的特点都是由键决定的。
- Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
- Map集合后面重复的键对应的值会覆盖前面重复键的值。
- Map集合的键值对都可以为null。
Map集合实现类特点
- HashMap:元素按照键是无序,不重复,无索引,值不做要求,基于哈希表(与Map体系一致)
- LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求,基于哈希表。
- TreeMap:元素按照建是排序,不重复,无索引的,值不做要求,可以做排序。
/**
目标:认识Map体系的特点:按照键无序,不重复,无索引。值不做要求。
*/
public class MapDemo1 {
public static void main(String[] args) {
// 1、创建一个Map集合对象
// Map<String, Integer> maps = new HashMap<>(); // 一行经典代码
Map<String, Integer> maps = new LinkedHashMap<>();
maps.put("萧炎", 3);
maps.put("云韵", 1);
maps.put("纳然嫣然", 100);
maps.put("美杜莎", 100); // 覆盖前面的数据
maps.put(null, null);
System.out.println(maps);
}
}
3. Map集合常用API
Map集合
- Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。
Map集合常用API:
方法名称 | 说明 |
---|---|
public V put(K key, V value): | 把指定的键与指定的值添加到Map集合中。 |
public V remove(Object key): | 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。 |
public V get(Object key) | 根据指定的键,在Map集合中获取对应的值。 |
public Set keySet(): | 获取Map集合中所有的键,存储到Set集合中。 |
public Set<Map.Entry<K,V>> entrySet(): | 获取到Map集合中所有的键值对对象的集合(Set集合)。 |
public boolean containsKey(Object key): | 判断该集合中是否有此键。 |
public boolean containValue(Object value): | 判断该集合中是否有此值。 |
public int size() | 获得该map集合中的键和值的个数 |
Collection values() | 将当前Map中所有的value以一个集合形式返回 |
/**
目标:Map集合的常用API(重点中的重点)
- public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
- public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
- public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
- public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
- public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
- public boolean containsKey(Object key):判断该集合中是否有此键。
- public boolean containValue(Object value):判断该集合中是否有此值。
*/
public class MapDemo {
public static void main(String[] args) {
// 1.添加元素: 无序,不重复,无索引。
Map<String , Integer> maps = new HashMap<>();
maps.put("iphoneX",10);
maps.put("娃娃",20);
maps.put("iphoneX",100);// Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
maps.put("huawei",100);
maps.put("生活用品",10);
maps.put("手表",10);
// {huawei=100, 手表=10, 生活用品=10, iphoneX=100, 娃娃=20}
System.out.println(maps);
// 2.清空集合
// maps.clear();
// System.out.println(maps);
// 3.判断集合是否为空,为空返回true ,反之!
System.out.println(maps.isEmpty());
// 4.根据键获取对应值:public V get(Object key)
Integer key = maps.get("huawei");
System.out.println(key);
System.out.println(maps.get("生活用品")); // 10
System.out.println(maps.get("生活用品2")); // null
// 5.根据键删除整个元素。(删除键会返回键的值)
System.out.println(maps.remove("iphoneX"));
System.out.println(maps);
// 6.判断是否包含某个键 ,包含返回true ,反之
System.out.println(maps.containsKey("娃娃")); // true
System.out.println(maps.containsKey("娃娃2")); // false
System.out.println(maps.containsKey("iphoneX")); // false
// 7.判断是否包含某个值。
System.out.println(maps.containsValue(100)); //
System.out.println(maps.containsValue(10)); //
System.out.println(maps.containsValue(22)); //
// {huawei=100, 手表=10, 生活用品=10, 娃娃=20}
// 8.获取全部键的集合:public Set<K> keySet()
Set<String> keys = maps.keySet();
System.out.println(keys);
System.out.println("------------------------------");
// 9.获取全部值的集合:Collection<V> values();
Collection<Integer> values = maps.values();
System.out.println(values);
// 10.集合的大小
System.out.println(maps.size()); // 4
// 11.合并其他Map集合。(拓展)
Map<String , Integer> map1 = new HashMap<>();
map1.put("java1", 1);
map1.put("java2", 100);
Map<String , Integer> map2 = new HashMap<>();
map2.put("java2", 1);
map2.put("java3", 100);
map1.putAll(map2); // 把集合map2的元素拷贝一份到map1中去
System.out.println(map1);
System.out.println(map2);
//12.将当前Map中所有的value以一个集合形式返回
Collection<Integer> values1 = map1.values();
for (Integer integer : values1) {
System.out.println(integer);
}
}
}
4. Map集合的遍历方式一:键找值
- 先获取Map集合的全部键的Set集合。
- 遍历键的Set集合,然后通过键提取对应值。
键找值涉及到的API:
方法名称 | 说明 |
---|---|
Set keySet() | 获取所有键的集合 |
V get(Object key) | 根据键获取值 |
/**
目标:Map集合的遍历方式一:键找值
Map集合的遍历方式有:3种。
(1)“键找值”的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
(2)“键值对”的方式遍历:难度较大。
(3)JDK 1.8开始之后的新技术:Lambda表达式。(暂时了解)
1.“键找值”的方式遍历Map集合。
1.先获取Map集合的全部键的Set集合。
2.遍历键的Set集合,然后通过键找值。
小结:
代码简单,需要记住!
*/
public class MapDemo01 {
public static void main(String[] args) {
Map<String , Integer> maps = new HashMap<>();
// 1.添加元素: 无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
// maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
// 1、键找值:第一步:先拿到集合的全部键。
Set<String> keys = maps.keySet();
// 2、第二步:遍历每个键,根据键提取值
for (String key : keys) {
int value = maps.get(key);
System.out.println(key + "===>" + value);
}
}
}
5. Map集合的遍历方式二:键值对
- 先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。
- 遍历Set集合,然后提取键以及提取值。
键值对涉及到的API:
方法名称 | 说明 |
---|---|
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
K getKey() | 获得键 |
V getValue() | 获取值 |
/**
2.“键值对”的方式遍历:
1.把Map集合转换成一个Set集合:Set<Map.Entry<K, V>> entrySet();
2.此时键值对元素的类型就确定了,类型是键值对实体类型:Map.Entry<K, V>
3.接下来就可以用foreach遍历这个Set集合,类型用Map.Entry<K, V>
*/
public class MapDemo02 {
public static void main(String[] args) {
Map<String , Integer> maps = new HashMap<>();
// 1.添加元素: 无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
// maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
// 1、把Map集合转换成Set集合
Set<Map.Entry<String, Integer>> entries = maps.entrySet();
// 2、开始遍历
for(Map.Entry<String, Integer> entry : entries){
String key = entry.getKey();
int value = entry.getValue();
System.out.println(key + "====>" + value);
}
}
}
6. Map集合的遍历方式三:lambda表达式
得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
Map结合Lambda遍历的API:
default void forEach(BiConsumer<? super K, ? super V> action):结合lambda遍历Map集合
/*
3.JDK 1.8开始之后的新技术:Lambda表达式。(暂时了解)
*/
public class MapDemo03 {
public static void main(String[] args) {
Map<String , Integer> maps = new HashMap<>();
// 1.添加元素: 无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);// Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
// maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
// maps.forEach(new BiConsumer<String, Integer>() {
// @Override
// public void accept(String key, Integer value) {
// System.out.println(key + "--->" + value);
// }
// });
maps.forEach((k, v) -> {
System.out.println(k + "--->" + v);
});
}
}
7. Map集合的实现类HashMap
称为散列表,哈希表。
HashMap的特点
- HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引
- 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。
- HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已。
- 增删改查的性能都较好。
- 当今查询速度最快的数据结构。
注意: 实际上:Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已
public class HashMapDemo1 {
public static void main(String[] args) {
// Map集合是根据键去除重复元素
Map<Student, String> maps = new HashMap<>();
Student s1 = new Student("唐三", 20, '男');
Student s2 = new Student("唐三", 20, '男');
Student s3 = new Student("小舞", 21, '女');
maps.put(s1, "广西");
maps.put(s2, "斗罗大陆");
maps.put(s3, "斗罗大陆");
System.out.println(maps);
}
}
Student类
public class Student {
private String name;
private int age;
private char sex;
public Student() {
}
public Student(String name, int age, char sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(char sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public char getSex() {
return sex;
}
}
8. Map集合的实现类LinkedHashMap
LinkedHashMap集合概述和特点
- 由键决定:有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
/**
目标:LinkedHashMap:由键决定:有序、不重复、无索引。
*/
public class LinkedHashMapDemo2 {
public static void main(String[] args) {
// 1、创建一个Map集合对象
Map<String, Integer> maps = new LinkedHashMap<>();
maps.put("鸿星尔克", 3);
maps.put("Java", 1);
maps.put("枸杞", 100);
maps.put("Java", 100); //覆盖前面的数据
maps.put(null, null);
System.out.println(maps);
}
}
9. Map集合的实现类TreeMap
TreeMap集合概述和特点
- 由键决定特性:不重复、无索引、可排序
- 可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
- TreeMap跟TreeSet一样底层原理是一样的。
注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
TreeMap集合自定义排序规则有2种
- 类实现Comparable接口,重写比较规则。
- 集合自定义Comparator比较器对象,重写比较规则。
public class TreeMapDemo3 {
public static void main(String[] args) {
Map<Integer, String> maps1 = new TreeMap<>();
maps1.put(13 , "萧炎");
maps1.put(1 , "美杜莎");
maps1.put(3 , "云韵");
System.out.println(maps1);
// TreeMap集合自带排序。 可排序 不重复(只要大小规则一样就认为重复) 无索引
Map<Apple, String> maps2 = new TreeMap<>(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return Double.compare(o2.getPrice() , o1.getPrice()); // 按照价格降序排序!
}
});
maps2.put(new Apple("红富士", "红色", 9.9, 500), "山东" );
maps2.put(new Apple("青苹果", "绿色", 15.9, 300), "广州");
maps2.put(new Apple("绿苹果", "青色", 29.9, 400), "江西");
maps2.put(new Apple("黄苹果", "黄色", 9.8, 500), "湖北");
System.out.println(maps2);
}
}
public class Apple implements Comparable<Apple>{
private String name;
private String color;
private double price;
private int weight;
public Apple() {
}
public Apple(String name, String color, double price, int weight) {
this.name = name;
this.color = color;
this.price = price;
this.weight = weight;
}
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", price=" + price +
", weight=" + weight +
'}';
}
/**
方式一:类自定义比较规则
o1.compareTo(o2)
* @param o
* @return
*/
@Override
public int compareTo(Apple o) {
// 按照重量进行比较的
return this.weight - o.weight ; // 去重重量重复的元素
// return this.weight - o.weight >= 0 ? 1 : -1; // 保留重量重复的元素
}
}
28 单元测试
28.1 单元测试概述
单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对Java方法的测试,进而检查方法的正确性。
Junit单元测试框架
- JUnit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用JUnit编写单元测试。
- 此外,几乎所有的IDE工具都集成了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试,JUnit目前最新版本
JUnit优点
- JUnit可以灵活的选择执行哪些测试方法,可以一键执行全部测试方法。
- Junit可以生成全部方法的测试报告。
- 单元测试中的某个方法测试失败了,不会影响其他测试方法的测试。
28.2 单元测试快速入门
JUnit单元测试的实现过程
- 必须导入Junit框架的jar包,maven项目填写依赖。
- 定义的测试方法必须是无参数无返回值,且公开的方法。
- 测试方法使用@Test注解标记。
JUnit测试某个方法,测试全部方法怎么处理?成功的标志是什么
- 测试某个方法直接右键该方法启动测试。
- 测试全部方法,可以选择类或者模块启动。
- 红色失败,绿色通过。
28.3 单元测试常用注解
注解 | 说明 |
---|---|
@Test | 测试方法 |
@Before | 用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次 |
@After | 用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次 |
@BeforeClass | 用来静态修饰方法,该方法会在所有测试方法之前只执行一次 |
@AfterClass | 用来静态修饰方法,该方法会在所有测试方法之后只执行一次 |
/**
测试类
*/
public class TestUserService {
// 修饰实例方法的
@Before
public void before(){
System.out.println("===before方法执行一次===");
}
@After
public void after(){
System.out.println("===after方法执行一次===");
}
// 修饰静态方法
@BeforeClass
public static void beforeClass(){
System.out.println("===beforeClass方法执行一次===");
}
@AfterClass
public static void afterClass(){
System.out.println("===afterClass方法执行一次===");
}
/**
测试方法
注意点:
1、必须是公开的,无参数 无返回值的方法
2、测试方法必须使用@Test注解标记。
*/
@Test
public void testLoginName(){
UserService userService = new UserService();
String rs = userService.loginName("admin","123456");
System.out.println(rs);
// 进行预期结果的正确性测试:断言。
Assert.assertEquals("您的登录业务可能出现问题", "登录成功", rs );
}
@Test
public void testSelectNames(){
UserService userService = new UserService();
userService.selectNames();
}
}
/**
业务方法
*/
public class UserService {
public String loginName(String loginName , String passWord){
if("admin".equals(loginName) && "123456".equals(passWord)){
return "登录成功";
}else {
return "用户名或者密码有问题";
}
}
public void selectNames(){
System.out.println(10/2);
System.out.println("查询全部用户名称成功~~");
}
}
29 反射
1. 反射概述
- 反射是指对于任何一个Class类,在"运行的时候"都可以直接得到这个类全部成分。
- 在运行时,可以直接得到这个类的构造器对象:Constructor
- 在运行时,可以直接得到这个类的成员变量对象:Field
- 在运行时,可以直接得到这个类的成员方法对象:Method
- 这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制。
反射的基本作用:
反射是在运行时获取类的字节码文件对象:然后可以解析类中的全部成分
反射的核心思想和关键就是:
得到编译以后的class文件对象
2. 反射获取类对象
反射的第一步:
获取Class类对象,如此才可以解析类的全部成分
获取Class类的对象的三种方式
- 方式一:Class c1 = Class.forName(“全类名”);
- 方式二:Class c2 = 类名.class
- 方式三:Class c3 = 对象.getClass();
public class Test {
public static void main(String[] args) throws Exception {
// 1、Class类中的一个静态方法:forName(全限名:包名 + 类名)
Class c = Class.forName("d2_reflect_class.Student");
System.out.println(c); // Student.class
// 2、类名.class
Class c1 = Student.class;
System.out.println(c1);
// 3、对象.getClass() 获取对象对应类的Class对象。
Student s = new Student();
Class c2 = s.getClass();
System.out.println(c2);
}
}
3. 反射获取构造器对象
使用反射技术获取构造器对象并使用
- 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象
- Class类中用于获取构造器的方法
方法 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有构造器对象的数组(只能拿public的几乎不用!!太弱了!) |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造器对象的数组,存在就能拿到,无所谓权限。建议使用!! |
Constructor getConstructor(Class<?>… parameterTypes) | 返回单个构造器对象(只能拿public的,几乎不用!) |
Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 返回单个构造器对象,存在就能拿到,不关心权限修饰符,建议使用! |
/**
目标:反射_获取Constructor构造器对象.
反射的第一步是先得到Class类对象。(Class文件)
反射中Class类型获取构造器提供了很多的API:
1. Constructor getConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只能拿public修饰的构造器,几乎不用!
2. Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
3. Constructor[] getConstructors()
获取所有的构造器,只能拿public修饰的构造器。几乎不用!!太弱了!
4. Constructor[] getDeclaredConstructors()
获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
小结:
获取类的全部构造器对象: Constructor[] getDeclaredConstructors()
-- 获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
获取类的某个构造器对象:Constructor getDeclaredConstructor(Class... parameterTypes)
-- 根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
*/
public class TestStudent01 {
// 1. getConstructors:
// 获取全部的构造器:只能获取public修饰的构造器。
// Constructor[] getConstructors()
@Test
public void getConstructors(){
// a.第一步:获取类对象
Class c = Student.class;
// b.提取类中的全部的构造器对象(这里只能拿public修饰)
Constructor[] constructors = c.getConstructors();
// c.遍历构造器
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "===>" + constructor.getParameterCount());
}
}
// 2.getDeclaredConstructors():
// 获取全部的构造器:只要你敢写,这里就能拿到,无所谓权限是否可及。
@Test
public void getDeclaredConstructors(){
// a.第一步:获取类对象
Class c = Student.class;
// b.提取类中的全部的构造器对象
Constructor[] constructors = c.getDeclaredConstructors();
// c.遍历构造器
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "===>" + constructor.getParameterCount());
}
}
// 3.getConstructor(Class... parameterTypes)
// 获取某个构造器:只能拿public修饰的某个构造器
@Test
public void getConstructor() throws Exception {
// a.第一步:获取类对象
Class c = Student.class;
// b.定位单个构造器对象 (按照参数定位无参数构造器 只能拿public修饰的某个构造器)
Constructor cons = c.getConstructor();
System.out.println(cons.getName() + "===>" + cons.getParameterCount());
}
// 4.getDeclaredConstructor(Class... parameterTypes)
// 获取某个构造器:只要你敢写,这里就能拿到,无所谓权限是否可及。
@Test
public void getDeclaredConstructor() throws Exception {
// a.第一步:获取类对象
Class c = Student.class;
// b.定位单个构造器对象 (按照参数定位无参数构造器)
Constructor cons = c.getDeclaredConstructor();
System.out.println(cons.getName() + "===>" + cons.getParameterCount());
// c.定位某个有参构造器
Constructor cons1 = c.getDeclaredConstructor(String.class, int.class);
System.out.println(cons1.getName() + "===>" + cons1.getParameterCount());
}
}
反射获取Class中的构造器对象Constructor作用:
- 也是初始化并得到类的一个对象返回。
Constructor类中用于创建对象的方法
方法 | 说明 |
---|---|
T newInstance(Object… initargs) | 根据指定的构造器创建对象 |
public void setAccessible(boolean flag) | 设置为true,表示取消访问检查,进行暴力反射 |
/**
目标: 反射_获取Constructor构造器然后通过这个构造器初始化对象。
反射获取Class中的构造器对象Constructor作用:
也是初始化并得到类的一个对象返回。
Constructor的API:
1. T newInstance(Object... initargs)
创建对象,注入构造器需要的数据。
2. void setAccessible(true)
修改访问权限,true代表暴力攻破权限,false表示保留不可访问权限(暴力反射)
小结:
可以通过定位类的构造器对象。
如果构造器对象没有访问权限可以通过:void setAccessible(true)打开权限
构造器可以通过T newInstance(Object... initargs)调用自己,传入参数!
*/
public class TestStudent02 {
// 1.调用构造器得到一个类的对象返回。
@Test
public void getDeclaredConstructor() throws Exception {
// a.第一步:获取类对象
Class c = Student.class;
// b.定位单个构造器对象 (按照参数定位无参数构造器)
Constructor cons = c.getDeclaredConstructor();
System.out.println(cons.getName() + "===>" + cons.getParameterCount());
// 如果遇到了私有的构造器,可以暴力反射
cons.setAccessible(true); // 权限被打开
Student s = (Student) cons.newInstance();
System.out.println(s);
System.out.println("-------------------");
// c.定位某个有参构造器
Constructor cons1 = c.getDeclaredConstructor(String.class, int.class);
System.out.println(cons1.getName() + "===>" + cons1.getParameterCount());
Student s1 = (Student) cons1.newInstance("孙悟空", 1000);
System.out.println(s1);
}
}
Student类
public class Student {
private String name;
private int age;
private Student(){
System.out.println("无参数构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参数构造器执行!");
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
反射得到的构造器可以做什么?
- 依然是创建对象的:public newInstance(Object… initargs)
如果是非public的构造器,需要打开权限(暴力反射),然后再创建对象
- setAccessible(boolean):暴力反射
- 反射可以破坏封装性,私有的也可以执行了。
4. 反射获取成员变量对象
使用反射技术获取成员变量对象并使用
- 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象。
- Class类中用于获取成员变量的方法
方法 | 说明 |
---|---|
Field getField(String name) | 返回所有成员变量对象的数组(只能拿public的) |
Field getDeclaredField(String name) | 返回所有成员变量对象的数组,存在就能拿到 |
Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的) |
Field[] getDeclaredFields() | 获得所有的成员变量对应的Field对象,只要申明了就可以得到 |
/**
目标:反射_获取Field成员变量对象。
反射的第一步是先得到Class类对象。
1、Field getField(String name);
根据成员变量名获得对应Field对象,只能获得public修饰
2.Field getDeclaredField(String name);
根据成员变量名获得对应Field对象,只要申明了就可以得到
3.Field[] getFields();
获得所有的成员变量对应的Field对象,只能获得public的
4.Field[] getDeclaredFields();
获得所有的成员变量对应的Field对象,只要申明了就可以得到
小结:
获取全部成员变量:getDeclaredFields
获取某个成员变量:getDeclaredField
*/
public class FieldDemo01 {
/**
* 1.获取全部的成员变量。
* Field[] getDeclaredFields();
* 获得所有的成员变量对应的Field对象,只要申明了就可以得到
*/
@Test
public void getDeclaredFields(){
// a.定位Class对象
Class c = Student.class;
// b.定位全部成员变量
Field[] fields = c.getDeclaredFields();
// c.遍历一下
for (Field field : fields) {
System.out.println(field.getName() + "==>" + field.getType());
}
}
/**
2.获取某个成员变量对象 Field getDeclaredField(String name);
*/
@Test
public void getDeclaredField() throws Exception {
// a.定位Class对象
Class c = Student.class;
// b.根据名称定位某个成员变量
Field f = c.getDeclaredField("age");
System.out.println(f.getName() +"===>" + f.getType());
}
}
反射获取成员变量: 取值和赋值。
Field的方法:给成员变量赋值和取值
方法 | 说明 |
---|---|
void set(Object obj, Object value): | 给对象注入某个成员变量数据 |
Object get(Object obj): | 获取对象的成员变量的值。 |
void setAccessible(true); | 暴力反射,设置为可以直接访问私有类型的属性。 |
Class getType(); | 获取属性的类型,返回Class对象。 |
String getName(); | 获取属性的名称。 |
/**
目标:反射获取成员变量: 取值和赋值。
Field的方法:给成员变量赋值和取值
void set(Object obj, Object value):给对象注入某个成员变量数据
Object get(Object obj):获取对象的成员变量的值。
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
Class getType(); 获取属性的类型,返回Class对象。
String getName(); 获取属性的名称。
*/
public class FieldDemo02 {
@Test
public void setField() throws Exception {
// a.反射第一步,获取类对象
Class c = Student.class;
// b.提取某个成员变量
Field ageF = c.getDeclaredField("age");
ageF.setAccessible(true); // 暴力打开权限
// c.赋值
Student s = new Student();
ageF.set(s , 18); // s.setAge(18);
System.out.println(s);
// d、取值
int age = (int) ageF.get(s);
System.out.println(age);
}
}
Student类
public class Student {
private String name;
private int age;
public static String schoolName;
public static final String COUNTTRY = "中国";
public Student(){
System.out.println("无参数构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参数构造器执行!");
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
5. 反射获取方法对象
方法 | 名称 |
---|---|
Method getMethod(String name,Class…args) | 根据方法名和参数类型获得对应的方法对象,只能获得public的 |
Method getDeclaredMethod(String name,Class…args) | 根据方法名和参数类型获得对应的方法对象,包括private的 |
Method[] getMethods() | 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的 |
Method[] getDeclaredMethods() | 获得类中的所有成员方法对象,返回数组,只获得本类申明的方法 |
Method类中用于触发执行的方法
方法 | 名称 |
---|---|
Object invoke(Object obj, Object… args) | 运行方法,参数一:用obj对象调用该方法。参数二:调用方法的传递的参数(如果没有就不写)返回值:方法的返回值(如果没有就不写) |
/**
目标:反射——获取Method方法对象
反射获取类的Method方法对象:
1、Method getMethod(String name,Class...args);
根据方法名和参数类型获得对应的方法对象,只能获得public的
2、Method getDeclaredMethod(String name,Class...args);
根据方法名和参数类型获得对应的方法对象,包括private的
3、Method[] getMethods();
获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
4、Method[] getDeclaredMethods();
获得类中的所有成员方法对象,返回数组,只获得本类申明的方法。
Method的方法执行:
Object invoke(Object obj, Object... args)
参数一:触发的是哪个对象的方法执行。
参数二: args:调用方法时传递的实际参数
*/
public class MethodDemo01 {
/**
* 1.获得类中的所有成员方法对象
*/
@Test
public void getDeclaredMethods(){
// a.获取类对象
Class c = Dog.class;
// b.提取全部方法;包括私有的
Method[] methods = c.getDeclaredMethods();
// c.遍历全部方法
for (Method method : methods) {
System.out.println(method.getName() +" 返回值类型:" + method.getReturnType() + " 参数个数:" + method.getParameterCount());
}
}
/**
* 2. 获取某个方法对象
*/
@Test
public void getDeclardMethod() throws Exception {
// a.获取类对象
Class c = Dog.class;
// b.提取单个方法对象
Method m = c.getDeclaredMethod("eat");
Method m2 = c.getDeclaredMethod("eat", String.class);
// 暴力打开权限了
m.setAccessible(true);
m2.setAccessible(true);
// c.触发方法的执行
Dog d = new Dog();
// 注意:方法如果是没有结果回来的,那么返回的是null.
Object result = m.invoke(d);
System.out.println(result);
Object result2 = m2.invoke(d, "骨头");
System.out.println(result2);
}
}
Dog类
public class Dog {
private String name ;
public Dog(){
}
public Dog(String name) {
this.name = name;
}
public void run(){
System.out.println("狗跑的贼快~~");
}
private void eat(){
System.out.println("狗吃骨头");
}
private String eat(String name){
System.out.println("狗吃" + name);
return "吃的很开心!";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
6. 反射的作用-绕过编译阶段为集合添加数据
- 反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的。
- 泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,泛型相当于被擦除了。
public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 需求:反射实现泛型擦除后,加入其他类型的元素
ArrayList<String> lists1 = new ArrayList<>();
ArrayList<Integer> lists2 = new ArrayList<>();
System.out.println(lists1.getClass());
System.out.println(lists2.getClass());
System.out.println(lists1.getClass() == lists2.getClass()); // ArrayList.class
System.out.println("---------------------------");
ArrayList<Integer> lists3 = new ArrayList<>();
lists3.add(23);
lists3.add(22);
// lists3.add("黑马");
Class c = lists3.getClass(); // ArrayList.class ===> public boolean add(E e)
// 定位c类中的add方法
Method add = c.getDeclaredMethod("add", Object.class);
boolean rs = (boolean) add.invoke(lists3, "唐三");
System.out.println(rs);
System.out.println(lists3);
ArrayList list4 = lists3;
list4.add("小舞");
list4.add(false);
System.out.println(lists3);
}
}
30 注解
1. 注解概述
- Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
- Java 语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。
注解的作用:
- 对Java中类、方法、成员变量做标记,然后进行特殊处理,至于到底做何种处理由业务需求来决定。
- 例如:JUnit框架中,标记了注解@Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行。
2. 自定义注解
自定义注解:
- 自定义注解就是自己做一个注解来使用。
格式:
public @interface 注解名称 {
public 属性类型 属性名() default 默认值;
}
特殊属性
- value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写!!
- 但是如果有多个属性, 且多个属性没有默认值,那么value名称是不能省略的。
/**
目标:学会自定义注解。掌握其定义格式和语法。
*/
@MyBook(name="《斗罗大陆》",authors = {"唐三", "小舞"} , price = 199.5)
//@Book(value = "/delete")
// @Book("/delete")
@Book(value = "/delete", price = 23.5)
//@Book("/delete")
public class AnnotationDemo1 {
@MyBook(name="《斗破苍穹》",authors = {"萧炎", "dlei"} , price = 199.5)
private AnnotationDemo1(){
}
@MyBook(name="《完美世界》",authors = {"石昊", "火灵儿"} , price = 199.5)
public static void main(String[] args) {
@MyBook(name="《遮天》",authors = {"叶天帝", "狠人大帝"} , price = 199.5)
int age = 21;
}
}
public @interface Book {
String value(); // 特殊属性
double price() ;
//double price() default 9.9;
}
public @interface MyBook {
String name();
String[] authors();
double price();
}
3. 元注解
元注解:就是注解注解的注解。
元注解有两个:
- @Target: 约束自定义注解只能在哪些地方使用,
- @Retention:申明注解的生命周期
@Target中可使用的值定义在ElementType枚举类中,常用值如下
- TYPE,类,接口
- FIELD, 成员变量
- METHOD, 成员方法
- PARAMETER, 方法参数
- CONSTRUCTOR, 构造器
- LOCAL_VARIABLE, 局部变量
@Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如下
- SOURCE: 注解只作用在源码阶段,生成的字节码文件中不存在
- CLASS: 注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
- RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)
/**
目标:认识元注解
*/
//@MyTest // 只能注解方法和成员变量
public class AnnotationDemo2 {
@MyTest
private String name;
@MyTest
public void test(){
}
public static void main(String[] args) {
}
}
@Target({ElementType.METHOD,ElementType.FIELD}) // 元注解
@Retention(RetentionPolicy.RUNTIME) // 一直活着,在运行阶段这个注解也不消失
public @interface MyTest {
}
3. 注解的解析
与注解解析相关的接口
- Annotation: 注解的顶级接口,注解都是Annotation类型的对象
- AnnotatedElement:该接口定义了与注解解析相关的解析方法
Annotation[] getDeclaredAnnotations():获得当前对象上使用的所有注解,返回注解数组
T getDeclaredAnnotation(Class annotationClass):根据注解类型获得对应注解对象
boolean isAnnotationPresent(Class annotationClass):判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
/**
目标:完成注解的解析
*/
public class AnnotationDemo3 {
@Test
public void parseClass(){
// a.先得到类对象
Class c = BookStore.class;
// b.判断这个类上面是否存在这个注解
if(c.isAnnotationPresent(Bookk.class)){
//c.直接获取该注解对象
Bookk book = (Bookk) c.getDeclaredAnnotation(Bookk.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.author()));
}
}
@Test
public void parseMethod() throws NoSuchMethodException {
// a.先得到类对象
Class c = BookStore.class;
Method m = c.getDeclaredMethod("test");
// b.判断这个类上面是否存在这个注解
if(m.isAnnotationPresent(Bookk.class)){
//c.直接获取该注解对象
Bookk book = (Bookk) m.getDeclaredAnnotation(Bookk.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.author()));
}
}
}
@Bookk(value = "《情深深雨濛濛》", price = 99.9, author = {"琼瑶", "dlei"})
class BookStore{
@Bookk(value = "《三少爷的剑》", price = 399.9, author = {"古龙", "熊耀华"})
public void test(){
}
}
public class AnnotationDemo4 {
public void test1(){
System.out.println("===test1===");
}
@MyTest
public void test2(){
System.out.println("===test2===");
}
@MyTest
public void test3(){
System.out.println("===test3===");
}
/**
启动菜单:有注解的才被调用。
*/
public static void main(String[] args) throws Exception {
AnnotationDemo4 t = new AnnotationDemo4();
// a.获取类对象
Class c = AnnotationDemo4.class;
// b.提取全部方法
Method[] methods = c.getDeclaredMethods();
// c.遍历方法,看是否有MyTest注解,有就跑它
for (Method method : methods) {
if(method.isAnnotationPresent(MyTest.class)){
// 跑它
method.invoke(t);
}
}
}
}
31 动态代理
代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,动态代理就是用来对业务功能(方法)进行代理的。
关键步骤:
- 必须有接口,实现类要实现接口(代理通常是基于接口实现的)。
- 创建一个实现类的对象,该对象为业务对象,紧接着为业务对象做一个代理对象。
动态代理的优点:
- 非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接本身做代理。
- 可以为被代理对象的所有方法做代理。
- 可以在不改变方法源码的情况下,实现对方法功能的增强。
- 不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。