javapublic class helloworld {
public static void main(String[] args)
{
System.out.println("hello world");
}
}
println
javaSystem.out.println("helloworld"); //该语句将输出"helloworld"
Scanner
javaimport java.util.Scanner;
Scanner sc = new Scanner(System.in);
int num = sc.nextInt(); //输入一个整数
java//nextInt()遇到空格,回车会停止
System.out.println("请输入一个整数:");
int num = sc.nextInt();
System.out.println(num);
System.out.println("请输入第二个整数:");
int num2= sc.nextInt();
System.out.println(num2);
//next()遇到空格,回车会停止
System.out.println("请输入一个字符串:");
String str1 = sc.next();
System.out.println(str1);
System.out.println("请输入第二个字符串:");
String str2 = sc.next();
System.out.println(str2);
//nextLine()遇到空格,制表符都不会停止,遇到回车停止
System.out.println("请输入一个字符串:");
String str1 = sc.nextLine();
System.out.println(str1);
System.out.println("请输入第二个字符串:");
String str2 = sc.nextLine();
System.out.println(str2);
bashjavac helloworld.java //javac将.java文件编译成.class 文件
bashjava helloworld //java用于运行.class 文件
graph TB
i("Java使用平台")
i---javaSE
i---javaME
i---javaEE
javaSE
javaME
javaEE
graph TB
id01("面向对象")
id02("安全性")
id03("多线程")
id04("跨平台")
id05("开源")
JDK是Java的开发环境
graph TB
subgraph JDK
subgraph JVM
end
subgraph 核心类库
end
subgraph 开发工具
end
end
JRE是Java的运行环境
graph TB
subgraph JRE
subgraph JVM
end
subgraph 核心类库
end
subgraph 运行工具
end
end
即数据在代码中的书写格式
graph TB
subgraph 字面量
subgraph 整数类型
end
subgraph 小数类型
end
subgraph 字符串类型
end
subgraph 字符类型
end
subgraph 布尔类型
end
subgraph 空类型
end
end
语法:
java数据类型 变量名 = 数据值
数据类型 | 描述 | 取值范围 |
---|---|---|
byte | 整数,一字节 | -128~127 |
short | 整数,两字节 | -32768~32767 |
int | 整数,四字节 | -2147483648~2147483647 (10位数) |
long | 整数,八字节 | -9223372036854775808~9223372036854775807 (19位数) |
float | 浮点数,四字节 | -3.401298e-38到3.402823e+38 |
double | 浮点数,八字节 | -4.9000000e-324到1.797693e+308 |
char | 字符 | 0-65535 |
String | 字符串型 | |
boolean | 布尔 | true,false |
注意:
即给类,方法,变量等起的名字。
标识符命名规则
由数字、字母、下划线(_)和美元符($)组成
不能以数字开头
不能是关键字(保留字)
区分大小写
命名建议
运算符
表达式
符号 | 作用 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取模,取余 |
++ | 自增 |
-- | 自减 |
注意:
i++
是先将i
原来的值赋出去后,再自增++i
是先将i
自增后,再将i
的值赋出去i--
、--i
和i++
、++i
同理javaint i = 10;
int X = i++; //x=10
int y = ++i; //x=11
隐式转换(自动类型提升)
byte
short
char
三种类型的数据在运算的时候,都会直接先提升为int
,然后再进行运算强制转化
目标数据类型 变量名 = (目标数据类型)被强转的数据;
符号 | 作用 | 说明 |
---|---|---|
= | 赋值 | int a=10,将10赋值给变量a |
+= | 加后赋值 | a+=b,将a+b的值给a |
-= | 减后赋值 | a-=b,将a-b的值给a |
*= | 乘后赋值 | a*=b,将axb的值给a |
/= | 除后赋值 | a/=b,将a÷b的商给a |
%= | 取余后赋值 | a%=b,将a÷b的余数给a |
符号 | 说明 |
---|---|
== | a==b,判断a和b的值是否相等,成立为true,不成立为false |
!= | a!=b,判断a和b的值是否不相等,成立为true,不成立为false |
> | a>b,判断a是否大于b,成立为true,不成立为false |
>= | a>=b,判断a是否大于等于b,成立为true,不成立为false |
< | a<b,判断a是否小于b,成立为true,不成立为false |
<= | a<=b,判断a是否小于等于b,成立为true,不成立为false |
符号 | 作用 | 说明 |
---|---|---|
& | 逻辑与(且) | 并且,两边都为真,结果才是真 |
| | 逻辑或 | 或者,两边都为假,结果才是假 |
^ | 逻辑异或 | 相同为false,不同为true |
! | 逻辑非 | 取反 |
短路:
符号 | 作用 | 说明 |
---|---|---|
&& | 短路与 | 结果和&相同,但是有短路效果 |
|| | 短路或 | 结果和|相同,但是有短路效果 |
语法:
(表达式1)?(表达式2):(表达式3)
即if
...else
运算符 | 含义 | 运算规则 |
---|---|---|
<< | 左移 | 向左移动,低位补0 |
>> | 右移 | 向右移动,高位补0或1 |
>>> | 无符号右移 | 向右移动,高位补0 |
顺序结构语句是Java程序默认的执行流程,按照代码的先后顺序,从上到下依次执行
if
语句javaif(关系表达式){
代码块
}
if
...else
javaif(关系表达式){
代码块1
}else{
代码块2
}
if
...else if
...else
javaif(关系表达式){
代码块1
}else if(关系表达式){
代码块2
}else{
代码块3
}
switch
javaswitch(表达式){
case 值1:
语句1;
break;
case 值2:
语句2;
break;
case 值3:
语句3;
break;
...
default:
语句体n;
break;
}
注意:
for
循环Javafor(初始化语句;条件判断语句;条件控制语句){
循环体
}
while
循环java初始化语句;
while(条件判断语句){
循环体;
条件控制语句;
}
for
和while
的区别
for
循环中,控制循环的变量,因为归属for
循环的语法结构中,在for
循环结束后,就不能再次被访问到了for
循环中:知道循环次数或者循环的范围while
循环中,控制循环的变量,对于while
循环来说不归属其语法结构中,在while
循环结束后,该变量还可以继续使用while
循环,不知道循环的次数和范围,只知道循环的结束条件。do
...while
循环
java初始化语句
do{
循环体
条件控制语句
}while(条件判断语句)
continue
语句
break
语句
数组指的是一种容器,可以用来存储同种数据类型的多个值
语法1:
java数据类型 []数组名
int []array
语法2:
java数据类型 数组名[]
int array[]
初始化:在内存中,为数组容器开辟空间,并将数据存入容器中的过程
语法(完整格式):
java数据类型[] 数组名=new数据类型[]{元素1,元素2,元素3...};
int[] array = new int[]{11,22,33};
语法(简写格式):
java数据类型[] 数组名={元素1,元素2,元素3...};
int[] array = {11,22,33};
动态初始化:初始化时只指定数组长度,由系统为数组分配初始值。
java数据类型[]数组名=new数据类型[数组长度];
int[] arr = new int[3];
数组默认初始化值的规律
动态初始化:手动指定数组长度,由系统给出默认初始化值。
静态初始化:手动指定数组元素,系统会根据元素个数,计算出数组的长度。
数组的地址值表示数组在内存中的位置
javaint[] array = {11,22,33};
System.out.println(array); //[I@776ec8df
Java地址含义
[
:表示当前是一个数组I
:表示当前数组元素都是int
类型@
表示一个间隔符号(固定格式)776ec8df
数组的地址值语法:
java数组名[索引]; //索引从0开始
array[1];
数组遍历:将数组中所有的内容取出来
javafor(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
栈
堆
方法区
本地方法栈
寄存器
方法(method)是程序中最小的执行单元。
语法:
javapublic static 返回值类型 方法名(参数1,参数2,....){
方法体
return 返回值;
}
形参:
实参:
javapublic static void main(String[] args){
method(10,20);//这里的10,20是实参
}
public static void method(int a,int b){
//a和b都是形参
}
方法的重载
java//method发生了重载(函数参数个数不同)
public static void method(int a,int b){
}
public static void method(int a,int b,int c){
}
//不同参数类型重载
public static void method(int a,int b){
}
public static void method(double a,double b){
}
什么是包?
使用其他类的规则
javapublic class Test {
public static void main(String[] args) {
com.example.domain.Student s = new com.example.domain.Student(); // 完整写法,使用全类名
}
}
import
关键字导入类javaimport com.example.domain.Student;
public class Test {
public static void main(String[] args) {
Student s = new Student(); // 导包后只需要写类名就行了
}
}
final修饰方法:
final修饰类:
final修饰变量:
final注意事项
final相当于c++的 const关键字,即不可修改,但也有Java的特性即不可继承
常量命名规则
权限修饰符
javapublic class Student {
private String name;
private int age;
}
权限修饰符的分类
修饰符 | 同一个类中 | 同一个包中其他类 | 不同包下的子类 | 不同包下的无关类 |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
使用情况
使用规则
代码块的分类
局部代码块
javapublic class Test {
public static void main(String[] args) {
{ // 局部代码块 局部变量只有在局部代码块中有效,运行完代码块中的内容,变量自动淘汰
int a = 10;
system.out.println(a);
}
}
}
构造代码块
javapublic class student {
private String name;
private int age;
{ // 构造代码块,写在成员变量位置,作用:可以把多个构造方法中重复的代码抽离出来
// 构造代码块的执行时机,优先于构造方法执行
System.out.println("开始创建对象了");
}
public student() {
}
public student ( String name,int age) {
this.name = name;
this.age = age;
}
}
静态代码块
javapublic class student {
private String name;
private int age;
static{ // 静态代码块,写在成员变量位置,
// 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
// 使用场景:在类加载的时候,做一些数据初始化的时候使用。
System.out.println("开始创建对象了");
}
public student() {
}
public student ( String name,int age) {
this.name = name;
this.age = age;
}
}
可变参数
格式: 属性类型...args
javapublic static void main(String[] args) {
System.out.println(func("add", 1, 2, 3, 4, 5));
System.out.println(func("sub", 1, 2, 3, 4, 5));
System.out.println(func("mul", 1, 2, 3, 4, 5));
System.out.println(func("div", 1, 2, 3, 4, 5));
/**
* 15
* -13
* 120
* 0
*/
}
public static int func(String pattern, int num, int... args) {
switch (pattern) {
case "add":
return add(num, args);
case "sub":
return sub(num, args);
case "mul":
return mul(num, args);
case "div":
return div(num, args);
default:
return 0;
}
}
private static int div(int num, int[] args) {
int result = num;
for (int arg : args) {
result /= arg;
}
return result;
}
private static int mul(int num, int[] args) {
int result = num;
for (int arg : args) {
result *= arg;
}
return result;
}
private static int sub(int num, int[] args) {
int result = num;
for (int arg : args) {
result -= arg;
}
return result;
}
private static int add(int num, int[] args) {
int result = num;
for (int arg : args) {
result += arg;
}
return result;
}
graph TB
subgraph 面向对象
subgraph 封装
end
subgraph 继承
end
subgraph 多态
end
end
类(设计图)
对象:
java//定义一个类
public class类名{
1、成员变量(代表属性,一般是名词)
2、成员方法(代表行为,一般是动词)
3、构造器
4、代码块
5、内部类
}
java//实例化对象
类名 对象名 = new 类名();
//访问对象属性
对象名.属性;
//访问对象方法
对象名.方法();
注意:
用来描述一类事物的类,专业叫做:Javabean
类。
Javabean
类中,是不写main
方法的。编写main
方法的类,叫做测试类。
javabean
类的对象并进行赋值调用。类名首字母建议大写,需要见名知意,驼峰模式。
一个Java
文件中可以定义多个class
类,且只能一个类是public
修饰,而且public
修饰的类名必须成为代码文件名。
private关键字
private
修饰的成员只能在本类中才能访问public关键字
public
修饰成员变量
局部变量
就近原则
this关键字
this
关键字指向离关键字最近的对象,使用this
关键字可以获取局部变量
this
关键字可以用来区分成员变量和局部变量
构造方法
语法:
javapublic class 类名{
修饰符 类名(参数){
方法体
}
}
特点
执行时机
注意:
构造方法的重载:
推荐:
Javabean类
测试类
工具类
标准的JavaBean
private
修饰setXxx()
/getXxx()
实例化一个对象的过程
class
文件java.lang.String
类代表字符串
Java程序中的所有字符串文字(例如“abc”)都为此类的对象。
javaString str = "abcd";
注意:
javaString str = "abcd";
构造方法 | 说明 |
---|---|
public string() | 创建空白字符串,不含任何内容 |
public string(string original) | 根据传入的字符串,创建字符串对象 |
public string(char[] chs) | 根据字符数组,创建字符串对象 |
public string(byte[] chs) | 根据字节数组,创建字符串对象 |
方法 | 描述 |
---|---|
boolean equals方法(要比较的字符串) | 完全一样结果才是true,否则为false |
boolean equalsIgnoreCase(要比较的字符串) | 忽略大小写的比较 |
方法 | 描述 |
---|---|
public char charAt(int index) | 根据索引返回字符 |
public int length() | 返回此字符串的长度 |
简介
StringBuilder
可以看成是一个容器,创建之后里面的内容是可变的作用
StringBuilder的构造方法
方法 | 描述 |
---|---|
public StringBuilder() | 创建一个空白可变字符串对象,不含有任何内容 |
public StringBuilder(String str) | 根据字符串的内容,来创建可变字符串对象 |
StringBuilder常用方法
方法 | 描述 |
---|---|
public StringBuilder append (任意类型) | 添加数据,并返回对象本身 |
public StringBuilder reverse() | 反转容器中的内容 |
public int length() | 返回长度(字符出现的个数) |
public String toString() | 通过toString() 就可以实现把StringBuilder 转换为String |
简介
StringJoiner
跟StringBuilder
一样,也可以看成是一个容器,创建之后里面的内容是可变的。作用
StringJoiner
在JDK8
之后出现
StringJoiner的构造方法
方法 | 描述 |
---|---|
public StringJoiner(间隔符号) | 创建一StringJoiner 对象,指定拼接时的间隔符号 |
public StringJoiner (间隔符号,开始符号,结束符号) | 创建一个StringJoiner 对象,指定拼接时的间隔符号、开始符号、结束符号 |
StringJoiner的常用方法
方法 | 描述 |
---|---|
public StringJoiner add (添加的内容) | 添加数据,并返回对象本身 |
public int length() | 返回长度(字符出现的个数) |
public String toString() | 返回一个字符串(该字符串就是拼接之后的结果) |
集合(ArrayList)是一种长度可变的存储类
集合可以存储
集合与数组的区别
java//完整格式
ArrayList<int> list = new ArrayList<int>();
//缩写格式
ArrayList<int> list = new ArrayList<>(); //JDK7以上支持
方法 | 描述 |
---|---|
boolean add(E e) | 添加元素,返回值表示是否添加成功 |
boolean remove(E e) | 删除指定元素,返回值表示是否删除成功 |
E remove(int index) | 删除指定索引的元素,返回被删除元素 |
E set(int index,E e) | 修改指定索引下的元素,返回原来的元素 |
E get(int index) | 获取指定索引的元素 |
int size() | 集合的长度,也就是集合中元素的个数 |
static表示静态,是Java中的一个修饰符,可以修饰成员方法,成员变量,被static修饰的成员变量,叫做静态变量,被static修饰的成员方法,叫做静态方法
特点
调用方法
特点
调用方法
static的注意事项
继承是面向对象三大特征之一,可以让类跟类之间产生子父的关系。
Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起继承关系。
javapublic class 子类 extends 父类{}
使用继承的好处
什么时候用继承?
继承后子类的特点?
Java中的继承
子类能继承父类中的哪些内容?
私有 | 非私有 | |
---|---|---|
构造方法 | 不能继承 | 不能继承 |
成员变量 | 能继承 | 能继承 |
成员方法 | 不能继承 | 能继承 |
就近原则
当父类的方法不能满足子类现在的需求时,需要进行方法重写
格式
@Override
重写注解
@Override
是放在重写后的方法上,校验子类重写时语法是否正确。@Override
注解重写的注意事项
重写万法的名称、形参列表必须与父类中的一致。
子类重写父类方法时,返回值类型子类必须小于等于父类
重写的方法尽量和父类保持一致。
只有被添加到虚方法表中的方法才能被重写
继承中构造方法的访问特点是什么?
super()
;什么是多态
多态的表现形式
多态的前提
多态的好处
调用成员变量
调用成员方法
多态的优势
java//在多态形式下,右边对象可以实现解耦合,便于扩展和维护
Person p = new Student();
p.work(); // 业务逻辑发生变化时,后续代码无需修改 例如 Student改为Teacher
多态的弊端
java// 通过强制类型转换为子类的类型
Person p = new Student();
Student s = (Student)p;
判断对象的类型时可以使用instanceof
关键字进行判断
javaif (a instanceof Animal) {
a.func();
} else if (d instanceof Dog) {
d.func();
}
抽象方法
抽象类
抽象方法的定义格式
public abstract 返回值类型 方法名(参数列表)
抽象类的定义格式
public abstract class 类名{}
抽象类和抽象方法的注意事项
接口的定义和使用
public interface 接口名{}
public class 类名 implements 接口名 {}
接口的注意
public class 类名 implements 接口名1, 接口名2{}
public class 类名 extends 父类 implements 接口名1, 接口名2{}
接口中成员的特点
public static final
public abstract
接口和类之间的关系
类和类的关系
类和接口的关系
接口和接口的关系
各代JDK对接口支持的方法
JDK8以后接口中新增的默认方法
JDK8以后接口中新增的静态方法
public static返回值类型方法名(参数列表){}
public static void show(){}
JDK9以后接口中新增的私有方法
private 返回值类型 方法名(参数列表){}
private void show(){}
private static 返回值类型 方法名(参数列表){}
private static void method(){}
设计模式
适配器设计模式
接口,接口实现类,接口适配器目录结构
写在类里面的类叫内部类
javapublic class Car { // 外部类
String carName;
int carAge;
int carColor;
class Engine { // 内部类
String engineName;
int engineAge;
}
}
内部类的访问特点:
内部类的分类
成员内部类
外部类名.内部类名 对象名 = new 外部类名.内部类名();
静态内部类
外部类名.内部类名 对象名=new 外部类名.内部类名();
外部类名.内部类名.方法名();
局部内部类
匿名内部类(重要)
java// 格式
new 类名或者接口名 () {
重写方法
};
Math
Math类的常用方法
方法名 | 说明 |
---|---|
abs(int a) | 获取参数绝对值 |
ceil(double a) | 向上取整 |
floor(double a) | 向下取整 |
round(float a) | 四舍五入 |
max(int a,int b) | 获取两个int值中的较大值 |
pow(double a, double b) | 返回a的b次幂的值 |
random() | 返回值为double的随机值,范围[0.0,1.0) |
System
System类中常用方法
方法名 | 说明 |
---|---|
public static void exit(int status) | 终止当前运行的Java虚拟机,status 0表示正常退出,非0表示异常停止 |
public static long currentTimeMillis() | 返回当前系统的时间毫秒值形式 |
public static void arraycopy(数据源数组,起始索引,目的地数组,起始索引,拷贝个数) | 数组拷贝 |
Runtime
Runtime类常用方法
方法名 | 说明 |
---|---|
public static Runtime getRuntime() | 当前系统的运行环境对象 |
public void exit(int status) | 停止虚拟机 |
public int availableProcessors() | 获得CPU的线程数 |
public long maxMemory() | VM能从系统中获取总内存大小(单位byte) |
public long totalMemory() | JVM已经从系统中获取总内存大小(单位byte) |
public long freeMemory() | JVM剩余内存大小(单位byte) |
public Process exec(String command) | 运行cmd命令 |
Object
Object构造方法
方法名 | 说明 |
---|---|
public Object() | 空参构造 |
Object的成员方法
方法名 | 说明 |
---|---|
public String toString() | 返回对象的字符串表示形式 |
public boolean equals(object obj) | 比较两个对象是否相等 |
protected object clone(int a) | 对象克隆 |
对象克隆
Objects
Objects的成员方法
方法名 | 说明 |
---|---|
public static boolean equals(Object a, Object b) | 先做非空判断,比较两个对象 |
public static boolean isNull(Object obj) | 判断对象是否为null,为null返回true ,反之 |
public static boolean nonNul1(Object obj) | 判断对象是否为null,跟isNull的结果相反 |
BigInteger
BigInteger构造方法
方法名 | 说明 |
---|---|
public BigInteger(int num, Random rnd) | 获取随机大整数,范围: [0 ~ 2的num次方-1] |
public BigInteger(String val) | 获取指定的大整数 |
public BigInteger(String val, int radix) | 获取指定进制的大整数 |
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类型整数,超出范围数据有误 |
BigInteger的存储上限
[1,-2147483648,0]
数组的最大长度是int的最大值:2147483647 数组中每一位能表示的数字:-2147483648 ~2147483647
Biglnteger能表示的最大数字为:42亿的21亿次方
程序中计算不精确问题
javapublic class main {
public static void main(String[] args) {
System.out.println(0.01 + 0.09);
System.out.println(0.216 - 0.1);
System.out.println(0.226 * 0.01);
System.out.println(0.09 / 0.1);
}
}
运行结果
0.09999999999999999 0.11599999999999999 0.0022600000000000003 0.8999999999999999
计算机中小数的表示
Java基本类型中的浮点数
类型 | 占用字节数 | 总bit位数 | 小数部分bit位数 |
---|---|---|---|
float | 4个字节 | 32个bit位 | 23个bit位 |
double | 8个字节 | 64个bit位 | 52个bit位 |
BigDecima的作用
BigDecimal的使用
方法名 | 说明 |
---|---|
public static BigDecimal valueof(double val) | 获取对象 |
public BigDecimal add(BigDecimal val) | 加法 |
public BigDecimal subtract(BigDecimal val) | 减法 |
public BigDecimal multiply(BigDecimal val) | 乘法 |
public BigDecimal divide(BigDecimal val) | 除法 |
public BigDecimal divide(BigDecimal val,精确几位,舍入模式) | 除法 |
世界时间标准
Date类
SimpleDateFormat类作用
构造方法
构造方法 | 说明 |
---|---|
public simpleDateFormat() | 构造一个simpleDateFormat,使用默认格式 |
public simpleDateFormat(String pattern) | 构造一个simpleDateFormat,使用指定的格式 |
常用方法
常用方法 | 说明 |
---|---|
public final String format(Date date) | 格式化(日期对象->字符串) |
public Date parse(String source) | 解析(字符串->日期对象) |
格式化的时间形式的常用的模式对应关系如下
Calendar类
获取Calendar日历类对象的方法
方法名 | 说明 |
---|---|
public static Calendar getInstance() | 获取当前时间的日历对象 |
Calendar常用方法
方法名 | 说明 |
---|---|
public final Date getTime() | 获取日期对象 |
public final setTime(Date date) | 给日历设置日期对象 |
public long getTimeInMillis() | 拿到时间毫秒值 |
public void setTimeInMillis(long millis) | 给日历设置时间毫秒值 |
public int get(int field) | 取日历中的某个字段信息 |
public void set(int field,int value) | 修改日历的某个字段信息 |
public void add(int field,int amount) | 为某个字段增加/减少指定的值 |
Arrays类
方法名 | 说明 |
---|---|
public static String toString(数组) | 把数组拼接成一个字符串 |
public static int binarySearch(数组,查找的元素) | 二分查找法查找元素 |
public static int[] copyOf(原数组,新数组长度) | 拷贝数组 |
public static int[] copyOfRange(原数组,起始索引,结束索引 | 拷贝数组(指定范围) |
public static void fill(数组,元素) | 填充数组 |
public static void sort(数组) | 按照默认方式进行数组排序 |
public static void sort(数组,排序规则) | 按照指定的规则排序 |
正则表达式
字符类
表达式 | 说明 |
---|---|
[abc] | 只能是a, b,或c |
[^abc] | 除了a, b, c之外的任何字符 |
[a-zA-Z] | a到zA到Z,包括(范围) |
[a-d[m-p]] | a到d,或m到p |
[a-z&&[def]] | a-z和def的交集。为: d, e, f |
[a-z&&[^bc]] | a-z和非bc的交集。(等同于[ad-z]) |
[a-z&&[^m-p]] | a到z和除了m到p的交集。(等同于[a-lq-z]) |
预定义字符(只匹配一个字符)
表达式 | 说明 |
---|---|
. | 任何字符 |
\d | 一个数字:[0-9] |
\D | 非数字:[^0-9] |
\s | 一个空白字符:[\t\n\x0B\f\r] |
\S | 非空白字符:[^\s] |
\w | [a-zA-Z_0-9] 英文、数字、下划线 |
\W | [^\w] 一个非单词字符 |
数量词
表达式 | 说明 |
---|---|
X? | X ,一次或0次 |
X* | X,零次或多次 |
X+ | X,一次或多次 |
X{n} | X,正好n次 |
X{n, } | X,至少n次 |
x{n,m} | X,至少n但不超过m次 |
符号
符号 | 含义 | 举例 |
---|---|---|
? | 0次或1次 | \\d? |
* | 0次或多次 | \\d* (abc)* |
+ | 1次或多次 | \\d+ (abc)+ |
{} | 具体次数 | a{7} \\d{7,19} |
(?i) | 忽略后面字符的大小写 | (?i)abc |
a((?i)b)c | 只忽略b的大小写 | a((?i)b)c |
条件
符号 | 说明 |
---|---|
? | 表示前面的数据为 |
` | ` |
= | 表示在前面的数据后面要接的数据,举例`Java(?=8 |
空 | 表示连接,例如`Java(8 |
: | 表示排除后面带有: 符号后面的数据的数据,例如`Java(?:8 |
贪婪匹配和非贪婪匹配
符号 | 说明 |
---|---|
+或者* | 表示贪婪匹配,例如ab+ 将匹配尽可能多的b |
+?或者*? | 表示非贪婪匹配 |
分组
分组
(\\d+)(\\d+)(\\d+)
\\1
表示内部使用编号为1的组$1
表示外部使用编号为1的组javaString str = "我要学学编编编编编编编程程程程程程程";
String result = str.replaceAll("(.)\\1+", "$1");
System.out.println(result); // 我要学编程
捕获分组
(.+).+\\1
其中\\1
就是把第一组再拿出来用一次(.+).+\\1*
其中*
表示重复用1组0次或多次非捕获分组
(?:)
,(?=)
,(?!)
都是非捕获分组javapublic static void main(String[] args) {
// 列出几个测试的手机号
String[] phoneNumbers = {
"13812345678",
"138123456789",
"1381234567",
"1381234567890",
"1381234567a"
};
// 遍历数组, 使用正则匹配
for (String number : phoneNumbers) {
System.out.println(number + " is " + (number.matches("1[3-9]\\d{9}")? "valid" : "invalid"));
}
}
输出结果
13812345678 is valid 138123456789 is invalid 1381234567 is invalid 1381234567890 is invalid 1381234567a is invalid
爬取网页数据
javapublic static void main(String[] args) throws Exception {
URL url = new URL("http://m.fsdpp.cn/suibi/13144755514090.html");
URLConnection conn = url.openConnection();
BufferedReader br = new BufferedReader(new java.io.InputStreamReader(conn.getInputStream()));
String line;
// 正则表达式
String regex = "[1-9]\\d{17}";
// 编译正则表达式,Pattern对象是正则表达式编译后在内存中的表示形式
Pattern pattern = Pattern.compile(regex);
while ((line = br.readLine()) != null) {
// 通过正则表达式对象得到一个匹配器对象
Matcher matcher = pattern.matcher(line);
while (matcher.find()) { // 如果匹配到了
System.out.println(matcher.group()); // 打印匹配到的内容
}
}
}
java410882197411292738
210124198508162281
210502198412020944
210411198504282942
622723198602013412
210304198504260488
210421198403162020
210303198412082729
210302198607160938
正则表达式在字符串方法中的使用
方法名 | 说明 |
---|---|
public string[] matches (string regex) | 判断字符串是否满足正则表达式的规则 |
public string replaceAll(String regex, String newStr) | 按照正则表达式的规则进行替换 |
public string[] split(String regex) | 按照正则表达式的规则切割字符串 |
包装类
方法名 | 说明 |
---|---|
public static String toBinaryString(int i) | 得到二进制 |
public static String toOctalString(int i) | 得到八进制 |
public static String toHexString(int i) | 得到十六进制 |
public static int parselnt(String s) | 将字符串类型的整数转成int类型的整数 |
函数式编程
Lambda表达式
@FunctionalInterface
注解java// 函数式匿名内部类
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
})
使用过Lambda表达式代替重写
javaArrays.sort(arr, (Integer o1, Integer o2) -> {
return o1 - o2;
}
)
集合体系 | 集合类别 | 填充数据 |
---|---|---|
Collection 体系 | 单列集合 | 填充一个数据 |
Map 体系 | 双列集合 | 填充一堆数据 |
Collection
体系
Map
体系
Collection
Collection常用方法
方法名称 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数/集合的长度 |
Collection遍历方式
迭代器通历
Collection集合获取迭代器
方法名称 | 说明 |
---|---|
Iterator<E> iterator() | 返回迭代器对象,默认指向当前集合的0索引 |
lterator中的常用方法
方法名称 | 说明 |
---|---|
boolean hasNext() | 判断当前位置是否有元素,有元素返回true ,没有元素返回false |
E next() | 获取当前位置的元素,并将迭代器对象移向下一个位置。 |
增强for遍历
格式
javafor (元素的数据类型 变量名 : 数组或集合) {
}
Lambda表达式遍历
格式
javacollection.forEach((String s) -> { System.out.println(s); })
List集合的特点
List集合的实现类
List集合特有方法
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
List集合的遍历方式
ArrayList
原理
集合的创建方法
java//完整格式
ArrayList<int> list = new ArrayList<int>();
//缩写格式
ArrayList<int> list = new ArrayList<>(); //JDK7以上支持
集合的常用方法
方法 | 描述 |
---|---|
boolean add(E e) | 添加元素,返回值表示是否添加成功 |
boolean remove(E e) | 删除指定元素,返回值表示是否删除成功 |
E remove(int index) | 删除指定索引的元素,返回被删除元素 |
E set(int index,E e) | 修改指定索引下的元素,返回原来的元素 |
E get(int index) | 获取指定索引的元素 |
int size() | 集合的长度,也就是集合中元素的个数 |
LinkList
LinkList特有方法
特有方法 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
Set集合
Set集合的实现类
哈希值
对象的哈希值特点
重写HashCode方法
Hashset底层原理
哈希表组成
LinkedHashSet
LinkedHashSet底层原理
TreeSet
TreeSet集合默认的规则
TreeSet的两种比较方式
java// Javabean类实现Comparable接口指定比较规则
public class Student implements Comparable<Student>{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student o) {
// 按照年龄升序排序
return this.getAge() - o.getAge();
}
}
Map
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() | 集合的长度,也就是集合中键值对的个数 |
Map的遍历方式
键找值
javapublic static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
// 插入测试数据
map.put("1","a");
map.put("2","b");
map.put("3","c");
Set<String> key = map.keySet();
for (String s : key) {
System.out.println(s + " " + map.get(s));
}
}
键值对
javapublic static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
// 插入测试数据
map.put("1","a");
map.put("2","b");
map.put("3","c");
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
Lambda表达式
javapublic static void main(String[] args) {
Map<String,String> map = new HashMap<String,String>();
// 插入测试数据
map.put("1","a");
map.put("2","b");
map.put("3","c");
map.forEach((k,v)->{
System.out.println(k + " " + v);
});
}
HashMap
LinkedHashMap
TreeMap
Collections
Collections常用API
方法名称 | 说明 |
---|---|
public static <T> boolean addA11(Collection<T> c,T... elements) | 批量添加元素 |
public static void shuffle(List<?> list) | 打乱List集合元素的顺序 |
public static <T> void sort(List<T> list) | 排序 |
public static <T> void sort(List<T> list,Comparator<T> c) | 根据指定的规则进行排序 |
public static <T> int binarysearch (List<T> list,T key) | 以二分查找法查找元素 |
public static <T> void copy(List<T> dest, List<T> src) | 拷贝集合中的元素 |
public static <T> int fill (List<T> list,T obj) | 使用指定的元素填充集合 |
public static <T> void max/min(collection<T> coll) | 根据默认的自然排序获取最大/小值 |
public static <T> void swap(List<?> list, int i, int j) | 交换集合中指定位置的元素 |
不可变集合
格式
方法名称 | 说明 |
---|---|
static <E> List<E> of(E...elements) | 创建一个具有指定元素的List集合对象 |
static <E> Set<E> of(E...elements) | 创建一个具有指定元素的Set集合对象 |
static <K,V> Map<K,V> of(E...elements) | 创建一个具有指定元素的Map集合对象 |
properties
Stream流
Stream流的使用步骤:
先得到一条Stream流(流水线),并把数据放上去
使用中间方法对流水线上的数据进行操作
使用终结方法对流水线上的数据进行操作
获取方式 | 方法名 | 说明 |
---|---|---|
单列集合 | default Stream<E> stream() | collection中的默认方法 |
双列集合 | 无 | 无法直接使用stream流 |
数组 | public static <T> Stream<T> stream(T[] array) | Arrays工具类中的静态方法 |
一堆零散数据 | public static<T> Stream<T> of(T... values) | Stream接口中的静态方法,方法的形参是一个可变参数,可以传递一堆零散的数据,也可以传递数组 但是数组必须是引用数据类型的,如果传递基本数据类型,是会把整个数组当做一个元素,放到Stream当中。 |
javapublic static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("one");
list.add("two");
list.add("three");
list.add("four");
list.stream().forEach(s -> {
System.out.println(s);
});
System.out.println("====================================");
Map<String,String> map = new HashMap<String,String>();
map.put("1","one");
map.put("2","two");
map.put("3","three");
map.put("4","four");
map.keySet().forEach(key -> {
System.out.println(key + " : " + map.get(key));
});
System.out.println("====================================");
Stream.of("one","two","three","four").forEach(s -> {
System.out.println(s);
});
}
/**
* one
* two
* three
* four
* ====================================
* 1 : one
* 2 : two
* 3 : three
* 4 : four
* ====================================
* one
* two
* three
* four
*/
名称 | 说明 |
---|---|
Stream<T> filter(Predicate<? super T> predicate) | 过滤 |
Stream<T> limit(long maxSize) | 获取前几个元素 |
Stream<T> skip(long n) | 跳过前几个元素 |
Stream<T> distinct() | 元素去重,依赖(hashCode和equals方法) |
static <T> Stream<T> concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
Stream<R> map(Function<T ,R> mapper) | 转换流中的数据类型 |
名称 | 说明 |
---|---|
void forEach(Consumer action) | 遍历 |
long count() | 统计 |
toArray() | 收集流中的数据,放到数组中 |
collect(Collector collector) | 收集流中的数据,放到集合中 |
javapublic static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("c");
set.add("d");
String[] strings = set.stream().toArray(String[]::new);
System.out.println(Arrays.toString(strings));
}
泛型
泛型的好处
统一数据类型。
把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来。
扩展知识点
中的泛型是伪泛型,即只是在存入时进行了检查,并在取出时进行了强转,存储时还是Object类型泛型类
java修饰符 class 类名<类型>{
}
// 例如
public class ArrayList<E> {
// 此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成:T、E、K、V等
}
泛型方法
<E>
java修饰符<类型>返回值类型 方法名(类型变量名){
}
// 例如
public <T> void show(T t) {
}
// 例如
public class MyArrayList <E>{
public boolean add(E e){
obj[size] = e;
size++;
return true;
}
}
泛型接口
java修饰符 interface 接口名<类型>{
}
// 例如
public interface List<E>{
}
泛型的继承性
泛型的通配符
?
? extend E
只能是某一个类型,及其子类型,其他类型不支持 (上限)? super E
只能是某一类型,及其父类型,其他类型不支持 (下限)方法引用
要求
javapublic class main {
public static void main(String[] args) {
Integer[] arr = { 3, 2, 1, 3, 4, 5 };
Arrays.sort(arr,main::subtraction);
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 3, 4, 5]
}
public static int subtraction(int a, int b) {
return a - b;
}
}
分类
格式:类名::静态方法名
格式:类名::成员方法名
格式:其他类名::成员方法名
其他类对象::方法名
this::方法名
super::方法名
格式:类名::new
Student::new
引用数组的构造方法
格式:数据类型[]::new
int[]::new
异常
Error
Exception:
运行时异常
编译时异常
异常的处理方式
JVM默认的处理方式
自己处理
格式
javatry {
可能出现异常的代码;
} catch(异常类名 变量名) {
异常的处理代码;
} finally {
一定会执行的代码,除非JVM退出
}
目的:当代码出现异常时,可以让程序继续往下执行。
如果我们要捕获多个异常,这些异常中如果存在父子关系的话,那么父类一定要写在下面
在JDK7之后,我们可以在catch中同时捕获多个异常,中间用|进行隔开表示如果出现了A异常或者B异常的话,采取同一种处理方案
抛出异常
throws
javapublic void 方法() throws 异常类名1,异常类名2... {
...
}
throw
javapublic void 方法() {
throw new Exception();
}
Throwable的成员方法
方法名称 | 说明 |
---|---|
public string getMessage() | 返回此 throwable的详细消息字符串 |
public string toString() | 返回此可抛出的简短描述 |
public void printStackTrace( ) | 把异常的错误信息输出在控制台,仅仅是打印信息,不会停止程序运行 |
自定义异常
javapublic class NameFormatException extends RuntimeException {
// 运行时异常继承RuntimeException
// 编译时异常继承Exception
public NameFormatException() {
}
public NameFormatException(String message) {
super(message);
}
}
相对路径
绝对路径
File
方法名称 | 说明 |
---|---|
public File(string pathname) | 根据文件路径创建文件对象 |
public File(String parent,String child) | 根据父路径名字符串和子路径名字符串创建文件对象 |
public File(File parent,String child) | 根据父路径对应文件对象和子路径名字符串创建文件对象 |
File常用成员方法(判断、获取)
方法名称 | 说明 |
---|---|
public boolean isDirectory() | 判断此路径名表示的File是否为文件夹 |
public boolean isFile() | 判断此路径名表示的File是否为文件 |
public boolean exists() | 判断此路径名表示的File是否存在 |
public long length() | 返回文件的大小(字节数量) |
public string getAbsolutePath() | 返回文件的绝对路径 |
public string getPath() | 返回定义文件时使用的路径 |
public string getName() | 返回文件的名称,带后缀 |
public long lastModified() | 返回文件的最后修改时间(时间毫秒值) |
File常用成员方法(创建、删除)
方法名称 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean mkdir() | 创建单级文件夹 |
public boolean mkdirs() | 创建多级文件夹 |
public boolean delete() | 删除文件、空文件夹 |
File常用成员方法(获取并遍历)
方法名称 | 说明 |
---|---|
public File[] listFiles() | 获取当前该路径下所有内容 |
IO流
IO流按照流向可以分类哪两种流?
IO流按照操作文件的类型可以分类哪两种流?
FileOutputStream
javapublic static void main(String[] args) throws Exception{
FileOutputStream fos = new FileOutputStream("a.txt");
String str = "Hello World!";
byte[] bytes = str.getBytes();
fos.write(bytes);
fos.close();
}
创建字节输出流对象
写数据
释放资源
FileOutputStream写数据的3种方式
方法名称 | 说明 |
---|---|
void write(int b) | 一次写一个字节数据 |
void write(byte[] b) | 一次写一个字节数组数据 |
void write(byte[] b, int off, int len) | 一次写一个字节数组的部分数据 |
FileInputStream
FilelnputStream流程
FileInputStream读取文件
方法名称 | 说明 |
---|---|
public int read() | 一次读一个字节数据 |
public int read(byte[] buffer) | 一次读一个字节数组数据 |
计算机的存储规则
GB2312字符集:1980年发布,1981年5月1日实施的简体中文汉字编码国家标准。 收录7445个图形字符,其中包括6763个简体汉字
BIG5字符集∶台湾地区繁体中文标准字符集,共收录13053个中文字,1984年实施。
GBK字符集:2000年3月17日发布,收录21003个汉字。 包含国家标准GB13000-1中的全部中日韩汉字,和BIG5编码中的所有汉字。 windows系统默认使用的就是GBK。系统显示
Unicode字符集:国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换。
Unicode的编码规则
为什么会有乱码?
Java中编码的方法
String类中的方法 | 说明 |
---|---|
public byte[] getBytes() | 使用默认方式进行编码 |
public byte[] getBytes (String charsetName) | 使用指定方式进行编码 |
Java中解码的方法
String类中的方法 | 说明 |
---|---|
String(byte[] bytes) | 使用默认方式进行解码 |
String(byte[] bytes,String charsetName) | 使用指定方式进行解码 |
构造方法 | 说明 |
---|---|
public FileReader(File file) | 创建字符输入流关联本地文件 |
public FileReader(String pathname) | 创建字符输入流关联本地文件 |
成员方法 | 说明 |
---|---|
public int read() | 读取数据,读到末尾返回-1 |
public int read(char[] buffer) | 读取多个数据,读到末尾返回-1 |
FileWriter构造方法
构造方法 | 说明 |
---|---|
public FileWriter(File file) | 创建字符输出流关联本地文件 |
public FileWriter(String pathname) | 创建字符输出流关联本地文件 |
public FileWriter(File file, boolean append) | 创建字符输出流关联本地文件,续写 |
public FileWriter(String pathname,boolean append) | 创建字符输出流关联本地文件,续写 |
FileWriter成员方法
成员方法 | 说明 |
---|---|
void write(int c) | 写出一个字符 |
void write(String str) | 写出一个字符串 |
void write(String str, int off, int len) | 写出一个字符串的一部分 |
void write(char[] cbuf) | 写出一个字符数组 |
void write(char[] cbuf, int off, int len) | 写出字符数组的一部分 |
缓存流
方法名称 | 说明 |
---|---|
public BufferedInputStream(InputStream is) | 把基本流包装成高级流,提高读取数据的性能 |
public BufferedOutputStream(OutputStream os) | 把基本流包装成高级流,提高写出数据的性能 |
javapublic static void main(String[] args) throws Exception{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));
long start = System.currentTimeMillis();
System.out.println("开始复制");
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
long end = System.currentTimeMillis();
System.out.println("复制完成 耗时:" + (end - start) + "ms");
bis.close();
bos.close();
}
字符缓冲流的构造方法
方法名称 | 说明 |
---|---|
public BufferedReader(Reader r) | 把基本流变成高级流 |
public BufferedWriter(Writer r) | 把基本流变成高级流 |
字符缓冲流特有方法
字符缓冲输入流特有方法 | 说明 |
---|---|
public string readLine() | 读取一行数据,如果没有数据可读了,会返回null |
字符缓冲输出流特有方法 | 说明 |
---|---|
public void newLine() | 跨平台的换行 |
转换流
转换流作用
序列化流
需要序列化的Javabean类需要实现Serializable接口
构造方法
构造方法 | 说明 |
---|---|
public ObjectOutputStream(OutputStream out) | 把基本流包装成高级流 |
成员方法
成员方法 | 说明 |
---|---|
public final void writeObject(Object obj) | 把对象序列化(写出)到文件中去 |
反序列化流
注意
javapublic class Student implements Serializble {
private static final long serialersionUID = 1L; // 指定版本号
// 其他属性
// ...
}
通过设置IDEA快速设置版本号
瞬态关键字
transient
,不会把当前属性序列化到本地文件当中构造方法
构造方法 | 说明 |
---|---|
public ObjectInputStream(InputStream out) | 把基本流变成高级流 |
成员方法
成员方法 | 说明 |
---|---|
public Object readObject() | 把序列化到本地文件中的对象,读取到程序中来 |
打印流
构造方法
构造方法 | 说明 |
---|---|
public PrintStream(OutputStream/File/String) | 关联字节输出流/文件/文件路径 |
public PrintStream(String fileName,Charset charset) | 指定字符编码 |
public PrintStream(OutputStream out, boolean autoFlush) | 自动刷新 |
public Printstream(OutputStream out, boolean autoFlush, String encoding) | 指定字符编码且自动刷新 |
成员方法
成员方法 | 说明 |
---|---|
public void write(int b) | 常规方法:规则跟之前一样,将指定的字节写出 |
public void println(Xxx xx) | 特有方法:打印任意数据,自动刷新,自动换行 |
public void print(xxx xx) | 特有方法:打印任意数据,不换行 |
public void printf(String format,Object... args) | 特有方法:带有占位符的打印语句,不换行 |
构造方法
构造方法 | 说明 |
---|---|
public PrintWriter(Write/File/String) | 关联字节输出流/文件/文件路径 |
public PrintWriter(String fileName,Charset charset) | 指定字符编码 |
public PrintWriter(Write w, boolean autoFlush) | 自动刷新 |
public PrintWriter(OutputStream out,boolean autoFlush,Charset charset) | 指定字符编码且自动刷新 |
Commons-io
常用方法-FileUtils类
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类(流相关相关)
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) | 写出数据 |
IO操作工具类
相关类 | 说明 |
---|---|
IoUtil | 流操作工具类 |
FileUtil | 文件读写和操作的工具类 |
FileTypeUtil | 文件类型判断工具类 |
WatchMonitor | 目录、文件监听 |
ClassPathResource | 针对ClassPath中资源的访问封装 |
FileReader | 封装文件读取 |
FileWriter | 封装文件写入 |
线程
进程
并发
并行
多线程的实现方式
javapublic class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + " is running");
}
}
}
测试
javapublic static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
javapublic class ThreadRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Thread t= Thread.currentThread();
System.out.println(t.getName() + " " + i);
}
}
}
javapublic class main {
public static void main(String[] args) {
ThreadRunnable tr = new ThreadRunnable();
Thread t1 = new Thread(tr);
Thread t2 = new Thread(tr);
t1.setName("Thread 1");
t2.setName("Thread 2");
t1.start();
t2.start();
}
}
实现Callable接口和Future接口
实现步骤
javapublic class ThreadCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum= 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
javapublic class main {
public static void main(String[] args) {
// 创建线程任务
ThreadCallable tc = new ThreadCallable();
// 创建FutureTask对象,用于管理线程返回值
FutureTask<Integer> ft = new FutureTask<>(tc);
// 创建线程对象
Thread t = new Thread(ft);
// 启动线程
t.start();
try {
// 获取线程返回值
System.out.println(ft.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
优点 | 缺点 | |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可以扩展性较差,不能再继承其他的类 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
实现callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
方法名称 | 说明 |
---|---|
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yieId() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
多个线程操作同一个数据时会出现问题,可能出现重复,甚至超出的问题
javapublic class main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
public class MyThread extends Thread{
static int count = 0;
@Override
public void run() {
while (true) {
if (count >= 100) {
break;
}
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + " " + count++);
}
}
}
解决方案:使用同步代码块
同步代码块
格式
javasynchronized (锁){
操作共享数据的代码
}
特点
javapublic class MyThread extends Thread{
static int count = 0;
// 锁对象,一定要是唯一
static Object lock = new Object();
@Override
public void run() {
while (true) {
// 同步代码块
synchronized (lock) {
if (count >= 100) {
break;
}
System.out.println(Thread.currentThread().getName() + " " + count++);
}
// 随眠线程不要放在同步代码块中
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
同步方法
格式:
修饰符 synchronized 返回值类型方法名(方法参数){...}
特点
MyThread.
虽然我们可以理解同步代码块和同步方法的锁对象问题, 但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁, 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock锁
void lock()
:获得锁void unlock()
:释放锁,解锁应该放在finally
中Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock
来实例化ReentrantLock
的构造方法
ReentrantLock()
:创建一个ReentrantLock
的实例
javapublic class MyThread extends Thread {
static int count = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (count >= 100) {
break;
}
System.out.println(Thread.currentThread().getName() + " " + count++);
} catch (Exception e) {
throw e;
} finally {
lock.unlock();
}
// 随眠线程不要放在同步代码块中
try {
sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
死锁
一种死锁情况:
javapublic class MyThread extends Thread {
static int count = 0;
static Object objA = new Object();
static Object objB = new Object();
@Override
public void run() {
while (true) {
if ("线程A".equals(getName())) {
synchronized (objA) {
System.out.println("线程A拿到A锁,准备拿B锁");
synchronized (objB) {
System.out.println("线程A拿到B锁");
}
}
} else if ("线程B".equals(getName())) {
synchronized (objB) {
System.out.println("线程B拿到B锁,准备拿A锁");
synchronized (objA) {
System.out.println("线程B拿到A锁");
}
}
}
}
}
}
生产者和消费者(等待唤醒机制)
常见方法
方法名称 | 说明 |
---|---|
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAl1() | 唤醒所有线程 |
java// Desk.java
public class Desk {
// 是否有食物 0:没有 1:有
public static int foodFlag = 0;
// 总个数
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}
// Cook.java
public class Cook extends Thread{
@Override
public void run() {
/**
* 1. 循环
* 2. 同步代码块
* 3. 判断共享数据到了末尾
* 4. 执行核心逻辑
*/
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
// 判断桌子上是否有食物
if (Desk.foodFlag == 1) {
// 如果有就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 如果没有就做
System.out.println("厨师做了一碗面条");
Desk.foodFlag = 1;
Desk.lock.notifyAll(); // 唤醒和这把锁绑定的所有线程
}
}
}
}
}
}
// Foodie.java
public class Foodie extends Thread{
@Override
public void run() {
/**
* 1. 循环
* 2. 同步代码块
* 3. 判断共享数据到了末尾
* 4. 执行核心逻辑
*/
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
if (Desk.foodFlag == 0) {
// 如果没有就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 如果有就吃
Desk.count--;
System.out.println("还能吃"+Desk.count+"碗面条");
Desk.lock.notifyAll(); // 唤醒和这把锁绑定的所有线程
Desk.foodFlag = 0;
}
}
}
}
}
}
// main.java
public class main {
public static void main(String[] args) {
// 生产者和消费者交替执行
Cook cook = new Cook();
Foodie foodie = new Foodie();
cook.start();
foodie.start();
}
}
// 运行结果
厨师做了一碗面条
还能吃9碗面条
厨师做了一碗面条
还能吃8碗面条
厨师做了一碗面条
还能吃7碗面条
厨师做了一碗面条
还能吃6碗面条
厨师做了一碗面条
还能吃5碗面条
厨师做了一碗面条
还能吃4碗面条
厨师做了一碗面条
还能吃3碗面条
厨师做了一碗面条
还能吃2碗面条
厨师做了一碗面条
还能吃1碗面条
厨师做了一碗面条
还能吃0碗面条
阻塞队列
put
take
阻塞队列的继承结构
代码实现
java// main
public static void main(String[] args) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
Cook cook = new Cook(queue);
Foodie foodie = new Foodie(queue);
cook.start();
foodie.start();
}
// Cook
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
// 不断把面条加入阻塞队列中
try {
queue.put("面条");
System.out.println("厨师做了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// Foodie
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
// 不断从阻塞队列中取出面条
try {
String take = queue.take();
System.out.println("吃货吃了一碗" + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
状态 | 说明 |
---|---|
新建状态(NEW ) | 创建线程对象 |
就绪状态(RUNNABLE ) | start方法 |
阻塞状态(BLOCKED ) | 无法获得锁对象 |
等待状态( WAITING ) | wait方法 |
计时等待(TIMED WAITING ) | sleep方法 |
结束状态(TERMINATED ) | 全部代码运行完毕 |
线程池
线程池原理
线程池使用
创建线程池
Executors
线程池的工具类通过调用方法返回不同类型的线程池对象。方法名称 | 说明 |
---|---|
public static Executorservice newCachedThreadPool() | 创建一个没有上限的线程池 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建有上限的线程池 |
代码实现
java// main
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.shutdown();
}
// MyRunnable
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
自定义线程池
使用ThreadPoolExecutor
参数 | 说明 |
---|---|
参数一:核心线程数量 | 不能小于0 |
参数二:最大线程数 | 不能小于0,最大数量>=核心线程数量 |
参数三:空闲线程最大存活时间 | 不能小于0 |
参数四:时间单位 | 用TimeUnit指定 |
参数五:任务队列 | 不能为null |
参数六:创建线程工厂 | 不能为null |
参数七:任务的拒绝策略 | 不能为null |
javapublic static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // 核心线程数
6, // 最大线程数
60, // 最大存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 任务队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
}
最大并行数
线程池的合适大小
CPU密集型运算
IO密集型运算
什么是网络编程?
常见的软件架构
通信的软件架构CS\BS的各有什么区别和优缺点
网络编程三要素
IP
IPv4
lPv6
lPv4的地址分类形式
特殊IP地址
端口号
协议
OSI参考模型 | TCP/IP参考模型 | TCP/IP参考模型各层对应协议 | 面向哪些 |
---|---|---|---|
应用层 | 应用层 | HTTP、FTP、Telnet、DNS... | 一把是应用程序需要关注的。如浏览器,邮箱。程序员一般在这一层开发 |
表示层 | 应用层 | HTTP、FTP、Telnet、DNS... | |
会话层 | 应用层 | HTTP、FTP、Telnet、DNS... | |
传输层 | 传输层 | TCP、UDP、... | 选择传输使用的TCP ,UDP协议 |
网络层 | 网络层 | IP、ICMP、ARP... | 封装自己的IP,对方的IP等信息 |
数据链路层 | 物理+数据链路层 | 硬件设备。 | 转换成二进制利用物理设备传输 |
物理层 | 物理+数据链路层 | 硬件设备。 | 转换成二进制利用物理设备传输 |
UDP协议
UDP发送数据步骤
UDP接收数据步骤
javapublic class UDPSend {
public static void main(String[] args) throws Exception {
// 1. 创建DataGramSocket对象
DatagramSocket ds = new DatagramSocket(8083);
// 2. 打包数据
String str = "hello world";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("localhost");
int port = 8082;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
// 3. 发送数据包
System.out.println("send to: " + address + ":" + port);
ds.send(dp);
// 4. 关闭资源
ds.close();
}
}
public class UDPReceive {
public static void main(String[] args) throws Exception {
// 1. 创建DataGramSocket对象
DatagramSocket ds = new DatagramSocket(8082);
byte[] bytes = new byte[1024];
// 2. 接收数据包
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
System.out.println("waiting for data...");
ds.receive(dp); // 阻塞式方法
// 3. 解析数据包
byte[] data = dp.getData();
int length = dp.getLength();
InetAddress address = dp.getAddress();
int port = dp.getPort();
System.out.println("data: " + new String(data, 0, length));
System.out.println("receive from: " + address + ":" + port);
// 4. 关闭资源
ds.close();
}
}
// 运行结果
send to: localhost/127.0.0.1:8082
waiting for data...
data: hello world
receive from: /127.0.0.1:8083
UDP的三种通信方式
javapublic class UDPSend {
public static void main(String[] args) throws Exception {
// 1. 创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket(3005);
// 2. 打包数据
String str = "hello world";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("224.0.0.1");
int port = 8082;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
// 3. 发送数据包
System.out.println("send to: " + address + ":" + port);
ms.send(dp);
// 4. 关闭资源
ms.close();
}
}
public class UDPReceive1 {
public static void main(String[] args) throws Exception {
// 1. 创建MulticastSocket对象
MulticastSocket ms = new MulticastSocket(8082);
// 2. 将本机添加到224.0.0.1的组中
InetAddress address = InetAddress.getByName("224.0.0.1");
ms.joinGroup(address);
// 2. 接收数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
System.out.println("waiting for data...");
ms.receive(dp); // 阻塞式方法
// 3. 解析数据包
byte[] data = dp.getData();
int length = dp.getLength();
String ip = dp.getAddress().getHostAddress();
int port = dp.getPort();
System.out.println("data: " + new String(data, 0, length));
System.out.println("receive from: " + address + ":" + port);
// 4. 关闭资源
ms.close();
}
}
public class UDPReceive2 {
public static void main(String[] args) throws Exception {
// ...
}
}
public class UDPReceive3 {
public static void main(String[] args) throws Exception {
// ...
}
}
TCP协议
传输控制协议TCP(Transmission Control Protocol)
TCP协议是面向连接的通信协议。 速度慢,没有大小限制,数据安全。
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象通信之前要保证连接已经建立(未连接会报错)
通过Socket产生IO流来进行网络通信
TCP通信客户端
创建客户端的Socket对象(Socket)与指定服务端连接
Socket(string host,int port)
获取输出流,写数据
outputstream getoutputstream()
释放资源 void close()
TCP通信服务器
创建服务器端的Socket对象(ServerSocket)
ServerSocket(int port)
监听客户端连接,返回一个Socket对象
Socket accept()
获取输入流,读数据,并把数据显示在控制台
Inputstream getInputStream()
释放资源
void close()
TCP通信程序(三次握手)
TCP通信程序(四次挥手)
什么是反射?
例如IDEA的代码提示功能,利用了反射获取类的信息
反射获取信息和使用
获取class对象的三种方式
Class.forName("全类名");
类名.class
对象.getClass();
Class类中用于获取构造方法的方法
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor<?>[]getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor<T>getConstructor(Class<?>... parameterTypes) | 返回单个公共构造方法对象 |
Constructor<T>getDeclaredConstructor(Class<?>... parameterTypes) | 返回单个构造方法对象 |
Constructor类中用于创建对象的方法
方法名 | 说明 |
---|---|
T newInstance(Object... initargs) | 根据指定的构造方法创建对象 |
setAccessible(boolean flag) | 暴力反射。设置为true,表示取消访问检查 |
Class类中用于获取成员变量的方法
方法名 | 说明 |
---|---|
Field [] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
Field类中用于创建对象的方法
方法名 | 说明 |
---|---|
void set(Object obj, Object value) | 赋值 |
Object get(Object obj) | 获取值。 |
Class类中用于获取成员方法的方法
方法名 | 说明 |
---|---|
Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的 |
Method getMethod(String name, Class<?>... parameterTypes) | 返回单个公共成员方法对象 |
Method getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回单个成员方法对象 |
Method类中用于创建对象的方法
Object invoke(Object obj, Object...args)
运行方法
代理
代理长什么样?
Java通过什么来保证代理的样子?
java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:
javapublic static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
参数一:用于指定用哪个类加载器,去加载生成的代理类
参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
参数三:用来指定生成的代理对象要干什么事情
注解(Annotation
)是Java SE 5.0 版本开始引入的概念,它是对 Java 源代码的说明,是一种元数据(描述数据的数据)。
@interface
表示,所有的注解会自动继承 java.lang.Annotation
接口,且不能再继承别的类或是接口。public
或默认 (default
) 访问权修饰来进行修饰。按照来源划分,注解可以分为 3 类
JAVA内置注解在 java.lang
中,4个元注解在 java.lang.annotation
中。
JAVA内置注解
元注解 (注解的注解)
@Target
@Target
注解表明该注解可以应用的JAVA元素类型。
Target类型 | 描述 |
---|---|
ElementType.TYPE | 应用于类、接口(包括注解类型)、枚举 |
ElementType.FIELD | 应用于属性(包括枚举中的常量) |
ElementType.METHOD | 应用于方法 |
ElementType.PARAMETER | 应用于方法的形参 |
ElementType.CONSTRUCTOR | 应用于构造函数 |
ElementType.LOCAL_VARIABLE | 应用于局部变量 |
ElementType.ANNOTATION_TYPE | 应用于注解类型 |
ElementType.PACKAGE | 应用于包 |
ElementType.TYPE_PARAMETER | 1.8版本新增,应用于类型变量 |
ElementType.TYPE_USE | 1.8版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型) |
@Retention
@Retention
表明该注解的生命周期。
生命周期类型 | 描述 |
---|---|
RetentionPolicy.SOURCE | 编译时被丢弃,不包含在类文件中 |
RetentionPolicy.CLASS | JVM加载时被丢弃,包含在类文件中,默认值 |
RetentionPolicy.RUNTIME | 始终不会丢弃,可以使用反射获得该注解的信息。由JVM 加载,包含在类文件中,在运行时可以被获取到。自定义的注解最常用的使用方式。 |
@Document
表明该注解标记的元素可以被Javadoc 或类似的工具文档化
@Inherited
表明使用了@Inherited注解的注解,所标记的类的子类也会拥有这个注解。
如各种框架的注解,如 Spring 的注解
使用元注解定义自己的注解
java/**
* 修饰符 @interface 注解名 {
* 注解元素的声明1
* 注解元素的声明2
* }
*/
java@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyTestAnnotation {
/**
* 注解的元素声明的两种形式
* type elementName();
* type elementName() default value;
*/
String value() default "test";
}
下面结合注解的语法,给出 @Service
注解的示例。
java@Target({ElementType.TYPE})// ElementType.TYPE 代表在注解上使用
@Retention(RetentionPolicy.RUNTIME)// RetentionPolicy.RUNTIME 代表运行时使用,可以通过反射获取到
@Documented//包含在JavaDoc中
@Component//允许通过包扫描的方式自动检测
public @interface Service {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
对于定义的注解,可以使用反射技术对注解进行处理。java.lang.reflect.AnnotationElement
接口提供了该功能。如下图所示,反射相关的类 Class
, Method
, Field
都实现了 AnnotationElement
接口。
因此,只要我们通过反射拿到 Class
, Method
, Field
类,就能够通过 getAnnotation(Class)
拿到我们想要的注解并取值。获取类方法和字段的注解信息,常用的方法包括
isAnnotationPresent
:判断当前元素是否被指定注解修饰getAnnotation
:返回指定的注解getAnnotations
:返回所有的注解此处给出利用反射技术,对自定义注解进行解析的示例。
定义自定义注解
java@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyTestAnnotation {
String value() default "test";
}
配置注解
java@Data
@Builder
@MyTestAnnotation
public class MyBean {
private String name;
private int age;
}
利用反射解析注解
javapublic class MyTest {
//isAnnotationPresent:判断当前元素是否被指定注解修饰
//getAnnotation:返回指定的注解
//getAnnotations:返回所有的注解
public static void main(String[] args) {
try {
//获取MyBean的Class对象
MyBean myBean = MyBean.builder().build();
Class clazz = myBean.getClass();
//判断myBean对象上是否有MyTestAnnotation注解
if (clazz.isAnnotationPresent(MyTestAnnotation.class)) {
System.out.println("MyBean类上配置了MyTestAnnotation注解!");
//获取该对象上MyTestAnnotation类型的注解
MyTestAnnotation myTestAnnotation = (MyTestAnnotation) clazz.getAnnotation(MyTestAnnotation.class);
System.out.println(myTestAnnotation.value());
} else {
System.out.println("MyBean类上没有配置MyTestAnnotation注解!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行程序,结果如下
MyBean类上配置了MyTestAnnotation注解! test
本文作者:peepdd864
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!