java基础
目录
java
入门知识
历史
java主要分为 javaSE和javaEE。
javaSE为标准版,javaEE为企业版, javaEE相对于javaSE拓展了更多的API和库-
JDK与JRE
JRE是java的虚拟机,并且包含其运行的库,JDK除了包含JRE还含有编译器,调试器等开发工具
路径
绝对路径:
一定是从盘符开始的 如 C:\
相对路径:
在File中指定的路径分为2种 //只是做区分,实际上File 类的相对路径是相对于 JVM 的工作目录
1.运行class文件,此时的相对路径是当前项目(模块)的根目录
在 IDEA 中,默认的工作目录是 项目的根目录
2.运行jar包,此时的相对路径是这个jar包所在的文件夹
在 JAR 包中,File 类的相对路径是相对于 JVM 的工作目录(即运行 java -jar 时的当前目录),而不是 JAR 包内部
如: " 文件夹\\文件.txt" 则该路径指向当前项目下 文件夹里面的文件.TXT
!在idea项目中 当前盘符为当前模块文件夹
!打包成jar文件后,当前盘符为当前jar所在文件夹的路径,如 app.jar包 则盘符为: "JAR包所在盘符"
可以使用 " ../ "来获得相对路径的前一个路径,若要前移多个文件夹可以使用多个 ../ 如 ../../ 会回退两级。
使用类加载器的相对路径:
调用 class对象.getClassLoader().getResourceAsStream("路径") 来获取一个指向对应路径的流
该路径在运行jar包时,根目录是jar包内部的根目录,因此一般用此来访问jar包内部文件
!如果路径以 / 开头,表示从 classpath 的根目录开始。
!如果路径不以 / 开头,表示相对于当前类所在的包路径。
在idea运行时没有打包时,文件会被编译,编译后的文件放在一个与模块名相同的文件中,此时相对路径是指向该文件夹
public static void main(String[] args) {
// D盘下的bbb.java文件
File f = new File("D:\\bbb.java");
System.out.println(f.getAbsolutePath());
// 项目下的bbb.java文件
File f2 = new File("bbb.java");
System.out.println(f2.getAbsolutePath());
}
}
输出结果:
D:\bbb.java
D:\idea_project_test4\bbb.java
//# 相对的是工作目录 (工作目录就是执行java命令的目录) 在idea中工作目录是当前模块
File file = new File("hello.txt")
//# 相对的是class文件所在的根目录
URL resource = ClassPathDemo.class.getResource("/");
//# 相对的是ClassPathDemo.class所在的目录
URL resource = ClassPathDemo.class.getResource("a/b/c");
//# 相对的是class文件所在的根目录,并且一定不能加斜杠
URL resource = ClassPathDemo.class.getClassLoader().getResource("hello.txt");
//直接获取流
InputStream in = this.getClass().getClassLoader().getResourceAsStream('路径') //获取资源路径下的文件
//构建类加载器:
对象.getClass.getClassLoad().getResource(路径);
// 路径地址为相对路径, 开头有 / 则从该模块根目录(main文件夹下)开始,如果不加 / 则从该类所在文件夹的位置开始
// 打包后main内的文件放在jar包根目录,因此从jar包内部根目录开始
字符集
计算机的存储规则:
在计算机中,任何数据都是以二进制的形式来存储的
字节是计算机最新的存储单元
字节:
英文名叫 byte, 一个字节由8个bit组成,一个bit(比特位)可表示0或1
ASCII字符集(美国):
该字符集内存储了127个字符,而一个字节最多可表示256个数据,因此一个字节就可存储英文
编码规则: 前面补0,补齐8位
解码规则: 直接转成10进制
GBK字符集(中国):
该字符集由2个字节组成,2个字节分位高位字节和低位字节
高位字节以1开头,用于与英文区分,来表示汉字
低位字节与ASCII码表兼容
Unicode(万国码)字符集:
UTF-8编码方式:
UTF-8是Unicode字符集中的一种编码方式
编码规则:用1~4个字节保存

中文(GBK)编码用3个字节存储
使用外部jar包
idea构建:
1.在模块下新建文件夹lib,并将jar包复制进lib文件夹
2.右键jar包,选择 Add as Library
杂项
var关键字
//var关键字
var是Java10中新增的局部类型变量推断。
var会根据后面的值来推断变量的类型
var必须要初始化。
//var不能使用场景
类成员变量类型。
方法返回值类型。
Java10中Lambda不能使用var,Java11中可以使用。
//定义局部变量
var a = 1;
等于
int a = 1;
//var接收方法返回时
var result = this.getResult();
等于
String result = this.getResult();
//var结合泛型
var list1 = new ArrayList<String>(); //在<>中指定了list类型为String
等于
List<String> list1 = new ArrayList<>();
var list2 = new ArrayList<>(); //<>里默认会是Object
//var在Lambda中使用 (java11才可以使用)
Consumer<String> Consumer = (var i) -> System.out.println(i);
等于
Consumer<String> Consumer = (String i) -> System.out.println(i);
可变参数
定义:
方法形参的个数是可以发生变化的
格式:
属性类型...名字
int...args
细节:
在方法的形参中最多只能写一个可变参数
在方法中,如果除了可变参数以外,还有其他的形参,那么可变参数要写在最后
方法重载:1.调用一个被重载的方法时,如果此调用既能够和固定长度的重载方法匹配,
又能和可变参数的重载方法匹配的话,会优先选择固定参数的重载方法。
2.当调用一个被重载的方法时,如果此调用能够和两个有可变参数的重载方法匹配,会出现编译错误。
//例
getSum(1,2,3,4,5,6);
public static void getSum(int...args){
for(int i : args){
print(i);
}
}
编码和解码
String类中的方法:
| 编码方法 | 说明 |
|---|---|
| public byte[] getBytes() | 使用默认方式进行编码 |
| public byte[] getBytes(String charsetName) | 使用指定方式进行编码 |
| 解码方法(构造方法) | |
| String(byte[] bytes) | 使用默认方式进行解码 |
| String(byte[] bytes , String charsetName) | 使用指定方式进行解码 |
idea默认使用UTF-8进行编码
java内存分配
概览
虚拟机内存分区: 1.本地方法栈 2.寄存器 3.栈 4.方法区 5.堆
//在JDK7 前 方法区和堆 是连在一起的(在真实的物理内存中也是一块连续的空间)
//在JDK8后,取消方法区,新增元空间,把原来方法区的功能进行了拆分,拆分进了堆中和元空间中
栈:方法运行时使用的内存,如main方法运行,进入方法栈中执行
堆:存储对象或数组,new创建的都存储在堆内存中
方法区:存储可以运行的class文件
本地方法栈:JVM在使用操作系统功能时使用,与开发无关
寄存器:给CPU使用,与我们无关
栈
方法运行时进栈,运行完毕出栈,先进后出,后进先出
在方法内所声明的变量会存在于方法中,变量的值分为两种,实际类型和引用类型,引用类型的值是一串指向堆中位置的地址值。
堆
new出来的对象会在堆内存中开辟空间,并产生地址,以表示其堆内存中的位置 (所产生的地址值是 一串十六进制数据)
//会根据方法区内加载的对象 开辟对应的对象空间,并且对象内的方法指向方法区内对应方法的地址值
方法区
字节码文件(class)的存储空间, 每个字节码文件中含有成员变量,成员方法的临时存储
当创建对象时,若是第一次创建,会在方法区内加载类。
类加载器
作用:负责将.class文件(存储的物理文件)加载在到内存中
类加载时机:
创建类的实例(对象)
调用类的类方法
访问类或者接口的类变量,或者为该类变量赋值
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类
总结而言:用到了就加载,不用不加载
类加载过程:
加载:
通过包名 + 类名,获取这个类,准备用流进行传输
把这个类加载到内存中
加载完毕创建一个class对象,该对象拥有该类的完整信息
链接:
验证:
确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全
文件中的信息是否符合虚拟机规范有没有安全隐患)
准备:
负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值
(初始化静态变量)
解析:
将类的二进制数据流中的符号引用替换为直接引用 //在最开始加载类时,引用数据类型会被以符号替代
(本类中如果用到了其他类,此时就需要找到对应的类)
初始化:
根据程序员通过程序制定的主观计划去初始化类变量和其他资源
(静态变量赋值以及初始化其他资源)
类加载器的分类:
分类:
Bootstrap class loader:虚拟机的内置类加载器,通常表示为null ,并且没有父加载器//它的父类加载器是 null
Platform class loader:平台类加载器,负责加载JDK中一些特殊的模块
System class loader:系统类加载器,负责加载用户类路径上所指定的类库
继承关系:
System的父加载器为Platform
Platform的父加载器为Bootstrap
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//获取系统类加载器的父加载器 --- 平台类加载器
ClassLoader classLoader1 = systemClassLoader.getParent();
//获取平台类加载器的父加载器 --- 启动类加载器
ClassLoader classLoader2 = classLoader1.getParent();
双亲委派模型:
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,
如果父类加载器可以完成类加载任务,就成功返回,
倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
ClassLoader中的2个方法:
| 方法名 | 说明 |
|---|---|
| public static ClassLoader getSystemClassLoader() | 获取系统类加载器 |
| public InputStream getResourceAsStream(String name) | 加载某一个资源文件 |
public class ClassLoaderDemo2 {
public static void main(String[] args) throws IOException {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//利用加载器去加载一个指定的文件
//参数:文件的路径(放在src的根目录下,默认去那里加载)
//返回值:字节流。
InputStream is = systemClassLoader.getResourceAsStream("prop.properties");
Properties prop = new Properties();
prop.load(is);
System.out.println(prop);
is.close();
}
}
XML
XML是可扩展标记语言(eXtensible Markup Language)的缩写,它是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据
特点:
是纯文本,默认使用UTF-8编码,
可嵌套,适合表示结构化数据。如果把XML内容存为文件,那么它就是一个XML文件,例如book.xml。此外,XML内容经常通过网络作为消息传输
结构:
XML有固定的结构,首行必定是<?xml version="1.0"?>,可以加上可选的编码。
紧接着,如果以类似<!DOCTYPE note SYSTEM "book.dtd">声明的是文档定义类型(DTD:Document Type Definition),DTD是可选的。
接下来是XML的文档内容,一个XML文档有且仅有一个根元素,根元素可以包含任意个子元素,
元素可以包含属性,例如,<isbn lang="CN">1234567</isbn>包含一个属性lang="CN",且元素必须正确嵌套。如果是空元素,可以用<tag/>表示。
由于使用了<、>以及引号等标识符,如果内容出现了特殊符号,需要使用&???;表示转义。
常见的特殊字符如下:
| 字符 | 表示 |
|---|---|
| < | < |
| > | > |
| & | & |
| " | " |
| ' | ' |
格式正确的XML(Well Formed)是指XML的格式是正确的,可以被解析器正常读取。
而合法的XML是指,不但XML格式正确,而且它的数据结构可以被DTD或者XSD验证。
DTD文档可以指定一系列规则,例如:
根元素必须是book
book元素必须包含name,author等指定元素
isbn元素必须包含属性lang
正确性验证:
最简单的方式是通过浏览器验证。可以直接把XML文件拖拽到浏览器窗口,如果格式错误,浏览器会报错。
和结构类似的HTML不同,浏览器对HTML有一定的“容错性”,缺少关闭标签也可以被解析,|
但XML要求严格的格式,任何没有正确嵌套的标签都会导致错误。
<!-- 例子 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE note SYSTEM "book.dtd">
<book id="1">
<name>Java核心技术</name>
<author>Cay S. Horstmann</author>
<isbn lang="CN">1234567</isbn>
<tags>
<tag>Java</tag>
<tag>Network</tag>
</tags>
<pubDate/>
</book>
数据类型
字面量
字面量:数据在程序中书写的形式
字面量类型: 整数,小数,字符串,字符,布尔,空类型
特殊字符:
制表符: \t 在打印时,把前面字符串的长度补齐到8,最少补1 最多补8个空格
注 1 : null,也就是空类型是不能直接打印的
变量
变量的声明: 类型 变量名 = 值;
变量就是用一个 标识符 来记录一个值或地址
在同一个代码块中不能对一个已经声明的变量进行重新声明,只能声明一次
局部变量必须在使用前进行赋值,其没有默认值
成员变量可以有默认值,因此不用在使用前进行赋值
变量声明的位置:
局部变量:在方法、构造方法或代码块内部声明,作用域限于声明它的块内部,必须先声明后使用。
成员变量:在类内部但方法、构造方法或代码块之外声明,可以是实例变量或静态变量,有默认初始化值。
方法参数:在方法声明的参数列表中声明,作用域限于方法内部。
异常处理:在catch块中声明捕获的异常对象,作用域限于catch块内部。
初始化块:在静态或实例初始化块中声明变量,作用域限于初始化块内部
匿名类和Lambda表达式:在匿名类或Lambda表达式内部声明变量,作用域限于内部。
生命周期:局部变量在方法执行完毕后销毁,成员变量随对象生命周期存在。
变量隐藏:内层块中声明与外层块同名的变量会隐藏外层变量。
强类型语言:变量类型在编译时确定,不能随意更改。
注 1 :
//在一条语句中可以定义多条变量
int d =1 ,e = 2, f = 4;
//变量在使用前需要赋值
int a;
System.out.println(a);//发生报错
注2:
变量范围只在他所属大括号内有效(代码块)
计算机中的数据存储
在计算机中任何数据都是以二进制的形式来存储的
不同进制在代码中的表现形式:
//二进制, 0b开头
//八进制 0开头
//十进制 不加任何前缀
//十六进制 0x开头
System.out.println(0b2) 二进制
System.out.println(02) 八进制
System.out.println(1) 十进制
System.out.println(0x2) 十六进制
任意进制转十进制:
公式:系数*基数的权次幂 相加
系数:每一位上的数 ;基数:当前进制数;权:从右往左,以此为0,1,2,3,4...
基本数据类型
分别为:整数,浮点数(byte,short,int,long),小数(float,double),字符(char),布尔(boolean)
注 1 :
long: 需要加入 L表示(大小写都可以) //如 long a = 10L
float: 需要加入 F表示(大小写都可以) //如 float a = 10f
基本数据类型变量存储的是真实的数据
其赋值给其他变量,也是赋真实的值
字符(char) 其定义可以通过数字定义,数字与ASCII 表的字符相对应 如: char a = 65// a为 'A' 字符
注 2:
基本数据类型声明变量时,其在栈空间内是直接给变量声明实际的值
用 ' == ' 比较时,比较的时变量对应的实际数据值
引用数据类型
除基本数据类型意外的数据
其需要在堆内存中创建,引用数据类型变量的值是指向堆内存中的地址值
赋值给其他变量,是赋其地址值
引用:使用了其他空间的数据
注1:
引用数据类型声明变量时,是先在堆空间内开辟对应的空间,然后将对应的地址值赋值给该变量
注2:
用 ' == ' 比较时,比较的时变量对应的地址值
标识符
硬性要求:
由数字,字母,下划线_和美元符($)组成
不能以数字开头,不能是关键字,区分大小写
标识符命名规则:
小驼峰命名法(方法,变量):
是一个单词时,全部小写
由多个单词组成时,首字母小写,其他单词首字母大写
大驼峰命名法(类名):
一个单词时,首字母大写,多个单词时,每个单词首字母大写
运算符
符号:
//+ , - , * , / , %(分别为 加 减 乘 除 取模)
取模:做除法运算,取得余数
注1:
在代码中,如果有小数参与运算,结果有可能不精确的
整数操作只能获得整数,要想获得小数,必须有浮点数参与运算
3 / 2 = 1;
1.1+1.1 = 2.2;
1.1 + 1.01 = 2.1100000000003;
//原因:由于浮点数在计算机的存储是以二进制的形式去表示的 某些十进制的小数在二进制中会表示成一个无限循环的小数
算数运算符:
数字进行运算时,数据类型不一样的不能运算,需要转成一样的才能运算
数据转换:
发生时刻:数据类型不同时,不能运算,需要转为相同类型才能进行运算
隐式转换(自动类型提升):取值范围小 => 范围大
强制转换:取值范围大 => 范围小
//隐式
//规则: 1.取值范围小的和取值范围大的进行运算时,小的会先自动提升为大的,在进行运算 2.byte short char 类型运算时,都直接提升为int再进行运算
int a = 10;
double b = a;
//强制
long a = 100;
int b = (int)a;
字符串的“+”操作:
发生时刻: 当 "+"操作中出现字符串时, "+"变为字符串连接符,会将前后的数据进行拼接,形成新的字符串
注 1 : 连续 "+"操作时 从左到右逐个进行
String a = "hello";
String b = "world";
// a + b = hello world
// 1 + 2 +a = 3hello
//a + 1 = hello1
字符相加:
当字符 + 字符 , 字符 + 数字 时, 会把字符通过 ASCII 码表查询对应的数字再进行运算
// 1 + 'a' = 98
// 'a' + "abc" = aabc
自增自减运算符:
分为++ 和 --
int a = 1;
c = a++; //先用后加, c = 1
int b = 1;
c = ++b; //先加后用,c = 2
赋值运算符:
分别为: = += -= *= /= %= ,如: a ?= b, a 最终为 a = a ? b
关系运算符:
分别为: == != > < >= <=
结果都是 两边成立为true,不成立为false
逻辑运算符:
分别为:& | ^ !
& //逻辑与 两边都为true结果才为true
| //逻辑或 两边都为false结果才为false
^ //逻辑异或 相同 false ,不同 true , 也就是 一true,一false 结果才为true
//一个数字异或同一个数字2次,得到的数字还是他本身
! //逻辑非 取反
注1: 1 < a < 2 //这种表达式是错误的,需要用逻辑运算符进行链接 1<a & a<2
短路运算符:
分别为: && ||
&& //结果与&相同
|| //与 | 相同
//区别: 短路运算符 如果左边就能确定结果,则右边不参与运行
int a = 1,b = 1;
boolean result = a++ > 5 && b++ > 5; // 最后 a = 2 , b 依旧为1
三元运算符:
格式:关系表达式 ? 表达式1 : 表达式2; 关系表达式结果true 执行表达式1,为假执行表达式2 ,并将可以直接将结果赋值
int a = 2,b=1;
int max = a > b ? a : b ; // max 为 2
运算优先级:
小括号优先于所有


源码,补码,反码 :
定义:
原码:十进制数据的二进制表示类型,最左边是符号位,0为正,1为负
反码:正数的反码是其本身,负数的反码是符号位保持不变,其余位取反(0变1,1变0)
补码:正数的补码是其本身,负数的补码是在其反码的基础上 + 1
原码的弊端:利用原码对附属进行计算,结果会出错,实际运算的结果,与预期的结果相反, 对 -1 加1 ,结果为 -2
注 1 :
计算机中的存储和计算都是以补码的形式进行的
//原码
56 => 0 0 1 1 1 0 0 0 //一个0或1 成为一个bit ,也叫比特位 ,8个bit分为一组叫做一个字节
(符号位)
//反码
-56 原码 => 10111000 反码 => 11000111
其他运算符(对二进制进行操作):
& | ^ >> << >>>
& //逻辑与 0为false,1为true 都为true 为1,其余为0
1 & 2
| //逻辑或 0为false,1为true 有一个true 为1
1 | 2
>> //右移 向右移动,高位补0或1
1 >> 2 //移2位
^ //异或 不同为1 相同为0
1 ^ 2
<< //左移 向左移动,低位补0 负数补1,正数补0
1 << 3 //移三位
>>> //无符号右移 向右移动,高位补0
1 >>> 1 //移动1位
整数左乘右除, 右移动一位除2 左移1为乘2
流程控制
顺序结构
定义:顺序结构语句是java程序默认的执行流程,按代码的先后顺序,从上到下依次执行
分支结构
if语句
注1: 1.在语句体中如果只有一行代码,大括号可以省略不写( int a = 100; 此为两句代码 1. 定义变量a , 2. 为变量a赋值 )
if(关系表达式){
语句体
}
if(关系表达式){
}else{
}
if(关系表达式){
}else if(){ //如果其中一个关系表达式结果为true则之后的判断不再进行
}else if(){
}else{
}
switch语句
1.首先计算表达式的值(得到一个具体的结果)
2.依次和case后面的值进行比较,如果有对应的值,就会执行相应的语句,执行过程中遇到break就停止
3.如果所有的case后面的值和表达式的值都不匹配则执行default内的语句
switch(表达式){
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
default:
语句体n+1;
break;
}
/*格式说明:
1.表达式(将要匹配的值):取值为byte,short,int,char, jkd5后可以是枚举,jkd7后可以是String
2.case后面的值只能是字面量不能是变量
3.case给出的值不能重复
*/
/*拓展知识点:
1.default可以省略,default可以写在任意位置
2.break不能省略,会导致case穿透
case穿透: 程序还是正常执行,当与case中的值匹配上时,执行语句,遇到break时结束语句,但如果没有
发现break语句,则执行下一个语句体,不在进行判断,直到遇到break或者右大括号 } */
//3.switch新特性(JDK12)
switch(){
case 1 ->{}
case 2 ->{}
default ->{}
} //相当于用箭头把break简化了 ,如果只有一行代码大括号可以省略
//4. 可以用一个变量去接受switch语句得到的值(JDK12新特性)
var a = switch(){
case 1:
"返回的值";
break;
case 2 -> "";
};
循环结构
for循环:
//格式
for(初始化语句;条件判断语句;条件控制语句)
for(int i = 1; i > 10; i++){
}
//注: 初始化语句可以通过 数据类型 名称 = 数值,名称 = 数值; 来定义多个初始化变量,但是不能声明多个类型
for(int a = 0,b=4;a < b; a++,b--){
}
while循环:
//格式
while(条件判断语句){
循环体语句;
条件控制语句;
}
do...while循环
do{
循环体语句;
条件控制语句;
}while(条件判断语句);
//先执行后判断
无限循环
for( ; ; ){
};
while(true){
};
do{
}while(true);
//跳过某一次循环
continue;//结束本次循环开始下次循环(直接将循环语句停止在此处,然后进行下一次循环)
//结束整个循环
break; //直接将循环终止
嵌套循环
for(;;){
for(;;){
}
}
//注意,在嵌套循环中,如果内循环中出现break语句, 只跳出单层循环,也就是内循环,外部循环不受影响
!!//可以使用标号来直接从内循环直接全部退出,包括外循环
循环名字: 循环体{
内循环{
break 循环名字; //可以直接结束外循环
}
}
loop: while(true){
while(true){
break loop;
}
}
//如果在方法内可以直接return结束方法,也可以用 System.exit(0);来直接停止虚拟机运行(直接停止整个程序)
数组
什么是数组
数组指的时一种容器,可以存储同种数据类型的多个值
注:
数组容器在存储数据的时候,要考虑隐式转换,
数组的定义
//注,数组的类型除了基本类型也可以是任意对象的类型
//1. 数据类型[] 数组名
int[] array
//2. 数据类型 数组名[]
int array[]
数组的静态初始化
初始化:在内存中,为数据容器开辟空间,并将数据存入容器中的过程
//初始化的2中方式
//1.静态初始化
数据类型[] 数组名 = new 数据类型[]{元素1,元素2};
int[] name = new int[] {1,2,3};
简化格式: 数据类型[] 数组名 = {元素};
int[] name = {1,2,3};
元素访问
索引:也叫做下标,角标, 数组的索引从0开始
//1.获取数组内的元素
数组名[索引];
int[] arr = {1,2,3};
获取3: int index = arr[2];
//2.把数据存入数组中
数据名[索引] = 数据
arr[1] = 1;
//遍历数组
for(int i = 0;i < arr.length;i++){ //数组的一个长度属性; length
println(arr[i]);
}
动态初始化
定义:初始化时,只指定数组长度,由系统分配初始值。
//格式: 数据类型[] 数组名 = new 数据类型[数组长度]
int[] name = new int[3];
//数组默认初始化值规律:
//整数类型:0
//小数类型:0.0
//字符类型:'/u0000' 空格
//布尔类型:false
//引用类型:null (如String)
二维数组
初始化:
//静态初始化
数据类型[][] 数组名 = new 数据类型[][]{ {元素} , {元素} };
int[][] name = new int[][] {{1,2,3},{1,2,3}}
int a = name[1][1];//a为 2
//name[0] 表示改二维数组中的第一个数组 name[0][0] 表示第一个数组的第一个元素
//动态初始化
数据类型[][] 数组名 = new 数据类型[大小][大小];
//特殊情况:
//1
int[][] arr = new int[2][]; //若在空中写了数字,java会自动为我们将一维数组创建好
int arr1 = {1,2};
int arr2 = {3,4,3,4,5};
arr[0] = arr1;
arr[1] = arr2;
//2
//若是[] 中写了数字并且下文由将新的数组赋值给二维数组,则原来的数组会被覆盖,然后被回收
常见问题
数组在初始化时就规定了界限, 超过界限就会触发 索引越界问题
方法
定义
方法是程序中最小的执行单元
//方法的定义格式
返回值 方法名(参数){
语句体;
}
//方法与方法之间是平级关系,不分上下顺序,及方法的执行顺序与编写顺序无关
//方法的返回值为void时,可以省略return语句,如果要写,不要跟任何具体数据
//return表示结束方法和返回结果,整个方法在return处结束
形参和实参
形参:全程形式参数,指方法定义中的参数
实参:实际参数,指方法调用中的参数
注: 方法调用中,形参与实参必须对应
方法的重载
定义:
同一个类中,定义了多个同名的方法,每个方法具有不同的参数类型或参数个数,这些同名的方法就构成了重载关系,与返回值无关
即:同一个类中,方法名相同,参数不同,就构成重载关系 (参数不同:个数不同,类型不同,顺序不同)
注:
在调用方法时,若出现了方法重载现象,调用实参和形参类型一致的那个方法
如果方法重载的参数类型是父子类关系,容易引起混淆。建议在设计时尽量避免这种情况
在调用重载方法时,Java 会根据 传入参数的实际类型 选择最匹配的方法。
父类给父类,子类给子类
方法传递参数
分为传递基本数据类型和引用数据类型
1.传递基本数据类型时, 是传递值
2.传递引用数据类型时,是传递堆内存中的地址值
this关键字
当形参名字与成员变量相同时,会在该方法栈内存中直接创建该形参的变量并赋值
在之后该方法访问该名字时,访问的时该形参所创建的局部变量,要访问成员变量,需要在名字前加上this
面向对象
类和对象
类:是对象共同特征的描述
类的五大成员:属性,方法,构造方法,代码块,内部类
对象:是真实具体存在的东西
//在java中必须先设计类才能获得对象
//类的定义
public class 类名{ //public可不写,使用默认权限修饰符
1.成员变量(代表属性)
2.成员方法(代表行为)
3.构造器
4.代码块
5.内部类
}
public class person{
}
//获取类对象
类名 对象名 = new 类名();
person temp = new person();
//使用对象
访问属性: 对象名.成员变量 访问行为: 对象名.方法名(...)
//注意事项
1.用来描述一类事物的类,叫做 javabean类,在javabean类中 不写main方法
2.一个java文件中可以定义多个class类,但是只能有一个类声明public,并且public修饰的类名必须为java文件名,如果没有public修饰,则文件名随意
3.成员变量默认值:
基本类型: byte,short,int,long 默认值为 0
float,double 默认值为 0.0
boolean 默认值为 false
引用类型:默认值为 null
拓展:
类的成员分别为: 构造方法,成员变量,成员方法
封装
定义:对象代表什么,就得封装对应的数据,并提供数据对应的行为
1.用private权限修饰符将属性私有
2.定义set...和get...方法来编辑和获取相应属性
构造方法
也叫做构造器,构造函数,在创建对象时,给成员变量进行初始化
//格式
修饰符 类名(参数){
方法体;
}
//方法名与类名相同,无返回值,void也没有
//可根据参数的不同定义多个构造方法(即构造方法重载)
//每一次创建对象都会调用一次构造方法
//若没有写任何构造方法,JAVA会自动创建一个空参构造
//在构造方法中可以调用其他方法(可把构造方法理解为在初始化时调用的一个方法)
!//若是定义了构造方法,无论有参无参,JAVA将不会在创建空参构造
可以在构造方法中通过调用 this(参数)来调用本类中的其他构造方法 //通过该方法来构造方法,该构造方法第一行就不会再自动添加super()构造了,因为在调用的另一个构造方法中第一行已经调用了super() 注:因此用this(参数)调用其他构造方法必须写在第一行
成员变量和局部变量
位置 初始化值 内存位置 生命周期 作用域
成员变量: 类中,方法外 有 堆中 与对象同在 整个类中
局部变量:方法内,方法声明上 无,使用前要完成赋值 栈中 与方法同在 当前方法
!//局部变量默认private修饰
权限修饰符
用来控制一个成员能够被访问的范围,可修饰 成员变量,方法,构造方法,内部类
public: 所修饰的类,成员可以被所有内部成员和外部对象访问
protect: 对于同一个包的类,这个类的方法或变量是可以被访问的。对于不同包的类,只有继承于该类的类才可以访问到该类的方法或者变量。
private: 所修饰的成员 仅可被内部成员访问 //子类也无法访问,但可以通过父类提供的方法进行访问(private成员被隐藏起来了)
默认修饰符: 所修饰的类,成员 仅可被内部成员和同一个包内对象访问
使用规则:
一般只用private和public ,成员变量私有,方法公开
如果方法是抽取其他方法中的共性(重复)代码,该方法一般也私有
注 1 :
类只可以被public和默认修饰符修饰
注2:
内部类可以访问父类所有成员,包括private所修饰的成员,但静态内部类只可以访问静态成员,包括private static 修饰的成员
权限修饰符是修饰 成员 的 无法用来修饰局部
static
定义:表示静态,是java中的一个修饰符,可以修饰成员方法,成员变量,也可以用来修饰内部类
//内部类可以被 public、protected、private 或默认(包私有)修饰符修饰
调用方式:类名调用或对象名调用
静态变量:
被static修饰的变量就是静态变量
//注: JDK8以前静态区时开辟在方法区内
当出现静态变量时,JAVA会在堆内存中开辟一个静态存储位置(静态区),静态变量会存储于里面
静态变量时随着类加载而加载的,是优先于对象出现的
静态方法:
被static修饰的方法
static注意事项:
静态方法只能访问静态变量和静态方法,非静态方法可以访问静态和非静态成员
静态方法中无this关键字
注: 静态方法只能访问静态是指,其不能访问本类中的动态成员,可以理解为静态方法是相对于本类动态成员独立出来的成员,但其可以通过 实例化任 意对象来访问动态成员
//在非静态方法中有一个隐藏的形参this,在调用方法时,虚拟机会自动给这个this赋值,谁调用这个方法,this就表示谁的地址值
void method(classname this){};
//在非静态方法中调用成员变量和成员方法时,成员变量和成员方法前会有一个隐藏的this name -> this.name method() -> this.method()
继承
可以把多个子类中重复的代码抽取到父类中,子类可以直接使用,减少代码冗余
如果子类只是重写了父类的方法,那么它们之间的关系就是 is-a 的关系,但如果子类增加了新的方法,那么它们之间的关系就变成了 is-like-a 的关系。
组合:
我们可以把一个创建好的类作为另外一个类的成员变量来使用,利用已有的类组成成一个新的类,被称为“复用”,组合代表的关系是 has-a 的关系。
若是组合不能满足对应需求,则使用继承
继承特点:
1.子类可以得到父类的属性和行为,子类可以直接使用
2.子类可以在父类的基础上新增其他功能
3.java只支持单继承,不支持多继承,但支持多层继承 //每个类都之间或间接继承Object类
4.子类只能访问父类中非私有的成员(不被private所修饰)
5.在对子类的成员进行访问时,会先访问其子类的成员,如果没有再访问其父类的成员(除构造方法,子类不继承父类的构造方法)
//子类能继承父类的哪些内容:
构造方发: 无论是否私有都不能继承
成员变量: 无论是否私有都能继承 //如果私有,会被隐藏,需要通过父类的方法才可以访问,即能继承但不可调用
成员方法: 可以继承非私有方法,不能继承私有方法
注:
成员变量:在子类的对象中会把父类的成员变量也继承下来,在堆中划分空间,一部分为父类变量,一部分为子类变量,多层继承则划分多部分
成员方法:如果子类对方法进行了重写,那么虚方法表中的方法是被覆盖掉的
成员变量访问特点
若出现子类与父类变量名称重合可通过 super.变量名称 来访问父类的成员变量
若不重合,则从子类到父类逐级向上访问成员变量
成员方法访问特点:
若是出现子类对父类的方法重写,则通过super.方法名 可以调用父类的方法
注:
!若是不对父类方法重写,调用继承下来的方法,对成员变量的调用是调用父类,对方法的调用时若该方法被子类重写则调用子类方法(在虚方法表 !内的才可以被重写)
!这里与java的动态分配和静态绑定有关
!!!为了避免复杂,在父类要定义get set方法,父类的成员变量,子类不要定义重复的变量,避免混杂
构造方法的访问特定:
子类不会继承父类的构造方法
子类中所有构造方法默认先访问父类的构造方法,再执行自己//子类构造方法第一行默认是 super,不写虚拟机也会自动加上
若要调用父类的有参构造,就必须手写super
方法重写:
书写格式:子类和父类书写一模一样的方法声明
重写方法的名称,形参列表必须与父类中的一致
子类重写父类方法时,重写方法的访问权限必须大于等于父类方法,返回值类型必须子类小于等于父类(父类类型的范围大于子类类型的范围)
子类不能重写父类的静态方法,若是方法名一样的静态方法,只能说是子类有一个与父类方法名一样的静态方法//重写只能重写虚方法内的方法
私有方法不能被重写,重名同上
**! **若父类方法没有抛出异常,子类重写该方法也不能抛出异常
定义(重名和形参列表相同)方法会报错的几个类型:
非静态方法与父类静态方法不能重名,若要重名需要形参列表不同才可以
final修饰的方法,若要重名需要形参列表不同才可以
@Override注解: 放在重写后的方法上,用以校验重写后的方法语法是否正确
方法重写的本质:
子类覆盖了从父类继承下来的虚方法表内的方法
继承的格式:
public class 子类 extend 父类(){}
继承的内存:
在new一个继承了其他类的对象时:
除了加载该类的字节码文件,其父类的字节码文件也会被加载
会在堆中开辟一个该对象的空间,并将该空间分为2部分,一部分存储父类成员变量,一部分存储子类成员变量
注:如果是多层继承,则根据继承分配部分,如 子类->父类->间接父类1->间接父类2 则分为4部分来存储变量
成员方法的继承:
在继承中,在继承顶端的父类会设立一个虚方法表,其中放入了常用的方法(非private,非static,非final),然后再继承时,将方法表依次处理传递
即子类只能继承虚方法表内的方法(注:继承是获得父类的成员,这里没有在方法表内的方法没有被继承,但可以被子类调用到)
之后再调用一个方法时,会先从虚方法里面找,若是不存在,则从子类依次往父类上级寻找
//注:虚方法表内的方法是记录着该方法原位置的内存
多态
定义:同种类型的对象,表现出的不同形态
表现形式: 父类类型 对象名称 = 子类对象;
多态的前提:1.有继承/实现关系,2.有父类引用指向子类对象: 父类 名称 = new 子类(); 3.有方法重写
多态调用成员的特点:
变量调用:编译看左边。运行也看左边。 //这里的调用是指,对象.变量名 来直接调用,而不是在方法内调用,在方法内调用与正常一样
方法调用:编译看左边,运行看右边
静态方法没有多态
成员变量没有多态
构造方法没有多态
多态的弊端:
不能调用子类特有的功能
//解决: 使用向下转型
instanceof关键字:
前面为变量名,后面为类名, 用以判断该 变量是否继承该类或就是该类(判断前面的对象是否属于后面的类,或者属于其子类)
//自动类型转换(向上转型)//多态的实现
father t = new son();
//向下转型(强制类型转换)
son t2 = (son)t;
//instanceof
if(t instanceof father);//true
//JDK14后新特性: 可把判断与强转合在一起
if(t instanceof son d);
相当于如果判断成功,直接把t强转然后赋值给d
包
包就是文件夹,用来管理不同功能的java类,方便后期的代码维护
包名的命名规则:公司域名反写+包的作用,需要全部英文小写
全类名(全限定名):包名.类名
//使用其他类时需要用到全类名
包名.类名 name = new 包名.类名();
//简化: 通过导包可以省却包名
import 包名;
类名 name = new 类名();
//使用其他类的规则:
1.使用同一个包中的类时,不需要导包
2.使用java.lang包中的类,不需要导包 (lang包是java的核心包)
3.其他情况都要导包
4.如果同时使用2个包中 类名相同的类时,需要使用全类名
final
修饰:
方法:该方法是最终方法,不可被重写
类:该类是最终类,不可被继承
变量:该变量称为常量,只能赋值一次,之后就不能被改变了//注:被final修饰不能默认初始化,必须在声明时就给他赋值了
常量:
一般作为系统的配置信息,方便维护
命名规则:
单个单词:全部大写
多个单词:全部大写,单词之间用下划线 _ 分隔开
final修饰基本类型:存储的数据值不能改变
final修饰引用类型:存储的地址值不可以改变,对象内部可以改变
代码块
局部代码块
局部:方法内部
局部代码块就是方法内部的代码块
构造代码块
成员:方法外部,类内部
构造代码块就是写在成员位置的代码块,当创建本类对象时,会优先于构造方法执行,可把多个构造方法重复的代码抽取到构造代码块内执行
//基本用不到
静态代码块
需要通过static关键字修饰,随着类的加载而加载,并自动触发,且只执行依次
使用场景:在类加载的时候,需要做一些数据初始化的时候使用
//局部代码块
public static void main(String[] args){
//局部代码块
{
int a=1;
}
print(a);//error, 变量生效范围只在他所属的代码块内
}
//构造代码块:
class person{
{
print("这里是构造代码块");
}
}
//静态代码块
class person{
static{
print("静态代码块执行,只执行一次")
}
}
抽象类
抽象方法:
将共性的方法抽取到父类后,每个子类的执行内容不同,所以在父类中不能确定具体的方法体,该方法就可以定义为抽象方法
抽象类:
如果一个类中存在抽象方法,这个类必须声明为抽象类
注意事项:
抽象类不能实例化,抽象类可以没有抽象方法,也可以有正常方法,但是有抽象方法的类一定是抽象类,
抽象类有构造方法:在子类创建对象时,给子类构造方法调用来给属性赋值
抽象类的子类:要么重写抽象类的所有抽象方法,要么子类也定义为抽象类
//抽象方法定义格式:
public abstract 返回值类型 方法名(参数列表); //不用写代码块
//抽象类定义格式:
public abstract class 类名{}
接口
接口的定义和使用:
接口用关键字interface来定义
接口不能实例化
接口和类之间时实现关系,通过implements关键字表示
接口的子类(实现类): 要么重写接口内的所有抽象方法,要么实现类是抽象类|
接口可以单实现也可以多实现: implements 接口1,接口2
实现类可以继承一个类的同时实现多个接口
接口中成员的特点:
成员变量:
只能是常量
默认修饰符:public static final //就算不写 java也会自动加上
无构造方法
成员方法:
JDK8以前:只能定义抽象方法
默认修饰符:public abstract
JDK8新特性:可以定义有方法体的方法(默认,静态)
JDK9新特性:可以定义私有方法
接口与接口的关系:
继承关系,可以单继承也可以多继承
如果实现类实现的时最底层接口,则该类要把该接口及其父接口的所有抽象方法都实现
接口应用总结:
接口代表规则,是最行为的抽象,想让哪个类拥有一个行为,让那个类实现对应的接口即可
接口多态:当一个方法的参数是接口时,可以传递该接口的所有实现类的对象,这种方式就称之为接口多态
格式: 接口名 变量名称 = new 实现类名();
适配器设计模式:
设计模式:是一套被反复使用,多数人知晓的,经过分类编目的,代码设计经验的总结。
使用设计模式是为了复用代码,让代码更容易被他人理解,保证代码的可靠性,程序的复用性
//简单理解:设计模式就是各种套路
适配器设计模式:解决接口与接口实现类之间的矛盾问题
使用:如果一个接口中抽象方法过多,可以编写中间类实现对应的接口,对接口进行空实现,然后再继承中间类,以实现过滤抽象方法的作用
//中间类一般用abstract修饰,以避免其他类创建适配器类对象,若实现类想要继承其他类,可让中间类继承对应类
//定义:
public interface 接口名{}
//使用:
1.单实现
public class 类名 implements 接口名{}
2.多实现
public class 类名 implements 接口1,接口2{}
//接口与接口:
public interface f1 extends f1,f2{}
//JDK8新增的方法:
//允许在接口内定义默认方法,需要用关键字 default 修饰,public是默认修饰符可以省略
//默认定义格式:
public default 返回值 方法名(形参){} //默认方法不是抽象方法,不强制重写,重写时要去掉default关键字
!//如果实现的多个接口中有相同方法名的默认方法,实现类必须重写该方法(实现类对象调用方法时无法分清调用哪个方法)
//静态方法定义格式
public static 返回值 方法名(形参){}
!!!//静态方法只能通过接口名调用,不能通过实现类名或对象名调用,public是默认修饰符可以省略
//JDK9新增的私有方法
//定义格式:
默认:
private 返回值类型 方法名(){} //普通的私有方法,为default方法服务
静态:
private static 返回值类型 方法名(){} //静态私有方法,为静态方法服务
//jdk8与jdk9新增的方法 只有default默认方法需要加default 其他与普通方法格式一样且public是默认修饰符
注:
当出现实现的2个接口有同名方法时,没有影响
内部接口:
内部接口也被称为嵌套接口,这意味着在另一个接口内声明一个接口。 例如,Entry接口声明在Map接口中
接口也同可以和内部类一样,其可以在内部定义内部接口
因为接口不能被实例化,所以内部接口只有在静态时才有意义。 因此,默认情况下,无论是否手动添加静态,内部接口都是静态的
public interface Map {
interface Entry{
int getKey();
}
void clear();
}
内部类
内部类就是在一个类里面再定义一个其他的类//如果一个类定义在另一个类的内部,这个类就是内部类
遵守规则:
内部类表示的事务一般是外部类的一部分
内部类单独出现没有意义
内部类的访问特点:
内部类可以直接访问外部类的成员,包括私有
外部类要访问内部类的成员,必须创建对象
成员内部类:
写在成员位置,属于外部类的成员
//在JDK16之前内部类内部不能定义静态变量,JDK16之后就可以定义了
//创建成员内部类对象时,对象中有一个隐含的 外部类名.this 来记录外部类的地址值(内部类成员可以用此来访问外部成员)
获取成员内部类对象:
方法一:在外部类编写方法,对外提供内部类对象
方法二:直接创建格式: 外部类名.内部类名 对象名 = 外部类对象.内部类对象
Outer.Inner object = new Outer().new Inner();
静态内部类:
在成员内部类前用static修饰,其可以直接访问外部类的静态成员,若要访问非静态成员需要创建对象来访问
//静态内部类不用依靠外部类而存在,且没有隐含的 外部类名.this
局部内部类:
将内部类定义在方法里面,类似于方法内的局部变量
外界无法直接使用局部内部类,需要在相应的方法内部创建对应对象并使用
该类可以直接访问外部类成员也可以直接访问方法内的局部变量
//局部默认被private修饰,不可更改
匿名内部类:
匿名内部类本质上是隐藏了类名的内部类
//包含了继承和实现关系(以类创建则此内部类就是该类的子类,以接口创建则内部类就是该接口的实现类)
//方法重写,创建对象
//匿名内部类可以写在成员位置也可写在局部位置 (也可写在方法参数内作为实参)
//成员内部类格式:
public class 外部类{
//成员内部类的地位与其他成员地位一样,其与其他成员一样可以被修饰符修饰(private,默认,protected,public,static等)
修饰符 class 内部类{}
}
//内部类的使用格式:
//外部类.内部类,访问内部类的类型都是用 外部类.内部类
public class Outer{
class Inner{}
//方法一 (通过外部类方法创建获得):
public Inner getInner(){return new Inner();}
}
//方法二 (创建外部类对象再用外部类对象创建内部类对象):
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
//简写
Outer.Inner object = new Outer().new Inner();
//静态内部类格式:
public class Outer{
static class inner{}
}
//创建静态内部类对象的格式:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner name = new Outer.Inner();
//调用静态内部类中非静态成员:
//先创建对象,用对象调用
//调用静态内部类中静态成员:
//直接用类名调用: Outer.Inner.方法名();
//局部内部类格式:
public class Outer{
void method(){
//局部内部类
class Inner{
//内部与正常类一样
public void method1(){}
public static void method2(){}//JDK16后才能在内部类里定义静态方法
}
//需要在方法内创建对象才可调用
Inner name = new Inner();
name.method1;
Inner.method2;
}
}
//匿名内部类格式:
new 类名或接口名(){ //后面的{} 里面的才是没有名字的类,它是前面类名或接口的 子类或实现类,然后new是创建一个该内部类的对象
重写方法;
}
//外部其他类:与这2个类无关的其他类(该类文件其他类或者该类文件中除外部类外的其他类)//一个类文件中可以声明多个类,但只可声明一个public修饰的类
内部类内存:
内部类与外部类编译出来的class文件是分开的,成员内部类的字节码文件名为: 外部类名&内部类名。class
//匿名内部类也一样,但是不会显示内部类名,而是将内部类名变为从上到下以1开始按顺序增长的数字
在新建内部类对象时,会和正常类一样在堆空间中开辟独立的空间,但其有一个隐藏的成员变量 外部类名.this 来记录外部类的地址值
//静态内部类没有 外部类名.this
枚举类
为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类
枚举类比较:
枚举类内的成员是该枚举类的类型,比较需要通过equals方法来比较
枚举类特点:
每个枚举成员都是常量
枚举类总是继承自java.lang.Enum,且无法被继承
枚举类无法被创建对象
枚举类的成员的类型就是该枚举类
枚举类作为内部类时,默认是静态内部类
枚举类可以应用在switch语句中
枚举类的方法:
name() :返回常量名 //如 String s = Weekday.SUN.name(); //s被赋值SUN
ordinal():返回定义的常量的顺序,从0开始计数,改变枚举常量定义的顺序就会导致ordinal()返回值发生变化
枚举类常量:
每个枚举类常量都是一个该枚举类类型的成员
可以定义该枚举类的构造方法来为枚举类成员获得一些属性
//定义
public enum 类名{
枚举名称1,枚举名称2,...;
}
//枚举类例子
public enum Color{
RED,GREEN,BLUE;
}
public final class Color extends Enum { // 继承自Enum,标记为final class
// 每个实例均为全局唯一:
public static final Color RED = new Color();
public static final Color GREEN = new Color();
public static final Color BLUE = new Color();
// private构造方法,确保外部无法调用new操作符:
private Color() {}
}
//定义枚举类常量
enum Weekday {
MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0); //为每个枚举类成员内部的dayValue属性赋值
public final int dayValue;
private Weekday(int dayValue) {
this.dayValue = dayValue; } }
对象内存
创建步骤:
1.加载class文件
2.声明局部变量
3.在堆内存中开辟空间
4.默认初始化 //为成员变量赋上默认的值
5.显示初始化 //根据类中对成员变量的赋值进行赋值
6.构造方法初始化//有代码块的话这里先执行代码块再执行构造方法
7.将堆内存中的地址值赋值给局部变量
在 Java 继承中,父子类初始化先后顺序为:
父类中静态成员变量和静态代码块
子类中静态成员变量和静态代码块
父类中普通成员变量和代码块,父类的构造方法
子类中普通成员变量和代码块,子类的构造方法
this的内存原理:
this本质:代表方法调用者的地址值
字符串
String
java.lang.String 类代表字符串,java中所有字符串文字都为此类的对象
字符串的内容是不会发生更改的,他的对象在创建后不能被更改
创建String对象的两种方法
//1.直接赋值
String temp = "赋值";
//2.使用new方法创建
1.空参
2.传递一个字符数组
3.传递一个字节数组//会根据字节在ASCII码表中对应数据进行转换
4.传入字符串
字符串的比较
//1.用 == 号比较 是比较其地址值
//2.用String对象的 equals 方法比较
boolean result = s1.equals(s2);
StringBuilder
StringBuidler可以看作是一个容器,其内容是可变的
String每一次拼接都会创建新的对象,而StringBuilder不会
//常用方法:
StringBuilder s = new StringBuilder("字符串");
append //添加数据并返回对象本身
reverse //反转容器中的内容
length //返回长度(字符出现的个数)
toString //把StringBuilder转换成String
//使用StringBuilder的场景: 1.字符串拼接 2.字符串反转
链式编程
定义:在调用一个方法的时候,不需要用一个变量去接收结果,可以继续调用其结果的其他方法
内存
StringTable串池:
在堆内存中(原在方法区中,JDK7开始移到堆内存中)
//当使用双引号("") 赋值时,系统会检查该字符串在串池中是否存在,不存在就创建新的,存在就引用对应地址值
//直接new, 每一次都会在堆内存中创建空间
字符串拼接底层原理
如果是 纯字符串 相拼接,都是字符串直接相加,会复用串池中的字符串(在编译后,直接就是拼接之后的结果)
//JDK8以前:如果有变量参与,每一次拼接,都会创建新的字符串(底层调用StringBuilder拼接,然后调用toString方法转字符串)//会创建多余对象
//JDK8开始:JAVA会先预估最终字符串的长度,然后创建对应的数组,然后将该数组变为字符串(解决了单行多变量拼接问题,多行仍会创建多余对象)
StringBuilder原理:
初始容量为16,当大于初始容量时,自动按 老容量(也就是16)*2+2 = 新容量来扩容(34)//如果仍超过扩容的容量,则按实际容量来存储
容器
集合长度可变,可以存引用数据类型,不能单独存基本数据类型,需要通过包装类来存储基本数据类型
---集合体系结构---
Collection
单列集合:每次添加一个元素

List系列集合:
添加的元素是有序,可重复,有索引
有序:存入和取出的顺序是一样的
Set系列集合
添加的元素是无序,不可重复,无索引
Map
双列集合:每次添加一对元素(键值对,分别为键和值)
键是唯一的不可重复,值不是唯一的可以重复,键和值是一一对应的关系
键+值这个整体,成为键值对,也叫键值对对象,在java中叫做 Entry 对象
键的特点:无序,不重复,无索引
数据结构
介绍:
数据结构就是 计算机存储,组织数据的方式,是指数据之间是以什么方式排列在一起的
数据结构是为了更加方便的管理和使用数据,不同的业务场景要选择不同的数据结构
栈:
后进先出,先进后出
队列:
先进先出,后进后出
数组:
查询速度快:查询数据通过地址值和索引定位,查询任意数据的时间相同(元素在内存中是连续存储的)
删除效率低:要将原始数据删除,同时后面每个数据前移
添加效率极低:添加位置后面的每个元素后移,再添加元素
及查询快增删慢
链表:
在链表中数据是存储在节点中,每个节点内有2个数据,一个是存储的数据,另一个是指向下一个节点的地址值
链表中的节点是独立的对象,在内存中是不连续的
链表查询慢,无论查询哪个节点都是从头开始找(第一个节点也有自己的数据,是头节点) ^符号表示该节点后面无节点了记录的是空地址值
//单项链表只能从头节点开始查,双向链表两边都可以查找
链表增删相对快(相对数组),首尾操作极快
当查询第几个元素时双向链表的查询效率比单项跟高: 双向链表可以比较该元素离头近还是离尾近

树:
树是由多个节点组成的
度:每个节点的子节点的数量
树高:树的总层数
根节点:最顶层的节点
左子树:该节点左边的所有节点
右子树:该节点右边的所有节点
节点:
每个节点都由 父节点,子节点的地址值,以及其记录的数据的值
二叉树:
每一个节点上最多有两个子节点
二叉查找树:
左子树上所有节点的值都小于根节点的值
右子树上所有节点的值都大于根节点的值
添加节点规则:小的存左边,大的存右边,一样的不存
平衡二叉树:
高度平衡:二叉树左右两个子树的高度差不超过1
任意节点的左右两个子树都是一颗平衡二叉树
平衡二叉树通过旋转形成
平衡实现:
通过确定支点然后在支点处发生旋转,使其成为平衡二叉树
确定支点:
从新添加的节点位置开始,不断的往父节点方向查找不平衡的节点
旋转:
当添加一个节点之后,该树不再是一颗平衡二叉树,就会发生旋转使其成为平衡二叉树
左旋:
就是将支点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的支点当右子节点
右旋:
就是将支点的左侧往右拉,左子节点变成了新的父节点,并把多余的右子节点出让,给已经降级支点当左子节点
平衡二叉树旋转的四种情况:
左左: 当根节点左子树的左子树有节点插入,导致二叉树不平衡
如何旋转: 直接对整体进行右旋即可
左右: 当根节点左子树的右子树有节点插入,导致二叉树不平衡
如何旋转: 先在左子树对应的节点位置进行左旋,在对整体进行右旋
右右: 当根节点右子树的右子树有节点插入,导致二叉树不平衡
如何旋转: 直接对整体进行左旋即可
右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡
如何旋转: 先在右子树对应的节点位置进行右旋,在对整体进行左旋
红黑树:
红黑树的增删改查的性能都很好
红黑树是一种自平衡的二叉查找树,是计算机科学用到的一种数据结构
红黑树的每个节点都有存储表示节点的颜色
每个节点可以是红或黑色,红黑树不是高度平衡的,它的平衡是通过红黑规则实现的
红黑规则:
每一个节点或是红色的,或者是黑色的,根节点必须是黑色
如果一个节点没有子节点或者父节点,则该节点相应的(子或父)节点的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的
//叶节点只是用来判断是否满足红黑规则的,其余没有任何意义
如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连 的情况)
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
添加节点的规则:
添加节点默认颜色是红色(效率更高)

哈希表:
哈希表是一种对于增删改查性能都比较好的结构
组成:
JDK8以前:数组+链表
JDK8后:数组+链表+红黑树
哈希值:
对象的整数表现形式,是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中的public int hashCode():返回对象的哈希码值
同一个对象多次调用hashCode()方法返回的哈希值是相同的//默认的hashCode方法是通过对象的地址值计算哈希值
因此会重写hashCode()方法,利用对象内部的属性值计算哈希值,可以实现让不同对象的哈希值相同
哈希碰撞:
int的大小是有限的,因此当对象足够多时,不同的属性且不同对象计算出来的哈希值也有可能一样
HashSet的底层原理:
1.创建一个默认长度为16,加载因子为0.75的数组。数组名为table //加载因子用于数组扩容
2.根据元素的哈希值和数组的长度计算出应存入的位置(公式: int index = (数组长度 - 1) & 哈希值 )
3.判断存入位置是否为null,为null存入
4.不为null,则调用equals方法比较两者的属性值,一样则舍弃不存,不一样则存入数组,形成链表
5.JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8后:新元素直接挂在老元素下面
//JDK8之后 当链表长度超过8,且数组长度大于64,链表自动转为红黑树
//若存储的时自定义对象,必须重写hashCode和equals方法 //2者默认都是根据对象的地址值进行计算
Collection
Collection是单列集合的祖宗接口,它的功能是所有单列集合都可以继承使用的
常见方法:

//contains方法:
底层是依赖顶级父类Object的equals方法进行判断是否存在的
因此,若集合中存储的是自定义对象(引用类型),在类中要重写equals方法
注:equals方法默认是判断2者地址值是否相等
Collection的遍历方式:
三种通用遍历方式:
因为set集合是无索引的,因此其父类Collection也无索引,所以要用特殊的遍历方式才能遍历
迭代器:在遍历中需要删除元素时可使用
增强for:只是遍历
Lambda:只是遍历
迭代器遍历:
迭代器在java中的类是 Iterator,迭代器是集合专用的遍历方式
迭代器遍历集合是不依赖索引的
获取迭代器:
Iterator
Iterator中的常用方法:
boolean hasNext():判断当前位置是否有元素可以被取出
E next():获取当前位置的元素,并将迭代器对象移向下一个索引位置
void remove(): 删除迭代器对象当前指向的元素
//迭代器
Collection<String> c = new ArrayList<>();
//添加元素
c.add("hello");
c.add("world");
c.add("java");
c.add("javaee");
//Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
Iterator<String> it = c.iterator(); //获取指针
//用while循环改进元素的判断和获取
while (it.hasNext()) { //判断指针处是否有元素
String s = it.next(); //获取元素,并移动指针
//当next将指针移动到没有元素的位置时,hasnext返回false
System.out.println(s);
}
//迭代器遍历完毕,指针不会复位,要再次遍历需要再次获取新的迭代器对象
//如果当前迭代器指向位置没有元素,还要.next()获取偶,会报NoSuchElementException异常
//迭代器遍历时,不能调用该迭代器集合的方法进行增加或删除元素(会报 并发修改 异常错误)
//若要删除,需要调用 迭代器 的remove方法
it.remove();//指向谁,那么此时就删除谁.
增强for遍历:
增强for的底层就是迭代器,其是为了简化迭代器的书写的
其在JDK5之后出现,内部原理是一个Iterator迭代器
只有单列集合和数组才能用增强for遍历
格式:
for( 类型 变量名 : 数组或集合){ }
将冒号后的数组或集合赋值给冒号前的变量,变量可在后面的代码块内使用,遍历的次数就是数组或集合的大小
//增强for
Collection<String> list = new ArrayList();
for(String s : list){
print(s);
}
//用idea快捷自动创建增强for遍历
集合或数组 + . + for + 回车 即可创建
//增强for获取的值是从新拿了一个新的变量记录集合或数组里面对象的数据
Lambda表达式遍历:
利用forEach方法,再结合lambda表达式的方式进行遍历
/*
lambda表达式遍历:
default void forEach(Consumer<? super T> action):
*/
//1.创建集合并添加元素
Collection<String> coll = new ArrayList<>();
coll.add("zhangsan");
coll.add("lisi");
coll.add("wangwu");
//2.利用匿名内部类的形式
//底层原理:
//其实也会自己遍历集合,依次得到每一个元素
//把得到的每一个元素,传递给下面的accept方法
//s依次表示集合中的每一个数据
coll.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//lambda表达式
coll.forEach(s -> System.out.println(s));
}
}
List
特点:
有序:存取有序
有索引:可以通过索引操作元素
可重复:存储的元素的值可以重复
List特有的方法:
| 方法名 | 描述 |
|---|---|
| void add(int index,E element) | 在此集合中的指定位置插入指定的元素//原来索引位置上的元素向后移动 |
| E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
| E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
| E get(int index) | 返回指定索引处的元素 |
List集合的五种遍历方式:
迭代器
列表迭代器 :ListIterator
增强for
Lambda表达式
普通for循环
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//普通for循环
//size方法跟get方法还有循环结合的方式,利用索引获取到集合中的每一个元素
/*for (int i = 0; i < list.size(); i++) {
//i:依次表示集合中的每一个索引
String s = list.get(i);
System.out.println(s);
}*/
// 列表迭代器
//ListIterator在Iterator的基础上额外增加了一个方法 add 可在遍历时添加元素
//获取一个列表迭代器的对象,里面的指针默认也是指向0索引的
//额外添加了一个方法:在遍历的过程中,可以添加元素
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String str = it.next();
if("bbb".equals(str)){
//qqq
it.add("qqq");//list迭代器新增方法,在迭代器指向的元素处后添加,后面的元素依次向后移动
}
}
ArrayList
//创建集合对象
ArrayList<String> arr = new ArrayList<String>();
//JDK7之后可以省略new对象时对泛型的声明
ArrayList<String> arr = new ArrayList<>();
//成员方法:
add//增加元素
remove//移除元素,可以删除指定元素,也可以删除指定索引元素
set//修改元素
get//获取元素
size//获取集合元素的个数
底层原理:
利用空参创建的集合,在底层创建一个默认长度为0的数组
添加第一个元素时,底层会创建一个新的长度为10的数组
存满时,会把这个长度为10的数组扩容1.5倍
如果一次添加的元素个数超过扩容1.5倍的数组大小,则新创建数组的长度以实际的元素个数为准
Linkedlist
底层数据结构时双链表,增删快,查询慢,
Linkedlist特有方法:
| 方法名 | 说明 |
|---|---|
| public void addFirst(E e) | 在该列表开头插入指定的元素 |
| public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
| public E getFirst() | 返回此列表中的第一个元素 |
| public E getLast() | 返回此列表中的最后一个元素 |
| public E removeFirst() | 从此列表中删除并返回第一个元素 |
| public E removeLast() | 从此列表中删除并返回最后一个元素 |
底层原理:

Set
特点:
无序:存取顺序不一致
不重复:数据不重复(可用来数据去重)
无索引:没有带索引的方法
Set接口中的方法基本上与Collection的API一致
Set的实现类:
HashSet:无序,不重复,无索引
LinkedHashSet:有序,不重复,无索引
TreeSet:可排序,不重复,无索引
HashSet
HashSet集合底层采取哈希表存储数据
哈希表:
哈希表是一种对于增删改查性能都比较好的结构
组成:
JDK8以前:数组+链表
JDK8后:数组+链表+红黑树
哈希值:
对象的整数表现形式,是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中的public int hashCode():返回对象的哈希码值
同一个对象多次调用hashCode()方法返回的哈希值是相同的//默认的hashCode方法是通过对象的地址值计算哈希值
因此会重写hashCode()方法,利用对象内部的属性值计算哈希值,可以实现让不同对象的哈希值相同
哈希碰撞:
int的大小是有限的,因此当对象足够多时,不同的属性且不同对象计算出来的哈希值也有可能一样
HashSet的底层原理:
1.创建一个默认长度为16,加载因子为0.75的数组。数组名为table //加载因子用于数组扩容
2.根据元素的哈希值和数组的长度计算出应存入的位置(公式: int index = (数组长度 - 1) & 哈希值 )
3.判断存入位置是否为null,为null存入
4.不为null,则调用equals方法比较两者的属性值,一样则舍弃不存,不一样则存入数组,形成链表
5.JDK8以前:新元素存入数组,老元素挂在新元素下面
JDK8后:新元素直接挂在老元素下面
//JDK8之后 当链表长度超过8,且数组长度大于64,链表自动转为红黑树
//若存储的时自定义对象,必须重写hashCode和equals方法 //2者默认都是根据对象的地址值进行计算
LinkedHashSet
LinkedHashSet是HashSet的子类
特点:
有序,不重复,无索引
这里的有序指的是保证存储和取出的元素顺序一致
原理:
底层数据结构依旧是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序
TreeSet
特点:
不重复,无索引,可排序
可排序:按照元素的默认规则(由小到大)排序
底层:基于红黑树的数据结构实现
排序:
方式一:自然/默认排序
javaBean类实现Comparable接口指定比较规则
步骤:实现Comparable接口,重写compareTo方法
java提供的类已定义了规则:
数值类型:Integer,Double 默认按从小到大排序
对于字符,字符串类型:按字符在ASCII码表中的数字升序(从小到大)排序
字符串: 比较首字母,相同则比较第二个字母,不按个数派排按靠前的字母排,没有字符处按最小算
//定义规则:
public class Student implements Comparable<Student>{ //实现Comparable接口并定义泛型类型
//重写compareTo方法
@Override
public int compareTo(Student o) {
//按照年龄从小到大排序
int result = this.age - o.age;
//this 表示当前要添加的元素, o 表示已经在红黑树存在的元素
//返回值为负数则认为当前要添加的元素是小的存左边,为整数则认为该元素是大的存右边,0则认为该元素已存在,舍弃
return result;
}
}
方式二:比较器排序
在创建TreeSet对象时,传递比较器对象 Comparator指定规则,并重写compare方法
TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
//o1表示现在要存入的那个元素
//o2表示已经存入到集合中的元素
int result = o1.getAge() - o2.getAge();
return result;
}
});
---双集合体系结构---
Map
常见API:
Map是双列集合的顶层接口,他的功能是全部双列集合都可以继承使用的
interface Map<K,V> K:键的类型;V:值的类型
键的特点:无序,不重复,无索引
Map集合的基本功能:
| 方法名 | 说明 |
|---|---|
| V put(K key,V value) | 添加元素 |
| V remove(Object key) | 根据键删除键值对元素 |
| void clear() | 移除所有的键值对元素 |
| boolean containsKey(Object key) | 判断集合是否包含指定的键 |
| boolean containsValue(Object value) | 判断集合是否包含指定的值 |
| boolean isEmpty() | 判断集合是否为空 |
| int size() | 集合的长度,也就是集合中键值对的个数 |
//创建集合对象 interface Map<K,V> K:键的类型;V:值的类型
Map<String,String> map = new HashMap<String,String>();
//添加
//V put(K key, V value) 将指定的值与该映射中的指定键相关联
map.put("itheima001","林青霞");
map.put("itheima002","张曼玉");
//删除
map.put("itheima001");
//清空
map.clear();
//输出集合对象
System.out.println(map);
在调用put方法添加数据时,若键不存在,则直接把键值对对象添加进集合中(返回值为null),若键存在。则把原来集合中的值进行覆盖,并返回被覆盖的值
Map集合的获取功能:
| 方法名 | 说明 |
|---|---|
| V get(Object key) | 根据键获取值 |
| Set |
获取所有键的集合 |
| Collection |
获取所有值的集合 |
| Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
Map集合的遍历:
1.键找值
2.键值对
3.Lambda表达式
//1.键找值
Map<String,String> m = new HashMap<String,String>();
Set<String> keySet = m.ketSet(); //获取所有键的集合
//遍历键的集合,获取到每一个键。用增强for实现
for (String key : keySet) {
//根据键去找值。用get(Object key)方法实现
String value = m.get(key);
System.out.println(key + "," + value);
}
//2.键值对
//获取所有键值对对象的集合
Set<Map.Entry<String, String>> entrySet = m.entrySet();
//遍历键值对对象的集合,得到每一个键值对对象
for (Map.Entry<String, String> me : entrySet) { //这里的entrySet可直接用m.entrySet()代替
//根据键值对对象获取键和值
String key = me.getKey();
String value = me.getValue();
System.out.println(key + "," + value);
}
//Lambda表达式
m.forEach(new BigConsumer<String,String>(){ //传递匿名内部类
@Override
public void accept(String key,String value) //原接口处参数名是 t 和 u 但重写方法不看参数名,只看类型和返回值以及修饰符
print(key,value);
});
m.forEach((key,value) -> print(key,value));//BigConsumer是参数式接口,可改写为Lambda表达式
HashMap
特点:
其父类是Map
特点都是由键决定的:无序不重复无索引
底层原理:
底层原理和HashSet一样,都是哈希表结构,
其哈希值是利用键对象计算
当存入新的对象时,若哈希值计算相同,则只调用 键对象的equals方法比较,若相同则新对象覆盖老对象(在HashSet中是舍弃新对象)|
利用hashCode()和equals()方法保证键的唯一
LinkedHashMap
特点:
其父类是HashMap
由键决定:有序,不重复,无索引 //有序:保证存储和取出的元素顺序一致
底层原理:
同LinkedHashSet一样,底层结构是哈希表,每个键值对元素又额外多了一个双链表的机制记录存储的顺序
TreeMap
特点:
由键决定:不重复,无索引,可排序 //可排序:对键进行排序
排序默认按键的从小到大排序,也可以自定排序规则
底层原理:
TreeMap跟TreeSet底层原理一样,都是红黑树结构
排序:
1.实现Comparable接口,指定比较规则
2.创建集合是传递Compartor比较器对象,指定比较规则
//javaBean类实现Comparable接口,重写comparaTo方法
public class Student implements Comparable<Student>{
@Override
public int compareTo(Student o) {
//按照年龄进行排序
int result = o.getAge() - this.getAge();
return result;
}
}
//在创建集合时传入比较器对象(其优先级高于实现接口)
TreeSet<String,String> t = new TreeSet<>(new Comparator<Integer>(){
@Override
public int compare(Teacher o1, Teacher o2) {
//o1表示现在要存入的那个元素
//o2表示已经存入到集合中的元素
return o1.getAge() - o2.getAge();;
}
});
Collections
定义:
Collections不是集合,是集合的工具类
常用方法:

创建不可变集合
不可变集合:不能被修改的集合

//List
List<String> list = List.of("参数");
//Set
//Set的不可变集合要保证元素的唯一性
Set<String> list = Set.of("参数");
//Map
//Map不的不可变集合,键不能重复
//Map里面的of方法,参数是有上限的,最多只能传递20个参数,10个键值对
Map<String,String> list = Map.of("键1","值1","键2","值2");
//如果我们要传递多个键值对对象,数量大于10个,在Map接口中还有方法
//copyOf方法 (jdk10之后出现的)
//创建一个普通的Map集合
HashMap<String, String> hm = new HashMap<>();
//利用上面的数据来获取一个不可变的集合
Map<String, String> map = Map.copyOf(hm);
//ofEntries方法
//获取到所有的键值对对象(Entry对象)
Set<Map.Entry<String, String>> entries = hm.entrySet();
//把entries变成一个数组
Map.Entry[] arr1 = new Map.Entry[0];
//toArray方法在底层会比较集合的长度跟数组的长度两者的大小
//如果集合的长度 > 数组的长度 :数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
//如果集合的长度 <= 数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用
Map.Entry[] arr2 = entries.toArray(arr1);
//不可变的map集合
Map map = Map.ofEntries(arr2);
Stream流
Stream流的使用:
1.获得一条Stream流,并把数据放上去
2.利用Stream流中API进行各种操作 (分为中间方法和终结方法)
使用中间方法对数据进行操作
使用终结方法对数据进行操作(使用终结方法后结束Stream流获得集合被更改后的对象)
获取Stream流:
Collection体系集合:使用默认方法stream()生成流, default Stream
Map体系集合:把Map转成Set集合,间接的生成流
数组:通过Arrays中的静态方法stream生成流
同种数据类型的多个数据:通过Stream接口的静态方法of(T... values)生成流

//Collection体系的集合可以使用默认方法stream()生成流
List<String> list = new ArrayList<String>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();
//Map体系的集合间接的生成流
Map<String,Integer> map = new HashMap<String, Integer>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
//数组可以通过Arrays中的静态方法stream生成流
String[] strArray = {"hello","world","java"};
Stream<String> strArrayStream = Arrays.stream(strArray);
//同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流
Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
Stream<Integer> intStream = Stream.of(10, 20, 30);
Stream流的中间方法:
| 方法名 | 说明 |
|---|---|
| Stream |
用于对流中的数据进行过滤 |
| Stream |
返回此流中的元素组成的流,截取前指定参数个数的数据 |
| Stream |
跳过指定参数个数的数据,返回由该流的剩余元素组成的流 |
| static |
合并a和b两个流为一个流 |
| Stream |
返回由该流的不同元素(根据Object.equals(Object) )组成的流 |
| Stream |
转换流中的数据类型 |
注:
中间方法,会返回新的stream流,原来的stream流只能使用一次,因此使用链式编程
修改Stream流中的数据,不会影响原集合或数组中的数据
ArrayList<String> list = new ArrayList<>();
list.add("张三丰");
list.add("张无忌");
list.add("张翠山");
list.add("王二麻子");
list.add("张良");
list.add("谢广坤");
//filter
// Stream<T> filter(Predicate predicate):过滤
// Predicate接口中的方法 boolean test(T t):对给定的参数进行判断,返回一个布尔值
//filter方法获取流中的 每一个数据.
//而test方法中的s,就依次表示流中的每一个数据.
//我们只要在test方法中对s进行判断就可以了.
//如果判断的结果为true,则当前的数据留下
//如果判断的结果为false,则当前数据就不要.
list.stream().filter(s ->s.startsWith("张")) //只留下以 张 开头的字符串
//limit&skip
//需求1:取前3个数据在控制台输出
list.stream().limit(3).forEach(s-> System.out.println(s));
//需求2:跳过3个元素,把剩下的元素在控制台输出
list.stream().skip(3).forEach(s-> System.out.println(s));
//需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
list.stream().skip(2).limit(2).forEach(s-> System.out.println(s));
//concat&distinct
//distinct依赖hashCode和equals方法
//需求1:取前4个数据组成一个流
Stream<String> s1 = list.stream().limit(4);
//需求2:跳过2个数据组成一个流
Stream<String> s2 = list.stream().skip(2);
//需求3:合并需求1和需求2得到的流,并把结果在控制台输出
Stream.concat(s1,s2).forEach(s-> System.out.println(s));
//需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
Stream.concat(s1,s2).distinct().forEach(s-> System.out.println(s));
//map
list.stream().map(s -> 对s的操作); //s表示流里的每一个数据,返回的值表示对s操作后的数据,map后得到的新流里的数据就是返回后的数据
Stream流的终结方法:
| 方法名 | 说明 |
|---|---|
| void forEach(Consumer action) | 对此流的每个元素执行操作 |
| long count() | 返回此流中的元素数 |
| 收集方法: | |
| toArray() | 收集流中的数据,放到数组中 |
| collect(Collector collector) | 收集流中的数据,放到集合中 |
ArrayList<String> list = new ArrayList<>();
list.add("张三丰");
list.add("张无忌");
list.add("张翠山");
list.add("王二麻子");
list.add("张良");
list.add("谢广坤");
//method1(list);
// long count():返回此流中的元素数
long count = list.stream().count();
private static void method1(ArrayList<String> list) {
// void forEach(Consumer action):对此流的每个元素执行操作
// Consumer接口中的方法void accept(T t):对给定的参数执行此操作
//在forEach方法的底层,会循环获取到流中的每一个数据.
//并循环调用accept方法,并把每一个数据传递给accept方法
//s就依次表示了流中的每一个数据.
list.stream().forEach(
new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
}
);
//lambda表达式的简化格式
list.stream().forEach(s->System.out.println(s));
Stream流的收集:
| 方法名 | 说明 |
|---|---|
| 工具类Collectors的静态方法(用于得到Collector传入stream的收集方法中) | |
| public static |
把元素收集到List集合中 |
| public static |
把元素收集到Set集合中 |
| public static Collector toMap(Function keyMapper,Function valueMapper) | 把元素收集到Map集合中 |
| Stream类的收集方法 | |
| R collect(Collector collector) | 把结果收集到集合中 |
| toArray() | 把结果收集到数组中 |
//toArray
//空参
Object[] arr =list.stream().toArray();//空参方法得到的数组是Object类型(若该stream流是基本数据类型数组转来的,直接获得对应类型)
//有参:可以获得指定类型数组
list.stream().toArray(value -> new type[value]); //type处是数组的类型,value在底层是toArray方法传入的原数组的长度
// toList和toSet方法
ArrayList<Integer> list1 = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
list1.add(i);
}
list1.add(10);
list1.add(10);
list1.add(10);
//collect负责收集数据.
//获取流中剩余的数据,但是他不负责创建容器,也不负责把数据添加到容器中.
//Collectors.toList() : 在底层会创建一个List集合.并把所有的数据添加到List集合中.
Set<Integer> set = list1.stream().filter(number -> number % 2 == 0).collect(Collectors.toSet());
//toMap方法
//收集的键不能重复
ArrayList<String> list = new ArrayList<>();
list.add("zhangsan,23");
list.add("lisi,24");
list.add("wangwu,25");
Map<String, Integer> map = list.stream().collect(Collectors.toMap(
s -> s.split(",")[0],
s -> Integer.parseInt(s.split(",")[1]) ));
//collect方法只能获取到流中剩余的每一个数据.
//在底层不能创建容器,也不能把数据添加到容器当中
//Collectors.toMap 创建一个map集合并将数据添加到集合当中
// s 依次表示流中的每一个数据
//第一个lambda表达式就是如何获取到Map中的键
//第二个lambda表达式就是如何获取Map中的值
//2个表达式中的返回值就是获得的对于的键或值
GUI
----组件----
JFrame
用以创建一个窗口(界面)
//1.创建主界面
JFrame frame = new JFrame();
//2.设置主界面的大小
jFrame.setSize(像素,像素);
//3.让主界面显示出来
jFrame.setVisible(true);
//其他设置:
//将主界面设置到屏幕的正中央
jFrame.setLocationRelativeTo(null);
//将主界面置顶
jFrame.setAlwaysOnTop(true);
//关闭主界面的时候让代码一起停止
jFrame.setDefaultCloseOperation(3);
//给主界面设置一个标题
jFrame.setTitle("拼图游戏单机版 v1.0");
//设置界面居中
jFrame.setLocationRelativeTo(null);
//设置关闭模式
jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
利用继承简化代码:
用子类来设置JFrame,之后要用到直接创建子类对象即可
public class LoginJFrame extends JFrame {
//LoginJFrame 表示登录界面
//以后所有跟登录相关的代码,都写在这里
//在构造方法里设置初始化属性
public LoginJFrame(){
//在创建登录界面的时候,同时给这个界面去设置一些信息
//比如,宽高,直接展示出来
this.setSize(488,430);
//设置界面的标题
this.setTitle("拼图 登录");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//让显示显示出来,建议写在最后
this.setVisible(true);
}
}
JMenuBar
菜单的组成:在菜单中有:JMenuBar、JMenu、JMenuItem三个角色。

JMenuBar:如上图中红色边框 JMenuBar是整体,一个界面中一般只有一个JMenuBar。
JMenu:如上图蓝色边框 JMenu是菜单中的选项,可以有多个。
JMenuItem:如上图绿色字体处 JMenuItem是选项下面的条目,也可以有多个。
创建顺序:1,创建JMenuBar对象
2,创建JMenu对象
3,创建JMenuItem对象
4,把JMenuItem添加到JMenu中
5,把JMenu添加到JMenuBar中
6,把整个JMenuBar设置到整个界面中
//创建一个菜单对象
JMenuBar jMenuBar = new JMenuBar();
//设置菜单的宽高
jMenuBar.setSize(514, 20);
//创建一个选项
JMenu jMenu1 = new JMenu("功能");
//创建一个条目
JMenuItem jMenuItem1 = new JMenuItem("重新游戏");
//把条目添加到选项当中
jMenu1.add(jMenuItem1);
//把选项添加到菜单当中
jMenuBar.add(jMenu1);
//把菜单添加到最外层的窗体当中
this.setJMenuBar(jMenuBar);
tips:
构造方法里内容过多,可抽取成方法调用,选中方法 然后 ctrl+alt+m 可快捷抽取方法
JLable
用来管理图片,文字的类, 可以用来设置位置,宽高。
界面左上角的点可以看做是坐标的原点,横向的是X轴,纵向的是Y轴。
图片的位置其实取决于图片左上角的点,在坐标中的位置。
如果是(0,0)那么该图片会显示再屏幕的左上角。
//添加步骤(以添加图片为例)
1,取消整个界面的默认布局
2,创建ImageIcon对象,并制定图片位置。
3,创建JLabel对象,并把ImageIcon对象放到小括号中。
4,利用JLabel对象设置大小,宽高。
5,将JLabel对象添加到整个界面当中。
//1,先对整个界面进行设置
//取消内部默认布局(不取消的话会默认居中)
this.setLayout(null);
//2,创建ImageIcon对象,并制定图片位置。
ImageIcon imageIcon1 = new ImageIcon("image\\1.png");
//3,创建JLabel对象,并把ImageIcon对象放到小括号中。
JLabel jLabel1 = new JLabel(imageIcon1);
//4,利用JLabel对象设置大小,宽高。
jLabel1.setBounds(0, 0, 100, 100);
//5,将JLabel对象添加到整个界面当中。
this.add(jLabel1);
JButton
//创建一个按钮对象
JButton jtb = new JButton("点我啊");
//设置位置和宽高
jtb.setBounds(0,0,100,50);
//给按钮添加动作监听
//jtb:组件对象,表示你要给哪个组件添加事件
//addActionListener:表示我要给组件添加哪个事件监听(动作监听包含鼠标左键点击,空格)
//参数:表示事件被触发之后要执行的代码
jtb.addActionListener(new 事件监听器());
//把按钮添加到界面当中
jFrame.getContentPane().add(jtb);
JTextField
文本输入框(明文)
//设置位置和宽高
setBounds(x,y,宽,高);
//返回输入框中用户输入的数据
//细节:如果用户没有输入,返回的是一个长度为0的字符串
getText();
//修改数据
setText(要修改的内容);
JPasswordField
文本输入框(密文)
--------------
事件
事件就是可以被组件识别的操作,当对组件做了某件事情时,就会执行对应的代码
事件源:按钮,图片,窗体...
事件:某些操作,如鼠标单击,鼠标划入...
绑定监听:当事件源上发生了某个事件,则执行对应代码
监听器:
实现对应的接口
实现方式:1.定义实现类实现 2.匿名内部类实现 3.本类直接实现
让后实现类重写对应的事件方法,以达到触发对应事件执行对应行为
常用监听://也就是常用接口,实现类重写这些监听接口的方法即可
KeyListener: 键盘监听
MouseListener: 鼠标监听
ActionListener: 动作监听 //鼠标,键盘监听的精简版
//一个事件的绑定:
//创建事件源
JButton button = new JButton();
button.setBounds(坐标,宽高);
//在事件源内添加监听器
button.addActionListener(new MyAddActionListener);
button.addActionListener(new addActionListener{
//重写对应监听方法
@Override
public void actionPerformed(ActionEvent e){
print(监听实现);
//获取事件来判断对应事件然后执行对应方法
ActionEvent event = e;
}
});
button.addActionListener(this);//本类实现监听
//1.创建实现类
public class MyAddActionListener implements addActionListener{
//重写对应监听方法
}
//鼠标事件:
JButton jbt = new JButton();
jbt.addMouseListener(new MouseListener{ //加入鼠标监听
//实现的是一个接口,重写所有方法
@Override
public void mouseClicked(MouseEvent e) {}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
});
//键盘事件
jbt.addKeyListener(new KeyListener{ //加入键盘监听
//实现的是一个接口,重写所有方法
@Override
public void keyTyped(KeyEvent e) {} //键入键时调用
@Override
public void keyPressed(KeyEvent e) {
//判断哪个键
int code = e.getKeyCode(); //获取所按那个键的编号
}
@Override
public void keyReleased(KeyEvent e) {}
});
常用API
API: application programming interface, 应用编程接口,是由别人提供的各种java类
导入外部jar包
有2种导入方式
1.idea默认导入
2.使用Maven
idea导入:
1.在模块目录中创建lib文件,将外部jar包放入里面
2.右键点击jar包,选择 Add as Library 即可导入
//或者在project structure中 在Library中加入jar包然后在Modules里面的dependencies中加入
使用Maven:
看应用笔记 -> Maven
Math
Math类所在包为java.lang包,因此在使用的时候不需要进行导包。并且Math类被final修饰了,因此该类是不能被继承的。
Math类包含执行基本数字运算的方法,我们可以使用Math类完成基本的数学运算。
常见方法:
public static int abs(int a) // 返回参数的绝对值
public static double ceil(double a) // 返回大于或等于参数的最小整数
public static double floor(double a) // 返回小于或等于参数的最大整数
public static int round(float a) // 按照四舍五入返回最接近参数的int类型的值
public static int max(int a,int b) // 获取两个int值中的较大值
public static int min(int a,int b) // 获取两个int值中的较小值
public static double pow (double a,double b) // 计算a的b次幂的值
public static double sqrt (double a) // 计算a的平方根的值
public static double cbrt (double a) // 计算a的立方根的值
public static double random() // 返回一个[0.0,1.0)的随机值
System
提供一些与系统相关的类
常见方法
public static long currentTimeMillis() // 获取当前时间所对应的毫秒值(当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置)
//获取的时间是从时间远点1970年1月1日0:0:0开始到现在过了几毫秒(在中国是东八区,时间为早上8点, 这个时间是c语言的初始日期)
public static void exit(int status) // 终止当前正在运行的Java虚拟机,0表示正常退出,非零表示异常退出
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); // 进行数组元素copy
Runtime
表示当前虚拟机的运行环境
常见方法:
public static Runtime getRuntime() //当前系统的运行环境对象
public void exit(int status) //停止虚拟机
public int availableProcessors() //获得CPU的线程数
public long maxMemory() //JVM能从系统中获取总内存大小(单位byte)
public long totalMemory() //JVM已经从系统中获取总内存大小(单位byte)
public long freeMemory() //JVM剩余内存大小(单位byte)
public Process exec(String command) //运行cmd命令
Object
Object是java中的顶级父类,所有的java类都间接或之间的继承Object类
常见方法:
public String toString() //返回该对象的字符串表示形式(可以看做是对象的内存地址值)
public boolean equals(Object obj) //比较两个对象地址值是否相等;true表示相同,false表示不相同
protected Object clone() //对象克隆
toString:
打印对象时,是打印对象内部toString方法返回的值,默认是返回地址值,因为继承object类的toString方法
若要自定义打印的值,则在子类中重写toString方法
equals:
默认情况下equals方法比较的是对象的地址值
比较对象的地址值是没有意义的,因此一般情况下我们都会重写Object类中的equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name) && Objects.equals(age, student.age); // 比较的是对象的name属性值和age属性值
}
clone:
对象克隆:把A对象的值完全拷贝给B对象,也叫对象拷贝,对象复制
浅克隆:不管对象内部是基本数据类型还是引用数据类型都拷贝过来
深克隆:基本数据类型拷贝过来,字符串复用,引用数据类型会重新创建新的
Object中的克隆是浅克隆
深克隆实现:1.可以重写clone方法 2.可以引入外部代码
Objects
Objects是一个工具类,提供了一些方法去完成一些功能
常见方法
public static String toString(Object o) // 获取对象的字符串表现形式
public static boolean equals(Object a, Object b) // 比较两个对象是否相等
public static boolean isNull(Object obj) // 判断对象是否为null
public static boolean nonNull(Object obj) // 判断对象是否不为null
public static <T> T requireNonNull(T obj) // 检查对象是否不为null,如果为null直接抛出异常;如果不是null返回该对象;
public static <T> T requireNonNullElse(T obj, T defaultObj) // 检查对象是否不为null,如果不为null,返回该对象;如果为null返回defaultObj值
public static <T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier) // 检查对象是否不为null,如果不为null,返回该对象;如果 // 为null,返回由Supplier所提供的值
BigInteger
其相当于是一个大的整数,若数字过大基本数据类型无法储存,就用BigInteger,其大小理论上最大到42亿的21亿次方
常见方法:
//构造方法
public BigInteger(int num, Random rnd) //获取随机大整数,范围:[0 ~ 2的num次方-1]
public BigInteger(String val) //获取指定的大整数
public BigInteger(String val, int radix) //获取指定进制的大整数
//还有一个获取BigInteger对象的静态方法
public static BigInteger valueOf(long val) //静态方法获取BigInteger的对象,内部有优化
//* 如果BigInteger表示的数字没有超出long的范围,可以用静态方法获取。
//* 如果BigInteger表示的超出long的范围,可以用构造方法获取。
!!!//* 对象一旦创建,BigInteger内部记录的值不能发生改变。
!!!//* 只要进行计算都会产生一个新的BigInteger对象
//常见成员方法
public BigInteger add(BigInteger val) //加法
public BigInteger subtract(BigInteger val) //减法
public BigInteger multiply(BigInteger val) //乘法
public BigInteger divide(BigInteger val) //除法
public BigInteger[] divideAndRemainder(BigInteger val) //除法,获取商和余数
public boolean equals(Object x) //比较是否相同
public BigInteger pow(int exponent) //次幂、次方
public BigInteger max/min(BigInteger val) //返回较大值/较小值
public int intValue(BigInteger val) //转为int类型整数,超出范围数据有误
BigDecimal
BigDecimal可以用于小数的精确计算,可以用来表示很大的小数
计算机中的小数:
由于float和double是占用有限的字节数,当遇到很大的小数时,会出现出界,出界的值会被舍去,造成数据不准确
因此当进行精确计算时要用BigDecimal来进行
常用方法:
BigDecimal(int val) //将int转换为BigDecimal
BigDecimal(long val) //将long转换为BigDecimal
BigDecimal(String val) //将BigDecimal的字符串表现形式转换为BigDecimal
//用double创建BigDecimal对象有一点不可预知性,所以一般用String构造方法直接传入 ("小数")
//成员方法
public static BigDecimal valueOf(double val) //获取对象
public BigDecimal add(BigDecimal value) // 加法运算
public BigDecimal subtract(BigDecimal value) // 减法运算
public BigDecimal multiply(BigDecimal value) // 乘法运算
public BigDecimal divide(BigDecimal value) // 除法运算(若是除不尽,要用下面这个方法)
public BigDecimal divide(BigDecimal value,精确几位,舍入模式) // 除法运算
JDK7前时间相关类
Date类
Date是一个JDK写好的JavaBean类,用来描述时间,精确到毫秒
利用空参构造创建,默认当前系统时间
利用有参构造创建,表示指定时间
//构造方法
public Date()://从运行程序的此时此刻到时间原点经历的毫秒值,转换成Date对象,分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。
public Date(long date)://将指定参数的毫秒值date,转换成Date对象,分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,
// 即1970年1月 1日00:00:00 GMT)以来的指定毫秒数。(在中国是东八区 所以是 08:00:00)早上八点
//简单来说:使用无参构造,可以自动设置当前系统时间的毫秒时刻;指定long类型的构造参数,可以自定义毫秒时刻
// 创建日期对象,把当前的时间
System.out.println(new Date()); // Tue Jan 16 14:37:35 CST 2020
// 创建日期对象,把当前的毫秒值转成日期对象
System.out.println(new Date(0L)); // Thu Jan 01 08:00:00 CST 1970
//常用方法
public long getTime()// 把日期对象转换成对应的时间毫秒值。
public void setTime(long time)// 把方法参数给定的毫秒值设置给日期对象
SimpleDateFormat类
作用:
格式化:可以把时间变成指定的形式
解析:把字符串表示的时间变为Date对象
常用的格式规则:
| 标识字母(区分大小写) | 含义 |
|---|---|
| y | 年 |
| M | 月 |
| d | 日 |
| H | 时 |
| m | 分 |
| s | 秒 |
//构造方法
public simpleDateFormat() //默认格式
public simpleDateFormat(String pattern) //指定格式
//1.定义一个字符串表示时间
String str = "2023-11-11 11:11:11";
//2.利用空参构造创建simpleDateFormat对象
// 细节:
//创建对象的格式要跟字符串的格式完全一致
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(str);
//3.打印结果
System.out.println(date.getTime());//1699672271000
//常用方法
public final string format(Date date) //格式化(日期对象 ->字符串)
public Date parse(string source) //解析(字符串 ->日期对象)
Calendar类:
Calendar代表了系统当前时间的日历对象,可以单独修改,获取时间中的年月日
细节:Calendar是一个抽象类,不能直接创建对象,我们可以使用它的子类:java.util.GregorianCalendar类
有两种方式可以获取GregorianCalendar对象:
直接创建GregorianCalendar对象;
通过Calendar的静态方法getInstance()方法获取GregorianCalendar对象
常用方法:

| 方法名 | 说明 |
|---|---|
| public static Calendar getInstance() | 获取一个它的子类GregorianCalendar对象。 |
| public int get(int field) | 获取某个字段的值。field参数表示获取哪个字段的值, 可以使用Calender中定义的常量来表示: Calendar.YEAR : 年 Calendar.MONTH :月 Calendar.DAY_OF_MONTH:月中的日期 Calendar.HOUR:小时 Calendar.MINUTE:分钟 Calendar.SECOND:秒 Calendar.DAY_OF_WEEK:星期 |
| public void set(int field,int value) | 设置某个字段的值 |
| public void add(int field,int amount) | 为某个字段增加/减少指定的值 |
//获取一个GregorianCalendar对象
Calendar instance = Calendar.getInstance();//获取子类对象
//常见方法
setXxx:修改
getXxx:获取
add:在原有的基础上进行增加或减少//负数就是减少
//注:日历类中的月份的范围是:0~11
// 日历类中星期的特点:星期日是一周中的第一天
JDK8新增时间相关类
| JDK8时间类类名 | 作用 |
|---|---|
| ZoneId | 时区 |
| Instant | 时间戳 |
| ZoneDateTime | 带时区的时间 |
| DateTimeFormatter | 用于时间的格式化和解析 |
| LocalDate | 年、月、日 |
| LocalTime | 时、分、秒 |
| LocalDateTime | 年、月、日、时、分、秒 |
| Duration | 时间间隔(秒,纳,秒) |
| Period | 时间间隔(年,月,日) |
| ChronoUnit | 时间间隔(所有单位) |
Date类:ZoneId,Instant,ZoneDateTime
日期格式化类 SimpleDateFormat:DateTimeFormatter
日历类Calendar:LocalDate,LocalTime,LocalDateTime
工具类:Duration,Period,ChronoUnit
JDK7:多线程环境下会导致数据安全问题
JDK8新增时间类:时间日期对象都是不可变的
ZoneId
//方法
static Set<string> getAvailableZoneIds() 获取Java中支持的所有时区
static ZoneId systemDefault() 获取系统默认时区
static Zoneld of(string zoneld) 获取一个指定时区
//1.获取所有的时区名称
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
System.out.println(zoneIds.size());//600
System.out.println(zoneIds);
//2.获取当前系统的默认时区
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);//Asia/Shanghai
//3.获取指定的时区
ZoneId zoneId1 = ZoneId.of("Asia/Pontianak");
System.out.println(zoneId1);//Asia/Pontianak
Instant
//方法
static Instant now() 获取当前时间的Instant对象(标准时间)
static Instant ofXxxx(long epochMilli) 根据(秒/毫秒/纳秒)获取Instant对象//参数的值是从时间原点往后走多少数值
ZonedDateTime atZone(ZoneIdzone) 指定时区
boolean isxxx(Instant otherInstant) 判断系列的方法
Instant minusXxx(long millisToSubtract) 减少时间系列的方法
Instant plusXxx(long millisToSubtract) 增加时间系列的方法
//1.获取当前时间的Instant对象(标准时间)
Instant now = Instant.now();
//2.根据(秒/毫秒/纳秒)获取Instant对象
Instant instant1 = Instant.ofEpochMilli(0L);
Instant instant2 = Instant.ofEpochSecond(1L);
Instant instant3 = Instant.ofEpochSecond(1L, 1000000000L);
//3. 指定时区
ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
//4.isXxx 判断
//用于时间的判断
//isBefore:判断调用者代表的时间是否在参数表示时间的前面
boolean result1=instant4.isBefore(instant5);
//isAfter:判断调用者代表的时间是否在参数表示时间的后面
boolean result2 = instant4.isAfter(instant5);
//Instant minusXxx(long millisToSubtract) 减少时间系列的方法
**ZoneDateTime **
//方法
static ZonedDateTime now() 获取当前时间的ZonedDateTime对象
static ZonedDateTime ofXxxx(。。。) 获取指定时间的ZonedDateTime对象
ZonedDateTime withXxx(时间) 修改时间系列的方法
ZonedDateTime minusXxx(时间) 减少时间系列的方法
ZonedDateTime plusXxx(时间) 增加时间系列的方法
//1.获取当前时间对象(带时区)
ZonedDateTime now = ZonedDateTime.now();
//2.获取指定的时间对象(带时区)1/年月日时分秒纳秒方式指定
ZonedDateTime time1 = ZonedDateTime.of(2023, 10, 1,11, 12, 12, 0, ZoneId.of("Asia/Shanghai"));
//通过Instant + 时区的方式指定获取时间对象
Instant instant = Instant.ofEpochMilli(0L);
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
ZonedDateTime time2 = ZonedDateTime.ofInstant(instant, zoneId);
//3.withXxx 修改时间系列的方法
ZonedDateTime time3 = time2.withYear(2000);
//4. 减少时间
ZonedDateTime time4 = time3.minusYears(1);
//5.增加时间
ZonedDateTime time5 = time4.plusYears(1);
DateTimeFormatter
//方法
static DateTimeFormatter ofPattern(格式) 获取格式对象
String format(时间对象) 按照指定方式格式化
//获取时间对象
ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
// 解析/格式化器
DateTimeFormatter dtf1=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm;ss EE a");
// 格式化
System.out.println(dtf1.format(time));
日历类Calendar:LocalDate,LocalTime,LocalDateTime 共性方法

LocalDate
//1.获取当前时间的日历对象(包含 年月日)
LocalDate nowDate = LocalDate.now();
//2.获取指定的时间的日历对象
LocalDate ldDate = LocalDate.of(2023, 1, 1);
//3.get系列方法获取日历中的每一个属性值//获取年
int year = ldDate.getYear();
//获取月//方式一:
Month m = ldDate.getMonth();
//方式二:
int month = ldDate.getMonthValue();
//获取日
int day = ldDate.getDayOfMonth();
//获取一年的第几天
int dayofYear = ldDate.getDayOfYear();
//获取星期
DayOfWeek dayOfWeek = ldDate.getDayOfWeek();
//is开头的方法表示判断
System.out.println(ldDate.isBefore(ldDate));
System.out.println(ldDate.isAfter(ldDate));
//with开头的方法表示修改,只能修改年月日
LocalDate withLocalDate = ldDate.withYear(2000);
//minus开头的方法表示减少,只能减少年月日
LocalDate minusLocalDate = ldDate.minusYears(1);
//plus开头的方法表示增加,只能增加年月日
LocalDate plusLocalDate = ldDate.plusDays(1);
//-------------
// 判断今天是否是你的生日
LocalDate birDate = LocalDate.of(2000, 1, 1);
LocalDate nowDate1 = LocalDate.now();
MonthDay birMd = MonthDay.of(birDate.getMonthValue(), birDate.getDayOfMonth());
MonthDay nowMd = MonthDay.from(nowDate1);
System.out.println("今天是你的生日吗? " + birMd.equals(nowMd));//今天是你的生日吗?
LocalTime
// 获取本地时间的日历对象。(包含 时分秒)
LocalTime nowTime = LocalTime.now();
int hour = nowTime.getHour();//时
int minute = nowTime.getMinute();//分
int second = nowTime.getSecond();//秒
int nano = nowTime.getNano();//纳秒
System.out.println(LocalTime.of(8, 20));//时分
System.out.println(LocalTime.of(8, 20, 30));//时分秒
System.out.println(LocalTime.of(8, 20, 30, 150));//时分秒纳秒
LocalTime mTime = LocalTime.of(8, 20, 30, 150);
//is系列的方法
System.out.println(nowTime.isBefore(mTime));
System.out.println(nowTime.isAfter(mTime));
//with系列的方法,只能修改时、分、秒
System.out.println(nowTime.withHour(10));
//plus系列的方法,只能修改时、分、秒
System.out.println(nowTime.plusHours(10));
LocalDateTime
// 当前时间的的日历对象(包含年月日时分秒)
LocalDateTime nowDateTime = LocalDateTime.now();
System.out.println("今天是:" + nowDateTime);//今天是:
System.out.println(nowDateTime.getYear());//年
System.out.println(nowDateTime.getMonthValue());//月
System.out.println(nowDateTime.getDayOfMonth());//日
System.out.println(nowDateTime.getHour());//时
System.out.println(nowDateTime.getMinute());//分
System.out.println(nowDateTime.getSecond());//秒
System.out.println(nowDateTime.getNano());//纳秒
// 日:当年的第几天
System.out.println("dayofYear:" + nowDateTime.getDayOfYear());
//星期
System.out.println(nowDateTime.getDayOfWeek());
//月份
System.out.println(nowDateTime.getMonth());
//LocalDateTime可以转为LocalDate,LocalTime
//转为LocalDate
LocalDate ld = nowDateTime.toLocalDate();
//转为LocalTime
LocalTime ld = nowDateTime.toLocalTime();
Duration
//计算时间间隔(秒,纳,秒)
// 本地日期时间对象。
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
// 出生的日期时间对象
LocalDateTime birthDate = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
System.out.println(birthDate);
Duration duration = Duration.between(birthDate, today);//第二个参数减第一个参数
System.out.println("相差的时间间隔对象:" + duration);
System.out.println("============================================");
System.out.println(duration.toDays());//两个时间差的天数
System.out.println(duration.toHours());//两个时间差的小时数
System.out.println(duration.toMinutes());//两个时间差的分钟数
System.out.println(duration.toMillis());//两个时间差的毫秒数
System.out.println(duration.toNanos());//两个时间差的纳秒数
Period
//计算时间间隔(年,月,日)
// 当前本地 年月日
LocalDate today = LocalDate.now();
System.out.println(today);
// 生日的 年月日
LocalDate birthDate = LocalDate.of(2000, 1, 1);
System.out.println(birthDate);
Period period = Period.between(birthDate, today);//第二个参数减第一个参数
System.out.println("相差的时间间隔对象:" + period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
System.out.println(period.toTotalMonths());
ChronoUnit
//计算时间间隔(所有单位)
//调用的是静态方法,不用创建对象
// 当前时间
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
// 生日时间
LocalDateTime birthDate = LocalDateTime.of(2000, 1, 1,0, 0, 0);
System.out.println(birthDate);
System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today));
System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today));
System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today));
System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today));
System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today));
System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today));
System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today));
System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today));
System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today));
System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today));
System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today));
System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today));
System.out.println("相差的世纪(百年)数:" + ChronoUnit.CENTURIES.between(birthDate, today));
System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today));
System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today));
包装类
定义:
包装类就是基本数据类型所对应的引用类型 (就是把基本数据类型变为对象)
包装类:用一个对象把基本数据类型包起来
| 基本类型 | 对应的包装类(位于java.lang包中) |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
Integer类
Integer类构造方法及静态方法
| 方法名 | 说明 |
|---|---|
| public Integer(int value) | 根据 int 值创建 Integer 对象(过时) |
| public Integer(String s) | 根据 String 值创建 Integer 对象(过时) |
| public static Integer valueOf(int i) | 返回表示指定的 int 值的 Integer 实例 |
| public static Integer valueOf(String s) | 返回保存指定String值的 Integer 对象 |
| static string tobinarystring(int i) | 得到二进制 |
| static string tooctalstring(int i) | 得到八进制 |
| static string toHexstring(int i) | 得到十六进制 |
| static int parseXxx(string s) | 将字符串类型的整数转成基本数据类型类型(除Character都可以) |
获取对象:
//---------------------------------
//在JDK5以前需要自己new或者通过静态方法获取
//public Integer(int value):根据 int 值创建 Integer 对象(过时)
Integer i1 = new Integer(100);
//public Integer(String s):根据 String 值创建 Integer 对象(过时)
Integer i2 = new Integer("100");
//Integer i2 = new Integer("abc"); //NumberFormatException
//public static Integer valueOf(int i):返回表示指定的 int 值的 Integer 实例
Integer i3 = Integer.valueOf(100);
//public static Integer valueOf(String s):返回保存指定String值的Integer对象
Integer i4 = Integer.valueOf("100");
//注:new和valueOf的区别:Integer会提前创建好 -128到127之间的对象,用valueOf获取对象时,若在这个范围内则不会创建新的对象
//---------------------------------
方法
//---------------------------------
public static string tobinarystring(int i) 得到二进制
public static string tooctalstring(int i) 得到八进制
public static string toHexstring(int i) 得到十六进制
public static int parseInt(string s) 将字符串类型的整数转成int类型的整数
//1.把整数转成二进制,十六进制
String str1 = Integer.toBinaryString(100);
//2.把整数转成八进制
String str2 = Integer.toOctalString(100);
//3.把整数转成十六进制
String str3 = Integer.toHexString(100);
//4.将字符串类型的整数转成int类型的整数
//强类型语言:每种数据在java中都有各自的数据类型
//在计算的时候,如果不是同一种数据类型,是无法直接计算的。
int i = Integer.parseInt("123");
//细节1:
//在类型转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错
//细节2:
//8种包装类当中,除了Character都有对应的parseXxx的方法,进行类型转换
String str = "true";
boolean b = Boolean.parseBoolean(str);
//---------------------------------
自动装箱和拆箱
自动装箱与自动拆箱
//---------------------------------
//由于我们经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成
//在JDK5后,int和Integer可以看作一个东西,因为在内部可以自动转换
Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;注:这个新的i是int类型
//加法运算完成后,再次装箱,把基本数值转成对象。
//在向包装类集合中装入基本数据类型数据时,也可以自动转换
ArrayList<Integer> list = new ArrayList<>;
list.add(100);
//---------------------------------
基本类型与字符串之间的转换
基本类型转换为String
//---------------------------------
- 转换方式
- 方式一:直接在数字后加一个空字符串
- 方式二:通过String类静态方法valueOf()
int number = 100;
//方式1
String s1 = number + "";
//方式2
//public static String valueOf(int i)
String s2 = String.valueOf(number);
//---------------------------------
String转换成基本类型
//---------------------------------
除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型:
public static byte parseByte(String s) //将字符串参数转换为对应的byte基本类型。
public static short parseShort(String s) //将字符串参数转换为对应的short基本类型。
public static int parseInt(String s) //将字符串参数转换为对应的int基本类型。
public static long parseLong(String s) //将字符串参数转换为对应的long基本类型。
public static float parseFloat(String s) //将字符串参数转换为对应的float基本类型。
public static double parseDouble(String s) //将字符串参数转换为对应的double基本类型。
public static boolean parseBoolean(String s) //将字符串参数转换为对应的boolean基本类型。
//方式一:先将字符串数字转成Integer,再调用valueOf()方法
//方式二:通过Integer静态方法parseInt()进行转换
String s = "100";
//方式1:String --- Integer --- int
Integer i = Integer.valueOf(s);
//public int intValue()
int x = i.intValue();
//方式2
//public static int parseInt(String s)
int y = Integer.parseInt(s);
//---------------------------------
正则表达式
概念
正则表达式可以校验字符串是否满足一定的规则,并用来校验数据格式的合法性
作用:
1.校验字符串是否满足规则
2.在一段文本中查找满足要求的内容
使用:
通过 字符串对象调用 matches("正则表达式规则") ,若满足规则返回true反之false
规则: 让字符与正则表达式里的内容匹配
教程:
正则表达式学习笔记(超级详细!!!)| 有用的小知识-CSDN博客
正则表达式 – 教程 | 菜鸟教程
Java 正则表达式 | 菜鸟教程
总体:



注:
一般来说 ^ 表示一个字符串的开头,但它用在一个方括号的开头的时候,它表示这个字符集是否定的。
在正则表达式中,想要匹配指定开头或结尾的字符串就要使用到锚点。^ 指定开头,$ 指定结尾
锚点号
锚点^号和\(号 在正则表达式中,想要匹配指定开头或结尾的字符串就要使用到锚点。^ 指定开头,\) 指定结尾
字符类
//语法:
// 基础: 用[] 来匹配
//表示这个字符要符合[]定义的规则
1. [abc]:代表a或者b,或者c字符中的一个。
2. [^abc]:代表除a,b,c以外的任何字符。
3. [a-z]:代表a-z的所有小写字符中的一个。
4. [A-Z]:代表A-Z的所有大写字符中的一个。
5. [0-9]:代表0-9之间的某一个数字字符。
6. [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
7. [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符。
//public boolean matches(String regex):判断是否与正则表达式匹配,匹配返回true
// 只能是a b c
System.out.println("a".matches("[abc]")); // true
System.out.println("z".matches("[abc]")); // false
// 不能出现a b c
System.out.println("a".matches("[^abc]")); // false
System.out.println("z".matches("[^abc]")); // true
System.out.println("zz".matches("[^abc]")); //false
System.out.println("zz".matches("[^abc][^abc]")); //true
// a到zA到Z(包括头尾的范围)
System.out.println("a".matches("[a-zA-z]")); // true
System.out.println("z".matches("[a-zA-z]")); // true
System.out.println("aa".matches("[a-zA-z]"));//false
System.out.println("zz".matches("[a-zA-Z]")); //false
System.out.println("zz".matches("[a-zA-Z][a-zA-Z]")); //true
System.out.println("0".matches("[a-zA-Z]"));//false
System.out.println("0".matches("[a-zA-Z0-9]"));//true
// [a-d[m-p]] a到d,或m到p
System.out.println("a".matches("[a-d[m-p]]"));//true
System.out.println("d".matches("[a-d[m-p]]")); //true
System.out.println("m".matches("[a-d[m-p]]")); //true
System.out.println("p".matches("[a-d[m-p]]")); //true
System.out.println("e".matches("[a-d[m-p]]")); //false
System.out.println("0".matches("[a-d[m-p]]")); //false
// [a-z&&[def]] a-z和def的交集。为:d,e,f
System.out.println("a".matches("[a-z&[def]]")); //false
System.out.println("d".matches("[a-z&&[def]]")); //true
System.out.println("0".matches("[a-z&&[def]]")); //false
// [a-z&&[^bc]] a-z和非bc的交集。(等同于[ad-z])
System.out.println("a".matches("[a-z&&[^bc]]"));//true
System.out.println("b".matches("[a-z&&[^bc]]")); //false
System.out.println("0".matches("[a-z&&[^bc]]")); //false
// [a-z&&[^m-p]] a到z和除了m到p的交集。(等同于[a-1q-z])
System.out.println("a".matches("[a-z&&[^m-p]]")); //true
System.out.println("m".matches("[a-z&&[^m-p]]")); //false
System.out.println("0".matches("[a-z&&[^m-p]]")); //false
逻辑运算符
//语法
1. &&:并且
2. | :或者
3. \ :转义字符
!// &&和 | 判断 要在[]里面或者用 () 把判断内容框起来,不然匹配位置是整个语句
// &&:并且
//1.要求字符串是小写辅音字符开头,后跟ad
[a-z&&[^aeiou]]ad
// | :或者
//2.要求字符串是aeiou中的某个字符开头,后跟ad
[a|e|i|o|u]ad;//这种写法相当于:regex = "[aeiou]ad";
// \ 转义字符
//改变后面那个字符原本的含义
//练习:以字符串的形式打印一个双引号
//"在Java中表示字符串的开头或者结尾
//此时\表示转义字符,改变了后面那个双引号原本的含义
//把他变成了一个普普通通的双引号而已。
System.out.println("\""); //这里的转义是java中的不是正则中的,在java正则中的转义需要用 // 把后面的/显示出来让java正则识别为转义字符
//Java正则中单个的\语法是错误的(会报错),除了\n或者\t等特殊的字符
//若要在java中使用/转义某个字符,需要用 //
// \表示转义字符
//两个\的理解方式:前面的\是一个转义字符,改变了后面\原本的含义,把他变成一个普普通通的\而已。
System.out.println("c:Users\\moon\\IdeaProjects\\basic-code\\myapi\\src\\com\\itheima\\a08regexdemo\\RegexDemo1.java");
预定义运算符
//语法
1. "." : 匹配任何字符。
2. "\d":任何数字[0-9]的简写;
3. "\D":任何非数字[^0-9]的简写;
4. "\s": 空白字符:[ \t\n\x0B\f\r] 的简写
5. "\S": 非空白字符:[^\s] 的简写
6. "\w":单词字符:[a-zA-Z_0-9]的简写
7. "\W":非单词字符:[^\w]
//使用 \时 前面要加上 \ 来把 \ 转意为普通字符串
//.表示任意一个字符
System.out.println("你".matches("..")); //false
System.out.println("你".matches(".")); //true
System.out.println("你a".matches(".."));//true
// \\d 表示任意的一个数字
// \\d只能是任意的一位数字
// 简单来记:两个\表示一个\
System.out.println("a".matches("\\d")); // false
System.out.println("3".matches("\\d")); // true
System.out.println("333".matches("\\d")); // false
//\\w只能是一位单词字符[a-zA-Z_0-9]
System.out.println("z".matches("\\w")); // true
System.out.println("2".matches("\\w")); // true
System.out.println("21".matches("\\w")); // false
System.out.println("你".matches("\\w"));//false
// 非单词字符
System.out.println("你".matches("\\W")); // true
System.out.println("---------------------------------------------");
// 以上正则匹配只能校验单个字符。
// 必须是数字 字母 下划线 至少 6位
System.out.println("2442fsfsf".matches("\\w{6,}"));//true
System.out.println("244f".matches("\\w{6,}"));//false
// 必须是数字和字符 必须是4位
System.out.println("23dF".matches("[a-zA-Z0-9]{4}"));//true
System.out.println("23 F".matches("[a-zA-Z0-9]{4}"));//false
System.out.println("23dF".matches("[\\w&&[^_]]{4}"));//true
System.out.println("23_F".matches("[\\w&&[^_]]{4}"));//false
数量词
//语法
1. X? : 0次或1次
2. X* : 0次到多次
3. X+ : 1次或多次
4. X{n} : 恰好n次
5. X{n,} : 至少n次
6. X{n,m}: n到m次(n和m都是包含的)
// X 处是单个匹配的正则表达式
// 必须是数字 字母 下划线 至少 6位
System.out.println("2442fsfsf".matches("\\w{6,}"));//true
System.out.println("244f".matches("\\w{6,}"));//false
// 必须是数字和字符 必须是4位
System.out.println("23dF".matches("[a-zA-Z0-9]{4}"));//true
System.out.println("23 F".matches("[a-zA-Z0-9]{4}"));//false
System.out.println("23dF".matches("[\\w&&[^_]]{4}"));//true
System.out.println("23_F".matches("[\\w&&[^_]]{4}"));//false
!注:
使用*等无线匹配的默认是贪婪匹配,它会匹配 到无法匹配才停止, 如果需求是,在第一个达到要求停止,则在后面加上? 即是非贪婪匹配
查找内容
Pattern:表示正则表达式
Matcher:文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。
在大串中去找符合匹配规则的子串。
需要通过这2个对象来实现查找一段文本中的目的内容
//使用案例:
String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
"因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
//1.获取正则表达式的对象
Pattern p = Pattern.compile("Java\\d{0,2}"); //传入的参数就是正则表达式
//2.获取文本匹配器的对象
//拿着m去读取str,找符合p规则的子串
Matcher m = p.matcher(str);
//3.利用循环获取
while (m.find()) {
String s = m.group();
System.out.println(s);
}
//Pattern:表示正则表达式
//Matcher: 文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。
// 在大串中去找符合匹配规则的子串。
//获取正则表达式的对象
Pattern p = Pattern.compile("Java\\d{0,2}");
//获取文本匹配器的对象
//m:文本匹配器的对象
//str:大串
//p:规则
//m要在str中找符合p规则的小串
Matcher m = p.matcher(str);
//拿着文本匹配器从头开始读取,寻找是否有满足规则的字符串
//如果没有,方法返回false
//如果有,返回true。在底层记录子串的起始索引和结束索引+1
// 0,4
boolean b = m.find();
//方法底层会根据find方法记录的索引进行字符串的截取
// 字符串截取方法 substring(起始索引,结束索引);包头不包尾
// (0,4)但是不包含4索引
// 会把截取的小串进行返回。
String s1 = m.group();
System.out.println(s1);
//第二次在调用find的时候,会继续读取后面的内容
//读取到第二个满足要求的子串,方法会继续返回true
//并把第二个子串的起始索引和结束索引+1,进行记录
b = m.find();
//第二次调用group方法的时候,会根据find方法记录的索引再次截取子串
String s2 = m.group();
System.out.println(s2);
}
}
按要求查找
// 1.正则表达式1(?=正则表达式2) ?= 代表前面的正则表达式,在文本中查找时,按1,2表达式凑起来一起查找,但获取的内容只有正则表达式1
// 2.?:
// 3. 正则表达式1(?!正则表达式2) ?! 代表排除后面的文本
//(?:) (?=) (?!)都是非捕获分组
String s = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
"因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
//1.定义正则表达式
//?理解为前面的数据Java
//=表示在Java后面要跟随的数据
//但是在获取的时候,只获取前半部分
//需求1: 爬取版本号为8,11.17的Java文本,但是只要Java,不显示版本号。
String regex1 = "((?i)Java)(?=8|11|17)";
//需求2: 爬取版本号为8,11,17的Java文本。正确爬取结果为:Java8 Java11 Java17 Java17
String regex2 = "((?i)Java)(8|11|17)";
String regex3 = "((?i)Java)(?:8|11|17)";
//需求3: 爬取除了版本号为8,11.17的Java文本
String regex4 = "((?i)Java)(?!8|11|17)";
Pattern p = Pattern.compile(regex4);
Matcher m = p.matcher(s);
while (m.find()) {
System.out.println(m.group());
贪婪爬取和非贪婪爬取
只写+和表示贪婪匹配,如果在+和后面加问号表示非贪婪爬取
+? 非贪婪匹配
*? 非贪婪匹配
贪婪爬取:在爬取数据的时候尽可能的多获取数据
非贪婪爬取:在爬取数据的时候尽可能的少获取数据
举例:
如果获取数据:ab+
贪婪爬取获取结果:abbbbbbbbbbbb
非贪婪爬取获取结果:ab
分组
分组就是一个小括号
每组是有组号的,也就是序号
规则1:从1开始,连续不间断
规则2:以左括号为基准,最左边的是第一组,其次为第二组,以此类推
(第一组)(第二组)(第三组)
(第一组(第二组))(第三组)
//使用:
//内部使用: \\组号 外部使用: $组号
//捕获分组:
//判断一个字符串的开始字符和结束字符是否一致?只考虑一个字符
//举例: a123a b456b 17891 &abc& a123b(false)
// \\组号:表示把第X组的内容再出来用一次
String regex1 = "(.).+\\1";
//分组替换
String str = "我要学学编编编编程程程程程程";
//需求:把重复的内容 替换为 单个的
//学学 学
//编编编编 编
//程程程程程程 程
// (.)表示把重复内容的第一个字符看做一组
// \\1表示第一字符再次出现
// + 至少一次
// $1 表示把正则表达式中第一组的内容,再拿出来用
String result = str.replaceAll("(.)\\1+", "$1");
//非捕获分组
//分组之后不需要再用本组数据,仅仅是把数据括起来。
//非捕获分组:仅仅是把数据括起来
//特点:不占用组号
//(?:) (?=) (?!)都是非捕获分组//更多的使用第一个
String regex1 ="[1-9]\\d{16}(?:\\d|x|x)\\1";//这里\\1报错原因:(?:)就是非捕获分组,此时是不占用组号的。
字符串中的方法
public String[] matches(String regex) //判断字符串是否满足正则表达式的规则
public String replaceAll(String regex,String newStr) //按正则表达式的规则进行替换
public String[] split(String regex) //按正则表达式的规则切割字符串
split
//参数regex表示正则表达式。可以将当前字符串中匹配regex正则表达式的符号作为"分隔符"来切割字符串。
String s = "小诗诗dqwefqwfqwfwq12312小丹丹dqwefqwfqwfwq12312小惠惠";
//细节:
//方法在底层跟之前一样也会创建文本解析器的对象
//然后从头开始去读取字符串中的内容,只要有满足的,那么就切割。
String[] arr = s.split("[\\w&&[^_]]+");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);}
replaceAll
//参数regex表示一个正则表达式。可以将当前字符串中匹配regex正则表达式的字符串替换为newStr。
String s = "小诗诗dqwefqwfqwfwq12312小丹丹dqwefqwfqwfwq12312小惠惠";
//细节:
//方法在底层跟之前一样也会创建文本解析器的对象
//然后从头开始去读取字符串中的内容,只要有满足的,那么就用第一个参数去替换。
String result1 = s.replaceAll("[\\w&&[^_]]+", "vs");
System.out.println(result1);
常见算法
----查找算法----
基本查找
也叫做顺序查找
基本思想:顺序查找也称为线形查找,属于无序查找算法。从数据结构线的一端开始,顺序扫描,依次将遍历到的结点与要查找的值相比较,若相等则表示查 找成功;若遍历结束仍没有找到相同的,表示查找失败。
//基本查找/顺序查找
//核心:
//从0索引开始挨个往后查找
//需求:定义一个方法利用基本查找,查询某个元素是否存在
//数据如下:{131, 127, 147, 81, 103, 23, 7, 79}
int[] arr = {131, 127, 147, 81, 103, 23, 7, 79};
int number = 82;
//参数:
//一:数组
//二:要查找的元素
//利用基本查找来查找number在数组中是否存在
for (int i = 0; i < arr.length; i++) {
if(arr[i] == number){
return true;
}
}
return false;
二分查找
也叫做折半查找
说明:元素必须是有序的,从小到大,或者从大到小都是可以的。
如果是无序的,也可以先进行排序。但是排序之后,会改变原有数据的顺序,查找出来元素位置跟原来的元素可能是不一样的,所以排序之后再查找只能判断当前数据是否在容器当中,返回的索引无实际的意义。
基本思想:也称为是折半查找,属于有序查找算法。用给定值先与中间结点比较。比较完之后有三种情况:
相等,说明找到了
要查找的数据比中间节点小,说明要查找的数字在中间节点左边
要查找的数据比中间节点大,说明要查找的数字在中间节点右边
//二分查找/折半查找
//核心:
//每次排除一半的查找范围
//需求:定义一个方法利用二分查找,查询某个元素在数组中的索引
int[] arr = {7, 23, 79, 81, 103, 127, 131, 147};
System.out.println(binarySearch(arr, 150));
public static int binarySearch(int[] arr, int number){
//1.定义两个变量记录要查找的范围
int min = 0;
int max = arr.length - 1;
//2.利用循环不断的去找要查找的数据
while(true){
if(min > max){
return -1;
}
//3.找到min和max的中间位置
int mid = (min + max) / 2;
//4.拿着mid指向的元素跟要查找的元素进行比较
if(arr[mid] > number){
//4.1 number在mid的左边
//min不变,max = mid - 1;
max = mid - 1;
}else if(arr[mid] < number){
//4.2 number在mid的右边
//max不变,min = mid + 1;
min = mid + 1;
}else{
//4.3 number跟mid指向的元素一样
//找到了
return mid;
}
}
分块查找
当数据表中的数据元素很多时,可以采用分块查找。
汲取了顺序查找和折半查找各自的优点,既有动态结构,又适于快速查找
分块查找适用于数据较多,但是数据不会发生变化的情况,如果需要一边添加一边查找,建议使用哈希查找
分块查找的过程:
1. 需要把数据分成N多小块,块与块之间不能有数据重复的交集。
2. 给每一块创建对象单独存储到数组当中
3. 查找数据的时候,先在数组查,当前数据属于哪一块
4. 再到这一块中顺序查找
----排序算法----
冒泡排序
步骤:
1.相邻的元素两两比较,大的放右边,小的放左边
2.第一轮比较完毕之后,最大值就已经确定,第二轮可以少循环一次,后面以此类推
3.如果数组中有n个数据,总共我们只要执行n-1轮的代码就可以
int[] arr = {2, 4, 5, 3, 1};
//2.利用冒泡排序将数组中的数据变成 1 2 3 4 5
//外循环:表示我要执行多少轮。 如果有n个数据,那么执行n - 1 轮
for (int i = 0; i < arr.length - 1; i++) {
//内循环:每一轮中我如何比较数据并找到当前的最大值
//-1:为了防止索引越界
//-i:提高效率,每一轮执行的次数应该比上一轮少一次。
for (int j = 0; j < arr.length - 1 - i; j++) {
//i 依次表示数组中的每一个索引:0 1 2 3 4
if(arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
选择排序
- 从0索引开始,跟后面的元素一一比较
- 小的放前面,大的放后面
- 第一次循环结束后,最小的数据已经确定
- 第二次循环从1索引开始以此类推
- 第三轮循环从2索引开始以此类推
- 第四轮循环从3索引开始以此类推。
int[] arr = {2, 4, 5, 3, 1};
//2.利用选择排序让数组变成 1 2 3 4 5
//外循环:几轮
//i:表示这一轮中,我拿着哪个索引上的数据跟后面的数据进行比较并交换
for (int i = 0; i < arr.length -1; i++) {
//内循环:每一轮我要干什么事情?
//拿着i跟i后面的数据进行比较交换
for (int j = i + 1; j < arr.length; j++) {
if(arr[i] > arr[j]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
插入排序
将0索引的元素到N索引的元素看做是有序的,把N+1索引的元素到最后一个当成是无序的。
遍历无序的数据,将遍历到的元素插入有序序列中适当的位置,如遇到相同数据,插在后面。
N的范围:0~最大索引
int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
//1.找到无序的哪一组数组是从哪个索引开始的。 2
int startIndex = -1;
for (int i = 0; i < arr.length; i++) {
if(arr[i] > arr[i + 1]){
startIndex = i + 1;
break;
}
//2.遍历从startIndex开始到最后一个元素,依次得到无序的哪一组数据中的每一个元素
for (int i = startIndex; i < arr.length; i++) {
//问题:如何把遍历到的数据,插入到前面有序的这一组当中
//记录当前要插入数据的索引
int j = i;
while(j > 0 && arr[j] < arr[j - 1]){
//交换位置
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
j--;
}
}
快速排序
- 从数列中挑出一个元素,一般都是左边第一个数字,称为 "基准数";
- 创建两个指针,一个从前往后走,一个从后往前走。
- 先执行后面的指针,找出第一个比基准数小的数字
- 再执行前面的指针,找出第一个比基准数大的数字
- 交换两个指针指向的数字
- 直到两个指针相遇
- 将基准数跟指针指向位置的数字交换位置,称之为:基准数归位。
- 第一轮结束之后,基准数左边的数字都是比基准数小的,基准数右边的数字都是比基准数大的。
- 把基准数左边看做一个序列,把基准数右边看做一个序列,按照刚刚的规则递归排序
/*
快速排序:
第一轮:以0索引的数字为基准数,确定基准数在数组中正确的位置。
比基准数小的全部在左边,比基准数大的全部在右边。
后面以此类推。
*/
int[] arr = {1,1, 6, 2, 7, 9, 3, 4, 5, 1,10, 8};
quickSort(arr, 0, arr.length - 1);
/*
* 参数一:我们要排序的数组
* 参数二:要排序数组的起始索引
* 参数三:要排序数组的结束索引
* */
public static void quickSort(int[] arr, int i, int j) {
//定义两个变量记录要查找的范围
int start = i;
int end = j;
if(start > end){
//递归的出口
return;
}
//记录基准数
int baseNumber = arr[i];
//利用循环找到要交换的数字
while(start != end){
//利用end,从后往前开始找,找比基准数小的数字
//int[] arr = {1, 6, 2, 7, 9, 3, 4, 5, 10, 8};
while(true){
if(end <= start || arr[end] < baseNumber){
break;
}
end--;
}
System.out.println(end);
//利用start,从前往后找,找比基准数大的数字
while(true){
if(end <= start || arr[start] > baseNumber){
break;
}
start++;
}
//把end和start指向的元素进行交换
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
}
//当start和end指向了同一个元素的时候,那么上面的循环就会结束
//表示已经找到了基准数在数组中应存入的位置
//基准数归位
//就是拿着这个范围中的第一个数字,跟start指向的元素进行交换
int temp = arr[i];
arr[i] = arr[start];
arr[start] = temp;
//确定6左边的范围,重复刚刚所做的事情
quickSort(arr,i,start - 1);
//确定6右边的范围,重复刚刚所做的事情
quickSort(arr,start + 1,j);
}
}
递归算法
递归指的是方法中调用方法本身的现象
注意点:递归一定要有出口,否则会出现内存溢出
作用: 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来解决
递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复计算
核心:
找出口:什么时候不在调用方法
找规则:如何把大问题变成规模较小的问题
常见算法的API
Arrays:
操作数组的工具类

自定义排序:

//sort 默认是以快速排序进行
//自定义排序的简单理解: o1-o2 "升序排序" ,o2-o1 "降序排序"
Lambda表达式
函数式编程
函数式编程是一种思想特点
函数式编程思想:忽略面向对象的复杂语法,强调做什么,而不是谁去做
面向对象:先找对象,让对象做事情
Lambda表达式就是函数式编程思想的体现
标准格式
Lambda表达式是JDK8开始后的一种新语法形式
()对应着方法的形参
->固定格式
{ } 对应着方法的方法体
()->{}
使用地点:
可以用来简化匿名内部类的书写
只能简化函数式接口的匿名内部类的写法
函数式接口:
有且仅有一个抽象方法的接口就是函数式接口,接口上方可加上 @FunctionalInterface 注解
好处:
Lambda表达式是一个匿名函数,可以把其理解为一段可以传递的代码,其可让java语言的表达能力得到提升
省略写法
省略核心:可推导,可省略
省略规则:
1.参数类型可以省略不写
2.如果只有一个参数,参数类型可以不写,同时()也可以省略
3.如果表达式的方法体只有一行,则 大括号,分号,return可以省略不写, 但要同时省略以上3个
//完整格式
(int a,int b) ->{
return a - b;
}
//1.省略参数类型
//2.只有一个参数
a ->{
return 参数;
}
//3方法体只有一行
(a,b) -> return a + b
方法引用
定义:
方法引用就是把已有的方法拿过来用,当做函数式接口中抽象方法的方法体
相当于将引用的方法与函数式接口内的方法替换,除了方法名不变,其他一样,以此来简化
方法引用是Lambda函数的进一步简写,只有Lambda函数处可使用方法引用
使用条件:
引用处需要是函数式接口
被引用的方法需要存在
被引用的方法的形参和返回值需要和函数值接口的抽象方法的形参和返回值保持一致 //返回值一致意思是,类型和数量一致
方法引用符:
:: 该符号为引用运算符,而它所在的表达式被称为方法引用
引用静态方法:
格式:类名::静态方法
public interface Converter {
int convert(String s);
}
public static void main(String[] args) {
//Lambda写法
useConverter(s -> Integer.parseInt(s));
//引用类方法
useConverter(Integer::parseInt);
}
private static void useConverter(Converter c) {
int number = c.convert("666");
}
引用成员方法:
格式:对象::成员方法
其他类: 其他类对象::成员方法
本类: this::成员方法
父类: super::成员方法
public class PrintString {
public void printUpper(String s) {
String result = s.toUpperCase();
System.out.println(result);}}
public interface Printer {
void printUpperCase(String s);}
//主方法
public static void main(String[] args) {
//Lambda简化写法
usePrinter(s -> System.out.println(s.toUpperCase()));
//引用对象的实例方法
usePrinter(new PrintString()::printUpper);}
引用构造方法:
格式:类名::new
使用条件:
构造方法的参数要和函数式接口的方法的参数一致
该构造方法构造出的对象需要和函数式接口的返回值一致
可理解为,该构造方法与函数式接口的方法替换,并将构造方法构造出的对象作为返回值
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;}
public interface StudentBuilder {
Student build(String name,int age); }
//主方法
public static void main(String[] args) {
//Lambda简化写法
useStudentBuilder((name,age) -> new Student(name,age));
//引用构造器
useStudentBuilder(Student::new); }
private static void useStudentBuilder(StudentBuilder sb) {
Student s = sb.build("林青霞", 30);
System.out.println(s.getName() + "," + s.getAge()); }
以类名引用成员方法:
格式:类名::成员方法
使用:
第一个参数作为被引用方法的调用者
后面的参数全部传递给该方法作为参数 //实例方法的第一个参数是 this 它本身
条件:
第一个参数是什么类型的,就只能引用什么类中的成员方法
public interface MyString {
String mySubString(String s,int x,int y);}
//主方法
public static void main(String[] args) {
//Lambda简化写法
useMyString((s,x,y) -> s.substring(x,y));
//引用类的实例方法
useMyString(String::substring);}
private static void useMyString(MyString my) {
String s = my.mySubString("HelloWorld", 2, 5);
System.out.println(s);
}
}
引用数组的构造方法:
格式:数据类型[]::new
Arraylist<Integer> list = new Arraylist<>();
Collections.addAll(list,1,2,3,4);
//Lambda表达式
Integer[] arr = list.stream().toArray(value -> new Integer[value]);
//数组构造方法
Integer[] arr = list.stream().toArray(Integer::new);
//数组构造方法同普通构造方法类似,但是数组构造方法只能在一个参数或无参方法处引用
泛型
概览
泛型是JDK5时引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
泛型的格式:
<类型>: 指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如:
<类型1,类型2…>: 指定多种类型的格式,多种类型之间用逗号隔开.例如: <E,T> <K,V>
注:泛型只能支持 引用数据类型 (数组也是一样,只能传递引用数据类型数组)
java中的泛型:
java中的泛型是伪泛型,泛型存在于 java文件中,当编译成class文件时,会发生泛型擦除,在实际class文件中 都是Object类型,只是在使用时
会被泛型自动转换为对应的对象
泛型的细节:
泛型中不能写基本数据类型 //泛型最终会转为Object类型,基本数据类型不是Object类型
在指定泛型具体类型后,传递数据时,可以传递该类类型或其子类类型
如果不写泛型,类型默认是Object
在以可变参数机制传入泛型时,基本数据类型和引用数据类型的数组情况有所不同,基本数据类型会直接看为一个Object处理方法接收到的参数数组只 有一个元素,即传入的基本类型数组本身,而引用数据类型可以直接作为泛型参数处理,方法接收到的参数数组展开为每个元素,每个元素作为泛型类 型处理。
public static <T> void method(T... args) {
System.out.println("args length: " + args.length);
for (T arg : args) {
System.out.println(arg);}}
public static void main(String[] args) {
int[] intArray = {1, 2, 3};
method(intArray); // 传入基本类型数组
String[] strArray = {"a", "b", "c"};
method(strArray); // 传入引用类型数组}}
//输出结果
args length: 1
[I@15db9742
args length: 3
a
b
c
泛型的定义
泛型可以写在 类后面,方法上面,接口后面
类后面:
泛型类
当一个类中,某个变量的数据类型不确定时,可以定义带有泛型的类
//格式:泛型定义在类名后面
修饰符 class 类名<泛型>{
}
//例
public class ArrayList<T>{}
// E: 创建对象时,E就确定类型
// 这里的E可以理解为变量,但不是用来记录数据的,而是记录类型,可以写成 T,E,K,V等
//在类后面定义的E可以记录创建该类对象时传入的类型, E可以在该类的内部使用,用以替换类型的声明
ArrayList<String> list = new ArrayList<>(); //JDK7之后可以省略new对象时对泛型的声明
方法上面:
泛型方法
方法中形参类型不确定时
1.可以使用类名后面定义的泛型 //所有方法都能用
2.可以使用方法上定义的泛型 //只有本方法可用
//格式:泛型定义在返回值前面
修饰符 <泛型> 返回值 方法名(参数){}
//例
public static <E> E method(E e){
//泛型方法的E是由使用者传入的参数类型决定
}
method(String s);//调用该方法 则该泛型方法内部的 E 变为String
public <E> E method(ArrayList<E> e){}
接口后面:
泛型接口
//格式:泛型定义在接口名后面
修饰符 interface 接口名<泛型>{}
//例
public interface List<E>{
add(E s);
}
//使用:
//1.实现类给出具体类型
class childList implements List<String>{//若在实现类给出具体类型,则该接口内用到泛型类型的地方都被定义类型,且重写方法时,泛型类型位置处写具体类型
@Override
add(String s){}
}
//2.实现类延续泛型,创建对象时在确定
class childList<E> implements List<E>{ //在创建对象时,给出具体类型,将同时确定实现类与接口的泛型类型
}
通配符
泛型不具备继承性:
泛型不具备继承性,但数据具有继承性
ArrayList<Father> list1 = new ArrayList<>();
ArrayList<Child> list2 = new ArrayList<>();
//泛型不具备继承性
method(list1);//正确语法
method(list2);//报错
//数据具有继承性
list1.add(new Child());//正确语法
static void method(ArrayList<Father> list){} //定义的是拥有什么泛型的类,就得传入对应的对象
//若要在method方法内传入其他集合,可定义泛型方法
static <E> void method(ArrayList<E> list){}
//但该方法可传入拥有任意泛型类型的集合
//要对其做出限制,需要用到 通配符
通配符:
格式:
<?>:可以传递任意类型
<? extend E类型>:可以传递E或者E所有的子类
<? super E类型>:可以传递E或者E所有的父类
static void method(ArrayList<?> list){}
static void method(ArrayList<? extend Father> list){}
static void method(ArrayList<? super Child> list){}
异常
异常体系
异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Error与java.lang.Exception,平常所说的异常指java.lang.Exception
Error:代表系统级别的错误,无法处理,只能避免
Exception:代表异常,通常会使用Exception以及它的子类来封装程序出现的问题
异常分类:
运行时异常:
RuntimeException及其子类,运行时期出现的异常,在编译阶段不需要处理
代码出错而导致程序出现的问题
编译时异常:
直接继承于Exception,编译阶段就会出现异常提醒,需要在编译阶段就处理
用于提醒调用者检查本地信息
编译与运行:
编译:
java文件执行javac命令转换为class字节码文件 的过程就是编译
编译阶段:java不会运行代码,只会检查语法是否错误,还有做一些性能的优化
运行:
对字节码文件执行java命令运行 的过程就是运行
异常的处理方式
JVM默认处理方式:
把异常的名称,原因及出现的位置等信息输出在控制台
程序停止运行,下面的代码不再执行
自己处理(捕获异常):
目的:当代码出现异常时,可以让程序继续执行下去
过程:在try后面的代码块中,里面的异常会被catch捕获,并与catch内部的异常进行匹配,
若能够匹配则执行catch内部的代码然后继续执行try..catch下面的代码
finally代码块:
定义在catch后面,里面的代码块无论异常是否发生,都会被执行
注:
若try...catch中出现的异常没有被catch中匹配捕获,则JVM默认处理,相当于try..catch没有存在
若try中有多个语句,当某个语句出现问题时,代码块就在这里停止,该语句下面的代码不再运行,直接跳转到catch进行匹配
//格式:
try{
可能出现异常的代码
}catch(异常类名 变量名){
异常处理代码
}
//如果出现了多个异常,可以写多个catch进行捕获,若捕获的异常有父子关系,父类要写在子类的下面,当出现异常时,在出现异常处会创建对象的异常对象,然后被catch以此捕获匹配
try{
}catch(Exception1 | Exception2 e){ //在JDK7之后,若不同异常的处理逻辑相同,可以用 | 符号简写
处理逻辑
}catch(){
}
//finally代码块
try{
}catch(异常类名 变量名){
}finally{//finally代码块内的代码一定会被执行
}
抛出处理:
throws :
写在方法定义处,表示声明一个异常,告诉调用者,使用本方法可能会有哪些异常
编译型异常必须要写,运行时异常可以不写
throw:
写在方法内,表示结束方法,手动抛出异常对象,交给调用者,throw下面的代码不再执行
注:
抛出的异常若不被捕获处理,最终会抛到JVM虚拟机处处理,JVM默认处理就是停止程序,并打印异常
//throws
public void method() throws 异常类名1,异常类名2{}
//throw
public void method2(){
throw new Exception();
}
异常的常见方法
Throwable成员方法:
| 方法名称 | 说明 |
|---|---|
| public String getMessage() | 返回此throwable的详细信息字符串 |
| public String toString | 返回此可抛出的简短描述 |
| public void printStackTrace | 把异常的错误信息输出到控制台(只是打印,不结束程序) |
自定义异常
步骤:
1.定义异常类 2.写继承关系 3.空参构造 4.带参构造
定义: 名字+Exception
继承:运行时异常:RuntimeException 编译时:Exception
public class LoginException extends Exception { //编译型异常
//空参构造
public LoginException() {}
//@param message 表示异常提示
public LoginException(String message) { //这里有参构造可在外部抛出该异常传入报错的信息
super(message); }}
IO
File
定义:
File对象就表示一个路径,可以是文件的路径,也可以是文件夹的路径
该路径可以存在也可以不存在
构造方法:
| 方法名称 | 说明 |
|---|---|
| public File(String pathname) | 根据文件路径创建文件对象 |
| public File(String parent, String child) | 根据 父路径和子路径 创建文件对象 |
| public File(File parent, String child) | 根据 父路径对应文件对象 和子路径 创建文件对象 |
常用方法:
判断,获取:

//length方法无法获取文件夹的大小
//若要获取文件夹大小,需要对文件夹内的文件遍历然后把每个文件的大小累加在一起
创建,删除:

//delete方法只能删除文件和空文件夹,并且删除的文件不走回收站
//createNewFile创建时,如果指定的文件的父级路径不存在,则抛出IO异常
//如果不指定文件后缀,会创建一个没有后缀的文件
//mkdirs可以创建单级或多级文件夹
获取文件夹:

//listFiles
//可获取当前指定路径下的所有内容,然后返回一个File[]
//调用者不存在或是文件时,会返回null
//空文件夹调用会返回一个长度为0的数组
//当调用者是一个有内容的文件夹时,会将里面的所有文件和文件夹的路径放到File数组中返回(包含隐藏文件夹)
//若File的路径是一个需要权限才能访问的文件夹,返回null
//不能获取子级文件夹内的文件
//listRoots,系统根就是盘符
//list方法仅能获取文件和文件夹的名字
//过滤器中的若返回值为true则该 file保留
IO流概述
IO流:
存储和读取数据的解决方案
用于读写文件中的数据(可以读写文件也可以读写网络中的数据)
分类:
流的方向:
输入流(读取,input):
文件(硬盘) => 程序(内存)
输出流(写出,ouput):
程序(内存) => 文件(硬盘)
操作文件类型:
字节流:
字节流可操作所有类型的文件
顶级父类为 InputStream OutputStream
一次读写一个字节
字符流:
字符流只可操作纯文本文件(用记事本打开可以读懂的文件)
字符流底层是字节流
顶级父类为 Reader Writer
输入流:一次读一个字节,遇到中文一次读多个字节
输出流:底层会把数据按指定的编码方式进行编码。变成字节再写到文件中
OuterStream
java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法
方法:
| 方法名称 | 说明 |
|---|---|
| public abstract void write(int b) | 一次写一个字节数据 |
| void write(byte[] b) | 一次写一个字节数组的数据 |
| void write(byte[] b,int off,int len) | 一次写一个字节数组的部分数据 |
| public void close() | 关闭此输出流并释放与此流相关联的任何系统资源 |
| public void flush() | 刷新此输出流并强制任何缓冲的输出字节被写出 |
InputStream
java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
方法:
| 方法名称 | 说明 |
|---|---|
| public void close() | 关闭此输入流并释放与此流相关联的任何系统资源 |
| public abstract int read() | 一次读一个字节数据,每调用一次,其在文件中的索引向后移一位,当越界时,返回-1 |
| public int read(byte[] b) | 从输入流中读取一些字节数,并将它们存储到字节数组 b中 |
FileOuterStream
操作本地文件的字节流
操作本地文件的字节输出流,可以把程序中的数据写到本地文件中
步骤:
1.创建对象 2.写数据 3.释放资源
构造方法:
public FileOutputStream(File file) 创建文件输出流以写入由指定的 File对象表示的文件
public FileOutputStream(String name) 创建文件输出流以指定的名称写入文件
续写:构造方法还有第二个参数,传入true表示打开续写,在创建流时,不会覆盖文件,而是继续写,若不写第二个参数,默认是关闭false
创建字节输出流:
参数可以是字符串路径或File对象
如果文件不存在会创建一个新的文件,要保证父级路径存在
如果文件已存在,会覆盖文件
写数据:
write方法的参数是整数,但实际上写入文件中的是该整数的二进制数据
字节输出流一次只能操作一个字节,传入整数的大小不能超出一个字节
换行写:
在写出一个换行符
Windows:\r\n ,linux: \n , Mac: \r或\n
| 方法名称 | 说明 |
|---|---|
| void write(int b) | 一次写一个字节数据 |
| void write(byte[] b) | 一次写一个字节数组的数据 |
| void write(byte[] b,int off,int len) | 一次写一个字节数组的部分数据 |
释放资源:
调用close()方法
每次使用完流后都要释放资源,以结束JAVA对该文件的占用
// 使用File对象创建流对象
File file = new File("a.txt");
FileOutputStream fos = new FileOutputStream(file);
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("b.txt");
//写数据
String str = "ewqweq"
byte[] arr = str.getBytes();
fos.write(arr);
//释放资源
fos.close();
FileInputStream
操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来
步骤:
1.创建对象 2.读数据 3.释放资源
构造方法:
FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名
FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名
创建字节输入流:
参数可以是字符串路径或File对象
如果文件不存在直接报错
读数据:
read方法读取,一次读一个字节,读出来的是数据在ASCII码表上对应的数字
读取到文件末尾,返回-1
数组读取(一次读多个字节):
向read方法内传入一个字节数组,每次读取会把读取到的数据存入数组中
返回值是每次读取的字节的个数,当读不到数据就返回-1
该字节数组大小一般为1024的整数倍
注:
每次读取是把读取到的数据存入数组,若读取的数据个数小于数组,则数组内前面的数据被新数据覆盖,但后面的数据不变
字符流读取中文数据会发生乱码
乱码的避免: 不用字节流读取文本文件(这里的读取是指获取里面的信息),编码和解码时使用同一个码表,同一个编码方式
| 方法名称 | 说明 |
|---|---|
| public void close() | 关闭此输入流并释放与此流相关联的任何系统资源 |
| public int read() | 一次读一个字节数据,每调用一次,其在文件中的索引向后移一位,当越界时,返回-1 |
| public int read(byte[] buffer) | 一次读一个字节数组的数据,每次读取尽可能把数组装满,数组大小一般为1024整数倍 |
// 使用File对象创建流对象
File file = new File("a.txt");
FileInputStream fos = new FileInputStream(file);
// 使用文件名称创建流对象
FileInputStream fos = new FileInputStream("b.txt");
//读取
int data = fos.read();
//释放资源
fos.close();
//循环读取
char data
while((data = (char)fos.read()) != -1){
print(data); }
//拷贝
//1.单读取拷贝
//创建输出对象
FileOutputStream outer = new FileOutputStream("b.txt");
while((data = (char)fos.read()) != -1){
outer.write(data); }
//2.多读取拷贝(一次读一个字节数组)
int length;
byte[] arr = new byte[1024 * 1024 * 5];
while((length = fos.read(arr)) != -1){
outer.write(arr,0,length); }
异常处理
一般IO流的异常都是抛出处理
try...catch...finally异常处理:
在捕获异常时,可能会遇到流没有关闭情况,可以使用finally来保证流关闭
//手动
FileOuterStream fos =null;
try{
fos = new FileOuterStream(file);
}catch(IOexception e){
}finally{
if(fos != null){ //防止空指针异常
try{//close也有异常,需要捕获
fos.close(); //处了JVM退出,否则finally代码块内的代码一定会执行
}catch(IOexception e){ }}
}
//JDK7方案,在try后面创建的流对象会自动释放资源(流需要再try后面的括号内创建,并且)
try (创建流对象1;创建流对象2) { //实现了AutoCloseable接口的类才能在里面创建对象
// 读写数据
} catch (IOException e) {
e.printStackTrace();
}
//JDK9方案,可以自动释放在外部创建的流
创建流对象1;
创建流对象1;
try (流1;流2) {//实现了AutoCloseable接口的类才能放里面
// 读写数据
} catch (IOException e) {
e.printStackTrace();
}
字符流
字符流只可操作纯文本文件(用记事本打开可以读懂的文件)
字符流底层是字节流
顶级父类为 Reader Writer
输入流:一次读一个字节,遇到中文一次读多个字节
输出流:底层会把数据按指定的编码方式进行编码。变成字节再写到文件中
Reader
字符输入流:
java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法
方法:
| 方法名 | 说明 |
|---|---|
| public void close() | 关闭此流并释放与此流相关联的任何系统资源 |
| public int read() | 从输入流读取一个字符 |
| public int read(char[] cbuf) | 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 |
Writer
字符输出流:
java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法
| 方法名 | 说明 |
|---|---|
| void write(int c) | 写入单个字符 |
| void write(char[] cbuf) | 写入字符数组 |
| abstract void write(char[] cbuf, int off, int len) | 写入字符数组的某一部分,off数组的开始索引,len写的字符个数 |
| void write(String str) | 写入字符串 |
| void write(String str, int off, int len) | 写入字符串的某一部分,off字符串的开始索引,len写的字符个数 |
| void flush() | 刷新该流的缓冲 |
| void close() | 关闭此流,但要先刷新它 |
FileReader
读取数据:
调用read方法,分为字符读取和字符数组读取
read方法:
按字节读取,遇到中文一次读多个字节 也就是按字符读取,一次读取一个字符
传入字符数组: 每次按字符数组内的大小读取,读取到的数据会被 解码,强转 后放入数组中
FileReader fr = new FileReader("文件地址");
//单个字符读取
int len;
while((len = fr.read()) != -1){
print((char)len);}
//字符数组读取
int len;
char[] arr = new char[2];
while((len = fr.read(arr)) != -1){
print(String(arr,0,len));}
底层原理:
在第一次读取时,会创建一个缓冲区(长度为8192的字节数组)
之后读取数据时,会线判断缓冲区是否有数据,没有就从文件内获取数据,尽可能装满缓冲区,若文件也没数据就返回-1
若有数据,则直接从缓冲区内获取数据
缓冲区创建时机:创建对象时
缓冲区填入数据时:第一次调用read方法
FileWriter
写数据:
不同于字节流,字符流写数据会把整数根据平台默认的字符集的编码方式进行编码,把编码后的数据写到文件中
底层原理:
也有一个缓冲区(8192的字节数组),每次写入数据都是写入缓冲区中
当缓冲区满时会自动将缓冲区内的数据保存到文件中
调用flush或关流时,也会保存数据到文件
注:
在JDK11后,字符流可以指定字符集读写,若不指定默认是按平台默认编码,idea默认编码方式是UTF-8
FileReader fr = new FileReader("地址",Charset,forName("字符集名称"));
缓冲流
Tip:
缓冲流可以将传入的流包装,成一个新的缓冲流,为其提供一个缓冲区,使效率提高
调用的方法底层是进过缓冲流的处理然后传给原来的流
在关闭流时,只关闭缓冲流即可,底层会把原来的流关闭
在构造方法中若只传入流,则默认生成8192长度的缓冲区,在构造方法第二个参数可以指定缓冲流的长度
字节流的缓冲区是字节数组,字符流的是字符数组
因为底层是基本流的,因此若要开启续写模式,需要在基本流的构造方法里面更改模式
缓冲输出流在写数据时,会先把数据写入缓冲区中,若缓冲区没有写满,也不调用flush或关闭该流,数据不会进入目的地
在使用缓冲输出流时,需要及时调用flush方法来保证数据成功写出
缓冲输入流不需要调用flush方法,其只是读取数据,其内部也有缓冲冲区
字节缓冲流:
原理:底层自带了长度为8192的缓冲区
构造方法:
| 方法名 | 说明 |
|---|---|
| public BufferedInputStream(InputStream in) | 把基本流包装成新的缓冲输入流 |
| public BufferedOutputStream(OutputStream out) | 把基本流包装成新的缓冲输出流 |
字符缓冲流:
| 构造方法: | 说明 |
|---|---|
| public BufferedReader(Reader in) | 把基本流包装为高级流 |
| public BufferedWriter(Writer out) | 把基本流包装为高级流 |
| 特有方法 | |
| 字符缓冲输入流 | |
| public String readLine() | 读一行文字,若无数据可读,返回null |
| 字符缓冲输出流 | |
| public void newLine() | 写一行行分隔符,由系统属性定义符号(跨平台换行) |
注:
readLine在读取数据时,不会读取换行符,自己使用时,要自己添加换行符
转换流
定义:
转换流属于字符流,是字符流和字节流之间的桥梁
转换流可以调用字符流的方法
作用:
1.指定字符集读写 //JDK11后淘汰
2.让字节流使用字符流中的方法
InputStreamReader:
读取字节,然后把字节根据指定的编码方式进行解码为对应的数字
| 构造方法 | 说明 |
|---|---|
| InputStreamReader(InputStream in) | 创建一个使用默认字符集的字符流 |
| InputStreamReader(InputStream in, String charsetName) | 创建一个指定字符集的字符流 |
OutputStreamWriter:
根据指定的编码方式,将写出的数字编码为对应的字节
| 构造方法 | 说明 |
|---|---|
| OutputStreamWriter(OutputStream in) | 创建一个使用默认字符集的字符流 |
| OutputStreamWriter(OutputStream in, String charsetName) | 创建一个指定字符集的字符流 |
注:
在JDK11后普通字符流构造方法新增参数,可传入需要指定的字符集,以此替代转换流的指定字符集读写的功能
序列化流
定义:
序列化流属于字节流,分为 输出流的序列化流 和 输入流的反序列化流
ObjectOutputStream:
序列化流
作用:
可以把java中的对象写到本地文件中
序列化对象条件:
该类必须实现java.io.Serializable 接口
该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰
| 构造方法 | 说明 |
|---|---|
| public ObjectOutputStream(OutputStream out) | 包装方法 |
| 方法 | |
| public final void writeObject (Object obj) | 将对象序列化(写出)到本地文件 |
ObjectInputStream:
反序列化流
作用:
把序列化到本地的对象,读取到程序中来
| 构造方法 | 说明 |
|---|---|
| public ObjectInputStream(InputStream in) | 包装方法 |
| 方法 | |
| public final Object readObject () | 把序列化到本地中的对象,读取到程序中 |
版本号:
实现了Serializable 接口的类,java会为其根据成员变量来计算出一个值,该值就是版本号,每当成员发生改变时,该值也会发生改变
固定版本号:
需要定义一个变量 private static final long serialVersionUID 来固定版本号
Tip:
若要序列化多个对象,一般存入数组内序列化,以便知道序列化对象的个数
打印流
打印流分为字节打印流(PrintStream)和字符打印流(PrintWriter)
字符打印流有缓冲区
该类能够方便地打印各种数据类型的值,具体查DOC文档
System.out就是获取一个打印流对象PrintStream

压缩流
定义:
压缩流分为压缩流(输出流)和解压缩流(输入流)
//解压缩流
public static void main(String[] args) throws IOException {
File src = new File("D:\\aaa.zip");
File dest = new File("D:\\");
//调用方法
unzip(src,dest);
}
//定义一个方法用来解压
public static void unzip(File src,File dest) throws IOException {
//解压的本质:把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地当中
//创建一个解压缩流用来读取压缩包中的数据
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
//要先获取到压缩包里面的每一个zipentry对象
//表示当前在压缩包中获取到的文件或者文件夹
ZipEntry entry;
while((entry = zip.getNextEntry()) != null){
System.out.println(entry);
if(entry.isDirectory()){
//文件夹:需要在目的地dest处创建一个同样的文件夹
File file = new File(dest,entry.toString());
file.mkdirs();
}else{
//文件:需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)
FileOutputStream fos = new FileOutputStream(new File(dest,entry.toString()));
int b;
while((b = zip.read()) != -1){
//写到目的地
fos.write(b); }
fos.close();
//表示在压缩包中的一个文件处理完毕了。
zip.closeEntry(); } }
zip.close(); } }
//压缩流
//压缩文件
public static void main(String[] args) throws IOException {
//把D:\\a.txt打包成一个压缩包
//1.创建File对象表示要压缩的文件
File src = new File("D:\\a.txt");
//2.创建File对象表示压缩包的位置
File dest = new File("D:\\");
//3.调用方法用来压缩
toZip(src,dest);
}
public static void toZip(File src,File dest) throws IOException {
//1.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
//2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
//参数:压缩包里面的路径
ZipEntry entry = new ZipEntry("aaa\\bbb\\a.txt");
//3.把ZipEntry对象放到压缩包当中
zos.putNextEntry(entry);
//4.把src文件中的数据写到压缩包当中
FileInputStream fis = new FileInputStream(src);
int b;
while((b = fis.read()) != -1){
zos.write(b); }
zos.closeEntry();
zos.close(); } }
//压缩文件夹
public static void main(String[] args) throws IOException {
// 把D:\\aaa文件夹压缩成一个压缩包
//1.创建File对象表示要压缩的文件夹
File src = new File("D:\\aaa");
//2.创建File对象表示压缩包放在哪里(压缩包的父级路径)
File destParent = src.getParentFile();//D:\\
//3.创建File对象表示压缩包的路径
File dest = new File(destParent,src.getName() + ".zip");
//4.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
//5.获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
toZip(src,zos,src.getName());//aaa
//6.释放资源
zos.close();
}
/*
* 作用:获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
* 参数一:数据源
* 参数二:压缩流
* 参数三:压缩包内部的路径
* */
public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
//1.进入src文件夹
File[] files = src.listFiles();
//2.遍历数组
for (File file : files) {
if(file.isFile()){
//3.判断-文件,变成ZipEntry对象,放入到压缩包当中
ZipEntry entry = new ZipEntry(name + "\\" + file.getName());//aaa\\no1\\a.txt
zos.putNextEntry(entry);
//读取文件中的数据,写到压缩包
FileInputStream fis = new FileInputStream(file);
int b;
while((b = fis.read()) != -1){
zos.write(b);
}
fis.close();
zos.closeEntry();
}else{
//4.判断-文件夹,递归
toZip(file,zos,name + "\\" + file.getName());
// no1 aaa \\ no1
}}}
properties
properties是一种配置文件,文件后缀为 .properties
properties配置文件中数据以键值对的形式存储
properties在java中是Map的子类,拥有Map的所有特点
特有:
其有一些特有的方法可直接按键值对的形式对 .properties文件进行读写
Properties prop = new Properties();
//store方法
向store方法传入流,可将prop集合中的数据存入对应文件
//load方法
向load方法传入流,可将对应文件中的数据存入集合中
多线程
线程
定义:
线程是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中的实际运作单位
在java中堆内存是唯一的,栈内存不是唯一的,是与线程相关的,一个线程对应一个栈,因此栈也可以叫做线程栈
简单理解: 线程就是软件中互相独立的,可以同时运行的功能
进程: 进程是程序的基本执行实体(一个软件运行之后就是一个进程)
并发:
在同一时刻,有多个指令在单个CPU上交替执行
并行:
在同一时刻,有多个指令在多个CPU上同时执行
创建多线程
java虚拟机允许应用程序并发的运行多个线程
java中有3中方式可实现多线程
1.继承Thread类:
自己定义一个类继承Thread类,重写run方法,把需要多线程执行的方法写入run方法内,在外部创建该类对象,调用start方法启动
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(i); }}}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
//void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
my1.start(); }}
2,实现Runnable接口:
定义一个类实现Runnable接口,并重写run方法,在外部创建一个Thread对象和该类对象,在构造参数里传入该类对象
使用Thread对象调用run方法启动
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0; i<100; i++) {
//这里的Thread.currentThread()方法可获得当前线程的对象
System.out.println(Thread.currentThread().getName()+":"+i); }}}
public class MyRunnableDemo {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
Thread t1 = new Thread(my,"坦克");
//启动线程
t1.start(); }}
3.利用Callable接口和Future接口:
定义一个类实现Callable接口,并定义泛型(该泛型就是返回值类型),重写call方法,将需要多线程执行的语句放入call方法
在外部创建该类对象,FutureTask对象(用于管理多线程运行的结果),以及Thread对象
在FutureTask构造方法内传入该类对象,并定义泛型类型为返回值类型
在Thread构造方法内传入FutureTask对象,调用Thread对象start方法启动线程,调用FutureTask对象get方法获取该线程返回值
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("i);
}
//返回值就表示线程运行完毕之后的结果
return "结果";}}
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程开启之后需要执行里面的call方法
MyCallable mc = new MyCallable();
//作为参数传递给Thread对象,获取线程执行完毕之后的结果.
FutureTask<String> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);
//开启线程
t1.start();
String s = ft.get();
System.out.println(s); }}
线程常见方法

线程优先级:
线程调度:
分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型
优先级设置:
可以使用setPriority()方法设置该线程的优先级
优先级有 1到10,默认优先级为5,优先级越高,抢到线程的概率越高
守护线程:
当其他非守护进程执行完毕后,守护进程会陆续结束
setDaemon()方法:将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
插入线程:
在一个线程运行方法内部创建一个新的线程,调用该线程的join()方法
该方法可将调用join方法的这个线程插入到当前线程之前,调用join方法线程执行完毕后,再执行当前线程
中断线程:
在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行
Tip:
如果目标线程处于等待状态,调用 interrupt()方法 join()方法会立即抛出异常
线程的生命周期:
New:新创建的线程,尚未执行 //new方法
Runnable:运行中的线程,正在执行run()方法的Java代码 //start方法
Blocked:运行中的线程,因为某些操作被阻塞而挂起 //无法获得锁对象
Waiting:运行中的线程,因为某些操作在等待中 //wait方法
Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待 //sleep方法
Terminated:线程已终止,因为run()方法执行完毕 //全部代码执行完毕
过程:
创建对象 => 调用start方法开始抢占执行权 => 抢占到执行权 => 线程代码运行完毕,变成垃圾被回收

在java中只有6种状态,没有运行状态
线程同步
同步代码块:
利用synchronized关键字声明一个代码块,在该关键字后面声明一个锁对象(该锁对象可以是任何对象)
在线程执行到该代码块时,若锁对象相同,会检测该代码块是否被别的线程使用,
若使用,则等待占用线程运行完毕该代码再执行同步代码块代码
Tip:
若需要改代码块只能被一个线程执行则设置锁对象为唯一对象
一般把锁对象设置为当且类的字节码文件对象, 类名.class
//声明
synchronized(任意对象) {
多条语句操作共享数据的代码
}
同步方法:
将synchronized关键字写到方法上
同步方法的锁是java指定的
非静态方法:锁对象为this
静态方法: 锁对象为当前类的字节码文件
//声明
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
//注:若使用实现Runnable接口线程,则调用该对象形成的线程时,都是调用同一个对象,因此使用非静态同步方法也可以保证锁对象唯一
// 并且内部的成员变量也是共享的
//Tip: StringBuilder与StringBuffer的不同就在于 StringBuffer内部的方法都是被 synchronized 修饰的同步方法
Lock锁(JDK5新增):
与 synchronized关键字加锁不同,Lock是自己调用方法上锁以及自己调用方法开锁
使用:
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
创建锁对象(用static修饰保证锁对象唯一)
lock() | 获得锁
unlock() | 释放锁
lock相当于synchronized的前{ unlock相当于 }
Tip:
与synchronized修饰不同, Lock锁必须执行unlock方法后才能开锁,因此可以用try..catch..finally方法来保证不会发生阻塞
死锁:
死锁是一种错误
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
避免: 避免出现2个锁嵌套起来
等待唤醒机制:
方法:
| Object类的等待和唤醒方法 | 说明 |
|---|---|
| void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
| void notify() | 唤醒正在等待对象监视器的单个线程 |
| void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
使用:
在synchronized内部可以调用wait()使线程进入等待状态;
必须在已获得的锁对象上调用wait()方法;
必须在已获得的锁对象上调用notify()或notifyAll()方法;
在synchronized内部可以调用notify()或notifyAll()唤醒其他等待线程
已唤醒的线程还需要重新获得锁后才能继续执行。
一次只有一个线程可以拥有对象的监视器
Tip:
wait()方法必须在当前获取的锁对象上调用,这里获取的是this锁,因此调用this.wait()
调用wait()方法后,线程进入等待状态,wait()方法不会返回,直到将来某个时刻,
线程从等待状态被其他线程唤醒后,wait()方法才会返回,然后,继续执行下一条语句
当一个线程执行 锁对象.wait()方法后,其会立即释放当前的锁
阻塞队列:

BlockingQueue的核心方法:
put(anObject): 将参数放入队列,如果放不进去会阻塞
take(): 取出第一个数据,取不到会阻塞
阻塞队列内部的方法会自定上锁,尽量避免在外部在加锁造成锁的嵌套
ArrayBlockingQueue在创建对象时可以传入一个int参数,代表其可以放入几个数据
线程中断:
Thread.interrupt():中断线程。这里的中断线程并不会立即停止线程,而是设置线程的中断状态为 true(默认是 flase)
Thread.isInterrupted():测试当前线程是否被中断
Thread.interrupted():检测当前线程是否被中断,与 isInterrupted() 方法不同的是,这个方法如果发现当前线程被中断,会清除线程的中断状态。
在线程中断机制里,当其他线程通知需要被中断的线程后,线程中断的状态被设置为 true,
但是具体被要求中断的线程要怎么处理,完全由被中断线程自己决定,可以在合适的时机中断请求,也可以完全不处理继续执行下去。
线程池
线程池的核心原理:
首先会创建一个池子,池子是空的
当提交线程任务时,线程池会创建新的线程对象,任务执行完毕,线程会回到线程池,下次再提交任务时,就不用穿件新的线程对象,
复用原来的线程对象即可。
线程池可以自定义大小,当线程池满时,再次提交任务,该任务会排队等待其他线程执行完毕,回到线程池再继续执行
代码实现:
可以使用Executors中所提供的静态方法来创建线程池
| 方法名 | 说明 |
|---|---|
| static ExecutorService newCachedThreadPool() | 创建默认线程池(没有上限) |
| static ExecutorService newFixedThreadPool(int nThreads) | 创建有上限的线程池 |
public static void main(String[] args) throws InterruptedException {
//1,创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
ExecutorService executorService = Executors.newCachedThreadPool();
//Executors --- 可以帮助我们创建线程池对象
//ExecutorService --- 可以帮助我们控制线程池
executorService.submit("Runnbale实现类或Callbale实现类"); //在调用submit方法后,传入的线程对象就会开始运行(若获得线程的话)
//销毁线程池
executorService.shutdown(); }
自定义线程池:
对象创建:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,
空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
运行细节:
当提交的任务未超过核心线程数量时,直接分配线程
当超过核心线程数量时,会把超过的部分放入任务队列中排队等候
当超过核心线程数列且也超过任务队列大小时,会创建临时线程,没有进入任务队列的任务会被分配临时线程
当超过最大线程数量和任务队列之和时,会把超出部分的任务根据任务拒绝策略进行处理
代码实现:
//使用列子
// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位 用TimeUnit指定
// 参数五:任务队列
// 参数六:创建线程工厂
// 参数七:任务的拒绝策略
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
pool.submit(new MyRunnable());
pool.shutdown(); }
//创建对象
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
// corePoolSize: 核心线程的最大值,不能小于0
// maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
// keepAliveTime: 空闲线程最大存活时间,不能小于0
// unit: 时间单位
// workQueue: 任务队列,不能为null
// threadFactory: 创建线程工厂,不能为null
// handler: 任务的拒绝策略,不能为null
//拒绝策略
// RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。
// ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
// ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
// ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
// ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
线程池大小:
CPU密集型计算:
最大并行数 + 1
I/O密集型计算:
最大并行数 * 期望CPU利用率 * ( 总时间(CPU计算时间 + 等待时间) / CPU计算时间)
最大并行数:
当前java虚拟机可使用的线程数量
调用 Runtime.getRuntime().avaliableProcessors() 获取
原子性
JMM模型:
JMM(Java Memory Model)Java内存模型,是java虚拟机规范中所定义的一种内存模型。
特点:
1.所有的共享变量都存储于主内存(计算机的RAM)这里所说的变量指的是实例变量和类变量。
不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。
2.每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本
3.线程对变量的所有的操作(读,写)都必须在工作内存中完成,而不能直接读写主内存中的变量,
不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存完成
简单解释:
每个线程都有一个工作内存,每个工作内存都保留了一份主内存的副本,副本内记录者共享变量的值
每次线程操作变量时,都是操作自己的工作内存,然后再把工作内存写入主内存中达到同步
volatile关键字:
为了保证多线程操作共享变量的可见性,可用volatile修饰变量
当一个共享变量被 volatile 修饰时,它会保证修改的值立即更新到主存当中,这样的话,当有其他线程需要读取时,就会从内存中读到新值。
普通的共享变量不能保证可见性,因为变量被修改后什么时候刷回到主存是不确定的,因此另外一个线程读到的可能就是旧值。
Java 的锁机制如 synchronized 和 lock 也是可以保证可见性的
作用:
当写一个 volatile 变量时,JMM 会把该线程在本地内存中的变量强制刷新到主内存中去;
volatile会禁止指令重排
当我们使用 volatile 关键字来修饰一个变量时,Java 内存模型会插入内存屏障
(一个处理器指令,可以对 CPU 或编译器重排序做出约束)来确保以下两点
写屏障(Write Barrier):当一个 volatile 变量被写入时,写屏障确保在该屏障之前的所有变量的写入操作都提交到主内存
读屏障(Read Barrier):当读取一个 volatile 变量时,读屏障确保在该屏障之后的所有读操作都从主内存中读取
也就是说执行到 volatile 变量时,其前面的所有语句都必须执行完,后面所有得语句都未执行。且前面语句的结果对 volatile 变量及其后面语句可见
注:
volatile只保证单个变量的原子性,以及其读和写的内存可见性
但不保证一整个过程的内存可见性,
如 线程1获取了 被volatile修饰的变量a,b也获取了a,此时2者获取的a值相同,若2者都对其修改,则不能实现预测的结果
网络编程
基础
网络编程:计算机与计算机之间通过网络进行数据传输
java可以使用java.net包下的基础进行网络编程
常见软件架构:
**CS: ** 客户端服务端模式
BS: 浏览器服务端模式
IP:
是网络中设备的唯一标识
一个IP地址用于唯一标识一个网络接口,一台联入互联网的计算机肯定有一个IP地址,但也可能有多个IP地址
IP地址分为IPv4和IPv6两种
IPv4:
采用32位地址(bit)(4个字节),总共有2的32次幂个(大约42亿)
点分十进制:IPv4地址都会以点分十进制进行表示,每8个bit分成一组,中间以 . 隔开 不区分正负数
IPv4的地址分类形式:
公网地址(万维网使用) 私有地址(局域网使用)
192.168.开头的就是私有地址,这些地址专门为组织机构内部使用,以此节省IP
本机IP: 127.0.0.1,也可以是localhost:,是回送地址也成本地回环地址,永远只会寻找当前所在本机
IPv6:
采用128位地址(16个字节),总共有2的128次幂个
冒分16进制:把16个字节按每组16个bit分为8组,每组按16进制进行转换,每组之间用 : 隔开 //可以把每组前面的0省略
端口号:
端口号是每个应用程序在设备中的唯一标识
是由2个字节表示的整数,范围 0~65535
0~1023之间的端口一般用于一些知名的网络服务或应用
一个端口号只能被一个应用程序使用
域名:
因为直接记忆 IP 地址非常困难,所以我们通常使用域名访问某个特定的服务。
域名解析服务器 DNS 负责把域名翻译成对应的 IP,客户端再根据 IP 地址访问服务器
InetAddress:
此类表示Internet协议(IP)地址
相关方法:
| 方法名 | 说明 |
|---|---|
| static InetAddress getByName(String host) | 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 |
| String getHostName() | 获取此IP地址的主机名 |
| String getHostAddress() | 返回文本显示中的IP地址字符串 |
//InetAddress address = InetAddress.getByName("itheima");
InetAddress address = InetAddress.getByName("192.168.1.66");
//public String getHostName():获取此IP地址的主机名
String name = address.getHostName();
//public String getHostAddress():返回文本显示中的IP地址字符串
String ip = address.getHostAddress();
协议:
连接和通讯的规则被称为网络通讯协议

UDP协议:
用户数据报协议
面向无连接的协议 //无连接:不管是否连接成功都发送数据
速度快,数据不安全,传输数据有64K的大小限制,易丢失数据
TCP协议:
传输控制协议
面向连接的协议
速度慢,无大小限制,数据安全
UDP
Java提供了DatagramSocket类作为基于UDP协议的Socket
发送数据:
构造方法
| 方法名 | 说明 |
|---|---|
| DatagramSocket() | 创建数据报套接字并将其绑定到本机地址上的任何可用端口 |
| DatagramPacket(byte[] buf,int len,InetAddress add,int port) | 创建数据包,发送长度为len的数据包到指定主机的指定端口 |
DatagramSocket类相关方法
| 方法名 | 说明 |
|---|---|
| void send(DatagramPacket p) | 发送数据报包 |
| void close() | 关闭数据报套接字 |
| void receive(DatagramPacket p) | 从此套接字接受数据报包 |
发送数据步骤:
创建发送端的Socket对象(DatagramSocket)
创建数据,并把数据打包
调用DatagramSocket对象的方法发送数据
关闭发送端
//创建发送端的Socket对象(DatagramSocket)
//若绑定端口,则发送数据时从绑定的那个端口发出
//若不绑定端口,则从所有可用端口中随机一个端口发出
DatagramSocket ds = new DatagramSocket();
//创建数据,并把数据打包
//构造一个数据包,发送长度为 length的数据包到指定主机上的指定端口号。
byte[] bys = "hello,udp,我来了".getBytes();
DatagramPacket dp = new DatagramPacket(bys,bys.length,InetAddress.getByName("127.0.0.1"),10086);
//调用DatagramSocket对象的方法发送数据
ds.send(dp);
//关闭发送端
ds.close();
接收数据:
DatagramPacket相关方法
| 方法名 | 说明 |
|---|---|
| byte[] getData() | 返回数据缓冲区 |
| int getLength() | 返回要发送的数据的长度或接收的数据的长度 |
接收数据步骤:
创建接收端的Socket对象(DatagramSocket)
创建一个数据包,用于接收数据
调用DatagramSocket对象的方法接收数据
解析数据包,关闭接收端
//创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(12345); //绑定端口
//创建一个数据包,用于接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//调用DatagramSocket对象的方法接收数据
ds.receive(dp); //该方法是阻塞的,执行到这时,会等待数据接收,当接收到数据时,结束阻塞
//解析数据包,并把数据在控制台显示
System.out.println("数据是:" + new String(dp.getData(), 0,dp.getLength()));
另一种方法:
可以不在DatagramPacket对象内指定地址值,而是通过客户端DatagramSocket对象调用connnct来指定地址值
//服务端
DatagramSocket ds = new DatagramSocket(6666); // 监听指定端口
while(true){ // 无限循环
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet); // 收取一个UDP数据包
String s = new String(packet.getData(), packet.getOffset(), packet.getLength(), StandardCharsets.UTF_8);
// 发送数据:
byte[] data = "ACK".getBytes(StandardCharsets.UTF_8);
packet.setData(data);
ds.send(packet);
}
//客户端
DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000); //设定超时1秒,意思是后续接收UDP包时,等待时间最多不会超过1秒
ds.connect(InetAddress.getByName("localhost"), 6666); // 连接指定服务器和端口
// 发送:
byte[] data = "Hello".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length);
ds.send(packet);
// 接收:
byte[] buffer = new byte[1024];
packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
String resp = new String(packet.getData(), packet.getOffset(), packet.getLength());
ds.disconnect();
// 关闭:
ds.close();
UDP的三种通讯方式:
单播,组播,广播
组播:
组播地址:224.0.0.0 ~ 239.255.255.255,其中224.0.0.0 ~ 224.0.0.255为预留的组播地址
广播: 广播地址:255.255.255.255
接收组播时,接收端需要 创建接收端Socket对象(MulticastSocket)
把当前计算机绑定一个组播地址,表示添加到这一组中.//调用MulticastSocket的joinGroup()方法,参数填InetAddress对象
TCP
概览:
TCP在通信的两端各建立一个Socket对象
通信之前需要保证连接已经建立
通过Socket产生IO来进行网络通信
Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信
Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
通过socket获取的流不用关闭,在关闭socket时会一起被关闭
发送数据:
Socket构造方法
| 方法名 | 说明 |
|---|---|
| Socket(InetAddress address,int port) | 创建流套接字并将其连接到指定IP指定端口号 |
| Socket(String host, int port) | 创建流套接字并将其连接到指定主机上的指定端口号 |
相关方法
| 方法名 | 说明 |
|---|---|
| InputStream getInputStream() | 返回此套接字的输入流 |
| OutputStream getOutputStream() | 返回此套接字的输出流 |
//1.创建Socket对象
//细节:在创建对象的同时会连接服务端
// 如果连接不上,代码会报错
Socket socket = new Socket("127.0.0.1",10000);
//2.可以从连接通道中获取输出流
OutputStream os = socket.getOutputStream();
//写出数据
os.write("aaa".getBytes());
//3.释放资源
os.close();
socket.close();
接收数据:
| ServletSocket构造方法 | 说明 |
|---|---|
| ServletSocket(int port) | 创建绑定到指定端口的服务器套接字 |
| ServletSocket相关方法 | |
| Socket accept() | 监听要连接到此的套接字并接受它 |
accept方法是阻塞的,作用就是等待客户端连接
客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
read方法也是阻塞的
客户端在关流的时候,还多了一个往服务器写结束标记的动作
最后一步断开连接,通过四次挥手协议保证连接终止
//1.创建对象ServerSocker
ServerSocket ss = new ServerSocket(10000);
//2.监听客户端的链接
Socket socket = ss.accept();
//3.从连接通道中获取输入流读取数据
InputStream is = socket.getInputStream();
int b;
while ((b = is.read()) != -1){
System.out.println((char) b);
}
//4.释放资源
socket.close();
ss.close();
关闭数据传输:
调用 结束方法 方法可以终止数据的传输,代表数据传输结束
| 方法名 | 说明 |
|---|---|
| void shutdownInput() | 将此套接字的输入流放置在“流的末尾” |
| void shutdownOutput() | 禁止用此套接字的输出流 |
中文乱码问题:
由于编码问题,需要用UTF-8编码去解码
可以使用转换流去包装获取到的流,以此来实现对读取数据的正确编码
三次握手和四次挥手:
三次握手(确保连接建立):
1.客户端向服务端发出连接请求,等待服务器确认
2.服务器向客户端返回一个响应,告诉客户端收到了请求
3.客户端向服务端再次发出确认信息,连接建立
四次挥手(确保连接断开,且数据处理完毕):
1.客户端向服务端发出取消连接请求
2.服务端向客户端返回一个响应,表示收到客户端的取消请求
3.在服务端将最后的数据处理完毕,向客户端发出确认取消信息
4.客户端再次发送确认信息,连接取消
注解
什么是注解
注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”
注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”
@Resource("hello")
public class Hello {
@Inject
int n;
@PostConstruct
public void hello(@Param String name) {
System.out.println(name);
}
@Override
public String toString() {
return "Hello";
}
}
注解分类:
由编译器使用的注解:
如:
@Override:让编译器检查该方法是否正确地实现了覆写
@SuppressWarnings:告诉编译器忽略此处代码产生的警告
这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了
由工具处理.class文件使用的注解:
有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。
这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。
在程序运行期能够读取的注解:
它们在加载后一直存在于JVM中,这也是最常用的注解。
定义注解
java语言使用@interface语法来定义注解(Annotation)
public @interface Report {
int type();
String level() default "info";
String value() default "";
}
注解的参数类似无参数方法,可以用default设定一个默认值(强烈推荐)。最常用的参数应当命名为value
定义注解:
第一步,用@interface定义注解
第二步,添加参数、默认值,把最常用的参数定义为value(),推荐所有参数都尽量设置默认值。
第三步,用元注解配置注解,其中,必须设置@Target和@Retention,
@Retention一般设置为RUNTIME,因为我们自定义的注解通常要求在运行期读取。一般情况下,不必写@Inherited和@Repeatable
定义一个注解时,还可以定义配置参数。配置参数可以包括
所有基本类型;
String;
枚举类型;
基本类型、String、Class以及枚举的数组。
因为配置参数必须是常量,所以,上述限制保证了注解在定义时就已经确定了每个参数的值。
注解的配置参数可以有默认值,缺少某个配置参数时将使用默认值。
大部分注解会有一个名为`value`的配置参数,对此参数赋值,可以只写常量,相当于省略了value参数。
如果只写注解,相当于全部使用默认值
元注解:
有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。
Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解
@Target
最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置
//类或接口:ElementType.TYPE;
//字段:ElementType.FIELD;
//方法:ElementType.METHOD;
//构造方法:ElementType.CONSTRUCTOR;
//方法参数:ElementType.PARAMETER。
//例如,定义注解@Report可用在方法上,我们必须添加一个@Target(ElementType.METHOD):
@Target(ElementType.METHOD)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
//定义注解@Report可用在方法或字段上,可以把@Target注解参数变为数组{ ElementType.METHOD, ElementType.FIELD }:
@Target({
ElementType.METHOD,
ElementType.FIELD
})
public @interface Report {
...
}
//实际上@Target定义的value是ElementType[]数组,只有一个元素时,可以省略数组的写法。
@Retention
@Retention定义了Annotation的生命周期
//仅编译期:RetentionPolicy.SOURCE;
//仅class文件:RetentionPolicy.CLASS;
//运行期:RetentionPolicy.RUNTIME。
//如果@Retention不存在,则该Annotation默认为CLASS。因为通常我们自定义的Annotation都是RUNTIME,
//所以,务必要加上@Retention(RetentionPolicy.RUNTIME)这个元注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
@Repeatable
Repeatable这个元注解可以定义Annotation是否可重复
@Repeatable(Reports.class)
@Target(ElementType.TYPE)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
@Target(ElementType.TYPE)
public @interface Reports {
Report[] value();
}
//经过@Repeatable修饰后,在某个类型声明处,就可以添加多个@Report注解:
@Report(type=1, level="debug")
@Report(type=2, level="warning")
public class Hello {
}
@Inherited
@Inherited定义子类是否可继承父类定义的Annotation。
@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效
@Inherited
@Target(ElementType.TYPE)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
//在使用的时候,如果一个类用到了@Report:
@Report(type=1)
public class Person {
}
//则它的子类默认也定义了该注解:
public class Student extends Person {
}
处理注解
Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置
SOURCE类型的注解在编译期就被丢掉了
CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM
RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取
如何使用注解完全由工具决定。SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。
CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。
只有RUNTIME类型的注解不但要使用,还经常需要编写
//因此,我们只讨论如何读取RUNTIME类型的注解。
//因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。
//Java提供的使用反射API读取Annotation的方法包括:
//判断某个注解是否存在于Class、Field、Method或Constructor:
Class.isAnnotationPresent(Class)
Field.isAnnotationPresent(Class)
Method.isAnnotationPresent(Class)
Constructor.isAnnotationPresent(Class)
例如:
// 判断@Report是否存在于Person类:
Person.class.isAnnotationPresent(Report.class);
//使用反射API读取Annotation:
Class.getAnnotation(Class)
Field.getAnnotation(Class)
Method.getAnnotation(Class)
Constructor.getAnnotation(Class)
//读取方法、字段和构造方法的Annotation和Class类似。但要读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。例如,对于以下方法定义的注解:
public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}
//要读取方法参数的注解,我们先用反射获取Method实例,然后读取方法参数的所有注解:
// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
if (anno instanceof Range r) { // @Range注解
r.max();
}
if (anno instanceof NotNull n) { // @NotNull注解
//
}
}
反射
什么是反射
反射允许对成员变量,成员方法和构造方法的信息进行编程访问
反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息
是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
对于任意一个对象,都能够调用它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制
反射的作用:
利用反射可以无视修饰符调用类里面的内容
可以与配置文件结合使用
Class对象
字节码文件和字节码文件对象:
字节码文件:就是通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)
字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。
这个对象里面至少包含了:构造方法,成员变量,成员方法。
获取Class对象:
三个获取方式对应着三个阶段:
方法一对应源代码阶段,这时java文件被编译为class文件
方法二对应加载阶段,这时class文件被加载到内存
犯法三对应运行阶段,该阶段该类的对象被创建
//1.Class这个类里面的静态方法forName
//Class.forName("类的全类名"): 全类名 = 包名 + 类名
Class clazz1 = Class.forName("com.itheima.reflectdemo.Student");
//2.通过class属性获取
//类名.class
Class clazz2 = Student.class;
//因为class文件在硬盘中是唯一的,所以,当这个文件加载到内存之后产生的对象也是唯一的
System.out.println(clazz1 == clazz2);//true
//3.通过Student对象获取字节码文件对象
Student s = new Student();
Class clazz3 = s.getClass();
API:
get:获取 set:设置
Constructor:构造方法 Parameter:参数
Field:成员变量 Modifiers:修饰符
Method:方法 Declared:私有的
获取构造方法
java中用Constructor类表示构造方法
| Class类获取Constructor对象 | 说明 |
|---|---|
| Constructor<?>[] getConstructors() | 获得所有的构造(只能public修饰) |
| Constructor<?>[] getDeclaredConstructors() | 获得所有的构造(包含private修饰) |
| Constructor |
获取指定构造(只能public修饰) |
| Constructor |
获取指定构造(包含private修饰) |
| Constructor类中创建对象方法 | |
| T newInstance(Object... initargs) | 根据该构造方法以及传入的参数创建对象 |
| setAccessible(boolean flag) | 设置权限的校验(true为取消) |
注:
获取指定构造时,传入的参数是对应构造方法参数的类型的Class对象
使用私有构造方法对象创建对象时,需要先调用setAccessible(true)取消权限的校验再创建对象// 暴力反射
获取字段(成员变量)
java中用Field类表示成员变量
| Class类获取Field对象 | 说明 |
|---|---|
| Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的) |
| Field[] getDeclaredFields() | 返回所有成员变量对象的数组,存在就能拿到 |
| Field getField(String name) | 返回单个成员变量对象(只能拿public的) |
| Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到 |
| Field获取值和修改值方法 | |
| void set(Object obj, Object value) | 赋值 |
| Object get(Object obj) | 获取值 |
| setAccessible(boolean flag) | 设置权限的校验(true为取消) |
注:
使用Field方法时,需要有对应的对象,如调用get方法,需要传入已有对象,然后获取到该对象内对应字段的值,并进行返回
通过反射获取的字段对象(Field)是与特定类绑定的,它只能作用于该类的实例,不能直接作用于其他类的实例//可以父类的用于子类
获取成员方法
java中用Method类表示成员方法
| Class类获取Method对象 | 说明 |
|---|---|
| Method[] getMethods() | 返回所有成员方法对象的数组(只能拿public的) |
| Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,存在就能拿到 |
| Method getMethod(String name, Class<?>... parameterTypes) | 返回单个成员方法对象(只能拿public的) |
| Method getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回单个成员方法对象,存在就能拿到 |
| Method类中方法 | |
| Object invoke(Object obj, Object... args) | 运行方法 参数一:用obj对象调用该方法 参数二:调用方法的传递的参数(如果没有就不写) 返回值:方法的返回值 |
| setAccessible(boolean flag) | 设置权限的校验(true为取消) |
注:
getMethods获取的方法包含父类中的所有公共方法
getDeclaredMethods获取的方法不包含父类
父类获取的方法可以给传入子类调用
动态代理
特点:
可以无侵入式的给代码增加额外的功能
调用者 => 代理 => 对象
在java中,被代理的对象和代理都需要实现同一个接口,接口中的方法就是被代理的所有方法
实现:
java.lang.reflect.Proxy提供了为对象产生代理的方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数一:用于指定用哪个类加载器,去加载生成的代理类
参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
参数三:用来指定生成的代理对象要干什么事情
返回值:构造完毕后得到的代理对象
public static Star createProxy(BigStar bigStar){
Star star = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),//参数一:用于指定用哪个类加载器,去加载生成的代理类
new Class[]{Star.class},//参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
new InvocationHandler() {//参数三:用来指定生成的代理对象要干什么事情
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//参数一:代理的对象
//参数二:要运行的方法
//参数三:调用方法时,传递的实参
if("sing".equals(method.getName())){
System.out.println("sing方法拓展");
}else if("dance".equals(method.getName())){
System.out.println("dance方法拓展");
}
//使用反射让被代理对象执行原本方法
return method.invoke(bigStar,args);}});
return star;
}
//BigStar是被代理对象,其实现了Star接口
//在获取到代理对象后,使用代理对象执行任务,流程会进入到传入的InvocationHandler接口的实现类的invoke方法内执行
//更好理解
public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;}};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");}}
interface Hello {
void morning(String name);
}
//上面的动态代理改写为静态实现类大概长这样
public class HelloDynamicProxy implements Hello {
InvocationHandler handler;
public HelloDynamicProxy(InvocationHandler handler) {
this.handler = handler;
}
public void morning(String name) {
handler.invoke(
this,
Hello.class.getMethod("morning", String.class),
new Object[] { name }); }}
常用工具包
Commons-io
Commons-io是Apache开源基金会提供的一组有关io操作的开源工具包
public static void main(String[] args) throws IOException {
/*
FileUtils类
static void copyFile(File srcFile, File destFile) 复制文件
static void copyDirectory(File srcDir, File destDir) 复制文件夹
static void copyDirectoryToDirectory(File srcDir, File destDir) 复制文件夹
static void deleteDirectory(File directory) 删除文件夹
static void cleanDirectory(File directory) 清空文件夹
static String readFileToString(File file, Charset encoding) 读取文件中的数据变成成字符串
static void write(File file, CharSequence data, String encoding) 写出数据
IOUtils类
public static int copy(InputStream input, OutputStream output) 复制文件
public static int copyLarge(Reader input, Writer output) 复制大文件
public static String readLines(Reader input) 读取数据
public static void write(String data, OutputStream output) 写出数据
*/
Hutool
官网:
https://hutool.cn/
API文档:
https://apidoc.gitee.com/dromara/hutool/
中文使用文档:
https://hutool.cn/docs/#/
java命令
javac
javac命令用于将Java源代码编译成Java字节码文件(.class文件),以便在Java虚拟机(JVM)上运行
javac [options] source_file(s)
javac HelloWorld.java
//options:javac命令提供了多个选项来控制编译过程和生成的字节码文件的行为。可以使用javac -help查看所有选项及其说明。
//source_file(s):指定要编译的源代码文件的路径。可以使用通配符(*)来编译整个文件夹中的所有Java文件。
//例如,javac *.java会同时编译当前目录中的所有Java文件
常见选项
-d directory:指定编译生成的.class文件的输出目录。
-classpath path:指定可供编译器使用的类路径。
-source version:指定要编译的Java源代码的版本(例如,-source 8表示使用Java 8的语言功能进行编译)。
-target version:指定要生成的字节码文件的Java版本(例如,-target 1.8表示要生成适用于Java 8的字节码文件)。
-Xlint:启用编译器警告消息。
-verbose:显示编译器的详细输出信息。
————————————————
java
java命令用于启动Java虚拟机并执行Java程序。使用java命令可以在命令行中直接运行编译后的Java程序。
java [options] class [args...]
java HelloWorld
//options:Java命令提供了多个选项来控制Java虚拟机和应用程序的行为。可以使用java -help查看所有选项及其说明。
//class:要运行的Java类名。
//args…:传递给主方法的参数。这些参数将作为字符串数组传递给main()方法。
常见选项
-classpath path:指定Java虚拟机应该搜索类文件的路径。与javac编译器选项-cp相同。
-Xmx size:指定堆大小的最大值,以字节为单位。例如,-Xmx1024m表示堆大小的最大值为1024 MB。
-Xms size:指定初始堆大小,以字节为单位。
-version:查看Java版本信息。
-jar file:执行指定的JAR文件。
-Dproperty=value:设置系统属性。例如,-Djava.awt.headless=true表示启用无头模式。
-Dfile.encoding= 字符集 , 设置虚拟机执行时使用的字符集
————————————————
javap
javap命令用于查看编译后的Java类文件的信息。它可以解析字节码文件,并显示有关类及其成员的信息。
javap [options] class
javap HelloWorld
//options:javap命令提供了多个选项来控制输出的内容和格式。可以使用javap -help查看所有选项及其说明。
//class:要查看的类名。该类必须已经编译成.class文件,否则javap将无法解析它。
-c:以指令列表的形式打印代码。这对于特定的“bytecode hacking”任务非常有用。
-l:以行号和本地变量表的形式打印代码。
-s:输出内部类型签名。
-verbose:以更详细的方式输出类信息。
-classpath path:指定类路径和搜索位置。
-J:向Java虚拟机传递选项。例如,-J-Xms1024m会设置最小堆大小为1024 MB。
-constants:只显示常量池中的常量,而不显示方法体的内容。
————————————————
jar
jar命令用于创建和管理JAR格式的归档文件,也可以用于解压缩包和对JAR文件进行签名等操作。
jar [options] archive_file file(s)
jar cf myJar.jar *.class
//options:jar命令提供了多个选项来控制生成的JAR文件的内容和行为。可以使用jar -help查看所有选项及其说明。
//archive_file:生成的JAR文件的名称。
//file(s):需要添加到JAR文件中的文件或目录列表。
c:表示新建一个JAR文件。
f:指定JAR文件的文件名以及可选路径
v:输出生成过程中处理的文件列表。
m:表示需要保留manifest文件。
x:从JAR文件中提取指定的文件。
t:列出JAR文件中存储的文件。
u: 更新现有的JAR文件中的文件。
i:为已有的JAR文件创建索引文件
————————————————

浙公网安备 33010602011771号