- Java基础 (9.28)
- DOS命令(了解)
- 变量及类型转换
- Java API
- 运算符
- 赋值运算符
- 三元运算符
- 运算符优先级
- 标识符、关键字、保留字
- 键盘输入语句
- 进制
- 程序控制结构
- 数组
- 二维数组
- 排序
- 查找
- 面对对象基础
- 成员方法传参机制
- 方法递归调用
- 方法重载
- 可变参数
- 作用域
- 构造方法/构造器
- javap的使用
- 对象创建的流程分析
- this关键字
- IDEA
- 包
- 访问修饰符
- 面向对象编程三大特征
- 封装
- 继承
- super
- 方法重写/覆盖(override)
- 多态
- Object类详解
- 断点调试(debug)
- 类变量(静态变量)
- 类方法(静态方法)
- main方法细节
- 代码块
- 设计模式
- final
- 抽象类
- 接口
- 内部类
- 枚举
- JDK注解
- 异常(Exception)
- 常用类
- 集合
- 泛型
- JUnit
- Java绘图
- 线程(基础)
- IO流
- 网络编程
- 反射(reflection)
- JDBC
- 正则表达式(regexp)
Java基础 (9.28)
JDK,JRE
java执行流程和编译
java开发注意事项
项目流程开发
Java代码规范
第一个程序
//1、Hello{} 表示一个类的开始和结束
//2、public static void main(String[] args){} 表示一个主方法,程序的入口
public class Hello{
//编写一个main方法
public static void main(String[] args) {
System.out.println("微咲欢迎来到java");
}
}
Java开发注意事项和细节说明
一个源文件中最多只能有一个public类。其他类的个数不限。
如果源文件包含一个public类,则文件名必须按该类名命名!
一个源文件中最多只能有一个public类,其他类的个数不限,也可以将main方法写在非public类中,然后指定运行非public类,这样入口方法就是非public的main方法
java转义字符
\t:制表符
\n:换行符
\ _ \’: \ _ ‘
\r:回车
java转义字符代码实现
//演示转义字符的使用
public class ChangeChar{
//编写一个main方法
public static void main(String[] args) {
// \t:制表符
System.out.println("我\t是\t你\t爹");
// \n:换行符
System.out.println("\n我\t是\t你\t爹");
// \\ \* \': \ * '
// \r:回车
System.out.println("\r你好");
}
}
Java容易犯的错误
注释(comment)
注释代码实现
/**
* 文档注释
* @author 微咲
* @version 1.0
* */
public class Commen{
public static void main(String[] args) {
//这是单行注释 ( ctrl + / )
/*
多行注释多行注释多行注释多行注释多行注释
多行注释多行注释多行注释多行注释多行注释
多行注释多行注释多行注释多行注释多行注释
多行注释多行注释多行注释多行注释多行注释
*/
}
}
注释格式
_单行注释: //注释文字_5
多行注释:/注释文字/
文档注释:/**
*
*/
文档注释
通过javadoc 可以生成一套一网页文件形式提现的改程序的说明文档。
使用细节
被注释的文字,不会被JVM(Java虚拟机)解释执行
多行注释里不允许有多行注释嵌套
Java代码规范部分
源文件使用utf-8
DOS命令(了解)
相对路径和绝对路径
相对路径
:从当前目录开始定位,形成的一个路径。
例如:Hello.java
绝对路径
:从顶级目录(c、d..盘)开始定位,形成的一个路径。
例如:C:\Users\46124\Desktop\javaCodeSource\JavaOneday\Hello.java
DOS介绍
Dos:Disk Operatring System 磁盘操作系统
常用的dos命令
help ():可以查看如何使用
- dir:查看当前目录所有文件
- cd:切换到其他盘下
例如:切换到c盘: cd/D c:
- cd ..:切换到上一级
- cd\:切换到根目录表
- tree:查看指定目录下所有的子级目录
- cls:清屏
- exit:退出DOS
- md:创建目录
- rd:删除目录
- copy:复制文件
- del:删除文件
- echo:输入内容到文件
- type:
- move:剪切
变量及类型转换
public class Var{
public static void main(String[] args) {
int age=19;
double length=175.5;
char gender='男';
String name="微咲";
System.out.println("我叫,是个生,今天岁了,身高是cm");
//long int
//int lint=99L; //报错 无法将long int->int
//long lInt=99L; //对的
//float
//float f1=1.1; //报错 无法将double->float
//float f2=1.1F; //ok
//double f3=1.1f; //ok
//0.123 可以将0省略
//double d1=.123;
//科学计数法 5.12e2 [5.12*10 二次方]
System.out.println(5.12e2);//512.0
System.out.println(5.12E-2);//0.0512
double num1 = 2.7;
double num2 = 8.1 / 3;
if (num1-num2<0.0001) {
System.out.println("这基本就是相等!");
}
System.out.print(Math.abs(num1-num2));
//char
char c1='c';
char c2='\t';
char c3='韦';
char c4=97; // 输出的是对应ASCII码对应的字符
char c5='a';
System.out.println((int)c5);
char c6='韦';
System.out.println((int)c6);
System.out.println('a'+3);
//boolean
boolean isPass = true;
if (isPass == true) {
System.out.println("恭喜");
}
else {
System.out.println("不恭喜");
}
}
}
- 变量是程序的基本组成单位
- 变量三要素:类型+名称+值
变量使用注意事项
+号的使用
- 当两边都是数字型时,做加法运算
- 当左右两边有一方为字符串时,则做拼接运算
数据类型
整数类型
Java的整数类型就是用来存放数值的。
类型|占用存储空间|范围
byte [字节]|1字节|-128~127(二进制) (8bit)
short [短整型]|2字节|-(2的15次方)~2的15次方-1
int [整型]|4字节|-(2的31次方)~2的31次方-1
long [长整型]|8字节|-(2的63次方)~2的63次方-1
long类型声明:long lNumber=9999L;
bit:计算机中最小存储单位。
byte:计算机中基本存储单元。
浮点类型
Java的浮点类型可以表示一个小数。
类型 | 占用储存空间 | 范围 |
---|---|---|
单精度float | 4字节 | -3.403E38~3.403E38 |
双精度double | 8字节 | -1.798E308~1.798E308 |
- 浮点数在机器中存放形式:浮点数=符号位+指数位+尾数位
- 尾数部分可能丢失,造成精度损失(小数都是近似值)所以通常使用double类型
float声明: flaot f1=1.1f;
double f2=1.1f;
浮点数使用陷阱:2.7 和 8.1 / 3比较
double num1 = 2.7;
double num2 = 8.1 / 3;
num2 = 2.66666667;
———>当我们对运算结果是小数进行相等判断时,要小心
字符类型
字符类型可以表示单个字符,字符类型是char,char是两个字节(可以存放汉字),多个字符我们用字符串String。
char c4=97; // 输出的是对应Unicode ASCII 码对应的字符
字符常量使用细节
- 字符常量是用单引号(’’)括起来的单个字符
- Java中还允许使用转义字符’\’来将其后字符转变成特殊字符型常量
- 在Java中,char的本质是一个整数,在输出时,是Unicode码对应的字符
- char类型是可以进行运算的
char c5=’a’;
System.out.println((int)a);
字符类型本质
1.字符型存储到计算机中,需要将字符对应的码值找出来。比如 ‘a’
存储:'a' == > 码值97 == > 二进制(1100001) == >存储
读取:二进制(1100001) == > 97== > 'a' == > 显示
2.字符和码值的对应关系是通过字符编码表决定的。
- ASCII(ASCII编码表,一个字节表示,共128个字符,实际上一个字节可以表示256个字符,但只用了128个)
- Unicode(Unicode编码表,固定大小的编码,使用两个字节表示字符,字母和汉字统一都是占用两个字节,这样浪费空间)
- utf-8(大小可变的编码,字母使用1个字节,汉字使用3个字节)
(互联网上使用最广的) - GBK(可以表示汉字,范围广,字母使用1个字节,汉字2个字节)
- GBK2312(可以表示汉字,GB2312<GBK)
- Big5码(繁体中文,台湾,香港)
Unicode兼容ASCII码
布尔类型
boolean:不可以用0或非0的整数替代false和true,这点和C不同。
基本数据类型转换
- 自动类型转换:当Java程序在进行赋值或者运算时,精度小的类型自动转换成精度大的数据类型,这个就是自动类型转换。
- 强制类型转换:自动转换的逆过程,将容量大的数据类型转换成容量小的数据类型,使用时要加上 强制转换符(),但可能会造成精度降低or溢出,需注意。
- 数据类型按精度(容量)大小排序为:
char -> int -> long -> float -> double
byte -> short -> int -> long -> float -> double
自动类型转换注意和细节
- 有多种类型的数据混合运算时,系统首先将所有数据转换成容量最大的那种数据类型,然后在进行计算
int n1 = 10; float d1 = n1 + 1.1; //错误
float d1 = n1 + 1.1L; //OK - (byte,short)和char之间不会相互自动转换
- byte,short,char,他们三者可以计算,在计算 byte b1 = 1;
short s1 = 1;
short s2 = b1 + s1;//错误,b1 + s1 =>int
byte b2 = 1;
byte b3 = b1 + b2;//错误b1 + b2=>int
- boolean不参与类型转换
- 自动提升原则:表达式结果的类型自动提升为,操作数中最大的类型
double = int + float + short + byte +long;
强制类型转换细节说明
- 当进行数据从把精度大的 => 精度小的,就需要强制转换
- 强转符号就近操作,可用小括号提高优先级
- char可以保存int常量值,但不能保存int变量值,需强转
- byte和short,char类型在进行运算时,当作int类型处理
基本数据类型和String类型的转换
- 基本类型转String类型:将该基本类型的值+””即可
int n1 = 1; String s1 = 1 + “”; - String类型转基本数据类型:通过基本类型的包装类调用parseXX方法。
int n2 = Integer.parseInt(“123”);
double n3 = Double.parseDouble(“123.123”);
获得字符串中的字符:
System.out.println(s1.charAt(0));
基本数据类型和String类型的转换注意事项
- 在将 String 类型转成 基本数据类型时, ,比如 我们可以把 “123” , 转成一 个整数,但是不能把 “hello” 转成一个整数
- 如果格式不正确,就会抛出异常,程序就会终止, 这个问题在异常处理章节中,会处理
Java API
Java类的组织形式:
JDK8->众包->每个包下有:接口,类(…),异常
类:->字段…+构造器(构造方法)+成员方法(方法)
运算符
运算符是一种特殊的符号,用以表示数据的运算、赋值、和比较等。
- 算术运算符
- 关系运算符 [比较运算符]
- 赋值运算符
- 逻辑运算符
- 三元运算符
- 位运算符 [需要二进制基础]
算术运算符
加减乘除模
+ - * / % :
% 取模,取余:在 % 的本质 看一个公式!!!! :a % b = a - a / b * b
自加自减运算符
++ :i++ => i = i + 1;
作为表达式使用
前++:++i 先自增后赋值
后++:i++先赋值后自增
int i = 1;//i->1
i = i++; //规则使用临时变量: (1) temp=i;(2) i=i+1;(3)i=temp;
System.out.println(i); // 1
int i=1;
i=++i; //规则使用临时变量: (1) i=i+1;(2) temp=i;(3)i=temp;
System.out.println(i); //2
关系运算符 [比较运算符]
- 关系运算符的结果都是 boolean 型,也就是要么是 true,要么是 false
- 关系表达式 经常用在 if 结构的条件中或循环结构的条件中
instanceof:检查是否是类的对象
细节说明
逻辑运算符
介绍:用于连接多个条件(多个关系表达式),最终的结果也是一个 boolean 值。
逻辑运算符一览:
- 短路与 && , 短路或 ||,取反 !
- 逻辑与 &,逻辑或 |,^ 逻辑异或
说明逻辑运算规则
&&和&基本规则
&& 和 & 使用区别
&& 和 & 使用区别
- &&短路与:如果第一个条件为 false,则第二个条件不会判断,最终结果为 false,效率高
- & 逻辑与:不管第一个条件是否为 false,第二个条件都要判断,效率低
短路或 || 与逻辑或 |
||和 | 基本规则、|| 和 | 使用区别
同上&& 与 &
! 非(取反)
! 非(取反) !条件 如果条件本身成立,结果为 false,否则为 true
逻辑异或 ^
a^b: 叫逻辑异或,当 a 和 b 不同时,则结果为 true, 否则为 false
赋值运算符
基本赋值运算符 = int a = 10;
复合赋值运算符
+= ,-= ,*= , /= ,%= 等 , 重点讲解一个 += ,其它的使用是一个道理
a += b; [等价 a = a + b; ]
a -= b; [等价 a = a - b; ]
三元运算符
基本语法
基本语法 :条件表达式 ? 表达式 1: 表达式 2;
运算规则:
- 如果条件表达式为 true,运算后的结果是表达式 1;
- 如果条件表达式为 false,运算后的结果是表达式 2;
使用细节
使用细节:
- 表达式 1 和表达式 2 要为可以赋给接收变量的类型(或可以自动转换)
- 三元运算符可以转成 if—else 语句
//使用一条语句实现求三个数之间的最大值
int max = (n1 > n2 ? n1 : n2) > n3 ?
(n1 > n2 ? n1 : n2) : n3;
System.out.println("最大数=" + max);
运算符优先级
运算符优先级:(由高到低)
- 运算符有不同的优先级,所谓优先级就是表达式运算中的运算顺序。如右表,上一行运算符总优先于下一行。
- 只有单目运算符、赋值运算符是从右向左运算的。
- 一览表, 不要背,使用多了,就熟悉了.
标识符、关键字、保留字
标识符的命名规则
规则:必须遵守,不然报错。
标识符的命名规范
规范:命名更加专业。
- 包名:多单词组成时所有字母都小写:aaa.bbb.ccc //比如 com.hsp.crm
- 类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz [大驼峰] 比如: TankShotGame
- 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz [小 驼峰, 简称 驼峰法] 比如: tankShotGame
- 常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ 比如 :定义一个所得税率 TAX_RATE
- 后面我们学习到 类,包,接口,等时,我们的命名规范要这样遵守,更加详细的看文档.
关键字
关键字的定义和特点
定义:被 Java 语言赋予了特殊含义,用做专门用途的字符串(单词)
特点:关键字中所有字母都为小写
保留字
介绍
Java 保留字:
现有 Java 版本**尚未使用**,但**以后版本可能会作为关键字使用**。自己命名标识符时要避免使用这些保留 字 **byValue、cast、future、 generic、 inner、 operator、 outer、 rest、 var 、 goto 、const**
键盘输入语句
介绍
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取。需要一个 扫描器(对象), 就是 **Scanner**
步骤
步骤 :
- 导入该类的所在包, java.util.*
- 创建该类对象(声明变量)
- 调用里面的功能
import java.util.Scanner; //表示java.util下的Scanner类导入
public class Input{
public static void main(String[] args) {
//演示接受用户的输入
//步骤:
//Sacnner类:简单文本扫描器,在java.util包内
//1.引入Scanner类所在的包
//2.创建Scanner 对象,new 创建一个对象
Scanner mySacnner = new Scanner(System.in);
//3.接受用户的输入,使用相关的方法
System.out.println("请输入名字:");
String name = mySacnner.next();//接受用户输入
System.out.println("请输入年龄:");
int age = mySacnner.nextInt();
System.out.println("请输入薪水:");
double salay = mySacnner.nextDouble();
System.out.println("name="+name+"age="+age+"salay="+salay);
}
}
进制
进制介绍
对于整数,有四种表示方式:
- 二进制:0,1 ,满 2 进 1.以 0b 或 0B 开头。
- 十进制:0-9 ,满 10 进 1。
- 八进制:0-7 ,满 8 进 1. 以数字 0 开头表示。
- 六进制:0-9 及 A(10)-F(15),满 16 进 1. 以 0x 或 0X 开头表示。此处的 A-F 不区分大小写。
进制的转换
二进制转换成十进制
规则:从最低位(右边)开始,将每个位上的数提取出来,乘以2的(位数-1)次方,然后求和。
八进制转换成十进制
规则:从最低位(右边)开始,将每个位上的数提取出来,乘以8的(位数-1)次方,然后求和。
十六进制转换成十进制
规则:从最低位(右边)开始,将每个位上的数提取出来,乘以16的(位数-1)次方,然后求和。
十进制转换成二进制
规则:将该数不断除以2,直到商为0位置,然后将每步得到的余数倒过来,就是对于的二进制。
十进制转换成八进制
规则:将该数不断除以8,直到商为0位置,然后将每步得到的余数倒过来,就是对于的八进制。
十进制转换成十六进制
规则:将该数不断除以16,直到商为0位置,然后将每步得到的余数倒过来,就是对于的十六进制。
二进制转换成八进制
规则:从低位开始,将二进制数每三位一组,转成对应的八进制数即可。
二进制转换成十六进制
规则:从低位开始,将二进制数每四位一组,转成对应的十六进制数即可。
八进制转换成二进制
规则:将八进制每一位数,转成对应的一个3位的二进制数即可。
十六进制转换成二进制
规则:将十六进制每一位数,转成对应的一个4位的二进制数即可。
原码、反码、补码
- 二进制的最高位是符号位:0表示正数,1表示负数
- 正数的原码、反码、补码都一样
- 负数的反码=它的原码符号位不变,其它位取反
- 负数的补码=它的反码+1,负数的反码=负数的补码-1
- 0的反码、补码都是0
- java没有无符号数,换言之,java中的数都是有符号的
- 在计算机运算的时候,都是以补码的方式来运算的
- 当我们看运算结果的时候,要看他的原码
127+1= - 128
位运算符
java 中有 7 个位运算(&、|、 ^ 、~、>>、<<和 >>>)
算术运算符本质:x>>y x/2^y
x<<y x*2^y
public class BitOperator{
public static void main(String[] args) {
//推导过程:
//1、首先要获得2、3的补码
//2、2的原码:00000000 00000000 00000000 00000010
// 3的原码:00000000 00000000 00000000 00000011
//3、2的补码:00000000 00000000 00000000 00000010
// 3的补码:00000000 00000000 00000000 00000011
//4、按位&
// 00000000 00000000 00000000 00000010
// 00000000 00000000 00000000 00000011
// 00000000 00000000 00000000 00000010 运算后的补码
// 将运算后的补码 -> 原码 2
System.out.println(2&3); //2
//推导过程:
//1、首先获得-2的补码
//2、-2的原码:10000000 00000000 00000000 00000010
//3、-2的反码:11111111 11111111 11111111 11111101 (符号位不变,其它位按位取反)
//3、-2的补码:11111111 11111111 11111111 11111110 (补码:反码+1)
//4、取反~: 00000000 00000000 00000000 00000001 运算后的补码
//5、 将运算后的补码 -> 原码
System.out.println(~-2); // 1
System.out.println(1>>2);
// 本质:
//x>>y x/2^y
//x<<y x*2^y
}
}
程序控制结构
顺序结构、循环结构、选择结构
分支控制if-else
基本语法1:
if(条件表达式){
执行代码块;
}
语法2:
if(条件表达式){
执行代码块;
}
else{
执行代码块;
}
语法3:
if(条件表达式){
执行代码块;
}
else if(条件表达式){
执行代码块;
}
······
else{
执行代码块;
}
import java.util.Scanner;
public class IfOperator{
public static void main(String[] args) {
Scanner myScanner = new Scanner(System.in);
// try{
System.out.println("请输入你的年龄:");
int age = myScanner.nextInt();
if (age>18) {
System.out.println("你成年了");
}
else{
System.out.println("小屁孩");
}
System.out.println("请输入你的成绩:");
double grade = myScanner.nextDouble();
if (grade >= 90) {
System.out.println("优秀");
}
else if (grade < 90 && grade >= 70) {
System.out.println("良好");
}
else if (grade < 70 && grade >= 70) {
System.out.println("几个");
}
else {
System.out.println("不及格");
}
switch ((int)grade / 10) {
case 9:System.out.println("优秀");
break;
case 8:
case 7:System.out.println("良好");
break;
case 6:System.out.println("及格");
break;
default:System.out.println("不及格");
break;
}
// }
// catch{
// System.out.println("输入格式错误!");
// }
}
}
switch分支结构
语法:
switch(表达式){
case 常量1:
语句块1;
break;
case 常量1:
语句块1;
break;
····
····
default:
defautl语句块;
break;
}
switch 细节:
for循环
语法:
for(循环变量初始化;循环条件;循环变量迭代)
{
循环操作代码块;
}
for循环四要素:
(1)循环变量初始化;(2)循环条件;(3)循环操作;(4)循环变量迭代
public class ForOperator
{
public static void main(String[] args)
{
//打印1~100内9的倍数
int count = 0;
int sum = 0;
for (int i = 0; i < 100; i++)
{
if (i % 9 == 0)
{
count++;
sum += i;
}
}
System.out.println(count);
System.out.println(sum);
}
}
while循环
语法:
循环变量初始化;
while(循环条件)
{
循环体;
循环变量迭代;
}
四要素:
(1)循环变量初始化;(2)循环条件;(3)循环操作;(4)循环变量迭代
注意事项和细节说明
- 循环条件是返回一个布尔值的表达式
- while 循环是先判断再执行语句
public class Whilewx{
public static void main(String[] args) {
// 某人有100,000元,没经过一次路口,需交费
// 当现金>500000,每次交5%
// <=50000,每次交1000
//计算该人可以经过多少次路口
double money=100000;
int count=0;
while(true){
if (money>50000) {
money*=0.95;
count++;
}
else if (money>1000) {
money-=1000;
count++;
}
else{
break;
}
}
System.out.println("可以过"+ count +"次路口");
}
}
do-while
语法:
循环变量初始化;
do
{
**循环体;**
**循环变量迭代;**
}while(循环条件);
注意:
- do while 是关键字
- 1也有循环四要素, 只是位置不一样
- 先执行,再判断,也就是说,一定会至少执行一次
- 最后 有一个 分号 ;
循环嵌套
介绍
- 将一个循环放在另一个循环体内,就形成了嵌套循环。其中,for ,while ,do…while 均可以作为外层循环和内层循环。 【建议一般使用两层,最多不要超过 3 层, 否则,代码的可读性很差】
- 实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为 false 时,才会完全跳出内 层循环,才可结束外层的当次循环,开始下一次的循环
- 设外层循环次数为 m 次,内层为 n 次,则内层循环体实际上需要执行 m*n 次。
public class MulFor
{
public static void main(String[] args)
{
//打印9*9乘法表
for (int i=1; i<10; i++)
{
for (int j=1;j<=i ;j++ )
{
System.out.print(j + " * " + i + " = " + j * i+'\t');
}
System.out.println();
}
}
}
打印空心金字塔:
import java.util.Scanner;
public class Stars
{
public static void main(String[] args)
{
// // 固定金字塔
// for (int i=1; i<=5; i++)
// {
// //在输出*之前要有空格
// for (int k=1;k<=5-i ;k++ )
// {
// System.out.println(" ");
// }
// //控制打印每层*的个数
// for (int j=1; j<=2*i-1; j++)
// {
// //当前行的第一个位置是*,最后一个也是*,最后一层全部输出*
// if (j==1||j==2*i-1||i==5)
// {
// System.out.print("*");
// }
// else
// {
// System.out.print(" ");
// }
// }
// System.out.println();
// }
Scanner myScanner = new Scanner(System.in);
int rows= myScanner.nextInt();
for (int i=1; i<=rows; i++)
{
//在输出*之前要有空格
for (int k=1;k<=rows-i ;k++ )
{
System.out.print(" ");
}
//控制打印每层*的个数
for (int j=1; j<=2*i-1; j++)
{
//当前行的第一个位置是*,最后一个也是*,最后一层全部输出*
if (j==1||j==2*i-1||i==rows)
{
System.out.print("*");
}
else
{
System.out.print(" ");
}
}
System.out.println();
}
}
}
break
介绍:
break 语句用于终止某个语句块的执行,一般使用在 switch 或者循环[for , while , do-while]中。
用户登陆:
import java.util.Scanner;
public class Breakwx
{
public static void main(String[] args)
{
int chance=3;
Scanner myScanner = new Scanner(System.in);
for(int i = 0;i < 3;i++)
{
System.out.println("请输入用户名:");
String userName = myScanner.next();
System.out.println("请输入密码:");
String pwd = myScanner.next();
//if (userName.equals("微咲") && pwd.equals("888888")) //使用==报错???
if ("微咲".equals(userName) && "888888".equals(pwd)) //能避免空字符串
{
System.out.println("登陆成功");
break;
}
System.out.println("登陆失败");
chance--;
System.out.println("还有"+ chance +"次机会");
if (i==2)
{
System.out.println("次数用光");
}
}
}
}
continue
基本介绍:
- continue 语句用于结束本次循环,继续执行下一次循环。
- continue 语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环 , 这个和前面的标签的 使用的规则一样.
return
退出当前函数、方法
数组
数组介绍
数组可以存放多个同一类型的数据。数组也是一种数据类型,是引用类型。
即:数(数据)组(一组)就是一组数据
数组的定义
1、数组类型[] 数组名 = new 数据类型[大小]
int[] a = new int[5];
2、
先声明、后创建
3、
String[] strs = new String[]{“a”,”b”,”c”};
数组使用注意事项和细节
- 数组是多个相同类型数据的组合,实现对这些数据的统一管理
- 数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用。
- 数组创建后,如果没有赋值,有默认值 int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null
- 使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值 3 使用数组
- 数组的下标是从 0 开始的。
- 数组下标必须在指定范围内使用,否则报:下标越界异常,比如 韩顺平循序渐进学 Java 零基础 第 148页 int [] arr=new int[5]; 则有效下标为 0-4
- 数组属引用类型,数组型数据是对象(object)
数组赋值机制
- 基本数据类型赋值,这个值就是具体的数据,而且相互不影响。
int n1 = 2;
int n2 = n1; - 数组在默认情况下是引用传递,赋的值是地址.
public class ArrayCopy{
public static void main(String[] args) {
//将 int[] arr1 = {10,20,30};拷贝到 arr2 数组中,要求空间独立~!
int[] arr1 = {10,20,30};
int[] arr3 = arr1; //空间不独立
//穿件一个新的数组arr2,开辟新的数据空间,大小为arr1.length
int[] arr2 = new int[arr1.length];
//遍历 arr1,copy到arr2中
for (int i = 0;i<arr1.length; i++) {
arr2[i] = arr1[i];
}
for (int i=0; i<arr2.length; i++) {
System.out.println(arr2[i]);
}
//修改 arr1[1]的值
arr1[1] = 9999;
System.out.println(arr1[1]);
System.out.println(arr2[1]);
System.out.println(arr3[1]);
}
}
数组反转
public class ArrayReverse {
//编写一个 main 方法
public static void main(String[] args) {
//定义数组
int[] arr = {11, 22, 33, 44, 55, 66};
//老韩思路
//规律
//1. 把 arr[0] 和 arr[5] 进行交换 {66,22,33,44,55,11}
//2. 把 arr[1] 和 arr[4] 进行交换 {66,55,33,44,22,11}
//3. 把 arr[2] 和 arr[3] 进行交换 {66,55,44,33,22,11}
//4. 一共要交换 3 次 = arr.length / 2
//5. 每次交换时,对应的下标 是 arr[i] 和 arr[arr.length - 1 -i]
//代码
//优化
int temp = 0;
int len = arr.length; //计算数组的长度
for( int i = 0; i < len / 2; i++) {
temp = arr[len - 1 - i];//保存
arr[len - 1 - i] = arr[i];
arr[i] = temp;
}
System.out.println("===翻转后数组===");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");//66,55,44,33,22,11
}
}
}
数组扩容、缩减
扩容
import java.util.Scanner;
public class ArrayAdd {
//编写一个 main 方法
public static void main(String[] args) {
/*
要求:实现动态的给数组添加元素效果,实现对数组扩容。ArrayAdd.java
1.原始数组使用静态分配 int[] arr = {1,2,3}
2.增加的元素 4,直接放在数组的最后 arr = {1,2,3,4}
3.用户可以通过如下方法来决定是否继续添加,添加成功,是否继续?y/n
思路分析
1. 定义初始数组 int[] arr = {1,2,3}//下标 0-2
2. 定义一个新的数组 int[] arrNew = new int[arr.length+1];
3. 遍历 arr 数组,依次将 arr 的元素拷贝到 arrNew 数组
4. 将 4 赋给 arrNew[arrNew.length - 1] = 4;把 4 赋给 arrNew 最后一个元素
5. 让 arr 指向 arrNew ; arr = arrNew; 那么 原来 arr 数组就被销毁
6. 创建一个 Scanner 可以接受用户输入
7. 因为用户什么时候退出,不确定,老师使用 do-while + break 来控制
*/
Scanner myScanner = new Scanner(System.in);
//初始化数组
int[] arr = {1,2,3};
do {
int[] arrNew = new int[arr.length + 1];
//遍历 arr 数组,依次将 arr 的元素拷贝到 arrNew 数组
for(int i = 0; i < arr.length; i++) {
arrNew[i] = arr[i];
}
System.out.println("请输入你要添加的元素");
int addNum = myScanner.nextInt();
//把 addNum 赋给 arrNew 最后一个元素
arrNew[arrNew.length - 1] = addNum;
//让 arr 指向 arrNew,
arr = arrNew;
//输出 arr 看看效果
System.out.println("====arr 扩容后元素情况====");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
//问用户是否继续
System.out.println("是否继续添加 y/n");
char key = myScanner.next().charAt(0);
if( key == 'n') { //如果输入 n ,就结束
break;
}
}while(true);
System.out.println("你退出了添加...");
}
}
缩减
import java.util.Scanner;
public class ArrayReduce{
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
// 有一个数组 {1, 2, 3, 4, 5}, 可以将该数组进行缩减,提示用户是否继续缩减,
//每次缩减最后那个元素。当只剩下最后一个元素,提示,不能再缩减。
Scanner myScanner = new Scanner(System.in);
while(true){
System.out.println("是否继续删除元素:y/n");
// char userInput = myScanner.next();
// if (userInput == 'n') {
// break;
// }
char key = myScanner.next().charAt(0);
if( key == 'n') { //如果输入 n ,就结束
break;
}
System.out.println("当前数组大小为:" + arr.length);
if (arr.length==1) {
System.out.println("数组只有一个元素了,不能再缩减了!");
break;
}
int[] arrNew = new int[arr.length - 1];
for (int i=0; i < arr.length - 1; i++ ) {
arrNew[i] = arr[i];
}
arr = arrNew;
//遍历arr
System.out.println("====arr 缩减后元素情况====");
for (int i=0;i<arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
}
二维数组
二维数组的声明
使用方式 1: 动态初始化
- 语法: 类型[][] 数组名=new 类型[大小][大小]
- 比如: int a[][]=new int[2][3]
使用方式 2: 动态初始化
- 先声明:类型 数组名[][];
- 再定义(开辟空间) 数组名 = new 类型[大小][大小]
- 赋值(有默认值,比如 int 类型的就是 0)
- 比如:int a[][]; a = new int[2][3]
使用方式 3: 动态初始化-列数不确定
public class TwoDimensionalArray03 {
//编写一个 main 方法
public static void main(String[] args) {
/*
看一个需求:动态创建下面二维数组,并输出
i = 0: 1
i = 1: 2 2
i = 2: 3 3 3 一个有三个一维数组, 每个一维数组的元素是不一样的
*/
//创建 二维数组,一个有 3 个一维数组,但是每个一维数组还没有开数据空间
int[][] arr = new int[3][];
for(int i = 0; i < arr.length; i++) {//遍历 arr 每个一维数组
//给每个一维数组开空间 new
//如果没有给一维数组 new ,那么 arr[i]就是 null
arr[i] = new int[i + 1];
//遍历一维数组,并给一维数组的每个元素赋值
for(int j = 0; j < arr[i].length; j++) {
arr[i][j] = i + 1;//赋值
}
}
System.out.println("=====arr 元素=====");
//遍历 arr 输出
for(int i = 0; i < arr.length; i++) {
//输出 arr 的每个一维数组
for(int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();//换行
}
}
}
使用方式 4: 静态初始化
- 定义 类型 数组名[][] = {{值 1,值 2..},{值 1,值 2..},{值 1,值 2..}}
- 使用即可 [ 固定方式访问 ]
二维数组使用细节和注意事项
- 一维数组的声明方式有:
int[] x 或者 int x[] - 二维数组的声明方式有:
int[][] y 或者 int[] y[] 或者 int y[][] - 二维数组实际上是由多个一维数组组成的,它的各个一维数组的长度可以相同,也可以不相同。
比如: map[][] 是 一个二维数组
int map [][] = {{1,2},{3,4,5}}
由 map[0] 是一个含有两个元素的一维数组 ,map[1] 是一个含有三个元素的一维数组构成,我们也称为列数不等 的二维数组
二维数组存放地址
杨辉三角
import java.util.Scanner;
public class YangHui{
public static void main(String[] args) {
/*
使用二维数组打印一个 10 行杨辉三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
规律
1.第一行有 1 个元素, 第 n 行有 n 个元素
2. 每一行的第一个元素和最后一个元素都是 1
3. 从第三行开始, 对于非第一个元素和最后一个元素的元素的值. arr[i][j]
arr[i][j] = arr[i-1][j] + arr[i-1][j-1]; //必须找到这个规律
*/
Scanner myScanner = new Scanner(System.in);
System.out.println("请输入杨辉三角的行数rows:");
int rows = myScanner.nextInt();
int[][] yangHui = new int[rows][];
for (int i = 0; i < yangHui.length; i++ ) {
//给每个一维数组(行)开辟空间
yangHui[i] = new int[i+1];
//给每个一维数组(行)赋值
for (int j = 0; j < yangHui[i].length; j++) {
//每一行的第一个元素和最后一个元素都是1
if (j == 0 || j == yangHui[i].length - 1) {
yangHui[i][j] = 1;
}else{
yangHui[i][j] = yangHui[i-1][j] + yangHui[i-1][j-1];
}
}
}
for (int i=0;i<yangHui.length;i++ ) {
for (int j=0;j<yangHui[i].length ;j++ ) {
System.out.print(yangHui[i][j]+" ");
}
System.out.println();
}
}
}
排序
排序的介绍
排序是将多个数据,依指定的顺序进行排列的过程。
排序的分类:
- 内部排序:
指将需要处理的所有数据都加载到内部存储器中进行排序。包括(交换式排序法、选择 式排序法和插入式排序法);
- 外部排序法:
数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(合并排序法和直接合并排序法)。
冒泡排序法
冒泡排序(Bubble Sorting)的基本思想是:
通过对待排序序列从后向前(从下标较大的元素开始),依次比较相邻元素 的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
查找
介绍:
在 java 中,我们常用的查找有两种:
- 顺序查找 SeqSearch.java
- 二分查找【二分法,我们放在算法讲解】
面对对象基础
类与对象
简介
public class Object01 {
//编写一个 main 方法
public static void main(String[] args) {
/*
张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。
还有一只叫小花,今年 100 岁,花色。请编写一个程序,当用户输入小猫的名字时,
就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,
则显示 张老太没有这只猫猫。
*/
//单独变量来解决 => 不利于数据的管理(你把一只猫的信息拆解)
//第 1 只猫信息
// String cat1Name = "小白";
// int cat1Age = 3;
// String cat1Color = "白色";
// //第 2 只猫信息
// String cat2Name = "小花";
// int cat2Age = 100;
// String cat2Color = "花色";
//数组 ===>(1)数据类型体现不出来(2) 只能通过[下标]获取信息,造成变量名字和内容
// 的对应关系不明确(3) 不能体现猫的行为
//第 1 只猫信息
// String[] cat1 = {"小白", "3", "白色"};
// String[] cat2 = {"小花", "100", "花色"};
//使用 OOP 面向对象解决
//实例化一只猫[创建一只猫对象]
//老韩解读
//1. new Cat() 创建一只猫(猫对象)
//2. Cat cat1 = new Cat(); 把创建的猫赋给 cat1
//3. cat1 就是一个对象
Cat cat1 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
cat1.weight = 10;
//创建了第二只猫,并赋给 cat2
//cat2 也是一个对象(猫对象)
Cat cat2 = new Cat();
cat2.name = "小花";
cat2.age = 100;
cat2.color = "花色";
cat2.weight = 20;
//怎么访问对象的属性呢
System.out.println("第 1 只猫信息" + cat1.name
+ " " + cat1.age + " " + cat1.color + " " + cat1.weight);
System.out.println("第 2 只猫信息" + cat2.name
+ " " + cat2.age + " " + cat2.color + " " + cat2.weight);
}
}
//使用面向对象的方式来解决养猫问题
//
//定义一个猫类 Cat -> 自定义的数据类型
class Cat {
//属性/成员变量
String name; //名字
int age; //年龄
String color; //颜色
//double weight; //体重
//行为
}
类和对象的区别和联系
通过上面的案例和讲解我们可以看出:
- 类是抽象的,概念的,代表一类事物,比如人类,猫类.., 即它是数据类型.
- 对象是具体的,实际的,代表一个具体事物, 即 是实例.
- 类是对象的模板,对象是类的一个个体,对应一个实例
对象存在形式
属性、成员变量、字段
基本介绍
- 从概念或叫法上看: 成员变量 = 属性 = field(字段) (即 成员变量是用来表示属性的)
- 属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)。比如我们前面定义猫类 的 int age 就 是属性
注意事项和细节说明
- 属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;
访问修饰符: 控制属性的访问范围 有四种访问修饰符 public, proctected, 默认, private 。 - 属性的定义类型可以为任意类型,包含基本类型或引用类型
- 属性如果不赋值,有默认值,规则和数组一致。具体说: int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000, boolean false,String null
Java 内存的结构分析
- 栈: 一般存放基本数据类型(局部变量)
- 堆: 存放对象(Cat cat , 数组等)
- 方法区:常量池(常量,比如字符串), 类加载信息
Java创建对象的流程简单分析
成员方法
成员方法的定义
访问修饰符 返回数据类型 方法名(形参列表..) {//方法体
语句;
return 返回值;
}
- 形参列表:表示成员方法输入 cal(int n) , getSum(int num1, int num2)
- 返回数据类型:表示成员方法输出, void 表示没有返回值
- 方法主体:表示为了实现某一功能代码块
- return 语句不是必须的。
成员方法的调用机制原理
访问修饰符
(作用是控制 方法使用的范围)
如果不写默认访问,[有四种: public, protected, 默认, private], 具体在后面说
返回数据类型
- 一个方法最多有一个返回值 [思考,如何返回多个结果 返回数组 ]
- 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
- 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和 return 的 值类型一致或兼容
- 如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return ;
方法名
遵循驼峰命名法,最好见名知义,表达出该功能的意思即可, 比如 得到两个数的和 getSum, 开发中按照规范
注意事项和使用细节
成员方法传参机制
值传递传递的是值。
引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!
public class Test{
public static void main(String[] args) {
Person p = new Person();
p.name = "wx";
p.age = 10;
A a = new A();
a.Test11(p);
System.out.println("main 的p.age= " + p.age);//10 ☆
}
}
class Person{
String name;
int age;
}
class A{
public void Test11(Person p){
p = null;
}
}
方法递归调用
基本介绍
简单的说: 递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂问题,同时可以让代码变 得简洁
两个简单例子
import java.util.Scanner;
public class Recursion01{
public static void main(String[] args) {
T t1 = new T();
System.out.println("请输入数字:");
Scanner myScanner = new Scanner(System.in);
int num = myScanner.nextInt();
t1.Test(num);
System.out.println("阶乘为:");
int factorial = t1.Factorial(num);
System.out.println(factorial);
}
}
class T{
public void Test(int n){
if (n > 2) {
Test(n - 1);
}
System.out.println("n = " + n );
}
public int Factorial(int n){
if (n == 1) {
return 1;
}else {
return Factorial(n - 1) * n;
}
}
}
递归重要规则
1.执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
2.方法的局部变量是独立的,不会相互影响,比如n变量
3.如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引
用类型的数据.
4.递归必须向退出递归的条件逼近,否则就是无限递归,出现
StackOverflowError,死龟了:)
5.当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就
将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。
递归应用
斐波那契数列
import java.util.Scanner;
public class Recursion02{
public static void main(String[] args) {
//求斐波那契数列, 1,1,2,3,5,8,13...
System.out.println("请输入斐波那契数列的第几项:");
Scanner myScanner = new Scanner(System.in);
int num = myScanner.nextInt();
int feibo = Feibo(num);
System.out.println("第"+ num +"的数值为"+ feibo);
}
public static int Feibo(int n){
if (n == 1 || n == 2) {
return 1;
}
return Feibo(n - 1) + Feibo(n - 2);
}
}
猴子吃桃
import java.util.Scanner;
public class Recursion02{
public static void main(String[] args) {
/*
猴子吃桃子问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!
以后每天猴子都吃其中的一半,然后再多吃一个。当到第 10 天时,
想再吃时(即还没吃),发现只有 1 个桃子了。问题:最初共多少个桃子?
思路分析 逆推
1. day = 10 时 有 1 个桃子
2. day = 9 时 有 (day10 + 1) * 2 = 4
3. day = 8 时 有 (day9 + 1) * 2 = 10
4. 规律就是 前一天的桃子 = (后一天的桃子 + 1) *2//就是我们的能力
5. 递归
*/
System.out.println("请输入第几天:");
int day = myScanner.nextInt();
int peaches = Peach(day);
System.out.println("第"+ day +"天,有"+ peaches +"个桃子");
}
public static int Peach(int day){
if (day == 10) {
return 1;
}else if (day >= 1 && day <= 9) {
return (Peach(day + 1) + 1) * 2;
}else {
System.out.println("猴子只吃了10天桃子");
return -1;
}
}
}
迷宫
public class MiGong{
public static void main(String[] args) {
//1、先创建迷宫,用二维数组表示 int[][] map = new int[8][7]
//2、先规定 map 数组的元素值:0 表示可以走,1 表示障碍物
int[][] map = new int[8][7];
//3、将最上面一行和最下面一行,全部设置为1
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
//4、将最左面一行和最右面一列,全部设置为1
for (int i = 0; i < 7; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
//5、
map[3][1] = 1;
map[3][2] = 1;
//map[2][2] = 1;
//输出当前地图
System.out.println("=====当前地图=====");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
findWay(map,1,1);
System.out.println("=====找路地图=====");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
//使用递归回溯的思想来解决老鼠出迷宫
//1.findWay方法就是专门来找出迷宫的路径
//2.如果找到,就返回true,否则返回false
//3.map就是二维数组,即表示迷宫
//4.i,j 就是老鼠的位置,初始化位置(1,1)
//5.因为我们是递归找路,所以我们规定map数组的各个值的含义
// 0:路 1:障碍物 2:可以走的路 3:走过,却走不通的路
//6.当map[6][5]=2 就说明找到通路
//7.先确定老鼠找路策略 下->右->上->左
public static boolean findWay(int[][] map,int i,int j){
if (map[6][5] == 2) {//说明已经找到
return true;
}else{
if (map[i][j] == 0) {//当前位置可以走
//假定可以走通
map[i][j] = 2;
//使用找路策略
if (findWay(map,i+1,j)) {//走下面
return true;
}else if (findWay(map,i,j+1)) {//右
return true;
}else if (findWay(map,i-1,j)) {//上
return true;
}else if (findWay(map,i,j-1)) {//左
return true;
}else{
map[i][j] = 3;
return false;
}
}else {// map[i][j] = 1,2,3
return false; //☆☆☆☆☆if(false)执行完后执行 else-if -> else-if -> else-if
}
}
}
}
汉诺塔
public class Tower{
public static void main(String[] args) {
Tower tower = new Tower();
tower.move(3, 'A', 'B', 'C');
}
//num 表示要移动的个数, a,b,c 分别表示A、B、C塔
public static void move(int num ,char a, char b, char c){
//如果只有1一个盘 num=1
if (num == 1) {
System.out.println(a +"->"+ c);
}else {
//如果有多个盘,可以看成两个,最下面一个和上面的所有
//(1)先移动上面所有的盘到 b, 借助 c
move(num - 1 , a, c, b);
//(2)把最下面的这个盘,移动到 c
System.out.println(a + "->" + c);
//(3)再把 b 塔的所有盘,移动到 c ,借助 a
move(num - 1, b, a, c);
}
}
}
八皇后
方法重载
基本介绍
java 中允许同一个类中,多个同名方法的存在,但要求 形参列表不一致!
重载的好处
- 减轻了起名的麻烦
- 减轻了记名的麻烦
方法重载注意事项和使用细节
可变参数
基本概念
java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。 就可以通过可变参数实现
基本语法
访问修饰符 返回类型 方法名(数据类型… 形参名)
{ }
public class VarParameter{
public static void main(String[] args) {
//计算2、3、4、5个数的和
int res = sum(11 , 99 , 66);
System.out.println(res);
}
//使用重载
// public static int sum(int n1,int n2){
// return n1 + n2;
// }
// public static int sum(int n1,int n2,int n3){
// return n1 + n2 + n3;
// }
// public static int sum(int n1,int n2,int n3,int n4){
// return n1 + n2 + n3 + n4;
// }
//使用可变参数
//1. int...表示接受的是可变参数,类型是int,既可以接受多个int(0->多)
//2. 使用可变参数是,可以当做数组来使用,即 nums 可以当做数组
public static int sum(int... nums){
int res = 0;
for (int i=0;i<nums.length ;i++ ) {
res += nums[i];
}
return res;
}
}
可变参数注意事项和使用细节
(2)可变参数的实参可以为数组
作用域
基本使用
4.全局变量(属性)可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,
才能使用,因为没有默认值。
作用域注意事项和细节使用
1.属性和局部变量可以重名,访问时遵循就近原则。
king
2.在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名。[举例]
3.属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。即在一次方法调用过程中。
4、作用域范围不同
全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)
局部变量:只能在本类中对应的方法中使用
5.修饰符不同
全局变量/属性可以加修饰符,局部变量不可以加修饰符
构造方法/构造器
基本语法
[修饰符] 方法名(形参列表){
方法体;
}
- 构造器的修饰符可以默认, 也可以是 public protected private
- 构造器没有返回值
- 方法名 和类名字必须一样
- 参数列表 和 成员方法一样的规则
- 构造器的调用, 由系统完成
public class Constructor{
public static void main(String[] args) {
Person p = new Person("微咲",19);
p.sayHello();
}
}
class Person{
String name;
int age;
//构造器
//1.构造器没有返回值,不能写void
//2.构造器的名字和类一样
//3.(String pName, int pAge) 是构造器形参列表,规则和成员方法一样
public Person(String pName,int pAge){
this.name = pName;
this.age = pAge;
}
public void sayHello(){
System.out.println(this.name + " " + this.age);
}
}
基本介绍
构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。它有几个特点:
- 方法名和类名相同
- 没有返回值
- 在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。
构造器注意事项和使用细节
1.一个类可以定义多个不同的构造器,即构造器重载
比如:我们可以再给Person类定义一个构造器,用来创建对象的时候,只指定人名不需要指定年龄
2.构造器名和类名要相同
3.构造器没有返回值
4.构造器是完成对象的初始化.并不是创建对象
5.在创建对象时,系统自动的调用该类的构造方法
6.如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也
叫默认构造器),比如Dog(){},使用 javap指令 反编译看看
Dog dog1 = new Dog();
7.一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无
参构造器,除非显式的定义一下,即:Dog(){}
javap的使用
1) javap是JDK提供的一个命令行工具,javap能对给定的class文件提供的字节代码进行反编译。
2) 通过它,可以对照源代码和字节码,从而了解很多编译器内部的工作,对更深入地理解如何提高程序执行的效率等问题有极大的帮助。
3) 使用格式
javap <options> <classes>
常用: javap -c -v 类名
等. . . ..
对象创建的流程分析
this关键字
什么是this
java虚拟机会给每个对象分配this,代表当前对象。
this的本质
public class ThisKeyword{
public static void main(String[] args) {
Dog dog1 = new Dog("我是第一条狗",99);
System.out.println("Dog1的hashCode:" + dog1.hashCode());
dog1.info();
Dog dog2 = new Dog("我是第二条狗",9999);
System.out.println("Dog2的hashCode:" + dog2.hashCode());
dog2.info();
}
}
class Dog{
public String name;
public int age;
public Dog(String name, int age){
this.name = name;
this.age = age;
System.out.println("构造器中的hashCode:" + this.hashCode());
}
public void info(){
System.out.println(this.name + '\t' + this.age + '\t' +
"当前对象的hashCode是:" + this.hashCode()); //使用hashCode看对象情况
}
}
结果:
this小结
简单的来说,哪个对象调用,this就就代表哪个对象。
this的注意事项和使用细节
- this 关键字可以用来访问本类的属性、方法、构造器
- this 用于区分当前类的属性和局部变量
- 访问成员方法的语法:this.方法名(参数列表);
- 访问构造器语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一 条语句)
(即在构造器中只能使用一个this)
- this 不能在类定义的外部使用,只能在类定义的方法中使用。
IDEA
自定义背景设置
: 自定义背景 自己设置的快捷键Ctrl+alt+0
字符编码设置
:Settings->Editor->File Encodings
Intellij IDEA如何设置为中文界面?
IDEA 项目目录
IDEA快捷键
- 删除当前行, 默认是 ctrl + Y
- 复制当前行, 默认是 ctrl + D
- 补全代码 alt + /
- 添加注释和取消注释 ctrl + / 【第一次是添加注释,第二次是取消注释】
- 导入该行需要的类 先配置 auto import , 然后使用 alt+enter 即可
配置auto import:Editor->General->Auto Import->
- 快速格式化代码 ctrl + alt + L
- 快速运行程序 自己定义 alt + S
- 生成构造器等 alt + insert [提高开发效率]
- 查看一个类的层级关系 ctrl + H [学习继承后,非常有用]
- 将光标放在一个方法上,输入 ctrl + B(ctrl + 鼠标左键) , 可以定位到方法 [学继承后,非常有用]
- 自动的分配变量名 , 通过 在后面加 .var
new Scanner(System.in).var 回车 (alt + 回车???\ ctrl + alt ???)
- 快速重命名所用引用:shift + F6
- Surround With : ctrl + alt + t
- 解决异常,引入 : Alt + Enter
- ctrl + shift + i : 可快速查看方法
IEDA 快捷输入
- sout : System.out.println();
- I , ().for : 挣钱for循环
- fori -> for(int i = 0 ; i < ; i++ ){}
- itit -> 迭代器
模板/自定义模板
crtl + j 快速查看当前可使用的模板
file -> settings -> editor -> Live templates ->
查看有哪些模板快捷键/可以自己增加模板
头信息
完整显示Debug
允许运行多个窗口
在xml中智能提示sql语句
包
包的三大作用
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
包的基本语法
package com.wxjava;
说明:
- package关键字,表示打包。
- com.wxjava:表示包名
包的本质分析(原理)
实际上就是创建不同的文件夹来保存类文件.
包的命名
命名规则
只能包含数字、字母、下划线、小圆点。但不能用数字开头,不能是关键字或保留字。
命名规范
一般是小写字母+小圆点
一般是 com.公司名.项目名.业务模块名
常用的包
一个包下,包含很多的类,java中常用的包有:
java.lang.* //lang包时基本包,默认引入,不需要再引入
java.util.* //util包,系统提供的工具包,工具类
java.net.* //网络包,网络开发
java.awt.* //是做 java 的界面开发,GUI
包的注意事项和使用细节
- package 的作用时恒明当前类所在的包,需要放再class的最上面,一个类中最多只有一句package。
- import指令,位置放在package的下面,再类定义前面,可以有多句且没有顺序要求。
package com.wexiao.pkg;
import java.util.Arrays;
//建议使用什么类就导入什么类
//import java.util.Scanner; //表示只会引入java.util包下的Scanner
//import java.util.*; //表示将java.util 包下的所有类都引入(导入)
public class Import01 {
public static void main(String[] args) {
//使用系统提供的 Array 完成数组排序
int[] arr = {0, 5, 13, -5, 48, 20, -7};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
// for (int i = 0; i < arr.length; i++) {
// System.out.print(arr[i] + " ");
// }
}
}
访问修饰符
基本介绍
java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用 public 修饰,对外公开
- 受保护级别:用 protected 修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符号,向同一个包的类公开.
- 私有级别:用 private 修饰,只有类本身可以访问,不对外公开.
访问修饰使用的注意事项
- 修饰符可以用来修饰类中的属性,成员方法以及类
- 只有默认的和public才能修饰类! 并且遵循上述访问权限的特点
- 成员方法的访问规则和属性完全一样.
package com.wexiao.modifier;
public class A {
//四个属性,分别使用不同的访问修饰符来修饰
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public void m1() {
//在同一类中,可以访问 public protected 默认 private 修饰属性和方法
System.out.println("n1=" + n1 + " n2=" + n2 + " n3=" + n3 + " n4=" + n4);
}
}
package com.wexiao.modifier;
public class B {
public void say(){
A a = new A();
//在同一个包下,可以访问 public,protected,和 默认修饰属性和方法,不能访问private
System.out.println("n1=" + a.n1 + "n2=" + a.n2 + "n3=" + a.n3);
}
}
package com.wexiao.modifier;
public class Test {
public static void main(String[] args) {
A a = new A();
a.m1();
}
}
面向对象编程三大特征
基本介绍
面向对象编程有三大特征:封装、继承、多态。
封装
封装介绍
封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。
封装的理解和好处
- 隐藏实现细节
- 可以对数据进行验证,保证安全合理
封装的实现步骤
- 将属性进行私有化private【不能直接修改属性】
- 提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx(类型参数名){/Xxx表示某个属性
//加入数据验证的业务逻辑
属性=参数名;
} - 提供一个公共的(public)get方法,用于获取属性的值public数据类型getXxx({//权限判断,Xxx某个属性
return xx;
}
构造器使用set
public Person(String name, int age, double salary) { this.setName(name); this.setAge(age); this.setSalary(salary); }
package com.wexiao.encap;
public class Encapsulation01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("wexiaol");
person.setAge(200);
person.setSalary(18000);
System.out.println(person.getSalary());
System.out.println(person.info());
//使用构造器初始化
Person wexiao = new Person("WEXIAO", 20, 19000);
System.out.println("=====wexiao的信息=====");
System.out.println(wexiao.info());
}
}
/*
那么在 java 中如何实现这种类似的控制呢?
请大家看一个小程序(com.wexiao.encap: Encapsulation01.java),
不能随便查看人的年龄,工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认
年龄, 必须在 1-120, 年龄, 工资不能直接查看 , name 的长度在 2-6 字符 之间
*/
class Person {
public String name;
private int age;
private double salary;
//构造器
public Person(){
}
public Person(String name, int age, double salary) {
// this.name = name;
// this.age = age;
// this.salary = salary;
this.setName(name);
this.setAge(age);
this.setSalary(salary);
}
//封装快捷键 Alt + Insert
public String getName() {
return name;
}
public void setName(String name) {
//加入对数据的限制,相当于增加了业务逻辑
if (name.length()>=2&&name.length()<=6){
this.name=name;
}else{
System.out.println("名字长度不对,2-6个字符");
this.name = "name";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
//判断
if (age >= 1 && age <= 120) {
this.age = age;
} else {
System.out.println("你设置的年龄不对,需要再1-120之间,否则给默认值-1");
this.age = -1;
}
}
public double getSalary() {
//可以在这里增加当前对象的权限判断
if (salary<=20000) {
return 40000;
}
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//写一个方法返回信息
public String info() {
return "信息为 name=" + name + " age=" + age + " salary=" + salary;
}
}
名字长度不对,2-6个字符
你设置的年龄不对,需要再1-120之间,否则给默认值-1
40000.0
信息为 name=name age=-1 salary=18000.0
=====wexiao的信息=====
信息为 name=WEXIAO age=20 salary=19000.0
Process finished with exit code 0
/**
* 创建程序,在其中定义两个类:Account 和 AccountTest 类体会 Java 的封装性。
* Account 类要求具有属性:姓名(长度为 2 位 3 位或 4 位)、余额(必须>20)、
* 密码(必须是六位), 如果不满足,则给出提示信息,并给默认值(程序员自己定)
* 通过 setXxx 的方法给 Account 的属性赋值。
* 在 AccountTest 中测试
*/
package com.wexiao.encap;
/**
* 创建程序,在其中定义两个类:Account 和 AccountTest 类体会 Java 的封装性。
* Account 类要求具有属性:姓名(长度为 2 位 3 位或 4 位)、余额(必须>20)、
* 密码(必须是六位), 如果不满足,则给出提示信息,并给默认值(程序员自己定)
* 通过 setXxx 的方法给 Account 的属性赋值。
* 在 AccountTest 中测试
*/
public class Account {
private String name;
private double balance;
private String pwd;
public String getName() {
return name;
}
//姓名长度2、3、4
public void setName(String name) {
if (name.length() >= 2 && name.length() <= 4) {
this.name = name;
} else {
System.out.println("姓名长度要求为2、3、4位");
this.name = "name";
}
}
public double getBalance() {
return balance;
}
//余额必须大于二十
public void setBalance(double balance) {
if (balance>20) {
this.balance = balance;
}else{
System.out.println("余额必须大于二十");
}
}
public String getPwd() {
return pwd;
}
//密码(必须是六位)
public void setPwd(String pwd) {
if (pwd.length()==6) {
this.pwd = pwd;
}else{
System.out.println("密码(必须是六位),默认密码为000000");
this.pwd = "000000";
}
}
public Account() {
}
public Account(String name, double balance, String pwd) {
this.setName(name);
this.setBalance(balance);
this.setPwd(pwd);
}
public void showInfo(){
System.out.println("账号信息name=" + name + " 余额=" + balance + " 密码=" + pwd);
}
}
package com.wexiao.encap;
public class TestAccount {
public static void main(String[] args) {
Account wexiao = new Account("微咲", 999999, "011005");
wexiao.showInfo();
}
}
继承
引入继承
继承可以解决再一些类中相同代码的复用。
例如:
package com.wexiao.extend_;
//小学生
public class Pupil {
public String name;
public int age;
private double score;
public void setScore(double score) {
this.score = score;
}
public void testing(){
System.out.println("小学生 " + name + "正在考小学数学");
}
public void showInfo(){
System.out.println("学生名:" + name + " 年龄:" + age + " 分数:" + score);
}
}
package com.wexiao.extend_;
//大学生
public class Graduate {
public String name;
public int age;
private double score;
public void setScore(double score) {
this.score = score;
}
public void testing(){
System.out.println("大学生 " + name + "正在考大学数学");
}
public void showInfo(){
System.out.println("学生名:" + name + " 年龄:" + age + " 分数:" + score);
}
}
package com.wexiao.extend_;
public class Extends01 {
public static void main(String[] args) {
Pupil pupil = new Pupil();
pupil.name = "wexiao";
pupil.age = 11;
pupil.setScore(99.9);
pupil.testing();
pupil.showInfo();
Graduate graduate = new Graduate();
graduate.name = "WeXiao";
graduate.age = 20;
graduate.setScore(60.6);
graduate.testing();
graduate.showInfo();
}
}
继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中 抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 **extends** 来 声明继承父类即可。
画出继承的示意图
继承的基本语法
class 子类 extends 父类{
}
- 子类就会自动拥有父类定义的属性和方法
- 父类又叫超类、基类
- 子类又叫派生类
继承细节
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访 问,要通过父类提供公共的方法去访问
```java package ExtendsDetail;
public class Base { //4个属性 public int n1 = 100; protected int n2 = 200; int n3 = 300; private int n4 = 400;
public int getN4(){
return n4;
}
public Base(){
System.out.println("base()...");
}
public void test(){
System.out.println("test");
}
} package ExtendsDetail;
public class Sub extends Base { public Sub() { //super(); //默认调用父类的无参构造器 System.out.println(“sub()…”); } public void sayOK(){ //非私有的属性和方法可以在子类直接访问 System.out.println(n1+n2+n3); //通过封装get访问private属性 System.out.println(“n4=” + getN4()); } } package ExtendsDetail;
public class TestExtends { public static void main(String[] args) { Sub sub = new Sub();//创建一个子类 sub.sayOK(); } }
2. 子类必须调用父类的构造器, 完成父类的初始化
2. 父类中的构造方法是不能继承的。
[java中子类能不能继承父类构造方法](https://blog.csdn.net/Mind_programmonkey/article/details/79514176)
2. **当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无 参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过**。<br />`public class Base{`<br /> `... ... ...`<br /> `public Base(String name, int age){`<br /> `System.out.println("Base(String name, int age)被调用")`<br /> `}`<br />`}`<br />`public clasee Sup extends Base{`<br /> `public Sup(){`<br /> `super("wx",10);`<br /> `}`<br /> `public Sup(String name){`<br /> `super("wx",10);`<br /> `}`<br />`}`
2. 如果希望指定去调用父类的某个构造器,则**显式的调用**一下 : super(参数列表)<br />`**super(参数列表)**`<br />`**suber(); //父类的无参构造器**`
2. super 在使用时,必须放在构造器第一行(super()能在构造器中使用)
2. **super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器**
2. java 所有类都是 Object 类的子类, Object 是所有类的基类.
2. **父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)**
2. 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。 思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】
2. 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系<br />Persong is a Music x<br />Cat is a animal √
<a name="0cb9c549"></a>
### 继承的本质分析
```java
package ExtendsTheory;
public class Theory {
public static void main(String[] args) {
Son son = new Son();
//1. 首先看子类是否有该属性
//2. 如果子类有这个属性,并且可以访问,则返回信息
//3. 如果子类没有这个属性,就看父类有没有这个属性(如果有,并且可以访问,就烦回信息)
//4. 如果父类没有就重复3. 继续找上级,直到Object
System.out.println(son.name); //儿子
System.out.println(son.age); //40
System.out.println(son.hobby); //英雄联盟
}
}
class GrandPa{
String name = "爷爷";
String hobby = "英雄联盟";
}
class Father extends GrandPa{
String name = "爸爸";
int age = 40;
}
class Son extends Father{
String name = "儿子";
}
继承小练习
一
package Extends_.ExtendsExercise.ExtendsExercise01;
public class A {
A(){
System.out.println("a");
}
A(String name){
System.out.println("a name");
}
}
class B extends A{
B(){
this("abc");
System.out.println("b");
}
B(String name){
System.out.println("b name");
}
}
package Extends_.ExtendsExercise.ExtendsExercise01;
public class Test {
public static void main(String[] args) {
B b = new B();
}
}
二
package Extends_.ExtendsExercise.ExtendsExercise02;
public class Exercise02 {
public static void main(String[] args) {
C c = new C();
}
}
class A{
public A(){
System.out.println("我是A类");
}
}
class B extends A{
public B(){
System.out.println("我是B类的无参构造器");
}
public B(String name){
System.out.println(name + "我是B类的有参构造器");
}
}
class C extends B{
public C(){
this("Hello");
System.out.println("我是C类的无参构造器");
}
public C(String name){
super("haha");
System.out.println("我是C类的有参构造器");
}
}
//A hahaB有参 C有参 C无参
三
package Extends_.ExtendsExercise.ExtendsExercise03;
public class Computer {
private String cpu ;
private int memory ;
private int disk;
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
}
public int getDisk() {
return disk;
}
public void setDisk(int disk) {
this.disk = disk;
}
public Computer(String cpu, int memory, int disk) {
setCpu(cpu);
setMemory(memory);
setDisk(disk);
}
public Computer(){
}
public String getDetails(){
return ("CPU=" + this.cpu + " Memory=" + this.memory + " Disk=" + this.disk);
}
}
package Extends_.ExtendsExercise.ExtendsExercise03;
public class NotePad extends Computer {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public NotePad(String cpu, int memory, int disk, String color) {
super(cpu, memory, disk);
setColor(color);
}
public void showInfo(){
System.out.println(getDetails() + " color=" + color);
}
}
package Extends_.ExtendsExercise.ExtendsExercise03;
public class PC extends Computer {
private String brand;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
//这里IDEA根据继承的规则,自动把构造器的调用写好
//体现:继承设计的基本思想,父类的构造器完成父类属性初始化
//子类的构造器完成子类属性初始化
public PC(String cpu, int memory, int disk, String brand) {
super(cpu, memory, disk);
setBrand(brand);
}
public void showInfo(){
System.out.println(getDetails() + " brand=" + brand);
}
}
package Extends_.ExtendsExercise.ExtendsExercise03;
public class Exercise03 {
public static void main(String[] args) {
PC pc = new PC("intel", 16, 500, "Dell");
pc.showInfo();
NotePad notePad = new NotePad("wx", 999, 9999, "green");
notePad.showInfo();
}
}
super
基本介绍
super 代表父类的引用,用于访问父类的属性、方法、构造器
super. 访问父类的属性方法。
super() 访问父类的构造器。
super细节
super 和 this 的比较
方法重写/覆盖(override)
基本介绍
方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法。
方法注意事项和使用细节
package Override;
public class Person {
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;
}
public Person(String name, int age) {
setName(name);
setAge(age);
}
public String say(){
return "名字=" + name + " 年龄=" + age;
}
}
package Override;
public class Student extends Person{
private int id;
private double score;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public Student(String name, int age, int id, double score) {
super(name, age);
setId(id);
setScore(score);
}
@Override
public String say() {
return super.say() + " ID=" + id + " 分数=" + score;
}
}
package Override;
public class OverrideExercise {
public static void main(String[] args) {
Person p = new Person("人", 100);
System.out.println(p.say());
Student wexiao = new Student("wexiao", 20, 666, 99.9);
System.out.println(wexiao.say());
}
}
方法的重载和重写比较
多态
多[多种]态[状态]基本介绍
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
多态的具体体现
- 方法的多态
重写和重载体现多态。
- 对象的多态
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象时,就确定了,不能改变
- 运行类型是可以变化的.
- 编译类型看定义时=号的左边,运行类型看=号的右边
getClass(),返回此Object的运行时类。
package com.wexiao.poly_.objectpoly_;
public class Animal {
public void cry(){
System.out.println("Animal cry() 动物在叫");
}
}
package com.wexiao.poly_.objectpoly_;
public class Cat extends Animal{
@Override
public void cry() {
System.out.println("Cat cry()小猫喵喵叫...");
}
}
package com.wexiao.poly_.objectpoly_;
public class Dog extends Animal {
@Override
public void cry() {
System.out.println("Dog cry() 小狗汪汪叫...");
}
}
package com.wexiao.poly_.objectpoly_;
public class PolyObject {
public static void main(String[] args) {
//体验对象多态特点
//animal 编译类型就是 Animal ,运行类型 Dog
Animal animal = new Dog();
animal.cry();
animal = new Cat();
animal.cry();
}
}
多态注意事项和细节
多态前提
多态的前提是:两个对象(类)存在继承关系
多态的向上转型
- 本质:父类的引用指向了子类的对象
- 语法:父类类型引用名 = new 子类类型();
- 特点:编译类型看左边,运行类型看右边。
可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员;
最终运行效果看子类的具体实现!
package com.wexiao.poly_.detail_;
public class Animal {
String name = "动物";
int age = 10;
public void sleep(){
System.out.println("睡");
}
public void run(){
System.out.println("跑");
}
public void eat(){
System.out.println("吃");
}
public void show(){
System.out.println("hello,你好");
}
}
package com.wexiao.poly_.detail_;
public class Cat extends Animal {
public void eat(){//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){//Cat 特有方法
System.out.println("猫抓老鼠");
}
}
package com.wexiao.poly_.detail_;
public class PolyDetail {
public static void main(String[] args) {
//向上转型:父类的引用指向了子类的对象
//语法:父类类型引用名 = new 子类类型();
Animal animal = new Cat();
//向上转型:
//animal可以调用类中的所有成员(需遵守访问权限)
//不能调用子类中特有成员
///(#)因为在编译阶段,能调用哪些成员,是由编译类型来决定的
//animal.catchMouse();错误
//最终运行效果看子类的具体实现,即调用方法时,按照从子类开始查找方法,然后调用
animal.eat(); //猫吃鱼
animal.run(); //跑
animal.show(); //hello,你好
animal.sleep(); //睡
}
}
多态的向下转型
- 语法:子类类型引用名 = (子类类型)父类引用;
- 只能强转父类的引用,不能强转父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 当向下转型后,可以调用子类类型中所有的成员
Animal animal = new Cat();
//向下转型
//语法:子类类型引用名 = (子类类型)父类引用;
Cat cat = (Cat)animal;
cat.catchMouse();
属性重写问题
属性没有重写之说!**属性的值看编译类型** ( 属性看编译,方法看运行 )
package com.wexiao.poly_.detail_;
public class PolyDetail02 {
public static void main(String[] args) {
//属性没有重写之说!属性的值看编译类型
Base base = new Sub();//向上转型
System.out.println(base.count);// ? 看编译类型 10
Sub sub = new Sub();
System.out.println(sub.count);//? 20
}
}
class Base { //父类
int count = 10;//属性
}
class Sub extends Base {//子类
int count = 20;//属性
}
instanceOf
instanceOf 比较操作符,用于判断对象的**运行类型是否为 XX 类型或 XX 类型的子类型**。
package com.wexiao.poly_.detail_;
public class PolyDetail03 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB);// true
System.out.println(bb instanceof AA);// true
//aa 编译类型 AA, 运行类型是 BB
//BB 是 AA 子类
AA aa = new BB();
System.out.println(aa instanceof AA);// true
System.out.println(aa instanceof BB);// true
//判断对象的运行类型
Object obj = new Object();
System.out.println(obj instanceof AA);//false
String str = "hello";
//System.out.println(str instanceof AA);
System.out.println(str instanceof Object);//true
}
}
class AA {} //父类
class BB extends AA {}//子类
多态练习
20 20 true 10(看编译类型,属性没有重写这个概念) 20(看运行类型,子类方法重写)
(属性看编译,方法看运行~)
<hr/
里式转换
(1)子类可以赋值给父类
(2)如果父类中装的是子类对象,那么可以将这个父类强转为子类对象。
java动态绑定机制
java的动态绑定机制
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明,那里使用
package com.wexiao.poly_.dynamic_;
public class DynamicBinding {
public static void main(String[] args) {
//a 的编译类型 A, 运行类型 B
A a = new B();//向上转型
System.out.println(a.sum());//?40 -> 30
System.out.println(a.sum1());//?30-> 20
}
}
class A { //父类
public int i = 10;
//动态绑定机制:
public int sum() {//父类 sum()
return getI() + 10;//20 + 10
}
public int sum1() {//父类 sum1()
return i + 10;//10 + 10
}
public int getI() {//父类 getI
return i;
}
}
class B extends A {//子类
public int i = 20;
// public int sum() {
// return i + 20;
// }
public int getI() {//子类 getI()
return i;
}
// public int sum1() {
// return i + 10;
// }
}
多态应用
多态数组
**数组的定义类型为父类类型,里面保存的实际元素类型为子类类型。**
应用实例:
1、现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组 中,并调用每个对象 say 方法.
2、应用实例升级:如何调用子类特有的方法,比如 Teacher 有一个 teach , Student 有一个 study 怎么调用?
package com.wexiao.poly_.polyarr_;
public class PolyArray {
public static void main(String[] args) {
Person[] persons = new Person[5];
persons[0] = new Person("微咲", 20);
persons[1] = new Student("xiaoming", 11, 99);
persons[2] = new Student("xiaoqiang", 12, 98);
persons[3] = new Teacher("dahua", 50, 15000);
persons[4] = new Teacher("dawei", 48, 13000);
//循环遍历多态数组,调用say
for (int i = 0; i < persons.length; i++) {
System.out.println(persons[i].say());//动态绑定机制
//
if (persons[i] instanceof Student) { //判断person[i]的运行类型是否为Student
Student student = (Student) persons[i];//向下转型
student.study();
//((Student)persons[i]).study();
} else if (persons[i] instanceof Teacher) {
Teacher teacher = (Teacher) persons[i];
teacher.teach();
} else if (persons[i] instanceof Person) {
} else {
System.out.println("类型错误");
}
}
}
}
//Person父类
package com.wexiao.poly_.polyarr_;
public class Person {
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;
}
public Person(String name, int age) {
setAge(age);
setName(name);
}
public String say() {
return name + "\t" + age;
}
}
//Student子类
package com.wexiao.poly_.polyarr_;
public class Student extends Person{
private double score;
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public Student(String name, int age, double score) {
super(name, age);
setScore(score);
}
//重写父类say方法
@Override
public String say() {
return super.say() + "\t" + score;
}
}
//Teacher子类
package com.wexiao.poly_.polyarr_;
public class Teacher extends Person {
private double salary;
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Teacher(String name, int age, double salary) {
super(name, age);
setSalary(salary);
}
@Override
public String say() {
return super.say() + "\t" + salary;
}
}
多态参数
**方法定义的形参类型为父类类型,实参类型允许为子类类型。**
Object类详解
equals方法
== 和 equals 的对比
==:
- ==:既可以判断基本类型,又可以判断引用类型
- ==:如果判断基本类型,判断的是值是否相等。示例: int i=10; double d=10.0;
- ==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象
equals(Object obj):
Object类中方法默认判断的是地址是否相等
子类中往往重写该方法,用于判断内容是否相等。比如Integer,String【看看String和 Integer的equals源代码】
equals原码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
Integer integer1 = new Integer(1000);
Integer integer2 = new Integer(1000);
System.out.println(integer1 == integer2);//false
System.out.println(integer1.equals(integer2));//true
String str1 = new String("hspedu");
String str2 = new String("hspedu");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
//自定义类
Person person1 = new Person("微咲", 20, '男');
Person person2 = new Person("微咲", 20, '男');
System.out.println(person1.equals(person2));//false
equals方法重写
package com.wexiao.object_;
public class EqualsExercise01 {
public static void main(String[] args) {
Person person1 = new Person("微咲", 20, '男');
Person person2 = new Person("微咲", 20, '男');
System.out.println(person1.equals(person2));//false
}
}
//判断两个 Person 对象的内容是否相等,
//如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false
class Person{
private String name;
private int age;
private char gender;
//重写Object的equals方法
public boolean equals(Object obj){
//如果比较的两个对象是同一个对象,则直接返回true
if (this == obj){
return true;
}
//类型判断
if (obj instanceof Person){ //是Person才比较
//进行向下转型,要得要obj的各个属性
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
}
return false;
}
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;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public Person(String name, int age, char gender) {
setName(name);
setAge(age);
setGender(gender);
}
}
equals练习
一、
package com.wexiao.object_;
public class EqualsExercise02 {
public static void main(String[] args) {
Person_ p1 = new Person_();
p1.name = "hspedu";
Person_ p2 = new Person_();
p2.name = "hspedu";
System.out.println(p1==p2); //False
System.out.println(p1.name .equals( p2.name));//T
System.out.println(p1.equals(p2));//False
String s1 = new String("asdf");
String s2 = new String("asdf");
System.out.println(s1.equals(s2));//T
System.out.println(s1==s2); //F
}
}
class Person_{//类
public String name;
}
二、
int it = 65;
float fl = 65.0f;
System.out.println(“65 和 65.0f 是否相等?” + (it == fl));//T
char ch1 = ‘A’; char ch2 = 12;
System.out.println(“65 和‘A’是否相等?” + (it == ch1));//T
System.out.println(“12 和 ch2 是否相等?” + (12 == ch2));//T
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1 和 str2 是否相等?"+ (str1 == str2)); //F
System.out.println(“str1 是否 equals str2?”+(str1.equals(str2)));//T
System.out.println(“hello” == new java.sql.Date()); //编译错误
hashCode
提高具有哈希结构的容器的效率!
两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
- 两个引用,如果指向的是不同对象,则哈希值是不一样的
- 哈希值主要根据地址号来的!, 不能完全将哈希值等价于地址。
- 案例演示[HashCode_.java]: obj.hashCode() [测试:A obj1 = new A(); A obj2 = new A(); A obj3 = obj1]
- 后面在集合,中 hashCode 如果需要的话,也会重写, 在讲解集合时,再说如何重写 hashCode()
package com.wexiao.object_;
public class HashCode_ {
public static void main(String[] args) {
AA aa = new AA();
AA aa2 = new AA();
AA aa3 = aa;
System.out.println("aa.hashCode()=" + aa.hashCode());
System.out.println("aa2.hashCode()=" + aa2.hashCode());
System.out.println("aa3.hashCode()=" + aa3.hashCode());
}
}
class AA {}
toString
基本介绍
默认返回:全类名+@+哈希值的十六进制,【查看 Object 的 toString 方法】 子类往往重写 toString 方法,用于返回对象的属性信息
重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式. 案例演示:Monster [name, job, sal] 案例: ToString_.java
- 当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调用 monster.toString()
Object 的 toString()原码:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
//getClass().getName() //类的全类名(包名+类名)
//Integer.toHexString(hashCode()) //将对象的hashCode值转成16进制字符串
toString()调用和重写:
package com.wexiao.object_;
public class ToString_ {
public static void main(String[] args) {
Monster monster = new Monster("小妖怪", "巡山", 1000);
System.out.println(monster.toString()); //重写前:com.wexiao.object_.Monster@1b6d3586。重写后,输出属性信息
//System.out.println(monster.hashCode());
System.out.println(monster.hobby.toString());//String类会重写toString()方法
//当直接输出一个对象时,toString()方法会被默认调用
System.out.println(monster);
}
}
class Monster{
private String name;
private String job;
private double salary;
@Override
public String toString() { //重写后,一般把对象的属性值输出,也可以自定义
return "Monster{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", salary=" + salary +
'}';
}
public String hobby = "吃人";
public Monster(String name, String job, double salary) {
this.name = name;
this.job = job;
this.salary = salary;
}
}
finalize
当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一
些释放资源的操作什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对
象,就会使用垃圾回收机制来 销毁该对象,在销毁该对象前,会先调用 finalize 方
法。- 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过
System.gc() 主动触发垃圾回收机制,
演示 Finalize的用法
package com.wexiao.object_;
//演示 Finalize的用法
public class Finalize_ {
public static void main(String[] args) {
Car bmw = new Car("宝马");
bmw = null;
//这时car指向的 就是一个垃圾,垃圾回收器就会销毁。
//在销毁对象前,会调用该对象的finalize方法
//,我们就可以重写finalize,然后在其中写自己的业务逻辑代码(比如释放资源,数据库连接,打开文件。。。)
// 如果没有重写,调用的就是Object类中的finalize方法
//主动调用垃圾回收器
System.gc();
System.out.println("程序结束...");
}
}
class Car{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Car(String name) {
setName(name);
}
@Override
protected void finalize() throws Throwable {
//super.finalize();
System.out.println("我们销毁汽车 " + getName());
System.out.println("释放了某些资源...");
}
}
断点调试(debug)
断点调试介绍
断点调试的快捷键
F7:跳入方法内
F8: 逐行执行代码.
shift+F8(跳出)
F9(resume,执行到下一个断点)
shift+F8: 跳出方法
ctrl+F2:退出debug
进入源码:
类变量(静态变量)
什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访
问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
类变量与实例变量(普通属性)区别:
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
定义类变量
访问修饰符 static 类型 变量名;
static 访问修饰符 类型 变量名;
如何访问类变量
类名.变量名;
对象名.变量名;
类变量内存布局
static 变量存放在哪里,有争议。
不管static变量在哪里,都有:(1)static类变量是用一个类所有对象共享。
(2)**static类变量,在类加载的时候就生成**了。
类方法(静态方法)
定义形式
访问修饰符 static 数据返回类型 方法名(){}
static 访问修饰符 数据返回类型 方法名(){}
注意事项
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区;类方法中无this的参数
- 类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以。
- 静态方法只能访问静态成员。非静态方法可访问静态成员和非静态成员。
往往我们会将一些常用的方法,设计成静态方法方便调用。
main方法细节
在idea中给mian() args数组传值:
package com.wexiao.main_;
public class Main01 {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
main()方法特别提示:
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
Ⅰ
代码块
代码块基本介绍
- 代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过包围起来。
- 但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
代码块基本语法
[修饰符]{
代码
};
注意:
- 修饰符可选,要写的话,也只能写static
- 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块。
- 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
- ;号可以写上,也可以省略。
代码块的好处和案例演示
理解:
1)相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
2)场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用
性
package com.wexiao.codeblock_;
public class CodeBlock01 {
public static void main(String[] args) {
Movie movie1 = new Movie("微咲");
Movie movie2 = new Movie("wx", 55.55);
}
}
class Movie{
private String name;
private double price;
//代码块
//不管调用哪个构造器创建对象都会先调用代码块中的内容
//代码块调用顺序优先于构造器
{
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始...");
};
public Movie(String name) {
this.name = name;
}
public Movie(String name, double price) {
this.name = name;
this.price = price;
}
}
代码块使用注意事项和细节讨论
- static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并
且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
- 类什么时候被加载[重要背!]
1)创建对象实例时(new)
2)创建子类对象实例,父类也会被加载
3)使用类的静态成员时(静态属性,静态方法) - 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。如果只
是使用类的静态成员时,普通代码块并不会执行。
package com.wexiao.codeblock_;
public class CodeBlockDetail01 {
public static void main(String[] args) {
//创建子类对象时,父类也会被加载,而且父类先被加载,然后加载子类
BB bb = new BB();
//使用类的静态成员时 , 调用类的静态成员时,静态代码块会被执行
System.out.println(Cat.n1);
//static 代码块, 是在类加载时执行的,只执行一次
AA a1 = new AA();
AA a2 = new AA();
}
}
class Cat{
public static int n1 = 99999;
//静态代码块
static {
System.out.println("Cat的静态代码块1被执行...");
}
}
class AA{
//静态代码块
static {
System.out.println("AA的静态代码块1被执行...");
}
}
class BB extends AA{
//静态代码块
static {
System.out.println("BB的静态代码块1被执行...");
}
}
AA的静态代码块1被执行...
BB的静态代码块1被执行...
Cat的静态代码块1被执行...
99999
调用顺序:静态成员 -> 普通成员 -> 构造器
package com.hspedu.codeblock_;
public class CodeBlockDetail02 {
public static void main(String[] args) {
A a = new A();// (1) A 静态代码块 01 (2) getN1 被调用...(3)A 普通代码块 01(4)getN2 被调用...(5)A() 构造器被调
用
}
}
class A {
{ //普通代码块
System.out.println("A 普通代码块 01");
}
private int n2 = getN2();//普通属性的初始化
static { //静态代码块
System.out.println("A 静态代码块 01");
}
//静态属性的初始化
private static int n1 = getN1();
public static int getN1() {
System.out.println("getN1 被调用...");
return 100;
}
public int getN2() { //普通方法/非静态方法
System.out.println("getN2 被调用...");
return 200;
}
//无参构造器
public A() {
System.out.println("A() 构造器被调用");
}
}
- 构造方法(构造器)的最前面其实隐含了super()和调用普通代码块,,静态相关的代码
块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的。
package com.hspedu.codeblock_;
public class CodeBlockDetail03 {
public static void main(String[] args) {
new BBB();//(1)AAA 的普通代码块(2)AAA() 构造器被调用(3)BBB 的普通代码块 (4)BBB() 构造器被调用
}
}
class AAA { //父类 Object
{
System.out.println("AAA 的普通代码块");
}
public AAA() {
//(1)super()
//(2)调用本类的普通代码块
System.out.println("AAA() 构造器被调用....");
}
}
class BBB extends AAA {
{
System.out.println("BBB 的普通代码块...");
}
public BBB() {
//(1)super()
//(2)调用本类的普通代码块
System.out.println("BBB() 构造器被调用....");
}
}
- 我们看一下创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,
普通代码块,普通属性初始化,构造方法的调用顺序如下:
1)父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
2)子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
3)父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
4)父类的构造方法
5)子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
6)子类的构造方法 - 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调
用任意成员。
小结
- static代码块是类加载时,执行,只会执行一次
- 普通代码块是在创建对象时调用的,创建一次,调用一次
- 类加载的3种情况,需要记住.
设计模式
什么是设计模式
- 静态方法和属性的经典使用
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的
思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考
和摸索。
单例模式
单例模式介绍
- 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
- 单例模式有两种方式:
1)饿汉式 (未使用都会被创建好,类加载时对象就创建了)
2)懒汉式 (不使用就不创建)
饿汉式
package com.wexiao.single_;
public class SingleTon01 {
public static void main(String[] args) {
//通过Girlfriend静态方法 可以 获取对象
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
}
}
//有一个类,只能有一个Girlfriend
class GirlFriend{
private String name;
private GirlFriend(String name) {
this.name = name;
}
//如果保障我们只能创建一个Girlfriend对象
//步骤 [ 单例模式-饿汉式 ]
//1. 将构造器私有化 (防止在外部创建)
//2. 在类的内部直接创建对象(被static修饰)
//为了能够在静态方法中,返回 gf 对象,需要将其修饰为static
private static GirlFriend gf = new GirlFriend("小湘");
//3. 提供一个公共的静态方法,返回 gf 对象 (这样就不需要创建对象调用方法,直接用类名...)
public static GirlFriend getInstance(){ //类加载时会初始化static字段,不会调用static方法
return gf;
}
//toString()
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
懒汉式
package com.wexiao.single_;
/**
* 演示懒汉式的单例模式
*/
public class SingleTon02 {
public static void main(String[] args) {
Cat instance1 = Cat.getInstance();
System.out.println(instance1);
Cat instance2 = Cat.getInstance();
System.out.println(instance2);
System.out.println(instance1 == instance2);
}
}
class Cat{
private String name;
//步骤[ 懒汉式 ] : 只有当对象使用getInstance时,才返回cat对象,后面再次调用时,会返回上次创建的cat对象,从而单例
//1. 构造器私有化
private Cat(String name) {
this.name = name;
}
//2. 定义一个static静态属性对象
private static Cat cat;
//3. 提供一个public static返回,可以返回一个Cat对象
public static Cat getInstance(){ //类加载时会初始化static字段,不会调用static方法
if ( cat == null){
cat = new Cat("小湘");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
饿汉式VS懒汉式
1.二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建。
2.饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
3.饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建
的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
4.在我们javaSE标准类中,java.lang.Runtime就是经典的单例模式。
final
final是一个关键字,可以用于修饰类,成员变量,成员方法。
final特点
- 它修饰的类不能被继承。
- 它修饰的成员变量是一个常量。
- 它修饰的成员方法是不能被子类重写的。
final修饰的常量定义一般都有书写规范,被final修饰的常量名称,所有字母都大写。
final修饰成员变量,必须初始化,初始化有两种
- 显示初始化;
- 构造方法初始化。
但是不能两个一起初始化 - (代码块初始化???)
final注意事项
- final修饰的属性又叫常量,一般用XX_XX_XX命名。
- final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一,
① 定义时
② 在构造器中
③ 在代码块中 - final修饰的静态属性在定义时,必须赋初值,且以后不能修改,赋值可以在如下位置之一。
① 定义时
② 在静态代码块中 - final类不能继承,但是可以实例化对象。
- 如果类不是final类,但是含有final方法,该方法虽然不能重写,但是可以被继承。
- 一般来说,如果一个类已经是final类了,该类成员就没有必要修饰成final
- final不能修饰构造方法(构造器)
final 和 static 往往搭配使用,效率更高,不会导致类加载。底层编译器做了优化处理。
```java class A{ public final static int num = 9999;static{
System.out.println("类加载ing...");
} }
//结果是 9999 //没有触发类加载
9. 包装类(Integer,Double,Float,Boolean,String等都是final修饰的类。
```java
package com.wexiao.final_;
public class FinalDetail01 {
}
class A{
//final修饰成员初始化
//1.显示初始化
private final String NAME_CHINA1 = "微咲";
private final String NAME_CHINA2;
private final String NAME_CHINA3;
//2.构造器初始化
public A() {
this.NAME_CHINA2 = "微咲";
}
//3.代码块初始化
{
NAME_CHINA3 = "微咲";
}
}
class B{
//final修饰静态成员初始化
//1.显示初始化
private static final double TAX_RATE1 = 0.12;
private static final double TAX_RATE2;
//private static final double TAX_RATE3;
//2.静态代码块初始化
static{
TAX_RATE2 = 0.03;
}
// //3.构造器初始化 不行,类加载时TAX_RATE3并没有值。
// public B() {
// this.TAX_RATE3 = 0.2;
// }
}
抽象类
抽象类介绍
- 用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{
} - 用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表); //没有方法体 - 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
- 抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多
抽象类使用的注意事项
- 抽象类不能被实例化。
- 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法。
- 一旦类包含了abstract方法,则这个类必须声明为abstract。
- abstract 只能修饰类和方法,不能修饰属性和其它的。
- 抽象类可以有任意成员。
- 抽象方法不能有主体,即不能实现。
- 如果一个类继承了抽象类,则它必须实现抽象类中所有抽象方法,除非它自己也声明为abstract类。
- 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
package com.wexiao.abstract_;
public class AbstractDetail01 {
public static void main(String[] args) {
//抽象类不能实例化
//new A();
A.sayHello();
//实现重写abstract方法
B b = new B("微咲");
b.say();
}
}
abstract class A{
//**abstract 只能修饰类和方法**,不能修饰属性和其它的。
//private abstract int num = 1;
//属性
private String name;
//构造器
public A(String name) {
this.name = name;
}
public abstract void say();
public static void sayHello(){
System.out.println("你好~~");
}
}
class B extends A{
public B(String name) {
super(name);
}
@Override
public void say() {
System.out.println("我是A的子类B");
}
}
抽象类最佳实践-模板设计模式
父类
package com.wexiao.abstract_;
public abstract class Template {
public abstract void job();
public void calculateTime(){
//得到开始的时间 ms
long start = System.currentTimeMillis();
job();
//获得结束的时间
long end = System.currentTimeMillis();
System.out.println(getClass() + "执行时间:" + (end - start));
}
}
子类1
package com.wexiao.abstract_;
public class AA extends Template{
@Override
public void job() {
int num = 0;
for (int i = 1; i <= 100000; i++) {
num += i;
}
}
}
子类2
package com.wexiao.abstract_;
public class BB extends Template{
@Override
public void job() {
int num = 0;
for (int i = 1; i <= 1000000; i++) {
num += (i / 2);
}
}
}
main
package com.wexiao.abstract_;
public class TestTemplate {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}
接口
快速入门
接口
package com.wexiao.interface_;
//接口
public interface Usbinterface {
//在接口中,抽象方法可以省略abstract关键字
public void start();
public void stop();
}
实现接口的类1
package com.wexiao.interface_;
//实现接口,需要将接口内的所有抽象方法全部实现
public class Phone implements Usbinterface {
@Override
public void start() {
System.out.println("手机开始工作...");
}
@Override
public void stop() {
System.out.println("手机结束工作...");
}
}
实现接口的类2
package com.wexiao.interface_;
//实现接口
public class Camera implements Usbinterface {
@Override
public void start() {
System.out.println("相机开始工作...");
}
@Override
public void stop() {
System.out.println("相机结束工作...");
}
}
调用接口的类
package com.wexiao.interface_;
public class Computer {
public void work(Usbinterface usbinterface){
//通过接口调用方法
usbinterface.start();
usbinterface.stop();
}
}
main
package com.wexiao.interface_;
public class Interface01 {
public static void main(String[] args) {
//创建相机、手机对象
Camera camera = new Camera();
Phone phone = new Phone();
//创建计算机
Computer computer = new Computer();
computer.work(phone);//把手机接入电脑
System.out.println("==========");
computer.work(camera);//把相机接入电脑
}
}
//手机开始工作...
//手机结束工作...
//==========
//相机开始工作...
//相机结束工作...
基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。语法:
interface接口名{
//属性
//抽象方法
}
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的接口的抽象方法;
}
小结:
接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体【idk7.0)】。**接口体现了程序设计的多态和高内聚低偶合的设计思想。**
特别说明:
**dk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现**
public interface InterfaceA
//属性
//抽象方法
//在jdk8后,方法可以有方法体,但是需要添加default关键字
default public void ok(){
System.out.println("ok");
//在jdk8后,静态方法
public static void hello(){
System.out.println("hello");
}
}
接口注意事项
- 接口不能实例化。
- 接口中所有的方法都是public方法,接口中抽象方法,可以不用abstract修饰。
- 普通类实现接口,就必须将该接口的所有方法都实现。 alt+enter
- 抽象类实现接口,可以不用实现接口的方法。
一个类同时可以实现多个接口。
interface IA{
void hi();
}
interface IB{
void hello();
}
class Test implements IA,IB{
public void hi(){
}
public void hello(){
}
}
接口中的属性,只能是final的,而且是public static final修饰符。(必须初始化)
- 接口中属性的访问形式:接口名.属性名
- 接口不能继承其他的类,但是可以继承多个别的接口。
- 接口的修饰符,只能用public和默认来修饰。
继承与接口
package com.hspedu.interface_;
public class ExtendsVsInterface {
public static void main(String[] args) {
LittleMonkey wuKong = new LittleMonkey("悟空");
wuKong.climbing();
wuKong.swimming();
wuKong.flying();
}
}
//猴子
class Monkey {
private String name;
public Monkey(String name) {
this.name = name;
}
public void climbing() {
System.out.println(name + " 会爬树...");
}
public String getName() {
return name;
}
}
//接口
interface Fishable {
void swimming();
}
interface Birdable {
void flying();
}
}
//继承
//小结: 当子类继承了父类,就自动的拥有父类的功能
// 如果子类需要扩展功能,可以通过实现接口的方式扩展.
// 可以理解 实现接口 是 对 java 单继承机制的一种补充
class LittleMonkey extends Monkey implements Fishable,Birdable {
public LittleMonkey(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");
}
@Override
public void flying() {
System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");
}
}
//小结: 当子类继承了父类,就自动的拥有父类的功能
// 如果子类需要扩展功能,可以通过实现接口的方式扩展.
// 可以理解 实现接口 是 对 java 单继承机制的一种补充
继承与接口的不同
- 接口和继承解决的问题不同:
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。
- 接口比继承更加灵活
接口比继承更加灵活,继承是满足is - a的关系,而接口只需满足like - a的关系。
fish is a animal fish like a swim
- 接口在一定程度上实现代码解耦[ 即 接口规范性+ 动态绑定机制]
接口的多态特性
- 多态参数
```java interfance IF{ } class A implements IF{ }
mian{ IF if = new A(); }
2. 多态数组
```java
interfance IF{
}
class A implements IF{
}
class B implements IF{
}
mian{
//多态数组 -> 接口类型数组
IF if = new IF[2];
if[0] = new A;
if[1] = new B;
}
- 接口存在多态传递现象。
package com.wexiao.interface_;
/**
* 演示接口多态传递现象
*/
public class InterfacePolyPass {
public static void main(String[] args) {
//接口类型的变量可以指向,实现了该接口的类的对象实例
IY iy = new Teacher();
//如果IY继承了IX接口,且Teacher类实现了 IY接口
//那么,相当于 Teacher 也实现了IX接口。
IX ix = new Teacher();
}
}
interface IX{}
interface IY extends IX{}
class Teacher implements IY{
}
内部类
类的五大成员: 属性、构造器、方法、代码块、内部类
基本介绍
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他
类的类称为外部类(outer class)。是我们类的第五大成员,内部类最大的特点就是可以直接访问
私有属性,并且可以体现类与类之间的包含关系。
基本语法
class Outer{ //外部类
class Inner{ //内部类
}
}
class Other{ //外部其他类
}
package com.wexiao.innerclass;
public class InnerClass01 { //外部其他类
public static void main(String[] args) {
}
}
class Outer{ //外部类
private int n1 = 100; //属性
public Outer(int n1) { // 构造器
this.n1 = n1;
}
public void m1(){ //方法
System.out.println("m1()");
}
{ //代码块
System.out.println("代码块");
}
class Inner{ //内部类
}
}
内部类的分类
- 定义在外部类局部位置上(比如方法内):
1)局部内部类(有类名)
2)匿名内部类(没有类名,重点!!!!!)
- 定义在外部类的成员位置上:
1)成员内部类(没用static修饰)
)2)静态内部类(使用static修饰)
局部内部类
class A{
public void M(){
class B{
}
}
}
说明
局部内部类是定义在外部类的局部位置,比如方法中,并且有类名,本质还是个类。
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是
可以使用final修饰,因为局部变量也可以使用final - 作用域:仅仅在定义它的方法或代码块中。
- 局部内部类—-访问——>外部类的成员[访问方式:直接访间]
- 外部类—-访问——>局部内部类的成员
访问方式:创建对象,再访问(注意:必须在作用域内) - 外部其他类—-不能访问——->局部内部类(因为局部内部类地位是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则
可以使用(外部类名.this.成员)去访问
package com.wexiao.innerclass;
/**
* 演示局部内部类的使用
*/
public class LocalInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();
//外部其他类---不能访问----->局部内部类(因为局部内部类地位是一个局部变量)
//Outer.Inner inner = new Outer.Inner();
}
}
class Outer02{
private int n1 = 100;
private void m2(){
System.out.println("我是m2方法");
}
public void m1(){
//局部内部类是定义在外部类的局部位置,通常在方法
class Inner02{ //局部内部类
private int n1 = 99;
//可以直接访问外部类的所有成员,包含私有的
public void f1(){
System.out.println("外部类的n1 = " + Outer02.this.n1);
m2();
//m1();
}
}
// 外部类---访问---->局部内部类的成员
// 访问方式:创建对象,再访问(注意:必须在作用域内)
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
匿名内部类
1)本质是类,2)内部类,3)该类没有名字,4)同时还是一个对象
说明
匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
匿名内部类基本语法
new 类或接口(参数列表){
类体;
};
基于接口:
package com.wexiao.innerclass;
/**
* 演示匿名内部类
*/
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04 { //外部类
private int n1 = 10;
public void method() {
//基于接口的匿名内部类
// 想使用IA接口,并创建对象
//传统方式:是写一个类,实现该接口,并创建对象
//现在:使用匿名类简化开发
//tiger 编译类型:IA
//tiger 运行类型:匿名内部类 XXX => 分配的类名:Outer04$1
/*
底层:
class Outer04$1 implements IA{
@Override
public void cry() {
System.out.println("老虎在哭...");
}
}
*/
//jdk底层在创建匿名内部类 Outer04$1,立即就创建了 Outer04$1实例,并且把地址返回给tiger
IA tiger = new IA() { //匿名内部类使用一次后,就不能再使用了
@Override
public void cry() {
System.out.println("老虎在哭...");
}
};
tiger.cry();
System.out.println("tiger的运行类型:" + tiger.getClass());
//基于类的匿名内部类
//father 编译类型:Father
//father 运行类型:Outer04$2
/*
class Outer04$2 extends Father{
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
}
*/
//直接返回匿名内部类Outer04$2的对象
Father father = new Father("jack") {
@Override
public void test() {
System.out.println("匿名内部类重写了test方法" );
}
};
System.out.println("father的运行类型:" + father.getClass());
father.test();
//基于抽象类的匿名内部类
/*
class Outer04$3 extends Animal{
@Override
public void eat(){
System.out.println("eat()");
}
}
*/
//基于抽象类的匿名内部类,必须要实现抽象类的抽象方法
Animal animal = new Animal() {
@Override
public void eat() {
System.out.println("eat()");
}
};
animal.eat();
}
}
interface IA { //接口
public void cry();
}
class Father { //类
public Father(String name) {
System.out.println("name = " + name);
}
public void test() {
}
}
abstract class Animal {
public abstract void eat();
}
//老虎在哭...
//tiger的运行类型:class com.wexiao.innerclass.Outer04$1
//name = jack
//father的运行类型:class com.wexiao.innerclass.Outer04$2
//匿名内部类重写了test方法
//eat()
匿名内部类细节
匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本
身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代
码分析可以看出这个特点,因此可以调用匿名内部类方法。new A(){
@Override
public void cry(){
System.out.println("hello");
}
}.cay();
A a = new A(){
@Override
public void cry(){
System.out.println("hello");
}
};
a.cry();
可以直接访问外部类的所有成员,包含私有的。
- 不能添加访问修饰符,因为它的地位就是一个局部变量。
- 作用域:仅仅再定义它的方法或代码块中。
- 匿名内部类—-访问——>外部类成员[访问方式:直接访问]
- 外部其他类—-不能访问——->匿名内部类(因为匿名内部类地位是一个局部变量)
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部
类的成员,则可以使用(外部类名.this.成员)去访问
匿名内部类的最佳实例
当作实参直接传递,简洁高效。
package com.wexiao.innerclass;
import com.wexiao.abstract_.AA;
public class InnerClassExercise01 {
public static void main(String[] args) {
//传统方法,硬编码
IL il =new WX();
f1(il);
//f1(new WX());
//当作实参直接传递,简洁高效。
f1(new IL() {
@Override
public void show() {
System.out.println("hello");
}
});
}
//静态方法,形参是接口IL类型
public static void f1(IL il){
il.show();
}
}
//接口
interface IL{
public void show();
}
class WX implements IL{
@Override
public void show() {
System.out.println("hello");
}
}
成员内部类
说明
成员内部类是定义在外部类的成员位置,并且没有static修饰。
成员内部类细节
- 可以直接访问外部类的所有成员,包含私有的
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
- 作用域:
和外部类的其他成员一样,为整个类体。比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法. - 成员内部类—-访问——>外部类成员(比如:属性)[访问方式:直接访问]
- 外部类—-访问———>成员内部类(说明)访问方式:创建对象,再访问
外部其他类—-访问——>成员内部类 (三种方式)
①
② 在外部类中,编写一个方法,可以返回Inner
③new Outer().new Inner();
Outer outer = new Outer();
//相当于把 new Inner() 当作是outer成员
Outer.Inner inner = outer.new Inner();
```java Outer outer = new Outer(); Outer.Inner inner = outer.getInner();
//外部类中 public Inner getInner(){ retrun new Inner(); }
7. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵**循就近原则**,如果想访问外部类的成员,则可以使用(**外部类名.this.成员**)去访问
<a name="60fc1783"></a>
#### 静态内部类
<a name="f411d0f1-3"></a>
##### 说明
静态内部类是定义在外部类的成员位置,并且有static修饰
<a name="8b07f74f"></a>
##### 静态内部类细节
1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
1. 可以添加任意访问修饰符(public.protected、默认、private),因为它的地位就是一个成员。
1. 作用域:同其他的成员,为整个类体。
1. 静态内部类---访问---->外部类(比如:静态成员)[访问方式:直接访问所有静态成员]
1. 外部类---访问------>静态内部类访问方式:创建对象,再访问
1. 外部其他类---访问----->静态内部类<br />① 因为静态内部类,是可以通过类名直接访问(前提是满足访问权限) <br />② 编写一个方法,可以返回静态内部类的对象实例
Outer.Inner inner = new Outer.Inner(); inner. …;
```java
Outer outer = nre Outer();
outer.getInner();
inner. ...;
//外部类中的方法
public Inner getInner(){
retrun new Inner();
}
- 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访
问外部类的成员,则可以使用(外部类名.成员)去访问。
枚举
自定义枚举类
1)构造器私有化
2)本类内部创建一组对象
3)对外暴露对象(通过为对象添加public final static修饰符)
4)可以提供get方法,但是不要提供set
package com.wexiao.enum_;
/**
* @author 微咲
* @version 1.0
*/
public class Enumeration01 {
public static void main(String[] args) {
// Season spring = new Season("春天", "温暖");
// Season summer = new Season("夏天", "炎热");
// Season autumn = new Season("秋天", "凉爽");
// Season winter = new Season("冬天", "寒冷");
System.out.println(Season.SPRING);
System.out.println(Season.SUMMER);
System.out.println(Season.AUTUMN);
System.out.println(Season.WINNER);
}
}
//演示自定义枚举类
class Season{
private String name;
private String desc;
public static final Season SPRING = new Season("春天", "温暖");
public static final Season SUMMER = new Season("春天", "温暖");
public static final Season AUTUMN = new Season("春天", "温暖");
public static final Season WINNER = new Season("春天", "温暖");
//1. 将构造器私有化,防止new
//2. 去掉set相关方法,防止属性被修改
//3. 在Season内部,直接创建固定的对象。
//4. 优化:可以加入 final 修饰符
public Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
enum枚举类
package com.wexiao.enum_;
/**
* @author 微咲
* @version 1.0
*/
public class Enumeration01 {
public static void main(String[] args) {
System.out.println(Season.SPRING);
System.out.println(Season.SUMMER);
System.out.println(Season.AUTUMN);
System.out.println(Season.WINNER);
}
}
//演示枚举类
enum Season{
//如果使用了 enum 来实现枚举类
//1. 使用关键字 enum 替代 class
//2. public static final Season SPRING = new Season("春天", "温暖") 直接使用
// SPRING("春天", "温暖") 解读 常量名(实参列表)
//3. 如果有多个常量(对象), 使用 ,号间隔即可
//4. 如果使用 enum 来实现枚举,要求将定义常量对象,写在前面
//5. 如果我们使用的是无参构造器,创建常量对象,则可以省略 ()
SPRING("春天", "温暖").
SUMMER("春天", "温暖").
AUTUMN("春天", "温暖").
WINNER("春天", "温暖");
private String name;
private String desc;
public Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
enum枚举类细节
- 当我们使用enum关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类.
- 传统的public static final Season SPRING = new Season(“春天”,””温暖”);简化成 SPRING(“春天”,”温暖”),这里必须知道,它调用的是哪个构造器.
- 如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略
- 当有多个枚举对象时,使用,间隔,最后有一个分号结尾
- 枚举对象必须放在枚举类的行首.
enum Gender{ //对的调用默认的无参构造器
BOY,GIRL;
}
enum Gender{ //父类 Enum 的 toString
BOY,GIRL;
}
main{
Gender boy = Gerder.BOY;
Gender boy2 = Gerder.BOY;
System.out.println(boy); //输出BOY,本质就是调用Gender 的父类Enum的 ToString
System.out.println(boy2 == boy); //True
}
enum常用方法
package com.wexiao.enum_;
/**
* @author 微咲
* @version 1.0
*/
public class EnumMethod {
public static void main(String[] args) {
//使用Season 枚举类,来演示各种方法
Season2 autumn = Season2.AUTUMN;
//输出枚举对象的名字
System.out.println(autumn.name());
//ordinal() 输出的是该枚举对象的次序 从0开始
System.out.println(autumn.ordinal()); // AUTUMN 枚举对象是第三个 所以输出2
//values() 返回含有定义的所有枚举对象数组
Season2[] values = Season2.values();
System.out.println("===遍历取出枚举对象===");
for (Season2 value : values) { //增强for循环,依次从values数组中取出元素-》Season2 value
System.out.println(value);
}
//valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常
//执行流程
//1. 根据你输入的 "AUTUMN" 到 Season2 的枚举对象去查找
//2. 如果找到了,就返回,如果没有找到,就报错
Season2 autumn1 = Season2.valueOf("AUTUMN");
System.out.println("autumn1 = " + autumn1);
//compareTo:比较两个枚举常量,比较的就是编号
//结果 self.ordinal - other.ordinal
System.out.println(Season2.SPRING.compareTo(Season2.WINTER)); // 0 - 3 = -3
}
}
enum 实现接口
- 使用enum关键字后,就不能再继承其它类了,因为enum会隐式组承Enum,而Java是单继承机制。
- 枚举类和普通类一样,可以实现接口,如下形式。
enum 类名 implements 接口1,接口2{}
枚举练习
package com.wexiao.homework;
/**
* @author 微咲
* @version 1.0
*/
public class Homework08 {
public static void main(String[] args) {
//演示一下枚举值的switch
Color color = Color.BLACK;
color.show();
switch (color) {
case RED:
System.out.println("匹配到红色");
break;
case BLUE:
System.out.println("匹配到蓝色");
break;
case GREEN:
System.out.println("匹配到绿色");
break;
case BLACK:
System.out.println("匹配到黑色");
break;
case YELLOW:
System.out.println("匹配到黄色");
break;
default:
break;
}
}
}
interface IMyInterface {
public void show();
}
enum Color implements IMyInterface {
RED(255, 0, 0), BLUE(0, 0, 255), BLACK(0, 0, 0),
YELLOW(255, 255, 0), GREEN(0, 255, 0);
private int redValue;
private int greenValue;
private int blueValue;
Color(int redValue, int greenValue, int blueValue) {
this.redValue = redValue;
this.greenValue = greenValue;
this.blueValue = blueValue;
}
@Override
public void show() {
System.out.println("属性值为" + redValue + "," + greenValue + "," + blueValue);
}
}
JDK注解
JDK内置的基本注解类型
注解介绍
- 注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。
- 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在 JavaEE 中注解占据了更重要的角 色,例如用来配置应用程序的任何切面,代替 java EE 旧版中所遗留的繁冗代码和 XML 配置等。
使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于
修饰它支持的程序元素
三个基本的 Annotation:
一、@Override
Override: 限定某个方法,是重写父类方法, 该注解只能用于方法
//1. @Override 注解放在 fly 方法上,表示子类的 fly 方法时重写了父类的 fly
//2. 这里如果没有写 @Override 还是重写了父类 fly
//3. 如果你写了@Override 注解,编译器就会去检查该方法是否真的重写了父类的
// 方法,如果的确重写了,则编译通过,如果没有构成重写,则编译错误
//解读: 如果发现 @interface 表示一个 注解类
/*
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
*/
@Override //说明
public void fly() {
System.out.println("Son fly....");
}
@Override使用说明
1.@Override表示指定重写父类的方法(从编译层面验证),如果父类没有fy方法,则会报错
2.如果不写@Override注解,而父类仍有public void fly(){},仍然构成重写
3.@Override只能修饰方法,不能修饰其它类,包,属性等等
4.查看@Override注解源码为 @Target(ElementType.METHOD),说明只能修饰方法
5.@Target是修饰注解的注解,称为元注解
二、@Deprecated
@Deprecated: 用于表示某个程序元素(类, 方法等)已过时
// @Deprecated 使用说明
//1. @Deprecated 修饰某个元素(此时元素名称上有中划线), 表示该元素已经过时
//2. 即不在推荐使用,但是仍然可以使用
//3. 查看 @Deprecated 注解类的源码
/*
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
*/
//4. 可以修饰方法,类,字段, 包, 参数 等等
//5. @Deprecated 可以做版本升级过渡使用
@Deprecated 使用说明
- @Deprecated 修饰某个元素(此时元素名称上有中划线), 表示该元素已经过时
- 即不在推荐使用,但是仍然可以使用
- 可以修饰方法,类,字段, 包, 参数 等等
- @Deprecated 可以做版本升级过渡使用
三、@SuppressWarnings
@SuppressWarnings: 抑制编译器警告
@SuppressWarnings使用
package com.wexiao.annotation_;
import java.util.ArrayList;
import java.util.List;
/**
* @author 微咲
* @version 1.0
*/
public class SuppressWarnings_ {
// 当我们不希望看到这些警告的时候,可以使用 SuppressWarnings注解来抑制警告信息
// 在{""}中,可以写入希望抑制(不显示)的类型
// 可以指定~~....
// @SuppressWarnings 作用范围和放置位置有关(成员、方法、类)
// @SuppressWarnings 源码:
/*
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
*/
//1.放置位置:TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE
//2.该注解类有数组 String[] value(), 所以可以设置一个数组比如:@SuppressWarnings({"rawtypes", "unchecked", "unused"})
@SuppressWarnings({"rawtypes", "unchecked", "unused"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
int i;
System.out.println(list.get(1));
}
}
@SuppressWarning 中的属性介绍以及属性说明
@SuppressWarning 中的属性介绍以及属性说明
all,抑制所有警告
boxing,抑制与封装/拆装作业相关的警告
cast,抑制与强制转型作业相关的警告
dep-ann,抑制与淘汰注释相关的警告
deprecation,抑制与淘汰的相关警告
fallthrough,抑制与switch陈述式中遗漏break相关的警告
finally,抑制与未传回finally区块相关的警告
hiding,抑制与隐藏变数的区域变数相关的警告
incomplete-switch,抑制与switch陈述式(enum case)中遗漏项目相关的警告
javadoc,抑制与javadoc相关的警告
nls,抑制与非nls字串文字相关的警告
null,抑制与空值分析相关的警告
rawtypes,抑制与使用raw类型相关的警告
resource,抑制与使用Closeable类型的资源相关的警告
restriction,抑制与使用不建议或禁止参照相关的警告
serial,抑制与可序列化的类别遗漏serialVersionUID栏位相关的警告
static-access,抑制与静态存取不正确相关的警告
static-method,抑制与可能宣告为static的方法相关的警告
super,抑制与置换方法相关但不含super呼叫的警告
synthetic-access,抑制与内部类别的存取未最佳化相关的警告
sync-override,抑制因为置换同步方法而遗漏同步化的警告
unchecked,抑制与未检查的作业相关的警告
unqualified-field-access,抑制与栏位存取不合格相关的警告
unused,抑制与未用的程式码及停用的程式码相关的警告
JDK 的元 Annotation(元注解, 了解)
元注解的基本介绍
JDK 的**元 Annotation 用于修饰其他 Annotation 元注解.**
元注解的种类 (使用不多,了解, 不用深入研究)
- Retention //指定注解的作用范围(时间),三种 SOURCE,CLASS,RUNTIME
- Target // 指定注解可以在哪些地方使用
- Documented //指定该注解是否会在 javadoc 体现
- Inherited //子类会继承父类注解
@Retention 注解
@Retention 注解说明
只能用于修饰一个 Annotation 定义, 用于指定该 Annotation **可以保留多长时间,** [@Rentention ](/Rentention ) 包含一个 RetentionPolicy 类型的成员变量, 使用 [@Rentention ](/Rentention ) 时必须为该 value 成员变量指定值:
@Retention 的三种值
- RetentionPolicy.SOURCE: 编译器使用后,直接丢弃这种策略的注释
- RetentionPolicy.CLASS: 编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
- RetentionPolicy.RUNTIME:编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注解. 程序可以 通过反射获取该注解
@Target 注解
@Target 注解基本说明
用于修饰 Annotation定义,用于指定被修饰的Annotation能用于修饰哪些程序元素.[@Target ](/Target )
也包含一个名为value的成员变量。
@Target 的值类型
**Target(value={TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_USE});**
@Documented 注解
@Documented 注解基本说明
@Documented:用于**指定被该元 Annotation修饰的Annotation类将被javadoc工具提取成文档,即在生成文档时,可以看到该注解。**
@Documented值
说明:定义为Documented的注解必须设置Retention值为RUNTIME.
@Inherited 注解
被它修饰的 Annotation将具有继承性.如果某个类使用了被**@Inherited修饰的Annotation,则**
其子类将自动具有该注解
异常(Exception)
异常快速入门
package com.wexiao.exception_;
/**
* @author 微咲
* @version 1.0
*/
public class Exception01 {
public static void main(String[] args) {
int num1 = 10;
int num2 = 0;
//int res = num1 / num2; //在这里成程序会抛出异常 ArithmeticException,抛出异常后,程序崩溃。
//将代码选中,输入快捷键 ctrl+alt+t
try {
int res = num1 / num2;
} catch (Exception e) {
//e.printStackTrace();
System.out.println("出现一样的原因是:" + e.getMessage());//输出异常信息
}
System.out.println("程序继续运行...");
}
}
异常基本概念
Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误
不是异常)
异常事件两大类
执行过程中所发生的异常事件为两大类
- Error(错误):Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比
如:StackOverflowError[栈溢出]和OOM(out of memory). Error是严重错误,程序会崩溃。 - Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行
处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等,
Exception分为两大类:
运行时异常[程序运行时,发生的异常]和编译时异常[编程时,编译器检查出的异常]。
异常体系图
ctrl + alt + b 查看子类
小结
- 异常分为两大类,运行时异常和编译时异常.
- 运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异
常。java.lang.RuntimeException类及它的子类都是运行时异常 - 对于运行时异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运
行效率产生影响 - 编译时异常,是编译器要求必须处置的异常。
常见运行时异常
NullPointerException 空指针异常
当应用程序试图在需要对象的地方使用null时,抛出该异常
public class NullPointerException_ {
public static void main(String[] args) {
String name = null;
System.out.println(name.length()); //
}
}
ArithmeticException 数学运算异常
当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例
ArrayIndexOutOfBoundsException 数组下标越界异常
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引
public class ArrayIndexOutOfBoundsException_ {
public static void main(String[] args) {
int[] arr = {1,2,4};
for (int i = 0; i <= arr.length; i++) { //数组下标越界
System.out.println(arr[i]);
}
}
}
ClassCastException 类型转换异常
当试图将对象强制转换为不是实例的子类时,抛出该异常。
public class ClassCastException_ {
public static void main(String[] args) {
A b = new B(); //向上转型
B b2 = (B)b;//向下转型,这里是 OK
C c2 = (C)b;//这里抛出 ClassCastException
}
}
class A {}
class B extends A {}
class C extends A {}
NumberFormatException 数字格式不正确异常[]
当应用程序试图将字符串转换成一种数值类型,**但该字符串不能转换为适当格式时,抛出该异**
常 => 使用异常我们 可以确保输入是满足条件数字.
public class NumberFormatException_ {
public static void main(String[] args) {
String name = "韩顺平教育";
//将 String 转成 int
int num = Integer.parseInt(name);//抛出 NumberFormatException
System.out.println(num);//1234
}
}
常见的编译异常
编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。
SQLException//操作数据库时,查询表可能发生异常
IOException//操作文件时,发生的异常
FileNotFoundException//当操作一个不存在的文件时,发生异常
ClassNotFoundException//加载类,而该类不存在时,异常
EOFException//操作文件,到文件未尾,发生异常
IllegalArguementException//参数异常
异常处理
异常处理介绍
异常处理就是当异常发生时,对异常处理的方式。
异常处理的方式
try-catch-finally
程序员在代码中捕获发生的异常,自行处理
throws
将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
try-catch方式处理异常
Java提供try和catch块来处理异常。**try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。**可以根据需要在程序中有多个try...catch块。
**快捷键:将代码选中,输入快捷键 ctrl+alt+t**
try-catch基本语法
try{
//可疑代码
//将异常生成对应的异常对象,传递给catch块
}catch(异常){
//对异常的处理
}
try-catch异常处理 - 细节和注意事项
- 如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块.
- 如果异常没有发生,则顺序执行try的代码块,不会进入到catch.
- 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)
则使用如下代码- finally {} - 可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,比如(Exception在后,NullPointerException在前),如果发生异常,只会匹配一个catch
- 可以进行try-finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉/退出。
应用场景:就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑
try-catch练习
finally{}是必须执行的。
如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止
package com.wexiao.exception_;
import java.util.Scanner;
/**
* @author 微咲
* @version 1.0
*/
public class TryCatchExercise04 {
public static void main(String[] args) {
//如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止
boolean flag = true;
Scanner scanner = new Scanner(System.in);
String inputStr = "";
int num = 0;
do {
System.out.println("请输入一个整数:");
inputStr = scanner.next();
try {
num = Integer.parseInt(inputStr);
flag = false;
} catch (Exception e) {
System.out.println("你输入的不是一个整数!");
}
} while (flag);
System.out.println("你输入的值是:" + num);
}
}
thorws方式处理异常
thorws基本介绍
- 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此
方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责
处理。 - 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产
生的异常类型,也可以是它的父类。public void f2() throws Exception{ //FileNotFoundException,NullPointerException,...
FileInputStream fis = new FileInputStream("d://aa.txt");
}
thorws异常处理 - 细节和注意事项
- 对于编译异常,程序中必须处理,比如try-catch或者throws
- 对于运行时异常,程序中如果没有处理,默认就是throws的方式处理[举例]
- 子类重写父类的方法时,对抛出异常的规定:
子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型 - 在throws过程中,如果有方法 try-catch,就相当于处理异常,就可以不必throws
package com.hspedu.throws_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* @author 韩顺平
* @version 1.0
*/
public class ThrowsDetail {
public static void main(String[] args) {
f2();
}
public static void f2() /*throws ArithmeticException*/ {
//1.对于编译异常,程序中必须处理,比如 try-catch 或者 throws
//2.对于运行时异常,程序中如果没有处理,默认就是 throws 的方式处理
int n1 = 10;
int n2 = 0;
double res = n1 / n2;
}
public static void f1() throws FileNotFoundException {
//这里大家思考问题 调用 f3() 报错
//老韩解读
//1. 因为 f3() 方法抛出的是一个编译异常
//2. 即这时,就要 f1() 必须处理这个编译异常
//3. 在 f1() 中,要么 try-catch-finally ,或者继续 throws 这个编译异常
f3(); // 抛出异常
}
public static void f3() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("d://aa.txt");
}
public static void f4() {
//老韩解读:
//1. 在 f4()中调用方法 f5() 是 OK
//2. 原因是 f5() 抛出的是运行异常
//3. 而 java 中,并不要求程序员显示处理,因为有默认处理机制
f5();
}
public static void f5() throws ArithmeticException {
}
}
class Father { //父类
public void method() throws RuntimeException {
}
}
class Son extends Father {//子类
//3. 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,
// 所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
//4. 在 throws 过程中,如果有方法 try-catch , 就相当于处理异常,就可以不必 throws
@Override
public void method() throws ArithmeticException {
}
}
自定义异常
自定义异常基本概念
当程序中出现了某些“错误”,但该错误信息并没有在Throwable子类中描述处理,这个时候可
以自己设计异常类,用于描述该错误信息。
自定义异常的步骤
- 定义类:自定义异常类名(程序员自己写)继承Exception或RuntimeException
- 如果继承Exception,属于编译异常
- 如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)
package com.wexiao.customexception_;
/**
* @author 微咲
* @version 1.0
*/
public class CustomException {
public static void main(String[] args) {
int age = 20;
if (!(age>=18&&age<=120)){
throw new AgeException("年龄要在18-120之间!");
}
System.out.println("成功~");
}
}
//自定义异常
// 一般情况下,我们自定义异常继承 RuntimeException
// 即把自定义异常做成运行异常,好处:我们可以使用默认的处理机制。
class AgeException extends RuntimeException{
//通过构造器设置自定义错误信息
public AgeException(String message) {
super(message);
}
}
throws 和 throw 区别
练习:
异常Homework
package com.wexiao.homework;
import java.util.Scanner;
/**
* @author 微咲
* @version 1.0
*/
public class Homework02 {
public static void main(String[] args) {
// 输入用户名、密码、邮箱,如果信息录入正确,则提示注册成功,否则生成异常对象要求:
// (1)用户名长度为2或3或4
// (2)密码的长度为6,要求全是数字isDigital(3)邮箱中包含@和.并且@在.的前面
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String userName = scanner.next();
System.out.println("请输入密码:");
String pwd = scanner.next();
System.out.println("请输入邮箱账号:");
String mail = scanner.next();
try {
userRegister(userName, pwd, mail);
System.out.println("注册成功~");
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
public static void userRegister(String userName, String pwd, String mail) {
//再加入一些校验
if(!(userName != null && pwd != null && mail != null)) {
throw new RuntimeException("参数不能为null!");
}
if (!(userName.length() >= 2 && userName.length() <= 4 )) {
throw new RuntimeException("用户名应该为2,3,4位!");
}
//老师:
// int i = email.indexOf('@');
// int j = email.indexOf('.');
// if (!(i > 0 && j > i)) {
// throw new RuntimeException("邮箱中包含@和. 并且@在.的前面");
// }
if (mail.contains("@") && mail.contains(".") && (mail.indexOf("@") < mail.indexOf("."))){
throw new RuntimeException("邮箱中必须包含@和.并且@在.的前面!");
}
if (pwd.length() == 6 && isDigital(pwd)){
throw new RuntimeException("密码的长度为6,要求全是数字!");
}
System.out.println("注册成功");
System.out.println("userName = " + userName);
System.out.println("password = " + pwd);
System.out.println("mail = " + mail);
}
//判断字符串中是否都为数字字符
public static boolean isDigital(String str) {
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (chars[i] < '0' || chars[i] > '9') {
return false;
}
}
return true;
}
}
常用类
八大Wrapper(包装)类
Boolean,Character,Byte,Short,Integer,Long,Float,Double
包装类和基本数据的转换
package com.wexiao.wrapper_;
/**
* @author 微咲
* @version 1.0
*/
public class Integer01 {
public static void main(String[] args) {
//演示 int <---> Integer 的装箱和拆箱
//jdk5前 是手动装箱和拆箱
//手动装箱 int -> Integer
int n1 = 100;
Integer integer = new Integer(n1);
Integer integer1 = Integer.valueOf(n1);
//手动拆箱
//Integer -> int
int n2 = integer.intValue();
int n3 = 200;
//自动装箱 int -> Integer
Integer integer2 = n3; //底层使用的是 Integer.valueOf(n2);
//自动拆箱 Integer -> int
int n4 =integer2; //底层使用的是integer2.intValue();
}
}
包装类型和String类型之间的相互转换
package com.wexiao.wrapper_;
/**
* @author 微咲
* @version 1.0
*/
public class WrapperVSString {
public static void main(String[] args) {
//包装类 -> String
Integer i = 100;//自动装箱
//方式1
String str1 = i+"";
//方式2
String str2 = i.toString();
//方式3
String str3 = String.valueOf(i);
//String -> 包装类
//方式1
String str4 = "12345";
Integer i2 = Integer.parseInt(str4); //自动装箱
//方式2
Integer i3 = new Integer(str4); //构造器
}
}
包装类常用方法
常用方法:
包装类练习
valueOf源码:
//1. 如果 i 在 IntegerCache.low(-128)~IntegerCache.high(127),就直接从数组返回
//2. 如果不在 -128~127,就直接 new Integer(i)
String 类
String类基础介绍
- String对象用于保存字符串,也就是一组字符序列
- 字符串常量对象是用双引号括起的字符序列。
- 字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节。
- 构造器
- String 类实现了接口 Serializable [ String 可以串行化:可以在网络上传输 ]
- String 类实现了接口 Compareable [ String 对象可以比较大小 ]
- String 类是 final 类,不能被其他类继承
- String 中属性
**private final char value[];**
用于存放字符串内容。
注意:value 是一个final 类型,(地址)不可修改。
创建String对象的两种方式
方式一:直接赋值 String s = “wexiao”;
先从常量池查看是否有"wexiao”数据空间,如果有,直接指向;如果没有则重新创建,然后指向。s最终指向的是常量池的空间地址.
方拾二:调用构造器 String s = new String(“wexiao”);
先在堆中创建空间,里面维护了value属性,指向常量池的hsp空间。如果常量池没有"wexiao",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。
String 基本练习
字符串的特性
说明
- String是一个final类,代表不可变的字符序列
- 字符串是不可变的。一个字符串对象一旦被分配,其内容是不可变的.
字符串特性练习
package com.wexiao.string_;
/**
* @author 微咲
* @version 1.0
*/
public class StringExercise08 {
public static void main(String[] args) {
String a = "hello";//创建a对象
String b = "abc";//创建b对象
//1. 先创建一个 StringBuilder sb = new StringBuilder();
//2. 执行 sb.append("hello");
//3. 执行 sb.append("abc");
//4. 执行 sb.toString();
// 最后其实是c指向堆中的对象(String) value[] -> 池中 "helloabc"
String c = a + b;//创建了几个对象?画出内存图?
String d = "helloabc";
System.out.println(c == d); //false
String e = "hello" + "abc"; //e指向常量池
System.out.println(d == e); //true
}
}
!!!!!! 最后其实是c指向堆中的对象(String) value[] -> 池中 “helloabc”
☆☆☆☆ 方法调用在栈中直接指向常量池,, 结束就断开了,,,
调用change,str一开始指向的是 堆中的str,,可是 给str 赋值了”java”所以方法中的str就指向了常量池中,,
String 类常用方法
String类是保存字符串常量的。**每次更新都需要重新开辟空间,效率较低**,因此java设计者还提
供了StringBuilder和 StringBuffer 来增强String的功能,并提高效率。
常用方法:
package com.wexiao.string_;
/**
* @author 微咲
* @version 1.0
*/
public class StringMethod01 {
public static void main(String[] args) {
//1. equals 前面已经讲过了. 比较内容是否相同,区分大小写
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));//
// 2.equalsIgnoreCase 忽略大小写的判断内容是否相等
String username = "johN";
if ("john".equalsIgnoreCase(username)) {
System.out.println("Success!");
} else {
System.out.println("Failure!");
}
// 3.length 获取字符的个数,字符串的长度
System.out.println("wexiao".length());
// 4.indexOf 获取字符(字符串)在字符串对象中第一次出现的索引,索引从 0 开始,如果找不到,返回-1
String s1 = "wer@terwe@g";
int index = s1.indexOf('@');
System.out.println(index);// 3
System.out.println("weIndex=" + s1.indexOf("we"));//0
// 5.lastIndexOf 获取字符(字符串)在字符串中最后一次出现的索引,索引从 0 开始,如果找不到,返回-1
s1 = "wer@terwe@g@";
index = s1.lastIndexOf('@');
System.out.println(index);//11
System.out.println("ter 的位置=" + s1.lastIndexOf("ter"));//4
// 6.substring 截取指定范围的子串
String name = "hello,张三";
//下面 name.substring(6) 从索引 6 开始截取后面所有的内容
System.out.println(name.substring(6));//截取后面的字符
//name.substring(0,5)表示从索引 0 开始截取,截取到索引 5-1=4 位置
System.out.println(name.substring(0, 5));//hello
}
}
package com.wexiao.string_;
/**
* @author 微咲
* @version 1.0
*/
public class StringMethod02 {
public static void main(String[] args) {
// 1.toUpperCase 转换成大写
String s = "heLLo";
System.out.println(s.toUpperCase());//HELLO
// 2.toLowerCase
System.out.println(s.toLowerCase());//hello
// 3.concat 拼接字符串
String s1 = "宝玉";
s1 = s1.concat("林黛玉").concat("薛宝钗").concat("together");
System.out.println(s1);//宝玉林黛玉薛宝钗 together
// 4.replace 替换字符串中的字符
s1 = "宝玉 and 林黛玉 林黛玉 林黛玉";
//在 s1 中,将 所有的 林黛玉 替换成薛宝钗
// 老韩解读: s1.replace() 方法执行后,返回的结果才是替换过的. // 注意对 s1 没有任何影响
String s11 = s1.replace("宝玉", "jack");
System.out.println(s1);//宝玉 and 林黛玉 林黛玉 林黛玉
System.out.println(s11);//jack and 林黛玉 林黛玉 林黛玉
// 5.split 分割字符串, 对于某些分割字符,我们需要 转义比如 | \\等
String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
//老韩解读:
// 1. 以 , 为标准对 poem 进行分割 , 返回一个数组
// 2. 在对字符串进行分割时,如果有特殊字符,需要加入 转义符 \
String[] split = poem.split(",");
poem = "E:\\aaa\\bbb";
split = poem.split("\\\\");
System.out.println("==分割后内容===");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}
// 6.toCharArray 转换成字符数组
s = "happy";
char[] chs = s.toCharArray();
for (int i = 0; i < chs.length; i++) {
System.out.println(chs[i]);
}
// 7.compareTo 比较两个字符串的大小,如果前者大,
// 则返回正数,后者大,则返回负数,如果相等,返回 0
// 老韩解读
// (1) 如果长度相同,并且每个字符也相同,就返回 0
// (2) 如果长度相同或者不相同,但是在进行比较时,可以区分大小
// 就返回 if (c1 != c2) {
// return c1 - c2;
// }
// (3) 如果前面的部分都相同,就返回 str1.len - str2.len
String a = "jcck";// len = 3
String b = "jack";// len = 4
System.out.println(a.compareTo(b)); // 返回值是 'c' - 'a' = 2 的值
// 8.format 格式字符串
/* 占位符有:
* %s 字符串 %c 字符 %d 整型 %.2f 浮点型
*
*/
String name = "john";
int age = 10;
double score = 56.857;
char gender = '男';
//将所有的信息都拼接在一个字符串.
String info = "我的姓名是" + name + "年龄是" + age + ",成绩是" + score + "性别是" + gender + "。希望大家喜欢我! ";
System.out.println(info);
//老韩解读
//1. %s , %d , %.2f %c 称为占位符
//2. 这些占位符由后面变量来替换
//3. %s 表示后面由 字符串来替换
//4. %d 是整数来替换
//5. %.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理
//6. %c 使用 char 类型来替换
//String info2 = String.format("我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!", name, age, score, gender);
String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!";
String info2 = String.format(formatStr, name, age, score, gender);
System.out.println("info2=" + info2);
}
}
StringBuffer 类
StringBuffer基本介绍
java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删很多方法与String相
同,但StringBuffer是可变长度的。
StringBuffer是一个容器。
- StringBuffer 的直接父类 是 AbstractStringBuilder
- StringBuffer 实现了 Serializable, 即 StringBuffer 的对象可以串行化
- 在父类中 AbstractStringBuilder 有属性 char[] value,不是 final // 该 value 数组存放 字符串
内容,引出存放在堆中的 - StringBuffer 是一个 final 类,不能被继承
- 因为 StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除) // 不用每次都更换地址
(即不是每次创建新对象), 所以效率高于 String
String vs StringBuffer
- String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效
率较低//private final char value[]; - StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更
新内容,不用每次更新地址,效率较高//char[] value;//这个放在堆.
StringBuffer的构造器
//创建一个大小为 16 的char[] 数组,用于存放字符内容
StringBuffer stringBuffer = new StringBuffer();
//通过构造器指定char[]大小
StringBuffer stringBuffer = new StringBuffer(100);
//通过给一个String创建StringBuffer,char[] 大小就是str.length() + 16
StringBuffer stringBuffer = new StringBuffer("hello");
StringBuffer 与 String 相互转换
package com.wexiao.stringbuffer_;
/**
* @author 微咲
* @version 1.0
*/
public class StringAndStringBuffer {
public static void main(String[] args) {
//看 String——>StringBuffer
String str = "hello tom";
//方式 1 使用构造器
//注意: 返回的才是 StringBuffer 对象,对 str 本身没有影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式 2 使用的是 append 方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
//看看 StringBuffer ->String
StringBuffer stringBuffer3 = new StringBuffer("韩顺平教育");
//方式 1 使用 StringBuffer 提供的 toString 方法
String s = stringBuffer3.toString();
//方式 2: 使用构造器来搞定
String s1 = new String(stringBuffer3);
}
}
StringBuffer 常见方法
…
package com.wexiao.stringbuffer_;
/**
* @author 微咲
* @version 1.0
*/
public class StringBufferMethod {
public static void main(String[] args) {
StringBuffer s = new StringBuffer("hello");
//增
s.append(',');// "hello,"
s.append("张三丰");//"hello,张三丰"
s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏 100true10.5" System.out.println(s);//"hello,张三丰赵敏 100true10.5"
//删
/*
* 删除索引为>=start && <end 处的字符
* 解读: 删除 11~14 的字符 [11, 14)
*/
s.delete(11, 14);
System.out.println(s);//"hello,张三丰赵敏true10.5"
//改
//老韩解读,使用 周芷若 替换 索引 9-11 的字符 [9,11)
s.replace(9, 11, "周芷若");
System.out.println(s);//"hello,张三丰周芷若true10.5"
//查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
int indexOf = s.indexOf("张三丰");
System.out.println(indexOf);//6
//插
//老韩解读,在索引为 9 的位置插入 "赵敏",原来索引为 9 的内容自动后移
s.insert(9, "赵敏");
System.out.println(s);//"hello,张三丰赵敏周芷若true10.5"
//长度
System.out.println(s.length());//22
System.out.println(s);
}
}
StringBuffer 练习
appendBull() 源码:
StringBuffer(String str) StringBuffer构造器源码:
package com.hspedu.stringbuffer_;
import java.util.Scanner;
/**
* @author 韩顺平
* @version 1.0
*/
public class StringBufferExercise02 {
public static void main(String[] args) {
/*
输入商品名称和商品价格,要求打印效果示例, 使用前面学习的方法完成:
商品名 商品价格
手机 123,564.59 //比如 价格 3,456,789.88
要求:价格的小数点前面每三位用逗号隔开, 在输出。
思路分析
1. 定义一个 Scanner 对象,接收用户输入的 价格(String)
2. 希望使用到 StringBuffer 的 insert ,需要将 String 转成 StringBuffer
3. 然后使用相关方法进行字符串的处理
代码实现
*/
//new Scanner(System.in)
String price = "8123564.59";
StringBuffer sb = new StringBuffer(price);
//先完成一个最简单的实现 123,564.59
//找到小数点的索引,然后在该位置的前 3 位,插入,即可
// int i = sb.lastIndexOf(".");
// sb = sb.insert(i - 3, ",");
//上面的两步需要做一个循环处理,才是正确的
for (int i = sb.lastIndexOf(".") - 3; i > 0; i -= 3) {
sb = sb.insert(i, ",");
}
System.out.println(sb);//8,123,564.59
}
}
StringBuilder 类
StringBuilder基本介绍
- 一个可变的字符序列。此类提供一个与StringBuffer兼容的API,但不保证同步(StringBuilder 不是
线程安全)。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用
的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。 - 在 StringBuilder上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。
StringBuild常用方法
**StringBuilder 和 StringBuffer均代表可变的字符序列,方法是一样的,所以使用和StringBuffer一样**
StringBuilder特性
//1. StringBuilder 继承 AbstractStringBuilder 类
//2. 实现了 Serializable ,说明 StringBuilder 对象是可以串行化(对象可以网络传输,可以保存到文件)
//3. StringBuilder 是 final 类, 不能被继承
//4. StringBuilder 对象字符序列仍然是存放在其父类 AbstractStringBuilder 的 char[] value; // 因此,字符序列是堆中
//5. StringBuilder 的方法,没有做互斥的处理,即没有 synchronized 关键字,因此在单线程的情况下
使用
String、StringBuffer、StringBuilder的比较
- StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且方法也一样
- String: 不可变字符序列, 效率低, 但是复用率高。
- StringBuffer: 可变字符序列、效率较高(增删)、线程安全
- StringBuilder: 可变字符序列、效率最高、线程不安全
- String使用注意说明:
string s=”a”;//创建了一个字符串
s +=”b”; //实际上原来的”a”字符串对象已经丢弃了,现在又产生了一个字符串s+”b”(也就
是”ab”)。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降
低效率。如果这样的操作放到循环中,会极大影响程序的性能
=>结论:如果我们对String 做大量修改,不要使用String
String、StringBuffer、StringBuilder的效率测试
效率:StringBuilder > StringBuffer > String
package com.wexiao.stringbuilder_;
/**
* @author 微咲
* @version 1.0
*/
public class StringVsStringBufferVsStringBuilder {
public static void main(String[] args) {
long startTime = 0L;
long endTime = 0L;
StringBuffer buffer = new StringBuffer("");
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//StringBuffer 拼接 80000 次
buffer.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuffer 的执行时间:" + (endTime - startTime));
StringBuilder builder = new StringBuilder("");
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//StringBuilder 拼接 80000 次
builder.append(String.valueOf(i));
}
endTime = System.currentTimeMillis();
System.out.println("StringBuilder 的执行时间:" + (endTime - startTime));
String text = "";
startTime = System.currentTimeMillis();
for (int i = 0; i < 80000; i++) {//String 拼接 80000
text = text + i;
}
endTime = System.currentTimeMillis();
System.out.println("String 的执行时间:" + (endTime - startTime));
}
}
//StringBuffer 的执行时间:14
//StringBuilder 的执行时间:10
//String 的执行时间:3418
String、StringBuffer、StringBuilder的选择
使用的原则
- 如果字符串存在大量的修改操作,一般使用 StringBuffer或StringBuilder
- 如果字符串存在大量的修改操作, 并在单线程的情况,使用StringBuilder
- 如果字符串存在大量的修改操作,并在多线程的情况,使用 StringBuffer
- 如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等
StringBuilder的方法使用和StringBuffer一样,不再说.
Math 类
Math类基本介绍
Math类包含用于**执行基本数学运算**的方法,如初等指数、对数、平方根和三角函数。
Math 方法
package com.wexiao.math_;
/**
* @author 微咲
* @version 1.0
*/
public class MathMethod {
public static void main(String[] args) {
//看看 Math 常用的方法(静态方法)
//1.abs 绝对值
int abs = Math.abs(-9);
System.out.println(abs);//9
//2.pow 求幂
double pow = Math.pow(2, 4);//2 的 4 次方
System.out.println(pow);//16
//3.ceil 向上取整,返回>=该参数的最小整数(转成 double);
double ceil = Math.ceil(3.9);
System.out.println(ceil);//4.0
//4.floor 向下取整,返回<=该参数的最大整数(转成 double)
double floor = Math.floor(4.001);
System.out.println(floor);//4.0
//5.round 四舍五入 Math.floor(该参数+0.5)
long round = Math.round(5.51);
System.out.println(round);//6
//6.sqrt 求开方
double sqrt = Math.sqrt(9.0);
System.out.println(sqrt);//3.0 如果是-0 NaN Not A Number
//7.random 求随机数
// random 返回的是 0 <= x < 1 之间的一个随机小数
// 思考:请写出获取 a-b 之间的一个随机整数,a,b 均为整数 ,比如 a = 2, b=7
// 即返回一个数 x 2 <= x <= 7
// 老韩解读 Math.random() * (b-a) 返回的就是 0 <= 数 <= b-a
// (1) (int)(a) <= x <= (int)(a + Math.random() * (b-a +1) )
// (2) 使用具体的数给小伙伴介绍 a = 2 b = 7
// (int)(a + Math.random() * (b-a +1) ) = (int)( 2 + Math.random()*6)
// Math.random()*6 返回的是 0 <= x < 6 小数
// 2 + Math.random()*6 返回的就是 2<= x < 8 小数
// (int)(2 + Math.random()*6) = 2 <= x <= 7
// (3) 公式就是 (int)(a + Math.random() * (b-a +1) )
for (int i = 0; i < 100; i++) {
System.out.println((int) (2 + Math.random() * (7 - 2 + 1)));
}
//max , min 返回最大值和最小值
int min = Math.min(1, 9);
int max = Math.max(45, 90);
System.out.println("min=" + min);
System.out.println("max=" + max);
}
}
Arrays 类
对数组操作的类
Arrays方法
1.toString();
Integer[] integers = {1,2,3};
System.out.println(Arrays.toString(integers));
//[1,2,3]
2.sort();
默认从小到大。
Arrays.sort(arr);
定制排序:sort(T[] a , Comparator<? super T> c);
//演示 sort 方法的使用
Integer arr[] = {1, -1, 7, 0, 89};
//进行排序
//1. 可以直接使用冒泡排序 , 也可以直接使用 Arrays 提供的 sort 方法排序
//2. 因为数组是引用类型,所以通过 sort 排序后,会直接影响到 实参 arr
//3. sort 重载的,也可以通过传入一个接口 Comparator 实现定制排序
//4. 调用 定制排序 时,传入两个参数
// (1) 排序的数组 arr
// (2) 实现了 Comparator 接口的匿名内部类 , 要求实现 compare 方法
//5. 先演示效果,再解释
//6. 这里体现了接口编程的方式 , 看看源码,就明白
// 源码分析
//(1) Arrays.sort(arr, new Comparator()
//(2) 最终到 TimSort 类的 private static <T> void binarySort(T[] a, int lo, int hi, int start, // Comparator<? super T> c)()
//(3) 执行到 binarySort 方法的代码, 会根据动态绑定机制 c.compare()执行我们传入的
// 匿名内部类的 compare ()
// while (left < right) {
// int mid = (left + right) >>> 1;
// if (c.compare(pivot, a[mid]) < 0)
// right = mid;
// else
// left = mid + 1;
// }
//(4) new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// Integer i1 = (Integer) o1;
// Integer i2 = (Integer) o2;
// return i2 - i1;
// }
// }
//(5) public int compare(Object o1, Object o2) 返回的值>0 还是 <0
// 会影响整个排序结果, 这就充分体现了 接口编程+动态绑定+匿名内部类的综合使用
// 将来的底层框架和源码的使用方式,会非常常见
//Arrays.sort(arr); // 默认排序方法
//定制排序
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Integer i1 = (Integer) o1;
Integer i2 = (Integer) o2;
return i2 - i1;
}
});
System.out.println("===排序后===");
System.out.println(Arrays.toString(arr));//
sort() 自定义排序,模拟: ☆☆☆☆☆☆☆☆☆☆
https://www.bilibili.com/video/BV1fh411y7R8?p=485&spm_id_from=pageDriver
排序练习, 没理解ing
package com.wexiao.arrays_;
import java.util.Arrays;
import java.util.Comparator;
/**
* @author 微咲
* @version 1.0
*/
public class ArraysSortCustom {
public static void main(String[] args) {
int arr[] = {1, 23, 4, 342, 324, 312, 23};
bubble01(arr);
bubble02(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
int i1 = (Integer) o1;
int i2 = (Integer) o2;
return i2 - i1;
}
});
System.out.println("===排序后的情况===");
System.out.println(Arrays.toString(arr));
}
public static void bubble01(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
//结合冒泡 + 定制接口
public static void bubble02(int[] arr, Comparator c) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (c.compare(arr[j], arr[j + 1]) > 0) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
3.binarySearch();
binarySearch 通过二分法搜索法进行查找,要求必须排好序。
int index = Arrays.binarySearch(arr,3);
package com.wexiao.arrays_;
import java.util.Arrays;
/**
* @author 微咲
* @version 1.0
*/
public class ArraysMethod02 {
public static void main(String[] args) {
Integer[] integers = {1,2,3,5,6,7};
// binarySearch 通过二分搜索(二叉查找)法查找,要求数组是有序的
int index = Arrays.binarySearch(integers, 5);
System.out.println(index);
// 如果数组不存在该值, return 该值该在的位置 + 1 取负 return -(low + 1)
index = Arrays.binarySearch(integers, 4);
System.out.println(index);
}
}
4.copyOf();
数组元素的复制。
//copyOf 数组元素的复制
// 如果拷贝的长度 > arr.length 就在新数组的后面 + null
// 该方法的底层使用的是 System.arraycopy()
Integer[] newArray = Arrays.copyOf(integers,3);
System.out.println("拷贝===");
System.out.println(Arrays.toString(newArray));
5.fill();
数组元素的填充。
//fill(); 数组元素的填充
Integer[] num ={1,231,23,213,12,1};
// 使用 9999 去填充 num数组,可以理解成替换原来的元素
Arrays.fill(num,9999);
System.out.println("数组填充后====");
System.out.println(Arrays.toString(num));
6.equals();
比较两个数组元素内容是否完全一致。
//equals(); 比较两个数组元素内容是否完全一致。
boolean b = Arrays.equals(integers,newArray);
System.out.println("比较结果:" + b);
7.asList();
将一组数据转换成list。
//asList();将一组数据转换成list。
// list1 编译类型:List(接口) ; 运行类型:java.util.Arrays$ArrayList,是Arrays类的静态内部类
List<Integer> list1 = Arrays.asList(num);
List<Integer> list2 = Arrays.asList(1, 23, 45, 6, 2);
System.out.println("list1 = " + list1);
System.out.println("list1的运行类型:" + list1.getClass());
System 类
1.exit 退出当前程序 System.exit(0);
2.arraycopy 复制数组元素,比较使用底层调用,一般使用
Arrays.copyOf()完成复制数组。
int[] src={1,2,3};
int[] dest = new int[3];
System.arraycopy(src, 0 , dest , 0 , 3);
// 原数组
// * @param src the source array.
// srcPos: 从原数组的哪个索引位置开始拷贝
// * @param srcPos starting position in the source array.
// dest : 目标数组,即把源数组的数据拷贝到哪个数组
// * @param dest the destination array.
// destPos: 把原数组的数据拷贝到 目标数组的哪个索引
// * @param destPos starting position in the destination data.
// length: 从原数组拷贝多少个数据到目标数组
// * @param length the number of array elements to be copied.
3.currentTimeMillis返回当前时间距离1970-1-1 的毫秒数
long
4.gc 运行垃圾回收机制 System.gc();
BigInteger 和 BigDecimal 类
介绍
应用场景:
- Biglnteger适合保存比较大的整型
- BigDecimal适合保存精度更高的浮点型(小数)
BigInteger 和 BigDecimal 常见方法
- add 加
result = bigInteger1.add(bigInteger2); ↓↓↓↓↓↓ - subtract减
- multiply乘
- divide除
package com.hspedu.bignum;
import java.math.BigInteger;
/**
* @author 韩顺平
* @version 1.0
*/
public class BigInteger_ {
public static void main(String[] args) {
//当我们编程中,需要处理很大的整数,long 不够用
//可以使用 BigInteger 的类来搞定
// long l = 23788888899999999999999999999l;
// System.out.println("l=" + l);
BigInteger bigInteger = new BigInteger("23788888899999999999999999999");
BigInteger bigInteger2 = new BigInteger("10099999999999999999999999999999999999999999999999999999999999999999999999999999999");
System.out.println(bigInteger);
//老韩解读
//1. 在对 BigInteger 进行加减乘除的时候,需要使用对应的方法,不能直接进行 + - * /
//2. 可以创建一个 要操作的 BigInteger 然后进行相应操作
BigInteger add = bigInteger.add(bigInteger2);
System.out.println(add);//
BigInteger subtract = bigInteger.subtract(bigInteger2);
System.out.println(subtract);//减
BigInteger multiply = bigInteger.multiply(bigInteger2);
System.out.println(multiply);//乘
BigInteger divide = bigInteger.divide(bigInteger2);
System.out.println(divide);//除
}
}
在调用 divide 方法时,指定精度即可. BigDecimal.ROUND_CEILING
package com.hspedu.bignum;
import java.math.BigDecimal;
/**
* @author 韩顺平
* @version 1.0
*/
public class BigDecimal_ {
public static void main(String[] args) {
//当我们需要保存一个精度很高的数时,double 不够用
//可以是 BigDecimal
// double d = 1999.11111111111999999999999977788d;
// System.out.println(d);
BigDecimal bigDecimal = new BigDecimal("1999.11");
BigDecimal bigDecimal2 = new BigDecimal("3");
System.out.println(bigDecimal);
//老韩解读
//1. 如果对 BigDecimal 进行运算,比如加减乘除,需要使用对应的方法
//2. 创建一个需要操作的 BigDecimal 然后调用相应的方法即可
System.out.println(bigDecimal.add(bigDecimal2));
System.out.println(bigDecimal.subtract(bigDecimal2));
System.out.println(bigDecimal.multiply(bigDecimal2));
//System.out.println(bigDecimal.divide(bigDecimal2));//可能抛出异常 ArithmeticException
//在调用 divide 方法时,指定精度即可. BigDecimal.ROUND_CEILING
//如果有无限循环小数,就会保留 分子 的精度
System.out.println(bigDecimal.divide(bigDecimal2, BigDecimal.ROUND_CEILING));
}
}
Date、Calendar、LocalDate类
Date
- 第一代日期Date:精确到毫秒,代表特定的瞬间.
- SimpleDateFormat:格式和解析日期的类
SimpleDateFormat 格式化和解析日期的具体类。
它允许进行格式化(日期->文本)、解析(文本->日期)和规范化.
package com.wexiao.date_;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author 微咲
* @version 1.0
*/
public class Date01 {
public static void main(String[] args) throws ParseException {
//老韩解读
//1. 获取当前系统时间
//2. 这里的 Date 类是在 java.util 包
//3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
Date d1 = new Date(); //获取当前系统时间
System.out.println("当前日期=" + d1);
Date d2 = new Date(9234567); //通过指定毫秒数得到时间
System.out.println("d2=" + d2); //获取某个时间对应的毫秒数
//
//老韩解读
//1. 创建 SimpleDateFormat 对象,可以指定相应的格式
//2. 这里的格式使用的字母是规定好,不能乱写
SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 hh:mm:ss E");
String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
System.out.println("当前日期=" + format);
//老韩解读
//1. 可以把一个格式化的 String 转成对应的 Date
//2. 得到 Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
//3. 在把 String -> Date , 使用的 sdf 格式需要和你给的 String 的格式一样,否则会抛出转换异常
String s = "1968 年 5 月 31 日 10:20:30 星期一";
Date parse = sdf.parse(s);
System.out.println("parse=" + sdf.format(parse));
}
}
Calendar
第二代日期类,主要就是Calendar类(日历)。
public abstract class Calendar extends Object implements Serializable,Cloneable,Comparable<calendar>
Calendar类是一个抽象类,它为特定瞬间与一组诸如YEAR、MONTH、DAY_OF_MONTH、
HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)
提供了一些方法。
package com.hspedu.date_;
import java.util.Calendar;
/**
* @author 韩顺平
* @version 1.0
*/
public class Calendar_ {
public static void main(String[] args) {
//老韩解读
//1. Calendar 是一个抽象类, 并且构造器是 private
//2. 可以通过 getInstance() 来获取实例
//3. 提供大量的方法和字段提供给程序员
//4. Calendar 没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
//5. 如果我们需要按照 24 小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance(); //创建日历类对象//比较简单,自由
System.out.println("c=" + c);
//2.获取日历对象的某个日历字段
System.out.println("年:" + c.get(Calendar.YEAR));
// 这里为什么要 + 1, 因为 Calendar 返回月时候,是按照 0 开始编号
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时:" + c.get(Calendar.HOUR));
System.out.println("分钟:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));
//Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" +
c.get(Calendar.DAY_OF_MONTH) +
" " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND) );
}
}
前面两代日期类的不足分析
JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar也存在问题是:
1)可变性:像日期和时间这样的类应该是不可变的。2)偏移性:Date中的年份是从1900开始的,而月份都从0开始。
3)格式化:格式化只对Date有用,Calendar则不行。
4)此外,它们也不是线程安全的;不能处理闰秒等(每隔2天,多出1s)。
LocalDate、LocalTime、LocalDateTime
jdk8加入
LocalDate只包含日期,可以获取日期字段LocalTime只包含时间,可以获取时间字段
LocalDateTime包含日期+时间,可以获取日期和时间字段
常见方法:
1.获取当前时间
LocalDateTime ldt = LocalDateTime.now();
ldt.get...()
2.DateTimeFormatter 格式日期类 (类似SimpleDateFormat)
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
3.Instant 时间戳
类似于Date,提供了一系列和Date类转换的方式
Instant -> Date:
Date date = Date.from(instant);
Date -> Instant:
Instant instant = date.toInstant();
4.第三代日期类更多方法
- LocalDateTime类
- MonthDay类:检查重复事件
- 是否是闰年
- 增加日期的某个部分
- 使用plus方法测试增加时间的某个部分
- 使用minus方法测试查看一年前和一年后的日期
package com.hspedu.date_;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author 韩顺平
* @version 1.0
*/
public class LocalDate_ {
public static void main(String[] args) {
//第三代日期
//老韩解读
//1. 使用 now() 返回表示当前日期时间的 对象
LocalDateTime ldt = LocalDateTime.now();
//LocalDate.now();//LocalTime.now()
System.out.println(ldt);
//2. 使用 DateTimeFormatter 对象来进行格式化
// 创建 DateTimeFormatter 对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
System.out.println("格式化的日期=" + format);
System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());
System.out.println("月=" + ldt.getMonthValue());
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());
LocalDate now = LocalDate.now(); //可以获取年月日
LocalTime now2 = LocalTime.now();//获取到时分秒
//提供 plus 和 minus 方法可以对当前时间进行加或者减
//看看 890 天后,是什么时候 把 年月日-时分秒
LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890 天后=" + dateTimeFormatter.format(localDateTime));
//看看在 3456 分钟前是什么时候,把 年月日-时分秒输出
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456 分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
}
}
DateTimeFormatter:
常用类Homework
package com.wexiao.homework;
import java.util.Arrays;
/**
* @author 微咲
* @version 1.0
*/
public class Homework01 {
public static void main(String[] args) {
//将字符串中指定部分进行反转。比如将"abcdef"反转为"aedcbf"
//编写方法 public static String reverse(String str, int start , int end)搞定
String str = "abcdef";
try {
System.out.println(reverse(str, 1, 4));
}catch (Exception e){
System.out.println(e.getMessage());
}
}
public static String reverse(String str, int start, int end) {
//对输入的参数做一个验证
// //老韩重要的编程技巧分享!!!
// //(1) 写出正确的情况
// //(2) 然后取反即可
// //(3) 这样写,你的思路就不乱
if(!(str != null && start >= 0 && end > start && end < str.length())) {
throw new RuntimeException("参数不正确");
}
//自己写的:
String strTemp1 = str.substring(start, end + 1);
char[] strChar = strTemp1.toCharArray();
String strTemp2 = "";
for (int i = strChar.length - 1; i >= 0; i--) {
strTemp2 += strChar[i];
}
String newStr = str.substring(0, start) + strTemp2 + str.substring(end + 1);
return newStr;
// //老师思路:
// public static String reverse(String str, int start, int end) {
//
//
// //对输入的参数做一个验证
// //老韩重要的编程技巧分享!!!
// //(1) 写出正确的情况
// //(2) 然后取反即可
// //(3) 这样写,你的思路就不乱
// if(!(str != null && start >= 0 && end > start && end < str.length())) {
// throw new RuntimeException("参数不正确");
// }
//
// char[] chars = str.toCharArray();
// char temp = ' '; //交换辅助变量
// for (int i = start, j = end; i < j; i++, j--) {
// temp = chars[i];
// chars[i] = chars[j];
// chars[j] = temp;
// }
// //使用chars 重新构建一个String 返回即可
// return new String(chars);
//
// }
}
}
集合
数组的不足和引出集合
数组的不足:
1)长度开始时必须指定,而且一旦指定,不能更改
2)保存的必须为同一类型的元素
3)使用数组进行增加元素的示意代码一比较麻烦
集合
1)可以动态保存任意多个对象,使用比较方便!
2)提供了一系列方便的操作对象的方法:add、remove、set、get等
3)使用集合添加,删除新元素的示意代码-简洁了
集合体系图
- 集合主要是两组(单列集合,双列集合)
- Collection接口有两个重要的子接口 List Set,他们的实现子类都是单列集合
- Map接口的实现子类是双列集合,存放的 K-V
Collection接口
Collection接口实现类的特点
public interface Collection<E> extends Iterable<E>
- collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,可以存放重复的元素,有些不可以
- 有些Collection的实现类;有些是有序的(List),有些不是有序(Set)
- Collection接口没有直接的实现子类,是通过它的子接口Set 和 List来实现的
Collection接口常用方法
Collection接口常用方法,可以通过子类ArrayList、Vector、LinkedList来实现
- add:添加单个元素
- remove:删除指定元素
- contains:查找元素是否存在.
- size:获取元素个数
- isEmpty:判断是否为空
- clear:清空
- addAll:添加多个元素
- containsAll:查找多个元素是否都存在
- removeAll:删除多个元素
package com.wexiao.collection_;
import java.util.ArrayList;
import java.util.List;
/**
* @author 微咲
* @version 1.0
*/
public class CollectionMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
// add:添加单个元素
list.add("jack");
list.add(10);//list.add(new Integer(10))
list.add(true);
System.out.println("list=" + list);
// remove:删除指定元素
//list.remove(0);//删除第一个元素
list.remove(true);//指定删除某个元素
System.out.println("list=" + list);
// contains:查找元素是否存在
System.out.println(list.contains("jack"));//T
// size:获取元素个数
System.out.println(list.size());//2
// isEmpty:判断是否为空
System.out.println(list.isEmpty());//F
// clear:清空
list.clear();
System.out.println("list=" + list);
// addAll:添加多个元素
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);
// containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));//T
// removeAll:删除多个元素
list.add("聊斋");
list.removeAll(list2);
System.out.println("list=" + list);//[聊斋]
}
}
Collection接口遍历方式
方式1 - 使用Iterator(迭代器)遍历
Iterator介绍
- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
- 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了lterator接口的对象,
即可以返回一个迭代器。 - lterator的结构
- Iterator仅用于遍历集合,lterator本身并不存放对象。
迭代器执行原理
快捷指令while迭代器方法循环: itit
Iterator 接口的方法
代码实现
package com.wexiao.collection_;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* @author 微咲
* @version 1.0
*/
public class CollectionIterator {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
System.out.println("col=" + col);
//现在老师希望能够遍历 col 集合
//1. 先得到 col 对应的 迭代器
Iterator iterator = col.iterator();
//2. 使用 while 循环遍历
// while (iterator.hasNext()) {//判断是否还有数据
// //返回下一个元素,类型是 Object
// Object obj = iterator.next();
// System.out.println("obj=" + obj);
// }
//老师教大家一个快捷键,快速生成 while => itit
//显示所有的快捷键的的快捷键 ctrl + j
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//3. 当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素
// iterator.next();//NoSuchElementException
//4. 如果希望再次遍历,需要重置我们的迭代器
iterator = col.iterator();
System.out.println("===第二次遍历===");
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
}
}
class Book {
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
方式二 - for循环增强遍历
基本介绍
增强for循环,可以代替iterator迭代器
特点:增强for就是简化版的iterator,**本质就是iterator迭代器**。只能用于遍历集合或数组。
基本语法
快捷指令:元素名.for / I /元素名.iter
for(元素类型 元素名:集合名或数组名){
访问元素 //sout("元素名");
}
遍历练习
package com.hspedu.collection_;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author 韩顺平
* @version 1.0
*/
public class CollectionExercise {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Dog("小黑", 3));
list.add(new Dog("大黄", 100));
list.add(new Dog("大壮", 8));
//先使用 for 增强
for (Object dog : list) {
System.out.println("dog=" + dog);
}
//使用迭代器
System.out.println("===使用迭代器来遍历===");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object dog = iterator.next();
System.out.println("dog=" + dog);
}
}
}
/**
* 创建 3 个 Dog {name, age} 对象,放入到 ArrayList 中,赋给 List 引用
* 用迭代器和增强 for 循环两种方式来遍历
* 重写 Dog 的 toString 方法, 输出 name 和 age
*/
class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
List接口
List接口基本介绍
List接口是Collection接口的子接口
- List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
- JDK API中List接口的实现类有:
package com.wexiao.list_;
import java.util.ArrayList;
import java.util.List;
/**
* @author 微咲
* @version 1.0
*/
public class List_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复 [案例]
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
list.add("wexiao");
list.add("tom");
System.out.println("list=" + list);
//2. List 集合中的每个元素都有其对应的顺序索引,即支持索引
// 索引是从 0 开始的
System.out.println(list.get(3));//wexiao
}
}
List体系继承图
List接口常用方法
void add(int index, Object ele):在index位置插入ele元素
boolean addAll(int index,Collection eles):从index位置开始将eles中的所有元素添加进来
- Object get(int index):获取指定index位置的元素
- int indexOf(Object obj):返回obj在集合中首次出现的位置
- int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
- Object remove(int index):移除指定index位置的元素,并返回此元素
- Object set(int index, Object ele):设置指定index位置的元素为ele ,相当于是替换.
- List subList(int fromlndex, int tolndex):返回从fromlndex到tolndex位置的子集合
package com.wexiao.list_;
import java.util.ArrayList;
import java.util.List;
/**
* @author 微咲
* @version 1.0
*/
public class ListMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三丰");
// void add(int index, Object ele):在 index 位置插入 ele 元素
//在 index = 1 的位置插入一个对象
list.add(1, "微咲");
System.out.println("list=" + list);
// boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
List list2 = new ArrayList();
list2.add("jack");
list2.add("tom");
list.addAll(1, list2);
System.out.println("list=" + list);
// Object get(int index):获取指定 index 位置的元素
//说过
// int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("tom"));//2
// int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
list.add("wexiao");
System.out.println("list=" + list);
System.out.println(list.lastIndexOf("wexiao"));
// Object remove(int index):移除指定 index 位置的元素,并返回此元素
list.remove(0);
System.out.println("list=" + list);
// Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换.
list.set(1, "玛丽");
System.out.println("list=" + list);
// List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
// 注意返回的子集合 fromIndex <= subList < toIndex
List returnlist = list.subList(0, 2);
System.out.println("returnlist=" + returnlist);
}
}
List练习
package com.hspedu.list_;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class ListExercise02 {
public static void main(String[] args) {
//List list = new ArrayList();
List list = new LinkedList();
//List list = new Vector();
list.add(new Book("红楼梦", "曹雪芹", 100));
list.add(new Book("西游记", "吴承恩", 10));
list.add(new Book("水浒传", "施耐庵", 19));
list.add(new Book("三国", "罗贯中", 80));
//list.add(new Book("西游记", "吴承恩", 10));
//如何对集合进行排序
//遍历
for (Object o : list) {
System.out.println(o);
}
//冒泡排序
sort(list);
System.out.println("==排序后==");
for (Object o : list) {
System.out.println(o);
}
}
//静态方法
//价格要求是从小到大
public static void sort(List list) {
int listSize = list.size();
for (int i = 0; i < listSize - 1; i++) {
for (int j = 0; j < listSize - 1 - i; j++) {
//取出对象 Book
Book book1 = (Book) list.get(j);
Book book2 = (Book) list.get(j + 1);
if (book1.getPrice() > book2.getPrice()) {//交换
list.set(j, book2);
list.set(j + 1, book1);
}
}
}
}
}
ArrayList
ArrayList注意事项
permits all elements, including null ,ArrayList 可以加入null,并且多个
ArrayList是由数组来实现数据存储的
- ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码.在多线程情况下,不建议徳用ArrayList
ArrayList扩容的底层操作机制源码分析 ☆
- ArrayList中维护了一个Object类型的数组elementData.[看源码]
transient Object[] elementData; //transient表示瞬间,短暂的,表示该属性不会被序列号
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
- 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容
elementData为1.5倍。
Vector
Vector基本介绍
- Vetcor类的定义说明
- Vector底层也是一个对象数组, protected Object[] elementData;
- Vector是线程同步的,即线程安全, Vector类的操作方法带有 synchronized
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArraylndexOutOfBoundsException(index);
return elementData(index);
}
- 在开发中,需要线程同步安全时,考虑使用Vector
Vector扩容底层源码
package com.wexiao.list_;
import java.util.Vector;
/**
* @author 微咲
* @version 1.0
*/
public class Vector_ {
public static void main(String[] args) {
//无参构造器
//有参数的构造
Vector vector = new Vector(8);
for (int i = 0; i < 10; i++) {
vector.add(i);
}
vector.add(100);
System.out.println("vector=" + vector);
//老韩解读源码
//1. new Vector() 底层
/*
public Vector() {
this(10);
}
补充:如果是 Vector vector = new Vector(8);
走的方法:
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
2. vector.add(i)
2.1 //下面这个方法就添加数据到 vector 集合
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2.2 //确定是否需要扩容 条件 : minCapacity - elementData.length>0
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
2.3 //如果 需要的数组大小 不够用,就扩容 , 扩容的算法
//newCapacity = oldCapacity + ((capacityIncrement > 0) ?
// capacityIncrement : oldCapacity);
//就是扩容两倍. private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
*/
}
}
Vector和ArrayList的比较
LinkedList
LinkedList基本介绍
- LinkedList底层实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
LinkedList的底层操作机制
- LinkedList底层维护了一个双向链表.
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
- 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
- 模拟双向链表:
package com.wexiao.list_;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class LinkedList01 {
public static void main(String[] args) {
//模拟一个简单的双向链表
Node wexiao = new Node("wexiao");
Node jack = new Node("jack");
Node mary = new Node("mary");
//连接3个节点,形成双向链表
wexiao.next = jack;
jack.next = mary;
mary.pre = jack;
jack.pre = wexiao;
//head,last
Node first = wexiao; //双向链表的头结点
Node last = mary; //双向链表的尾结点
//遍历链表(从头到尾)
System.out.println("遍历链表(从头到尾)");
while (true) {
if (first == null) {
break;
}
//输出当前Node信息,并且下移一位
System.out.println(first);
first = first.next;
}
//遍历链表(从尾到头)
System.out.println("遍历链表(从尾到头)");
while (true) {
if (last == null) {
break;
}
//输出当前Node信息,并且上移一位
System.out.println(last);
last = last.pre;
}
//链表添加数据
//在jack 和 mary之间插入 black
Node black = new Node("black");
jack.next = black;
black.pre = jack;
black.next = mary;
mary.pre = black;
//让 first 重新指向第一位
first = wexiao; //双向链表的头结点
//遍历链表(从头到尾)
System.out.println("遍历链表(从头到尾)");
while (true) {
if (first == null) {
break;
}
//输出当前Node信息,并且下移一位
System.out.println(first);
first = first.next;
}
//删除数据 jack
wexiao.next = black;
black.pre = wexiao;
//让 first 重新指向第一位
first = wexiao; //双向链表的头结点
//遍历链表(从头到尾)
System.out.println("遍历链表(从头到尾)");
while (true) {
if (first == null) {
break;
}
//输出当前Node信息,并且下移一位
System.out.println(first);
first = first.next;
}
}
}
class Node {
public Object item; //真正存放数据
public Node next; //指向后一个结点
public Node pre; //指向前一个结点
public Node(Object name) {
this.item = name;
}
public String toString() {
return "Node name=" + item;
}
}
LinkerList的CRUD底层源码
package com.wexiao.list_;
import java.util.Iterator;
import java.util.LinkedList;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class LinkedListCRUD {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
System.out.println("linkedList = " + linkedList );
//add源码阅读:
/*
1.LinkedList linkedList = new LinkedList();
执行源码:
public LinkedList() {}
这时 linkeList 的属性 first = null last = null
2.linkedList.add(1);
执行源码:添加
public boolean add(E e) {
linkLast(e);
return true;
}
将新的节点,添加到链表的最后
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
*/
linkedList.remove();//删除第一个节点
System.out.println("linkedList = " + linkedList);
/*
linkedList.remove();//删除第一个节点
remove源码阅读:
1.执行remove()
public E remove() {
return removeFirst();
}
2.执行removeFirst()
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
3.执行unlinkFirst(f) ,将 f 指向的双向链表的第一个节点拿掉
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
*/
//修改某个节点对象
linkedList.set(1,9999); //(index,obj)
System.out.println("linkedList = " + linkedList);
//得到某个节点对象
System.out.println(linkedList.get(1)); //get(1) 第二个对象 999
//遍历
for (Object o : linkedList) {
System.out.println("o = " + o);
}
//迭代器
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()) {
Object o = iterator.next();
System.out.println("o = " + o);
}
}
}
ArrayList 和 LinkedList比较
如何选择ArrayList和LinkedList
- 如果我们改查的操作多,选择ArrayList
- 如果我们增删的操作多,选择LinkedList
- 一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
- 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList,也就是说,要根据业务来进行选择
Set接口
Set接口基本介绍
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,所以最多包含一个null
- JDK API中Set接口的实现类有:
Set体系继承图
Set接口的常用方法
和List接口一样, Set接口也是Collection的子接口,因此,常用方法和Collection接口一样.
Set接口遍历元素方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
1.可以使用迭代器
2.增强for
3.不能使用索引的方式来获取.
package com.wexiao.set_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class SetMethod {
public static void main(String[] args) {
//1. 以 Set 接口的实现类 HashSet 来讲解 Set 接口的方法
//2. set 接口的实现类的对象(Set 接口对象), 不能存放重复的元素, 可以添加一个 null
//3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
//4. 注意:取出的顺序的顺序虽然不是添加的顺序,但是他的固定.
Set set = new HashSet();
set.add("john");
set.add("lucy");
set.add("john");//重复
set.add("jack");
set.add("wexiao");
set.add("mary");
set.add(null);//
set.add(null);//再次添加 null
for(int i = 0; i <10;i ++) {
System.out.println("set=" + set);
}
//遍历
//方式 1: 使用迭代器
System.out.println("=====使用迭代器====");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
set.remove(null);
//方式 2: 增强 for
System.out.println("=====增强 for====");
for (Object o : set) {
System.out.println("o=" + o);
}
//set 接口对象,不能通过索引来获取
}
}
HashSet
HashSet的全面说明
- HashSet实现了Set接口
HashSet实际上是HashMap,看下源码.
public HashSet() { map = new HashMap<>(); }
可以存放null值,但是只能有一个null
HashSet不保证元素是有序的,取决于hash后,再确定索引的结果.(即不能保证存放元素的顺序和取出顺序一致)
- 不能有重复元素/对象.继承Set接口。
package com.wexiao.set_;
import java.util.HashSet;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashSet_ {
public static void main(String[] args) {
/*
public HashSet() {
map = new HashMap<>();
}
*/
HashSet<Object> hashSet = new HashSet<>();
System.out.println(hashSet.add(null));
System.out.println(hashSet.add(null));
hashSet.add("wexiao");
hashSet.add(21312);
hashSet.add(false);
System.out.println(hashSet.add(new Dog("tom")));//
System.out.println(hashSet.add(new Dog("tom")));//地址不同,ok
System.out.println(hashSet.add(new String("微咲")));//true
System.out.println(hashSet.add(new String("微咲")));//false
System.out.println(hashSet.add(new String("wexiao")));//false
System.out.println("hashSet = " + hashSet);
}
}
class Dog{
private String name;
public Dog(String name) {
this.name = name;
}
}
HashSet底层机制
分析**HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)**(**效率高**)
模拟 数组+链表:
package com.wexiao.set_;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashSetStructure {
public static void main(String[] args) {
Node[] table = new Node[16];
Node john = new Node("john",null);
table[2] = john;
Node jack = new Node("jack", null);
john.next = jack;
Node rose = new Node("rose", null);
jack.next = rose;
Node lucy = new Node("lucy", null);
table[3] = lucy;
System.out.println("table = " + table);
}
}
class Node{
Object item;
Node next;
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
HashSet添加元素底层实现
(hashCode() + equals())
- HashSet底层是HashMap
- 添加—一个元素时,先得到hash值-会转成->索引值
- 找到存储数据表table,看这个索引位置是否已经存放的有元素
- 如果没有,直接加入
- 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
- 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且
table的大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)
代码实现:
package com.wexiao.set_;
import java.util.HashSet;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
HashSet<Object> hashSet = new HashSet<>();
hashSet.add("java");
hashSet.add("php");
hashSet.add("java");
System.out.println("hashSet = " + hashSet);
/*
源码解读:
HashSet<Object> hashSet = new HashSet<>();
执行:构造器
public HashSet() {
map = new HashMap<>();
}
hashSet.add("java");
执行:1.add()
public boolean add(E e) {
return map.put(e, PRESENT)==null; //PRESENT => private static final Object PRESENT = new Object();
}
2.put() 该方法会执行 hash(key)方法 得到k对应的hash值 算法是:h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
3.putVal()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
//table 就是 HashMap 的一个属性,类型是Node[]
//if 语句表示,如果当前table是 null ,或者 大小 = 0
//就是第一次扩容,到16个空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据key ,得到hash 去计算该key应该存放到table表的哪个索引位置
// 并把这个位置的对象,赋给p
//(2)再判断p是否为空
//(2.1) 如果p为空,表示还没有存放元素,就创建一个Node (key = "java" ,value = PERSENT)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//一个开发技巧:在需要局部变量的时候,在创建
Node<K,V> e; K k;
//如果当前索引位置对于链表的第一个元素和准备添加的key的hash值一样
// 并且满足下面两个条件之一:
//(1) 准备加入的key 和 p指向的Node结点 的key是一个对象
//(2) p指向的Node结点 的key 的equals() 和准备加入的key比较后相同
// 就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断 p 是不是一颗红黑树
//如果是一颗红黑树,就调用putTreeVal,来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//如果table对应索引位置,已经是一个链表,就使用for循环比较
//(1) 依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
// 注意在把元素添加到链表后,立即判断,该链表是否已经达到8个结点
// ,就调用treeifyBin(),对当前这个链表进行树化(转成红黑树)
// 在进行树化时,还进行一个判断,判断条件:
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
// resize();
// 如果条件成立,就先扩容
// 不成立时,树化
//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况就直接break
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//只要增加了一个节点,size就会++
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
*/
}
}
HashSet的扩容和转成红黑树机制
- HashSet底层是HashMap,第一次添加时,table 数组扩容到16,临界值(threshold)是
16*加载因子(loadFactor)是0.75 = 12 - 如果table数组使用到了临界值12,就会扩容到162= 32,新的临界值就是320.75 = 24,
依次类推 - 在Java8中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是8).并且table的
大小>=MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树).否则仍然采用数组扩容
机制
HashSet练习
package com.wexiao.set_;
import java.util.HashSet;
import java.util.Objects;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashSetExercise {
public static void main(String[] args) {
Employee wexiao = new Employee("wexiao", 20);
Employee jack = new Employee("jack", 23);
Employee weixiao = new Employee("wexiao", 20);
HashSet<Employee> employees = new HashSet<>();
System.out.println(employees.add(wexiao));
System.out.println(employees.add(jack));
System.out.println(employees.add(weixiao));
System.out.println(employees.add(new Employee("wexiao", 20)));
}
}
class Employee {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
System.out.println("相同员工...");
return age == employee.age &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
LinkedHashSet
LinkedHashSet基本介绍
- LinkedHashSet是 HashSet的子类
- LinkedHashSet底层是一个 LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
- LinkedHashSet不允许添重复元素
LinkedHashSet底层机制示意图
package com.wexiao.set_;
import java.util.LinkedHashSet;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class LinkedHashSetSource {
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new String("AA"));
linkedHashSet.add(456);
linkedHashSet.add(456);
linkedHashSet.add(new Customer("wexiao",21323123));
linkedHashSet.add("wexiao");
System.out.println("linkedHashSet = " + linkedHashSet);
//解读:
//1. LinkedHashSet 加入顺序和取出元素的顺序一致
//2. LinkedHashSet 底层维护的是 LinkedHashMap(HashMap的子类)
//3. LinkedHashSet 底层结构(数组+双向列表)
//4. 添加第一次时,直接将 数组table 扩容到16,存放的节点类型是 LinkedHashMap$Entry
//5. 数组是 HashMap$Node[] 存放的元素/数据是 LinkedHashMap$Entry类型
/*
//继承关系是在内部类完成
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
*/
}
}
class Customer{
private String name;
private int num;
public Customer(String name, int num) {
this.name = name;
this.num = num;
}
}
TreeSet
使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则
无序(指输入顺序和输出顺序不同)???(未证实)
package com.wexiao.set_;
import java.util.Comparator;
import java.util.TreeSet;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class TreeSet_ {
public static void main(String[] args) {
//默认a-z升序排序
//使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则
//TreeSet treeSet = new TreeSet()
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照长度大小排序
return ((String) o1).length() - ((String) o2).length();
}
});
treeSet.add("jack");
treeSet.add("tom");//tom,3
treeSet.add("tom");//同为"tom"的元素compare return0 ,无法加入,替换value,这里的value为PRESENT
treeSet.add("sp");
treeSet.add("a");
treeSet.add("abc");//同长度为3的元素compare return0 ,无法加入,替换value,这里的value为PRESENT
System.out.println("treeSet=" + treeSet);
//源码:
/*
1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 在调用 tree.add("tom"); 在底层会执行到
Comparator<? super K> cpr = comparator;
if (cpr != null) { //cpr 就是我们的匿名内部类(对象)
do {
parent = t;
// 动态绑定到我们的匿名内部类 compare方法
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆如果相等,即返回0,这个Key就加入不了☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
return t.setValue(value);
} while (t != null);
}
*/
}
}
Map接口
[k - v]
以下都是JDK8中的内容。
Map接口特点
Map与Collection并列存在。Map用于保存具有映射关系的数据:Key-Value(双列元素)
Map中的 key和 value可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map 中的key 不允许重复,原因和HashSet一样,前面分析过源码.
- Map中的value可以重复
- Map 的key 可以为null, value也可以为null,注意key为null, 只能有一个,value为null ,可以多个.
- 常用String类作为Map的key
- key 和 value之间存在单向一对一关系,即通过指定的key总能找到对应的 value
package com.wexiao.map_;
import java.util.HashMap;
import java.util.Map;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class Map_ {
public static void main(String[] args) {
// 解读Map接口实现类的特点,使用实现类HashMap
Map map = new HashMap();
map.put("no1","wexiao");//k-v
map.put("no2","小湘");//k-v
map.put("no1","wxl");//当有相同的k,就等价于替换
map.put("no3","小湘");
map.put(null,null);
map.put("no4",null);
map.put("no5",null);
// 通过get方法,传入key,会返回对应的value
System.out.println(map.get("no3"));
System.out.println("map = " + map);
}
}
Map体系继承图
Map接口的常用实现类:HashMap、Hashtable和Properties。
Map接口常用方法
- put:添加
- remove:根据键删除映射关系
- get:根据键获取值
- size:获取元素个数
- isEmpty:判断个数是否为
- clear:清除
- containsKey:查找键是否存在
package com.wexiao.map_;
import java.util.HashMap;
import java.util.Map;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class MapMethod {
public static void main(String[] args) {
//演示 map 接口常用方法
Map map = new HashMap();
map.put("邓超", new Book("", 100));//OK
map.put("邓超", "孙俪");//替换-> 一会分析源码
map.put("王宝强", "马蓉");//OK
map.put("宋喆", "马蓉");//OK
map.put("刘令博", null);//OK
map.put(null, "刘亦菲");//OK
map.put("鹿晗", "关晓彤");//OK
map.put("wexiao", "小湘");
System.out.println("map=" + map);
// remove:根据键删除映射关系
map.remove(null);
System.out.println("map=" + map);
// get:根据键获取值
Object val = map.get("鹿晗");
System.out.println("val=" + val);
// size:获取元素个数
System.out.println("k-v=" + map.size());
// isEmpty:判断个数是否为 0
System.out.println(map.isEmpty());//F
// clear:清除 k-v
//map.clear();
System.out.println("map=" + map);
// containsKey:查找键是否存在
System.out.println("结果=" + map.containsKey("wexiao"));//T
}
}
class Book {
private String name;
private int num;
public Book(String name, int num) {
this.name = name;
this.num = num;
}
}
map={邓超=孙俪, 宋喆=马蓉, 刘令博=null, null=刘亦菲, 王宝强=马蓉, wexiao=小湘, 鹿晗=关晓彤}
map={邓超=孙俪, 宋喆=马蓉, 刘令博=null, 王宝强=马蓉, wexiao=小湘, 鹿晗=关晓彤}
val=关晓彤
k-v=6
false
map={邓超=孙俪, 宋喆=马蓉, 刘令博=null, 王宝强=马蓉, wexiao=小湘, 鹿晗=关晓彤}
结果=true
Process finished with exit code 0
Map接口遍历方法
常使用方法:
containsKey:查找键是否存在
keySet:获取所有的键
- entrySet:获取所有关系k-v
- values:获取所有的值
package com.hspedu.map_;
import java.util.*;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
//第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keyset = map.keySet();
//(1) 增强 for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的 values 取出
Collection values = map.values();
//这里可以使用所有的 Collections 使用的遍历方法
//(1) 增强 for
System.out.println("---取出所有的 value 增强 for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的 value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过 EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强 for
System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
for (Object entry : entrySet) {
//将 entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
Map接口练习
package com.wexiao.map_;
import java.util.*;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class MapExercise {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put(1,new Emp("wexiao",5000000,1));
hashMap.put(2,new Emp("wexiao1",500000,2));
hashMap.put(3,new Emp("wexi",5000,3));
//使用for keySet
Set keyset = hashMap.keySet();
for (Object key :keyset) {
Emp emp = (Emp)hashMap.get(key);
if (emp.getSalary() > 18000){
System.out.println(key + "-" + hashMap.get(key));
}
}
//通过 EntrySet 来获取 k-v
Set entrySet = hashMap.entrySet();
//迭代器
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Object entrt = iterator.next();
//System.out.println(entrt.getClass()); class java.util.HashMap$Node
//将迭代器中元素entrt -> Map.Entry 才可以使用(getKey,getValue)
Map.Entry m = (Map.Entry) entrt;
//System.out.println(m.getClass()); class java.util.HashMap$Node
//将m.getValue()对象转为 Emp类
Emp emp = (Emp) m.getValue();
if (emp.getSalary() > 18000){
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
}
class Emp{
private String name;
private double salary;
private int id;
public Emp(String name, double salary, int id) {
this.name = name;
this.salary = salary;
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", salary=" + salary +
", id=" + id +
'}';
}
}
HashMap
HashMap全面介绍
- Map接口的常用实现类:HashMap、Hashtable和Properties.
- HashMap是 Map接口使用频率最高的实现类。
- HashMap是以key-val对的方式来存储数据[案例 Entry ]
- kev 不能重复,但是值可以重复允许使用null键和null值。
- 如果添加相同的key,则会覆盖原来的key-val ,等同于修改.(key不会替换, val会替换)
- 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的。(JDK8的HashMap 底层 :数组+链表+红黑树).
- HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
可以使用ConcurrentHashMap ,ConcurrentHashMap 处理了线程安全问题,即线程同步处理,在多线程下是安全的。
HashMap底层机制及源码剖析
HashMap扩容机制
[ 和HashSet相同 ]
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(loadfactor)初始化为0.75.
- 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素。
如果没有元素直接添加。
如果该索引处有元素,继续判断该元素的key和准备加入的key相是否等,如果相等,则直接替换val;
如果不相等需要判断是树结构还是链表结构,做出相应处理。
如果添加时发现容量不够,则需要扩容。
- **第1次添加,则需要扩容table容量为16,临界值(threshold)为12(160.75)
- 以后再扩容,则需要扩容table容量为原来的2倍(32),临界值为原来的2倍,即24,依次类推.
- 在Java8中,如果一条链表的元素个数超过TREEIFYTHRESHOLD(默认是8),并table的大小>= MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)
package com.wexiao.map_;
import javax.swing.tree.TreeNode;
import java.util.HashMap;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashMapSource1 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("java", 10);//ok
hashMap.put("php", 10);//ok
hashMap.put("java", 20);//替换value
/*老韩解读 HashMap 的源码+图解
1. 执行构造器 new HashMap()
初始化加载因子 loadfactor = 0.75
HashMap$Node[] table = null
2. 执行 put 调用 hash 方法,计算 key 的 hash 值(h = key.hashCode()) ^ (h >>> 16)
public V put (K key, V value){//K = "java" value = 10
return putVal(hash(key), key, value, false, true);
}
3. 执行 putVal
final V putVal ( int hash, K key, V value,boolean onlyIfAbsent, boolean evict){
Node<K, V>[] tab;
Node<K, V> p;
int n, i;//辅助变量
//如果底层的 table 数组为 null, 或者 length =0 , 就扩容到 16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//取出 hash 值对应的 table 的索引位置的 Node, 如果为 null, 就直接把加入的 k-v
//, 创建成一个 Node ,加入该位置即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K, V> e;
K k;//辅助变量
// 如果 table 的索引位置的 key 的 hash 相同和新的 key 的 hash 值相同,
// 并 满足(table 现有的结点的 key 和准备添加的 key 是同一个对象 || equals 返回真)
// 就认为不能加入新的 k-v
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//如果当前的 table 的已有的 Node 是红黑树,就按照红黑树的方式处
理
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
else {
//如果找到的结点,后面是链表,就循环比较
for (int binCount = 0; ; ++binCount) {//死循环
if ((e = p.next) == null) {//如果整个链表,没有和他相同,就加到该链表的最后
p.next = newNode(hash, key, value, null);
//加入后,判断当前链表的个数,是否已经到 8 个,到 8 个,后
//就调用 treeifyBin 方法进行红黑树的转换
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && //如果在循环比较过程中,发现有相同,就 break,就只是替换 value
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value; //替换,key 对应 value
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//每增加一个 Node ,就 size++
if (++size > threshold[12 - 24 - 48])//如 size > 临界值,就扩容
resize();
afterNodeInsertion(evict);
return null;
}
5. 关于树化(转成红黑树)
//如果 table 为 null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
//否则才会真正的树化 -> 剪枝
final void treeifyBin (Node < K, V >[]tab,int hash){
int n, index;
Node<K, V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
}
*/
System.out.println("hashMap = " + hashMap);
}
}
hash值都相等所以添加到一条链上
HashTable
HashTable基本介绍
- 存放的元素是键值对:即K-V
hashtable的键和值都不能为 null,否则会抛出NullPointerException
hashTable使用方法基本上和HashMap一样
- hashTable是线程安全的(synchronized),hashMap是线程不安全的
package com.hspedu.map_;
import java.util.Hashtable;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashTableExercise {
public static void main(String[] args) {
Hashtable table = new Hashtable();//ok
table.put("john", 100); //ok
//table.put(null, 100); //异常 NullPointerException
//table.put("john", null);//异常 NullPointerException
table.put("lucy", 100);//ok
table.put("lic", 100);//ok
table.put("lic", 88);//替换
table.put("hello1", 1);
table.put("hello2", 1);
table.put("hello3", 1);
table.put("hello4", 1);
table.put("hello5", 1);
table.put("hello6", 1);
System.out.println(table);
//简单说明一下Hashtable的底层
//1. 底层有数组 Hashtable$Entry[] 初始化大小为 11
//2. 临界值 threshold 8 = 11 * 0.75
//3. 扩容: 按照自己的扩容机制来进行即可.
//4. 执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry
//5. 当 if (count >= threshold) 满足时,就进行扩容
//6. 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.
}
}
HashTable与HashMap对比
Properties
Properties基本介绍
- Properties类继承自Hashtable类开且实现Map按口,也是使用一种键值对的形式来保存数据。
- 他的使用特点和Hashtable类似
- Properties 还可以用于从 xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
- 说明:工作后 Xxx.properties 文件通常作为配置文件,这个知识点在IO流举例,有兴趣可先看文章
Java 读写Properties配置文件
Properties CRUD
package com.wexiao.map_;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class Properties_ {
public static void main(String[] args) {
//老韩解读
//1. Properties 继承 Hashtable
//2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
//增加
Properties properties = new Properties();
//properties.put(null, "abc");//抛出 空指针异常
//properties.put("abc", null); //抛出 空指针异常
properties.put("john", 100);//k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的 key , value 被替换
System.out.println("properties=" + properties);
//通过 k 获取对应值
System.out.println(properties.get("lic"));//88
//删除
properties.remove("lic");
System.out.println("properties=" + properties);
//修改
properties.put("john", "约翰");
System.out.println("properties=" + properties);
}
}
//properties={john=100, lic=88, lucy=100}
//88
//properties={john=100, lucy=100}
//properties={john=约翰, lucy=100}
TreeMap
使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则
package com.wexiao.map_;
import java.util.Comparator;
import java.util.TreeMap;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class TreeMap_ {
public static void main(String[] args) {
//TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照String长度
return ((String)o1).length() - ((String)o2).length();
//return ((String)o1).compareTo((String)o2); 按照String大小排序
}
});
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
treeMap.put("hsp", "韩顺平");//加入不了 //长度相同时,替换value
System.out.println("treemap = " + treeMap);
/*
1. 构造器把传入的实现了Comparator接口的匿名内部类(对象),传给给TreeMHap的camparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 调用put方法
2.1 第一次添加,把k-v 封装到 Entry对象,放入root
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
2.2 之后添加
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do { //遍历所有key
parent = t;
cmp = cpr.compare(key, t.key); //动态绑定到我们的匿名内部类的compare
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果遍历过程中,发现准备添加Key和当前已有的Key 相等,就不添加,然后更新value
return t.setValue(value);
} while (t != null);
}
*/
}
}
总结-开发中如何选择集合实现类
Collections工具类
Collections工具类介绍
- Collections是一个操作 Set、List 和Map等集合的工具类
- Collections中提供了一系列静态的方法对集合元素进行排序、查询和修嗄等操作
Collections方法
…
Collections常用方法
- reverse(List):反转 List 中元素的顺序
- shuffle(List):对List 集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定List 集合元素按升序排序
- sort(List,Comparator):根据指定的 Comparator产生的顺序对 List 集合元素进行
排序 - swap(List, int,int):将指定list集合中的i处元素和j处元素进行交换
- Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
- Object max(Collection, Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
- Object min(Collection)
- Object min(Collection,Comparator)
- int frequency(Collection,Object):返回指定集合中指定元素的出现次数
- void copy(List dest,List src):将src中的内容复制到dest中
- boolean replaceAll(List list,Object oldVal,Object newVal):使用新值
替换List对象的所有旧值
集合homework
package com.wexiao.homework;
import java.util.ArrayList;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class Homework01 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add(new News("新冠确诊病例超千万,数百万印度教信徒赴恒河“圣浴”引民众担忧"));
arrayList.add(new News("男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生"));
for (int i = arrayList.size() - 1; i >= 0; i--) {
News news = (News) arrayList.get(i);
System.out.println(processTitle(news.getTitle()));
}
}
public static String processTitle(String s) {
if (s == null) {
return "";
}
if (s.length() > 15) {
return s.substring(0, 15) + "..."; //[0,15)
} else {
return s;
}
}
}
class News {
private String title;
private String content;
public News(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
package com.wexiao.homework;
import java.util.*;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class Homework03 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("jack", 650);
hashMap.put("tom", 1200);
hashMap.put("smith", 2900);
System.out.println(hashMap);
hashMap.put("jack", 2600); //替换
System.out.println(hashMap);
//为所有员工加100
Set keySet = hashMap.keySet();
for (Object key : keySet) {
//更新
hashMap.put(key, (Integer) hashMap.get(key) + 100);
}
System.out.println(hashMap);
//遍历 EntrySet
Set entrySet = hashMap.entrySet();
//迭代器
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
//取出工资
Collection values = hashMap.values();
for (Object value : values) {
System.out.println("工资 = " + value);
}
}
}
例如:字符串的compareTo方法
is ok
Person{name=’AA’,id=1001}
泛型
泛型引入
package com.hspedu.generic.improve;
import java.util.ArrayList;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class Generic02 {
public static void main(String[] args) {
//使用传统的方法来解决===> 使用泛型
//老韩解读
//1. 当我们 ArrayList<Dog> 表示存放到 ArrayList 集合中的元素是 Dog 类型 (细节后面说...)
//2. 如果编译器发现添加的类型,不满足要求,就会报错
//3. 在遍历的时候,可以直接取出 Dog 类型而不是 Object
//4. public class ArrayList<E> {} E 称为泛型,那么 Dog->E
ArrayList<Dog> arrayList = new ArrayList<Dog>();
arrayList.add(new Dog("旺财", 10));
arrayList.add(new Dog("发财", 1));
arrayList.add(new Dog("小黄", 5));
//假如我们的程序员,不小心,添加了一只猫
//arrayList.add(new Cat("招财猫", 8));
System.out.println("===使用泛型====");
for (Dog dog : arrayList) {
System.out.println(dog.getName() + "-" + dog.getAge());
}
}
}
/*
1.请编写程序,在 ArrayList 中,添加 3 个 Dog 对象
2.Dog 对象含有 name 和 age, 并输出 name 和 age (要求使用 getXxx())
3.老师使用泛型来完成代码
*/
class Dog {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Cat { //Cat 类
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
泛型好处
泛型介绍
- 泛型又称参数化类型,是Jdk5.0出现的新特性,解决数据类型的安全性问题
- 在类声明或实例化时只要指定好需要的具体的类型即可。
- Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生
ClassCastException异常。同时,代码更加简洁、健壮
- 泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方
法的返回值的类型,或者是参数类型。
package com.wexiao.generic_;
/**
* @author 微咲
* @version 1.0
*/
public class Generic03 {
public static void main(String[] args) {
//注意,特别强调: E 具体的数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
Person<String> person = new Person<String>("韩顺平教育");
person.show(); //String
/*
你可以这样理解,上面的 Person 类
class Person {
String s ;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E
是什么类型
public Person(String s) {//E 也可以是参数类型
this.s = s;
}
public String f() {//返回类型使用 E
return s;
}
}
*/
Person<Integer> person2 = new Person<Integer>(100);
person2.show();//Integer
/*
class Person {
Integer s ;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E
是什么类型
public Person(Integer s) {//E 也可以是参数类型
this.s = s;
}
public Integer f() {//返回类型使用 E
return s;
}
}
*/
}
}
//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
// 或者是某个方法的返回值的类型,或者是参数类型
class Person<E> {
E s;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
public Person(E s) {//E 也可以是参数类型
this.s = s;
}
public E f() {//返回类型使用 E
return s;
}
public void show() {
System.out.println(s.getClass());//显示 s 的运行类型
}
}
泛型语法
泛型的声明
interface接口
//比如: List , ArrayList
说明:
1)其中,T,K,V不代表值,而是表示类型。
2)任意字母都可以。常用T表示,是Type的缩写
泛型的实例化
要在类名后面指定类型参数的值(类型)。如:
List
strList = new ArrayList ();[举例说明] lterator
iterator = customers.iterator0;
泛型使用细节
- interface List
{} , public class HashSet {}..等等
说明:T,E只能是引用类型
看看下面语句是否正确?:
List list = new ArrayList{}; //true
List list2 = new ArrayList();//false
- 在指定泛型具体类型后,可以传入该类型或者其子类类型
- 泛型使用形式:
List list1 = new ArrayList();
List list2 = new ArrayList<>();
如果我们这样写List list3 = new ArrayList();默认给它的泛型是[E就是Object]
自定义泛型
自定义泛型类
自定义泛型类基本语法
class 类名<T,R...>{//...表示可以有多个泛型
//成员
}
自定义泛型类细节
- 普通成员可以使用泛型(属性、方法)
- 使用泛型的数组,不能初始化
- 静态方法中不能使用类的泛型
- 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
- 如果在创建对象时,没有指定类型,默认为Object
class Person<T,R,M>{
String name;
T t;
R r;
M m;
//使用泛型的数组,不能初始化;因为数组在new的时候不能确定T的类型,就无法在内存开辟空间,可以定义
//T[] tArray = new T[8];
T[] tArray;
public void m1(R r){
}
//静态方法不能使用泛型,因为静态是和类相关的,在类加载时,对象还没有创建(static在类加载时会初始化);所以,如果静态方法和静态属性使用了泛型,JVM就无法完成初始化。
//static R r2;//报错
//public static void m2(R r){
//
//}
}
自定义泛型接口
自定义泛型接口基本语法
interface 接口名<T,R...>{
}
自定义泛型接口细节
- 接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
- 泛型接口的类型,在继承接口或者实现接口时确定
- 没有指定类型,默认为Object
package com.hspedu.customgeneric;
/**
* @author 韩顺平
* @version 1.0
*/
public class CustomInterfaceGeneric {
public static void main(String[] args) {
}
}
/**
* 泛型接口使用的说明
* 1. 接口中,静态成员也不能使用泛型
* 2. 泛型接口的类型, 在继承接口或者实现接口时确定
* 3. 没有指定类型,默认为 Object
*/
//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double> {
}
//当我们去实现 IA 接口时,因为 IA 在继承 IUsu 接口时,指定了 U 为 String R 为 Double
//,在实现 IUsu 接口的方法时,使用 String 替换 U, 是 Double 替换 R
class AA implements IA {
@Override
public Double get(String s) {
return null;
}
@Override
public void hi(Double aDouble) {
}
@Override
public void run(Double r1, Double r2, String u1, String u2) {
}
}
//实现接口时,直接指定泛型接口的类型
//给 U 指定 Integer 给 R 指定了 Float
//所以,当我们实现 IUsb 方法时,会使用 Integer 替换 U, 使用 Float 替换 R
class BB implements IUsb<Integer, Float> {
@Override
public Float get(Integer integer) {
return null;
}
@Override
public void hi(Float aFloat) {
}
@Override
public void run(Float r1, Float r2, Integer u1, Integer u2) {
}
}
//没有指定类型,默认为 Object
//建议直接写成 IUsb<Object,Object>
class CC implements IUsb {//等价 class CC implements IUsb<Object,Object> {
@Override
public Object get(Object o) {
return null;
}
@Override
public void hi(Object o) {
}
@Override
public void run(Object r1, Object r2, Object u1, Object u2) {
}
}
interface IUsb<U, R> {
int n = 10;
//U name; 不能这样使用
//普通方法中,可以使用接口泛型
R get(U u);
void hi(R r);
void run(R r1, R r2, U u1, U u2);
//在 jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
default R method(U u) {
return null;
}
}
自定义泛型方法
自定义泛型方法基本语法
修饰符 <T,R...> 返回类型 方法名 (参数列表){
}
自定义泛型方法细节
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中
- 当泛型方法被调用时,类型会确定
- public void eat(E e){},修饰符后没有
eat方法不是泛型方法,而是使用了泛型
自定义泛型方法练习
Integer
Dog
泛型的继承和通配符
- 泛型不具备继承性
List<Object> list = new ArrayList<String>0;1/对吗?
- ︰支持任意泛型类型
- :支持A类以及A类的子类,规定了泛型的上限
- :支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
package com.wexiao.generic_;
import java.util.ArrayList;
import java.util.List;
/**
* @author 微咲
* @version 1.0
*/
public class GenericExtends {
public static void main(String[] args) {
Object o = new String("xx");
//泛型没有继承性
//List<Object> list = new ArrayList<String>();
//举例说明下面三个方法的使用
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AA> list3 = new ArrayList<>();
List<BB> list4 = new ArrayList<>();
List<CC> list5 = new ArrayList<>();
//如果是 List<?> c ,可以接受任意的泛型类型
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
//List<? extends AA> c: 表示 上限,可以接受 AA 或者 AA 子类
// printCollection2(list1);//×
// printCollection2(list2);//×
printCollection2(list3);//√
printCollection2(list4);//√
printCollection2(list5);//√
//List<? super AA> c: 支持 AA 类以及 AA 类的父类,不限于直接父类
printCollection3(list1);//√
//printCollection3(list2);//×
printCollection3(list3);//√
//printCollection3(list4);//×
//printCollection3(list5);//×
}
// ? extends AA 表示 上限,可以接受 AA 或者 AA 子类
public static void printCollection2(List<? extends AA> c) {
for (Object object : c) {
System.out.println(object);
}
}
//说明: List<?> 表示 任意的泛型类型都可以接受
public static void printCollection1(List<?> c) {
for (Object object : c) { // 通配符,取出时,就是 Object
System.out.println(object);
}
}
// ? super 子类类名 AA:支持 AA 类以及 AA 类的父类,不限于直接父类,
//规定了泛型的下限
public static void printCollection3(List<? super AA> c) {
for (Object object : c) {
System.out.println(object);
}
}
}
class AA {
}
class BB extends AA {
}
class CC extends BB {
}
泛型练习
package com.wexiao.homework;
import org.junit.jupiter.api.Test;
import java.util.*;
/**
* @author 微咲
* @version 1.0
*/
public class Homework01 {
public static void main(String[] args) {
}
@Test
public void testList() {
//给泛型T指定User
DAO<User> dao = new DAO<>();
dao.save("001", new User(1, 10, "jack"));
dao.save("002", new User(2, 18, "wexiao"));
dao.save("002", new User(3, 20, "xiaoxiang"));
dao.update("002",new User(2,20,"wexiao"));
dao.update("004",new User(4,40,"smile"));
dao.delete("001");
List<User> list = dao.list();
System.out.println("list = " + list);
}
}
class DAO<T> {
HashMap<String, T> map = new HashMap<String, T>();
public T get(String id) {
return map.get(id);
}
public void update(String id, T entity) {
map.put(id, entity);
}
//返回 map 中存放的所有T对象
//遍历map [k-v],将map的所有value(T entity),封装到ArrayList返回即可
public List<T> list() {
//创建Arraylist
List<T> list = new ArrayList<>();
//遍历map
Set<String> keySet = map.keySet();
for (String key : keySet) {
list.add(map.get(key));
}
return list;
}
public void delete(String id) {
map.remove(id);
}
public void save(String id, T entity) {
map.put(id, entity);
}
}
class User {
private int id;
private int age;
private String name;
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
JUnit
为什么需要JUnit
- 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中
- 如果有多个功能代码测试,就需要来回注销,切换很麻烦
- 如果可以直接运行一个方法,就方便很多,并且可以给出相关信息,就好了-> JUnit
基本介绍
- JUnit是一个Java语言的单元测试框架
- 多数Java的开发环境都已经集成了JUnit作为单元测试的工具
使用步骤
Java绘图
java 绘图坐标体系
快速入门:画圆
绘图原理
Component类提供了两个和绘图相关最重要的方法:
- paint(Graphics g)绘制组件的外观
repaint()刷新组件的外观。
当组件第一次在屏幕显示的时候,程序会目动的调用paint()方法来绘制组件。
在以下情况paint()将会被调用:
- 窗口最小化再最大化
- 窗口的大小发生变化
- repaint函数被调用
package com.wexiao.draw;
import javax.swing.*;
import java.awt.*;
/**
* @author 微咲
* @version 1.0
* 演示如何在画板上画出圆形
*/
public class DrawCircle extends JFrame{ //JFrame =一个窗口,可以理解成画框
//定义一个面板
private MyPanel mp =null;
public static void main(String[] args) {
new DrawCircle();
}
public DrawCircle(){//构造器
//初始化面板
mp = new MyPanel();
//把面板放入到窗口(画框)
this.add(mp);
//设置窗口大小
this.setSize(400,300);
//可视
this.setVisible(true);
//一旦点击关闭,程序就会被释放
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
//1. 先定义一个MyPanel,继承JPanel类,画图形,就在面板上画
class MyPanel extends JPanel{
//理解 MyPanel=画板,Graphics =画笔
//Graphics提供了很多方法
@Override
public void paint(Graphics g) {
super.paint(g);
//划出一个圆形
g.drawOval(10,10,100,100);
}
}
Java绘图技术Graphics类
Java事件处理机制
小球移动
package com.wexiao.event_;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
* @author 微咲
* @version 1.0
* 演示小球通过键盘控制上下左右的移动 -> 连接Java的事件控制
*/
public class BallMove extends JFrame{
MyPanel mp = null;
public static void main(String[] args) {
BallMove ballMove = new BallMove();
}
public BallMove(){
mp = new MyPanel();
this.add(mp);
this.setSize(400,300);//窗口大小
//可视
this.setVisible(true);
//一旦点击关闭,程序就会被释放
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//窗口JFrame对象 可以监听mp的键盘事件(即可以监听到面板发生的键盘事件)
this.addKeyListener(mp);
}
}
//在面板画小球
//KeyListener是一个监听器,可以监听键盘事件
class MyPanel extends JPanel implements KeyListener {
//为了让小球移动,把他的左上角坐标(x,y)设置成变量
int x = 10;
int y = 10;
@Override
public void paint(Graphics g) {
super.paint(g);
g.fillOval(x,y,20,20); //默认黑色
}
//有字符输出时,该方法会触发
@Override
public void keyTyped(KeyEvent e) {
}
//当某个键按下,该方法会触发
@Override
public void keyPressed(KeyEvent e) {
//System.out.println((char)e.getKeyCode() + "被按下...");
//System.out.println("X = " + this.getX() + "Y = " + this.getY());
//根据用户的按键来处理小球的移动
//在java中会给每个键分配一个值
if (e.getKeyCode() == KeyEvent.VK_DOWN){ // ↓ 的Code值 -> KeyEvent.VK_DOWN
y+=10;
}
if (e.getKeyCode() == KeyEvent.VK_UP){ // ↑ 的Code值 -> KeyEvent.VK_UP
y-=10;
}
if (e.getKeyCode() == KeyEvent.VK_LEFT){ // ← 的Code值 -> KeyEvent.VK_LEFT
x-=10;
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT){ // → 的Code值 -> KeyEvent.VK_RIGHT
x+=10;
}
this.repaint();
}
//当某个键(释放)松开了,该方法会触发
@Override
public void keyReleased(KeyEvent e) {
}
}
基本说明
java事件处理是采取"委派事件模型"。当事件发生时,产生事件的对象,会把此"信息"传递给"事件的监听者"处理,这里所说的"信息"实际上就是java.awt.event事件类库里某个类所创建的对象,把它称为"事件的对象"。
- 示意图
事件处理机制深入理解
- 前面我们提到几个重要的概念 事件源,事件, 事件监听器我们下面来全面的介绍它们。
- 事件源 : 事件源是一个产生事件的对象,比如按钮,窗口等。
- 事件 : 事件就是承载事件源状态改变时的对象,比如当键盘事件、鼠标事件、窗口事件等等,会生成一个事件对象,该对象保存着当前事件很多信息,比如KeyEvent对象有含义被按下键的Code值。 java.awt.event包和javax.swing.event包中定义了各种事件类型
- 事件类型:
- 事件监听接口:
(1)当事件源产生一个事件,可以传送给事件监听者处理
(2)事件监听者实际上就是一个类,该类实现了某个事件监听器接口,比如前面我们案例中的MyPanle就是一个类,它实现了KeyListener接口,它就可以作为一个事件监听者,对接受到的事件进行处理
(3)事件监听器接口有多种,不同的事件监听器接口可以监听不同的事件,一个类可以实现多个监听接口
(4)这些接口在java.awt.event包和javax.swing.event包中定义。列出常用的事件监听器接口,查看jdk文档聚集了.
线程(基础)
线程相关概念
程序(program)
是为完成特定任务、用某种语言编写的一组指令的集合。
进程
- 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
线程
- 线程由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程,如下图
单线程
同一个时刻,只允许执行一个线程
多线程
同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
并发
同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发。
并行
同一个时刻,多个任务同时执行。多核cpu可以实现并行。(可并发并行)
Runtime runtime = Runtime.getRuntime();
//获取当前电脑的cpu数量
int cpuNums = runtime.availableProcessors();
System.out.println("当前有cpu个数=" + cpuNums();
创建线程的两种方式
继承Thread类,重写run方法
代码实现
package com.wexiao.threaduse;
/**
* @author 微咲
* @version 1.0
* 演示通过继承 Thread类 创建线程
*/
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建一个Cat对象,当做线程使用
Cat cat = new Cat();
cat.start(); //启动线程,最终会执行cat的run方法
//start源码
/*
(1)
public synchronized void start(){
start0();
}
(2)
//start0() 是native本地方法,是JVM机调用的,底层是c/c++实现
//真正实现多线程的效果,是start0(),而不是run() ==== [ start0()使用多线程调用run() ]
private native void start0();
*/
//cat.run(); //run方法就是一个普通的方法,没有真正的启动一个线程,把run方法执行完后才向下执行。
// 也不会和main线程串行(会阻塞),并且此时的线程是main
//说明:当main线程启动一个子线程 Thread-0,主线程不会阻塞,会继续执行
//这时,主线程和子线程是交替执行
System.out.println("main,主线程不会阻塞,会继续执行...");
for (int i = 1; i <= 60; i++) {
System.out.println("i = " + i + "主线程 : " + Thread.currentThread().getName());
Thread.sleep(1000);
}
}
}
//1. 当一个类继承了Thread类,该类就可以当做线程使用
//2.我们会重写run方法,写上自己的业务代码
//3.run Thread 类实现了 Runnable接口的run方法
/*
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
class Cat extends Thread {
@Override
public void run() {//重写run方法,写上自己的业务代码
int times = 0;
while (true) {
//该线程每隔1秒。在控制台输出"喵喵,我是小猫咪"
System.out.println("喵喵,我是小猫咪" + ++times + "线程名:" + Thread.currentThread().getName());
//让该线程休眠1s Surround with Ctrl + alt + t
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 80) {
break; //当times = 80,退出while,这时线程也就退出..
}
}
}
}
关于start()方法
cat.start(); //启动线程,最终会执行cat的run方法
//start源码
/*
(1)
public synchronized void start(){
start0();
}
(2)
//start0() 是native本地方法,是JVM机调用的,底层是c/c++实现
//真正实现多线程的效果,是start0(),而不是run() ==== [ start0()使用多线程调用run() ]
private native void start0();
*/
//cat.run(); //run方法就是一个普通的方法,没有真正的启动一个线程,把run方法执行完后才向下执行。
// 也不会和main线程串行(会阻塞),并且此时的线程是main
实现Runnable接口,重写run方法
说明
- java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了。
- java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
代码实现 + 静态代理
package com.wexiao.threaduse;
/**
* @author 微咲
* @version 1.0
*/
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog.start(); 这里不能调用start
//创建Thread对象,把dog对象(实现Runnable),放入Thread
Thread thread = new Thread(dog);
thread.start();
//使用线程代理类ThreadProxy
// ThreadProxy threadProxy = new ThreadProxy(dog);
// threadProxy.start();
}
}
//线程代理类 , 模拟了一个极简的 Thread 类
class ThreadProxy implements Runnable {//你可以把 Proxy 类当做 ThreadProxy
private Runnable target = null;//属性,类型是 Runnable
@Override
public void run() {
if (target != null) {
target.run();//动态绑定(运行类型 Dog)
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start() {
start0();//这个方法时真正实现多线程方法
}
public void start0() {
run();
}
}
class Dog implements Runnable{ //通过实现Runnable接口,开发线程
int count = 0;
@Override
public void run() {
while(count < 10){
System.out.println("小狗汪汪叫"+ ++count + Thread.currentThread().getName());
//休眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
继承Thread 和 实现Runnable 的区别
- 从java的设计来看通过 继承Thread 或者 实现Runnable接口 来创建线程本质(start() -> start0())上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
(建议)
```java T3 t3 = new T3(“hello”); Thread thread01 = new Thread(t3); Thread thread02 = new Thread(t3); thread01.start(); thread01.start();
class T3 implements Runnable{
}
<a name="jconsole"></a>
### jconsole
在 终端Terminal 输入 jconsole 调用 Java监视和管理控制台 查看线程
<a name="526b936d"></a>
### 多线程售票问题
[售票系统],编程模拟三个售票窗口售票100,分别使用继承 Thread和实现 Runnable方式,并分析有什么问题?SellTicket.java
问题在于:不稳定,会多售票 ,后面会解决
```java
package com.wexiao.ticket;
/**
* @author 微咲
* @version 1.0
* 使用多线程,模拟三个窗口同时售票 100 张
*/
public class SellTicket {
public static void main(String[] args) {
// //使用继承Thread
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
// //超卖了...
// sellTicket01.start();
// sellTicket02.start();
// sellTicket03.start();
//实现Runnable 多个线程同时处理一个对象
SellTicket02 sellTicket2 = new SellTicket02();
new Thread(sellTicket2).start();
new Thread(sellTicket2).start();
new Thread(sellTicket2).start();
}
}
//使用Thread方式
class SellTicket01 extends Thread {
private static int ticketNum = 100; //让多个线程共享 ticketNum
@Override
public void run() {
while (true) {
//同时... ->
if (ticketNum <= 0) {
System.out.println("票已售罄...");
break;
}
//休眠 50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票,"
+ "剩余票数 = " + (--ticketNum)); //同时... -> -- -> 0,-1,-2
}
}
}
//实现Runnable
class SellTicket02 implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("票已售罄...");
break;
}
//休眠 50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票,"
+ "剩余票数 = " + (--ticketNum));
}
}
}
线程终止
基本说明
- 当线程完成任务后,会自动退出。
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
代码实现
让主线程main休眠10s,再通知t线程停止
package com.wexiao.exit_;
/**
* @author 微咲
* @version 1.0
*/
public class ThreadExit_ {
public static void main(String[] args) {
T t = new T();
t.start();
//如果希望main线程去控制t1 线程的终止,必须可以修改loop
//让t1 退出run方法,从而终止 t1线程 -> 通知方式
//让主线程main休眠10s,再通知t线程停止
try {
Thread.sleep(10 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.setLoop(false);
}
}
class T extends Thread {
private int count = 0;
private boolean loop = true;
@Override
public void run() {
while (loop) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "在运行" + ++count + "次");
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
线程常用方法使用
线程常用方法
- setName //设置线程名称,使之与参数name相同
- getName //返回该线程的名称
- start //使该线程开始执行;Java虚拟机底层调用该线程的start0方法
- run //调用线程对象run方法;
- setPriority //更改线程的优先级
- getPriority //获取线程的优先级
- sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
- interrupt //中断线程
- yield //线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
- join //线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
注意细节
- start底层会创建新的线程,调用run, run就是一个简单的方法调用,不会启动新线程
- 线程优先级的范围
- interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
- sleep:线程的静态方法,使当前线程休眠
代码实现
1~8
package com.wexiao.threadmethod;
/**
* @author 微咲
* @version 1.0
*/
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
//测试相关的方法
T t = new T();
t.setName("阿韦");
t.setPriority(Thread.MIN_PRIORITY);//1
t.start();//启动子线程
//主线程打印5 hi ,然后我就中断 子线程的休眠
for(int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi " + i);
}
System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1
t.interrupt();//当执行到这里,就会中断 t线程的休眠.
}
}
class T extends Thread { //自定义的线程类
@Override
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
//Thread.currentThread().getName() 获取当前线程的名称
System.out.println(Thread.currentThread().getName() + " 吃包子~~~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
Thread.sleep(20000);//20秒
} catch (InterruptedException e) {
//当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码
//InterruptedException 是捕获到一个中断异常.
System.out.println(Thread.currentThread().getName() + "被 interrupt了");
}
}
}
}
9,10
案例:创建一个子线程,每隔1s输出hello,输出20次,主线程每隔1秒,输出hi,输出20次.要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续,
package com.wexiao.threadmethod;
/**
* @author 微咲
* @version 1.0
*/
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for(int i = 1; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("主线程(小弟) 吃了 " + i + " 包子");
if(i == 5) {
System.out.println("主线程(小弟) 让 子线程(老大) 先吃");
//join, 线程插队
t2.join();// 这里相当于让t2 线程先执行完毕
//Thread.yield();//礼让,不一定成功..
System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃..");
}
}
}
}
class T2 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程(老大) 吃了 " + i + " 包子");
}
}
}
用户线程和守护线程
用户线程
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
守护线程
一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
常见的守护线程:垃圾回收机制
package com.wexiao.threadmethod;
/**
* @author 微咲
* @version 1.0
*/
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我们希望当main线程结束后,子线程自动结束
//,只需将子线程设为守护线程即可
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for (int i = 1; i <= 10; i++) {//main线程
System.out.println("宝强在辛苦的工作...");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread {
public void run() {
for (; ; ) {//无限循环
try {
Thread.sleep(1000);//休眠1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");
}
}
}
线程的生命周期
线程状态图
线程状态转换图
通过程序查看线程状态
package com.wexiao.state_;
/**
* @author 微咲
* @version 1.0
*/
public class ThreadState_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName() + " 状态 " + t.getState());
t.start();
while (Thread.State.TERMINATED != t.getState()) {
System.out.println(t.getName() + " 状态 " + t.getState());
Thread.sleep(500);
}
System.out.println(t.getName() + " 状态 " + t.getState());
}
}
class T extends Thread {
int i = 0;
@Override
public void run() {
while (i <= 10) {
System.out.println("hi " + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*
Thread-0 状态 NEW
Thread-0 状态 RUNNABLE
hi 1
Thread-0 状态 TIMED_WAITING
hi 2
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi 3
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi 4
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi 5
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi 6
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi 7
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi 8
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi 9
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi 10
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TERMINATED
Process finished with exit code 0
*/
线程同步机制
同步机制介绍
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这里理解 : 线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.
同步具体方法 - Synchronized
- 同步代码块
synchronized(对象){ //得到对象的锁,才能操作同步代码
//需要被同步代码;
} - synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m (String name){
//需要被同步的代码
}
理解同步机制:
就好像某小火伴上厕所前先把门关上(上锁).完事后再出来(解锁).那么其它小伙伴就可在使用厕所了。
解决多线程售票问题
使用synchronized实现线程同步
package com.wexiao.ticket;
/**
* @author 微咲
* @version 1.0
* 使用多线程,模拟三个窗口同时售票 100 张
*/
public class SellTicket {
public static void main(String[] args) {
//实现Runnable 多个线程同时处理一个对象
SellTicket01 sellTicket01 = new SellTicket01();
new Thread(sellTicket01).start();
new Thread(sellTicket01).start();
new Thread(sellTicket01).start();
}
}
//实现接口方式,使用synchronized实现线程同步
class SellTicket01 implements Runnable {
private int ticketNum = 100;
private boolean loop = true;
public synchronized void sell() {//同步方法,在用一时刻,只能有一个线程执行run方法
if (ticketNum <= 0) {
System.out.println("票已售罄...");
loop = false;
return;
}
//休眠 50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票,"
+ "剩余票数 = " + (--ticketNum));
}
@Override
public void run() { //如果synchronized 加在此方法上 就会造成只有一个线程在此方法内循环
while (loop) {
sell();
}
}
}
分析synchronized同步原理
互斥锁
互斥锁基本介绍
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
- 同步的局限性: 导致程序的执行效率要降低
- 同步方法(非静态的) 的锁可以是this,也可以是其他对象(要求是同一个对象)
- 同步方法((静态的) 的锁为当前类本身。
互斥锁注意事项与细节
- 同步方法如果没有使用static修饰: 默认锁对象为this
- 如果方法使用static修饰,默认锁对象: 当前类.class
- 实现的落地步骤:
- 需要先分析上锁的代码
- 选择同步代码块或同步方法
- 要求多个线程的锁对象为同一个即可!
代码实现 默认锁对象()
售票的部分代码:
Object object = new Object();
//同步方法(静态的)的锁为当前类本身
// 解读
//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
//2. 如果在静态方法中,实现一个同步代码块.
/*
synchronized (SellTicket03.class) {
System.out.println("m2");
}
*/
public synchronized static void m1() {
}
public static void m2() {
synchronized (SellTicket03.class) {
System.out.println("m2");
}
}
// 说明:
//1. public synchronized void sell() {} 就是一个同步方法
//2. 这时锁在 this 对象
//3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在 this 对象
public /*synchronized*/ void sell() {
//同步方法, 在同一时刻, 只能有一个线程来执行 sell 方法
synchronized (/*this*/ object) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}
线程的死锁
线程的死锁基本介绍
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.
代码解释死锁
package com.wexiao.syn;
/**
* @author 微咲
* @version 1.0
*/
public class DeadLock_ {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A 线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B 线程");
A.start();
B.start();
}
}
//线程
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
//2. 如果线程 A 得不到 o2 对象锁,就会 Blocked
//3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
//4. 如果线程 B 得不到 o1 对象锁,就会 Blocked
if (flag) {
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入 1");
synchronized (o2) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入 3");
synchronized (o1) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 4");
}
}
}
}
}
释放锁
下面操作会释放锁
- 当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来 - 当前线程在同步代码块、同步方法中遇到break、return.
案例:没有正常的完事,经理叫他修改bug,不得已出来 - 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束案例:
没有正常的完事,发现忘带纸,不得已出来 - 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去
下面操作不会释放锁
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield(方法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会 - 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
提示;应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用
线程练习
package com.wexiao.homework;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Scanner;
/**
* @author 微咲
* @version 1.0
*/
public class Homework01 {
public static void main(String[] args) {
A a = new A();
a.start();
B b = new B(a);
b.start();
}
}
//创建A线程类
class A extends Thread {
private boolean loop = true;
public void setLoop(boolean loop) {
this.loop = loop;
}
@Override
public void run() {
while (loop) {
//输出 1-100 之间的数字
System.out.println((int) (Math.random() * 100 + 1));
//休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("a线程退出...");
}
}
//知道第二个线程从键盘读取到Q命令
class B extends Thread {
private A a;
public B(A a) {
this.a = a;
}
private Scanner scanner = new Scanner(System.in);
@Override
public void run() {
while (true) {
System.out.println("请输入‘Q’结束线程A...");
char key = scanner.next().toUpperCase().charAt(0);
if (key == 'Q') {
a.setLoop(false);
//System.out.println("a线程退出...");
System.out.println("b线程退出...");
break;
}
}
}
}
IO流
文件
文件基本概念
文件,对我们并不陌生,文件是保存数据维地方,比如大家经常使用的word文档,txt文件.excel文件...都是文件。它既可以保存一张图片,也可以保持视频,声音..
文件流
文件在程序中是以流的形式来操作的
流:数据在数据源(文件)和程序(内存)之间经历的路径
输入流:数据从数据源(文件)到程序(内存)的路径
输出流:数据从程序(内存)到数据源(文件)的路径
文件的操作
创建文件对象相关构造器和方法
new File(String pathname)//根据路径构建一个File对象
new File(File parent,String child)//根据父目录文件+子路径构建
new File(String parent,String child)//根据父目录+子路径构建
package com.wexiao.file_;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
/**
* @author 微咲
* @version 1.0
* 演示创建文件
*/
public class FileCreate {
public static void main(String[] args) {
}
/**
* 演示new File(String pathname)//根据路径构建一个File对象
*/
@Test
public void create01(){
String filePath = "C:\\Users\\46124\\Desktop\\news1.txt";
File file = new File(filePath);
try {
//这里的file对象,在Java程序中,只是一个对象
//只有执行了createNewFile方法,才会正真的在磁盘中创建该文件
file.createNewFile();
System.out.println("文件创建成功.");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* new File(File parent,String child)//根据父目录文件+子路径构建
*/
@Test
public void create02(){
//C:\Users\46124\Desktop\news2.txt
//C:\Users\46124\Desktop\ + news2.txt
File parentFile = new File("C:\\Users\\46124\\Desktop\\");
String fileName = "news2.txt";
File file = new File(parentFile, fileName);
try {
file.createNewFile();
System.out.println("文件创建成功..");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* new File(String parent,String child)//根据父目录+子路径构建
*/
@Test
public void create03(){
//C:\Users\46124\Desktop\ + news3.txt
String parentPath ="C:\\Users\\46124\\Desktop\\";
String fileName = "news3.txt";
File file = new File(parentPath, fileName);
try {
System.out.println("文件创建成功...");
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
获取文件相关信息的方法
`getName、getAbsolutePath、getParent、length、exists、isFileisDirectory...`
package com.wexiao.file_;
import org.junit.jupiter.api.Test;
import javax.crypto.spec.PSource;
import java.io.File;
/**
* @author 微咲
* @version 1.0
*/
public class FileInformation {
public static void main(String[] args) {
}
/**
* 获取文件信息
*/
@Test
public void info(){
//先创建对象
File file = new File("C:\\Users\\46124\\Desktop\\StudyCodeAndNote\\English.txt");
//调用相应方法得到对应信息
System.out.println("文件名字 : " + file.getName());
System.out.println("文件绝对路径 : " + file.getAbsolutePath());
System.out.println("文件父级目录 : " + file.getParent());
System.out.println("文件大小(字节) : " + file.length());
System.out.println("文件是否存在 : " + file.exists());//T
System.out.println("是不是一个文件 : " + file.isFile());//T
System.out.println("是不是一个目录 : " + file.isDirectory());//F
}
}
/*
文件名字 : English.txt
文件绝对路径 : C:\Users\46124\Desktop\StudyCodeAndNote\English.txt
文件父级目录 : C:\Users\46124\Desktop\StudyCodeAndNote
文件大小(字节) : 1290
文件是否存在 : true
是不是一个文件 : true
是不是一个目录 : false
*/
目录、删除操作
**mkdir创建一级目录、mkdirs创建多级目录、delete删除空目录或文件**
[//file.mkdir](//file.mkdir)()创建一级目录<br /> [//file.mkdirs](//file.mkdirs)()创建多级目录<br /> [//file.createNewFile](//file.createNewFile)()创建文件
package com.wexiao.file_;
import org.junit.jupiter.api.Test;
import java.io.File;
/**
* @author 微咲
* @version 1.0
*/
public class Directory_ {
public static void main(String[] args) {
}
/**
* 判断 C:\Users\46124\Desktop\news.1txt是否存在,存在就删除
*/
@Test
public void m1(){
String filePath = "C:\\Users\\46124\\Desktop\\demo02\\news1.txt";
File file = new File(filePath);
if (file.exists()){
if (file.delete()){
System.out.println(filePath + "删除成功..");
}else{
System.out.println(filePath + "删除失败..");
}
}else{
System.out.println("该文件不存在..");
}
}
//在java编程中,目录也被当做文件
/**
* 判断C:\Users\46124\Desktop\demo02 是否删除
*/
@Test
public void m2(){
String filePath = "C:\\Users\\46124\\Desktop\\demo02";
File file = new File(filePath);
if (file.exists()){
if (file.delete()){
System.out.println(filePath + "删除成功..");
}else{
//只能删除空文件夹
System.out.println(filePath + "删除失败..");
}
}else{
System.out.println("该文件不存在..");
}
}
/**
* 判断C:\Users\46124\Desktop\demo02\a\b\c目录是否存在,如果存在就提示已经存在,否则就创建
*/
@Test
public void m3(){
String directoryPath = "C:\\Users\\46124\\Desktop\\demo02\\a\\b\\c";
File file = new File(directoryPath);
if (file.exists()){
System.out.println("该目录存在..");
}else{
System.out.println("该文件不存在..");
//file.mkdir()创建一级目录
//file.mkdirs()创建多级目录
//file.createNewFile()创建文件
if (file.mkdirs()){
System.out.println(directoryPath + "创建成功...");
}else{
System.out.println(directoryPath + "创建失败...");
}
}
}
}
IO流原理、分类
Java IO 流原理
- I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输如读/写文件,网络通讯等。
- Java程序中,对于数据的输入/输出操作以”流(stream)”的方式进行。
- java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
- 输入input : 读取外部数据 (磁盘、光盘等存储设备的数据)到程序(内存)中。
- 输出output : 将程序(内存)数据输出到磁盘、光盘等存储设备中
流的分类
- 按操作数据单位不同分为:字节流(8 bit),字符流(按字符)
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流/包装流
- Java的IO流共涉及40多个类,实际上非常规则,都是从如上4个抽象基类派生的。
- 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
(字节流:Stream为后缀 字符流:以Reader or Writer 为后缀)
IO流体系图 - 常用类
IO流体系图
InputStream 字节输入流
InputStream抽象类是所有类字节输入流的超类
InputStream 体系图(部分)
InputStream 常用的子类
- FilelnputStream:文件输入流
- BufferedInputStream:缓冲字节输入流
- ObjectlnputStream:对象字节输入流
FileInputStream
FileInputStream读取文件
- 使用FileInputStream read() 逐字节读取文件
- 使用FileInputStream read(byte[] b) 多字节读取文件
package com.wexiao.inputstream_;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* @author 微咲
* @version 1.0
*/
public class FileInputStream_ {
public static void main(String[] args) {
}
/**
* 使用FileInputStream read() 读取文件
* 单个字节读取,效率低
*/
@Test
public void readFile01() {
String filePath = "C:\\Users\\46124\\Desktop\\StudyCodeAndNote\\English.txt";
//创建文件字节输入流 读取对象
FileInputStream fileInputStream = null;
//实际读取
int readDate = 0;
try {
fileInputStream = new FileInputStream(filePath);
//一次读一个字节,读取不到时,返回-1
//从该输入流读取一个字节的数据。如果没有输入可用,此方法将阻塞
while ((readDate = fileInputStream.read()) != -1) {
//转成char
System.out.print((char) readDate);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流释放资源
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 使用FileInputStream read(byte[] b) 读取文件
*/
@Test
public void readFile02() {
String filePath = "C:\\Users\\46124\\Desktop\\StudyCodeAndNote\\English.txt";
//创建文件字节输入流 读取对象
FileInputStream fileInputStream = null;
byte[] buffer = new byte[8];//一次读取8个字节
//实际读取
int readLen = 0;
try {
fileInputStream = new FileInputStream(filePath);
//从该输入流读取最多b.length字节的数据到字节数组。此方法将阻塞,直到某些输入可用。
//read(byte[] b)返回实际读取到的字节数
//读取不到时,返回-1
while ((readLen = fileInputStream.read(buffer)) != -1) {
//转成char
System.out.print(new String(buffer, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流释放资源
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
OutputStream 字节输出流
FileOutputStream
FileOutputStream 介绍
FileOutputStream 构造器
FileOutputStream(String name, boolean append)
构造器
FileOutputStream写入数据
演示使用FileOutputStream 将数据写入文件中如果文件不存在,就会创建该文件
- new FileOutputStream(filePath); 再次写入会覆盖
- new FileOutputStream(filePath, true); 再次写入时,是追加到文件后
- write(‘*’);
- write(byte[] b);
- write(byte[] b, int off, int len)
package com.wexiao.outputstream_;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author 微咲
* @version 1.0
*/
public class FileOutputStream01 {
public static void main(String[] args) {
}
/**
* 演示使用FileOutputStream 将数据写入文件中
* 如果文件不存在,则创建该文件
*/
@Test
public void writeFile() {
String filePath = "C:\\Users\\46124\\Desktop\\a.txt";
//创建 FileOutputStream对象
FileOutputStream fileOutputStream = null;
try {
//将Path放入 fileOutputStream对象中
//1. new FileOutputStream(filePath); 再次写入会覆盖
//2. new FileOutputStream(filePath, true); 再次写入时,是追加到文件后
fileOutputStream = new FileOutputStream(filePath, true);
//写入一个字节
//fileOutputStream.write('A');
//写入字符串
String str = "我是通过fileOutputStream.write(byte[] b)方法实现的";
//str.getBytes() String -> byte[]
fileOutputStream.write(str.getBytes());
//fileOutputStream.write(str.getBytes(), 0, 59);//write(byte[] b, int off, int len)将len字节从位于偏移量 off的指定字节数组写入此文件输出流。
System.out.println("写入成功");
} catch (IOException e) { //上面有很多异常,直接改成 IOException e
e.printStackTrace();
} finally {
try {
//关闭文件流释放资源
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
使用FileInputStream和FileOutputStream实现文件复制
package com.wexiao.outputstream_;
import com.wexiao.file_.FileInformation;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author 微咲
* @version 1.0
*/
@SuppressWarnings({"all"})
public class FileCopy {
public static void main(String[] args) {
}
/**
* 使用FileInputStream,FileOutputStream 完成文件的复制
*/
@Test
public void fileCopy() {
//被复制文件地址
String s1 = "F:\\Create one's own\\wallhaven\\wallhaven-kwqx11.jpg";
//要复制到的文件地址
String s2 = "C:\\Users\\46124\\Desktop\\wallhaven-kwqx11.jpg";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
//一次读取 1024 字节
byte[] buffer = new byte[1024];
//实际读取字节
int readLen = 0;
try {
fileInputStream = new FileInputStream(s1);
fileOutputStream = new FileOutputStream(s2); //文件流没有关闭,写入都是在此次程序运行中,所以不用加true
while ((readLen = fileInputStream.read(buffer)) != -1) { //循环读取1024字节
//读取到后,就写入到文件
//即边读取边写入
fileOutputStream.write(buffer, 0, readLen);//一定要使用write(byte[] b, int off, int len)
//如果使用write(byte[] b)可能造成最后一次的字节数组读取错误,文件无法正常显示
//所以一定要用前者,确保完全读取
}
System.out.println("COPY YES !");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭文件流释放资源
if (fileInputStream != null) {
fileInputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Reader 字符输入流
InputStreamReader
FileReader
FileReader关系继承图:
FileReader 相关方法:
- new FileReader(File/String)
- read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
- read(char[] c):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
char -> String 相关APl:
- new String(char):将char[]转换成String
- new String(char[].off,len):将char[]的指定部分转换成String
FileReader读取文件代码实现:
- 创建FileReader对象,实现fileReader.read()逐字符读取
- 创建FileReader对象,实现fileReader.read(char[] c)逐b个字符读取
package com.wexiao.reader_;
import org.junit.jupiter.api.Test;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
* @author 微咲
* @version 1.0
*/
public class FileReader_ {
public static void main(String[] args) {
}
/**
* 创建FileReader对象,实现fileReader.read()逐字符读取
*/
@Test
public void readFile01(){
String filePath = "C:\\Users\\46124\\Desktop\\StudyCodeAndNote\\English.txt";
FileReader fileReader = null;
int date = 0;
try {
fileReader = new FileReader(filePath);
//循环读取
while ((date = fileReader.read()) != -1) {
System.out.print((char) date);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 创建FileReader对象,实现fileReader.read()逐字符读取
*/
@Test
public void readFile02(){
String filePath = "C:\\Users\\46124\\Desktop\\StudyCodeAndNote\\English.txt";
FileReader fileReader = null;
char[] buffer = new char[8];
int readlen = 0;
try {
fileReader = new FileReader(filePath);
//循环读取,使用read(buffer),返回的是实际读取到的字符数
// 如果返回-1 ,说明读取到文件结束
while ((readlen = fileReader.read(buffer)) != -1) {
System.out.print(new String(buffer,0,readlen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Writer 字符输出流
OutputStreamWriter
FileWriter
FileWriter关系继承图:
FileWriter 相关方法:
- new FileWriter(File/String):覆盖模式,相当于流的指针在首端
- new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
- write(int):写入单个字符
- write(char[]):写入指定数组
- write(char[,off,len):写入指定数组的指定部分
- write (string):写入整个字符串
- write(string,off,len):写入字符串的指定部分
String -> char[] 相关API:
String类: toCharArray : 将String转换成char[]
注意:
FileWriter使用后,必须**要关闭(close)或刷新(flush)**,否则写入不到指定的文件!
代码实现FileWriter 把数据写入文件:
- 实现FileWriter 把数据写入文件
- FileWriter使用后,必须要关闭(close)或刷新(flush)
package com.wexiao.writer_;
import org.junit.jupiter.api.Test;
import java.io.FileWriter;
import java.io.IOException;
/**
* @author 微咲
* @version 1.0
*/
public class FileWriter_ {
public static void main(String[] args) {
}
@Test
/**
* 实现FileWriter 把数据写入文件:
*/
public void fileWriter(){
String filePath = "C:\\Users\\46124\\Desktop\\a.txt";
FileWriter fileWriter = null;
char[] chars = {'e','x','i'};
try {
fileWriter = new FileWriter(filePath,false); //true : 追加
// 1) write(int):写入单个字符
fileWriter.write('w');
// 2) write(char[]):写入指定数组
fileWriter.write(chars);
// 3) write(char[,off,len):写入指定数组的指定部分
fileWriter.write("wexiao".toCharArray(),4,1);
// 4) write (string):写入整个字符串
fileWriter.write("o");
// 5) write(string,off,len):写入字符串的指定部分
fileWriter.write("微咲",0,2);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileWriter != null ) {
try {
//对应FileWriter,一定要关闭or刷新流,才能将真正的数据写入到文件中
fileWriter.close();
//fileWriter.flush(); fileWriter.close() = flush() + 关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
文件读取不同方式
1、按字节读取文件内容2、按字符读取文件内容3、按行读取文件内容4、随机读取文件内容
节点流和处理流
节点流和处理流基本介绍
节点流和处理流一览图
处理流封装节点流
处理流(包装流)构造器(举例): (装饰者模式/修饰器模式)
BufferedReader(Reader)
BufferedWriter(Write)
节点流和处理流的区别和联系
- 节点流是底层流/低级流,直接跟数据源相接。
- 处理流(包装流) 包装 节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
- 处理流(也叫包装流)对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
[模拟修饰器设计模式]C:\Users\46124\Desktop\StudyCodeAndNote\Javacode\stydu0611\src\com\wexiao\imitate
处理流功能
1.性能的提高:主要以增加缓冲的方式来提高输入输出的效率。
2.操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
处理流
BufferedReader
BufferedReader体系图
BufferedReader介绍
**BufferedReader 和 BufferedWriter属于字符流**,是按照字符来读取数据的。
关闭时处理流,只需要关闭外层流即可。
BufferdeReader读取文件
- 创建BufferedReader对象,实现bufferedReader.readLine()逐行读取
- 只需要关闭 BufferedReader流,new FileReader(filePath)在底层会自动关闭
package com.wexiao.reader_;
import org.junit.jupiter.api.Test;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
* @author 微咲
* @version 1.0
* 演示BufferedReader的使用
*/
public class BufferedReader_ {
public static void main(String[] args) {
}
/**
* 创建BufferedReader对象,实现bufferedReader.readLine()逐行读取
*/
@Test
public void read() {
String filePath = "C:\\Users\\46124\\Desktop\\StudyCodeAndNote\\English.txt";
BufferedReader bufferedReader = null;
String line;
try {
bufferedReader = new BufferedReader(new FileReader(filePath));
//readLine()按行读取,当返回null时结束读取
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 关闭流,这里注意,只需要关闭 BufferedReader ,
// 因为底层会自动的去关闭节点流FileReader。
bufferedReader.close();
/*
源码:
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close(); //这里的in就是new FileReader(filePath),然后关闭了
} finally {
in = null;
cb = null;
}
}
}
*/
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedWriter
BufferedWriter体系图
BufferedWriter介绍
**BufferedReader 和 BufferedWriter属于字符流**,是按照字符来读取数据的。
关闭时处理流,只需要关闭外层流即可。
BufferedWriter写入文件
- 实现BufferedWriter 把数据写入文件
new BufferedWriter(new FileWriter(filePath,true));
//true:追加newLine();
//创建一个newLine => 插入一个换行
package com.wexiao.writer_;
import org.junit.jupiter.api.Test;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
/**
* @author 微咲
* @version 1.0
*/
public class BufferedWriter_ {
public static void main(String[] args) {
}
/**
* 实现BufferedWriter 把数据写入文件:
*/
@Test
public void fileWriter() {
String filePath = "C:\\Users\\46124\\Desktop\\test.txt";
BufferedWriter bufferedWriter = null;
try {
bufferedWriter = new BufferedWriter(new FileWriter(filePath,true)); //true:追加
bufferedWriter.write(filePath);
bufferedWriter.newLine();//插入一个换行
bufferedWriter.write(filePath);
bufferedWriter.newLine();//插入一个换行
System.out.println("写入成功...");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bufferedWriter != null) {
bufferedWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
使用BufferedReader和BufferedWriter完成文件Copy
- 使用BufferedReader,BufferedWriter 完成文件的复制
- BufferedReader,BufferedWriter是按照字符操作,所以不要去处理 二进制文件[视频、声音、图片、doc] 否则会造成文件损坏
package com.wexiao.writer_;
import org.junit.jupiter.api.Test;
import java.io.*;
/**
* @author 微咲
* @version 1.0
*/
public class FileCopy {
public static void main(String[] args) {
}
/**
* 使用BufferedReader,BufferedWriter 完成文件的复制
*/
@Test
public void fileCopy() {
//BufferedReader,BufferedWriter是按照字符操作
//所以不要去处理 二进制文件[视频、声音、图片、doc] 否则会造成文件损坏
//被复制文件地址
String s1 = "C:\\Users\\46124\\Desktop\\StudyCodeAndNote\\English.txt";
//要复制到的文件地址
String s2 = "C:\\Users\\46124\\Desktop\\test.txt";
//处理图片,会出错
// String s1 = "F:\\Create one's own\\wallhaven\\wallhaven-kwqx11.jpg";
// String s2 = "C:\\Users\\46124\\Desktop\\11.jpg";
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
//一次读取 1 line
String line;
try {
bufferedReader = new BufferedReader(new FileReader(s1));
bufferedWriter = new BufferedWriter(new FileWriter(s2)); //文件流没有关闭,写入都是在此次程序运行中,所以不用加true
while ((line = bufferedReader.readLine()) != null) { //循环读取1Line
//读取到后,就写入到文件
//即边读取边写入
bufferedWriter.write(line);
//bufferedWriter.newLine();
}
System.out.println("COPY YES !");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭文件流释放资源
if (bufferedReader != null) {
bufferedReader.close();
}
if (bufferedWriter != null) {
bufferedWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedInputStream
BuuferedInputStream体系图
//BufferedInputStream extends InputStream
public class FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;
/**
* Creates a <code>FilterInputStream</code>
* by assigning the argument <code>in</code>
* to the field <code>this.in</code> so as
* to remember it for later use.
*
* @param in the underlying input stream, or <code>null</code> if
* this instance is to be created without an underlying stream.
*/
protected FilterInputStream(InputStream in) {
this.in = in;
}
}
BufferedInputStream介绍
BufferedlnputStream是**字节流**,在创建 BufferedInputStream时,会创建一个内部缓冲区数组.
BufferedOutputStream
BufferedOutputStream体系图
BufferedOutputStream介绍
BufferedoutputStream是**字节流**,实现缓冲的输出流,可以将多个字节写入底层输出流中,而不必对每次字节写入调用底层系统
使用BufferedInputStream和BufferedOutputStream通过二进制流实现Copy文件
- 演示使用BufferedInputStream和BufferedOutputStream通过二进制流实现Copy文件
package com.wexiao.outputstream_;
import org.junit.jupiter.api.Test;
import java.io.*;
/**
* @author 微咲
* @version 1.0
* 演示使用BufferedInputStream和BufferedOutputStream通过二进制流实现Copy文件
*/
public class BufferedInputStreamAndBufferedOutputStream {
public static void main(String[] args) {
}
@Test
public void copy() {
//被复制文件地址
String srcFilePath = "F:\\Create one's own\\wallhaven\\wallhaven-kwqx11.jpg";
//要复制到的文件地址
String destFilePath = "C:\\Users\\46124\\Desktop\\wallhaven-kwqx11.jpg";
//创建BufferedInputStream和BufferedOutputStream对象
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
//FileInputStream是 InputStream 子类
bufferedInputStream = new BufferedInputStream(new FileInputStream(srcFilePath));
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destFilePath));
//循环读取文件,并写入
byte[] buffer = new byte[1024];
int readLen = 0;
while ((readLen = bufferedInputStream.read(buffer)) != -1) {
bufferedOutputStream.write(buffer, 0, readLen);
}
System.out.println("Copy!");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭外层流即可,底层会关联的关闭节点流
try {
if (bufferedInputStream!=null){
bufferedInputStream.close();
}
if (bufferedOutputStream!=null){
bufferedOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
对象处理流
序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
Serializable //(可序列化的)这是一个标记接口,没有任何方法
Externalizable // 该接口有方法需要实现,因此我们一般实现上面的Serializable 接口
对象处理流基本介绍
- 功能 : 提供了对基本类型或对象类型的序列化和反序列化的方法
- ObjectOutputStream 提供序列化功能
- ObjectlnputStream 提供反序列化功能
ObjectOutputStream
ObjectOutputStream体系图
ObjectOutputStream序列化数据、对象
- 演示ObjectOutputStream的使用,完成数据的序列化
- 序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
- 如果需要序列化某个类的对象,要实现 Serializable 接口
package com.wexiao.outputstream_;
import org.junit.jupiter.api.Test;
import java.io.*;
/**
* @author 微咲
* @version 1.0
* 演示ObjectOutputStream的使用,完成数据的序列化
*/
public class ObjectOutputStream_ {
public static void main(String[] args) {
}
@Test
public void serialize() throws IOException {
//序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
String filePath = "C:\\Users\\46124\\Desktop\\test.txt";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到 filePath
oos.write(100); //int -> Integer (实现了serializable)
oos.writeInt(99);
oos.writeBoolean(true); //boolean -> Boolean (实现了serializable)
oos.writeChar('S'); //char -> Character (实现了serializable)
oos.writeChars("writeChars"); //String
oos.writeUTF("writeUTF"); //String
//序列化对象
oos.writeObject(new A("a"));
oos.close();
System.out.println("序列化成功...");
}
}
//如果需要序列化某个类的对象,要实现 Serializable 接口 (要加public,才能在反序列化程序中调用更多)
class A implements Serializable{
private String name;
//serialVersionUID序列化的版本号,可以提高兼容性
private static final long serialVersionUID = 1l;
public A(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
ObjectInputStream
ObjectInputStream体系图
ObjectInputStream反序列化数据、对象
- 演示ObjectInputStream的使用,完成数据的反序列化
- 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
package com.wexiao.inputstream_;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
/**
* @author 微咲
* @version 1.0
* 演示ObjectInputStream的使用,完成数据的反序列化
*/
public class ObjectInputStream_ {
public static void main(String[] args) {
}
@Test
public void deserialization() throws IOException, ClassNotFoundException {
String filePath = "C:\\Users\\46124\\Desktop\\test.txt";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
// 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readUTF());
Object a = ois.readObject(); //底层Object -> A
System.out.println("运行类型:" + a.getClass());
System.out.println("a信息:" + a);
//关闭外层流即可,底层会自动联动关闭FileInputStream节点流
ois.close();
System.out.println("反序列化成功");
}
}
对象处理流注意事项和细节说明
- 读写顺序要一致
- 要求序列化或反序列化对象,需要实现Serializable
- 序列化的类中建议添加SerialVersionUID,为了提高版本的兼容性
- 序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
- 序列化对象时,要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现序列化
标准输入输出流
(处理流、包装流)
标准输入输出流介绍
标准输入输出流原理
package com.wexiao.standard;
import java.util.Scanner;
/**
* @author 微咲
* @version 1.0
*/
public class InputAndOutput {
public static void main(String[] args) {
// System 类的 public final static InputStream in = null;
// System.in 编译类型 InputStream
// System.in 运行类型 BufferedInputStream
// 标准输入 -> 键盘
System.out.println(System.in.getClass());
// System 类的 public final static PrintStream out = null;
// System.out 编译类型 PrintStream
// System.out 运行类型 PrintStream
// 标准输出 -> 显示器
System.out.println(System.out.getClass());
//举例:
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
System.out.println(s);
}
}
转换流
使用字符流读取文件时遇到乱码问题
package com.wexiao.transformation;
import java.io.*;
/**
* @author 微咲
* @version 1.0
*/
public class CodeQuestion {
public static void main(String[] args) throws IOException {
// 读取
String filePath = "C:\\Users\\46124\\Desktop\\test.txt";
BufferedReader br = new BufferedReader(new FileReader(filePath));
// 默认情况下,读取文件是按照 utf-8 编码
String s = br.readLine();
System.out.println("读取到的内容:" + s);
br.close();
}
}
// 万多元为广大吴爱国和端午过后的uawyagwd哈维和达瓦大网红的
//读取到的内容:�� ���ԪΪ����Ⱞ���Ͷ�������uawyagwd��ά�ʹ��ߴ������
InputStreamReader
InputStreamReader体系图
InputStreamReader介绍
- lnputStreamReader:Reader的子类,可以将lnputStream(字节流)包装成(转换)Reader(字符流)
- 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
- 可以在使用时指定编码格式(比如utf-8, gbk , gb2312, ISO8859-1等)
InputStreamReader指定编码读取文件
- 演示使用 InputStreamReader 转换流解决中文乱码问题
将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8
最后转换成处理流 BufferReader - 利用构造器 InputStreamReader(InputStream , Charset)
- 关闭最外层流即可
package com.wexiao.transformation;
import java.io.*;
/**
* @author 微咲
* @version 1.0
* 演示使用 InputStreamReader 转换流解决中文乱码问题
* 将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8
* 最后转换成处理流 BufferReader
*/
public class InputStreamReader_ {
public static void main(String[] args) throws IOException {
String filePath = "C:\\Users\\46124\\Desktop\\test.txt";
//1. 把FIleInputStream 转换成 InputStreamReader
//2. 指定编码 gbk
InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
//3. 把InputStreamReader 转换成 BufferedReader
BufferedReader br = new BufferedReader(isr);
//BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "gbk"));
//4. 读取
//isr.read(); 需要循环着读
String s = br.readLine();
System.out.println("读取内容" + s);
// 关闭外层流
br.close();
}
}
OutputStreamWriter
OutputStreamWriter体系图
OutputStreamWriter介绍
- OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装成Writer(字符流)
- 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
- 可以在使用时指定编码格式(比如utf-8, gbk , gb2312, ISO8859-1等)
OutputStreamWriter指定编码写入文件
- 演示OutputStreamWriter 使用
把FileOutputStream字节流,转成字符流OutputStreamWriter
指定处理的编码gbk/utf-8/utf8 - (怎么修改记事本的默认编码)
package com.wexiao.transformation;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
/**
* @author 微咲
* @version 1.0
* 演示OutputStreamWriter 使用
* 把FileOutputStream字节流,转成字符流OutputStreamWriter
* 指定处理的编码gbk/utf-8/utf8
*/
public class OutputStreamWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "C:\\Users\\46124\\Desktop\\1.txt";
String charSet = "gbk";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
osw.write("wexiao");
osw.close();
System.out.println("按照 " + charSet + " 形式,保存文件成功~");
}
}
打印流
打印流只有输出流,没有输入流
PrintStream
PrintStream体系图
演示PrintStream (字节打印流)
- 默认情况下,PrintStream输出数据的位置是标准输出,即显示器
- 可以修改打印流输出的位置/设备
package com.wexiao.print_;
import java.io.IOException;
import java.io.PrintStream;
/**
* @author 微咲
* @version 1.0
* 演示PrintStream (字节打印流)
*/
public class PrintStream_ {
public static void main(String[] args) throws IOException {
PrintStream out = System.out;
//在默认情况下,PrintStream输出数据的位置是标准输出,即显示器
/*
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
*/
out.print("hello");
//因为print底层使用的是write ,所以我们可以直接调用write进行打印/输出
out.write("hello".getBytes());
out.close();
// 可以修改打印流输出的位置/设备
// 1. 输出修改到 C:\Users\46124\Desktop\1.txt
/*
public static void setOut(PrintStream out) {
checkIO();
setOut0(out); //native方法,修改了out
}
*/
System.setOut(new PrintStream("C:\\Users\\46124\\Desktop\\1.txt"));
System.out.println("setOut");
}
}
PrintWriter
PrintWriter体系图
演示PrintWriter (字符打印流)
package com.wexiao.print_;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author 微咲
* @version 1.0
*/
public class PrintWriter_ {
public static void main(String[] args) throws IOException {
//PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("C:\\Users\\46124\\Desktop\\1.txt"));
printWriter.print("这是用PrintWriter类实现的打印...");
printWriter.close(); //flush + close 才会将数据写入文件
}
}
Properties
引出Properties
- 使用传统方式读取 .properties配置文件
- 比较麻烦
package com.wexiao.properties_;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
* @author 微咲
* @version 1.0
*/
public class Properties01 {
public static void main(String[] args) throws IOException {
//读取 mysql.properties 文件,并得到ip,user,pwd
BufferedReader br = new BufferedReader(new FileReader("sec\\mysql.properties"));
String line = "";
while ((line = br.readLine()) != null) { //循环读取
String[] split = line.split("=");
System.out.println(split[0] + "值是:" + split[1]);
//如果我们想要得到指定的值,非常麻烦
if ("ip".equals(split[0])){
System.out.println(split[0] + "值是:" + split[1]);
}
}
br.close();
}
}
mysql.properties
ip=192.169.100
user=微咲
pwd=12345
Properties基本介绍
- 专门用于读写配置文件的集合类
配置文件的格式:
键=值
键=值 - 注意:键值对不需要有空格,值不需要用引号一起来。默认类型是String
Properties体系图
Properties常用方法
- load: 加载配置文件的键值对到Properties对象.
- list: 将数据显示到指定设备/流对象
- getProperty(key): 根据键获取值
- setProperty(key,value): 设置键值对到Properties对象
- store: 将Properties中的键值对存储到配置文件,在idea中,保存信息到配置文件,如果含有中文(使用字节输出流),会存储为unicode码
Properties代码演示
1、使用Properties类完成对mysql.properties的读取
package com.wexiao.properties_;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 使用Properties类完成对mysql.properties的读取
*/
public class Properties02 {
public static void main(String[] args) throws IOException {
//1. 创建Properties对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
//3. 把k-v显示在控制台
properties.list(System.out);
//4. 根据key 获取value
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名:" + user);
System.out.println("密码:" + pwd);
}
}
2、使用Properties类添加key-val到新文件mysql2.properties中,修改
- 使用Properties类添加key-val到新文件mysql2.properties中
- setProperty(“key”, “value”)如果该文件有这个key就是修改,没有就是创建
- 使用字节输出流 FileOutputStream 保存汉字就会转换成 Unicode码,字符输出流是汉字
package com.wexiao.properties_;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 使用Properties类添加key-val到新文件mysql2.properties中
*/
public class Properties03 {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
//创建
properties.setProperty("charset","utf-8");
properties.setProperty("user","微咲");
//set 如果该文件有这个key就是修改,没有就是创建
/*
Properties 父类是 Hashtable , 底层就是 Hashtable 核心方法
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;//如果 key 存在,就替换
return old;
}
}
addEntry(hash, key, value, index);//如果是新 k, 就 addEntry
return null;
}
*/
properties.setProperty("pwd","666666");
properties.setProperty("pwd","888 888");
//将k-v储存到文件中 (使用字节输出流 FileOutputStream 汉字就会转换成 Unicode码,字符输出流是汉字)
properties.store(new FileOutputStream("src\\mysql2.properties"),"这是我实验的"); //(OutputStream out,String comments//注释,null就是没有)
//properties.store(new FileWriter("src\\mysql2.properties"),null);
System.out.println("保存配置文件成功...");
}
}
FileHomework
1
package com.wexiao.homework;
import java.io.*;
/**
* @author 微咲
* @version 1.0
*/
public class Homework01 {
public static void main(String[] args) throws IOException {
String directoryPath = "e:\\mytemp";
File file = new File(directoryPath);
if (!file.exists()) { // 判断是否有该文件夹
//创建文件夹
if (file.mkdir()) {
System.out.println("创建" + directoryPath + "成功");
} else {
System.out.println("创建" + directoryPath + "失败");
}
}else {
System.out.println(directoryPath + "文件夹已存在");
}
//文件名
String filePath = directoryPath + "\\hello.txt";
file = new File(filePath);
if (!file.exists()) { //判断是否有该文件
//创建文件
if (file.createNewFile()) {
System.out.println("创建" + filePath + "成功");
BufferedWriter bw = new BufferedWriter(new FileWriter(filePath));
bw.write("hello~");
System.out.println("写入成功");
bw.close();
} else {
System.out.println("创建" + filePath + "失败");
}
} else {
System.out.println(filePath + "文件已存在");
}
}
}
2
- 1、使用字符输入处理流
- 2、使用转换流将字节流FileInputStream 转换成字符流InputStreamReader, 指定编码 gbk/utf-8
然后包装流 BufferedReader
package com.wexiao.homework;
import java.io.*;
/**
* @author 微咲
* @version 1.0
*/
public class Homework02 {
public static void main(String[] args) {
String filePath = "C:\\Users\\46124\\Desktop\\1.txt";
BufferedReader br = null;
String line = "";
int lineNum = 0;
String charSet ="gbk";
try {
//使用指定编码读取文件
InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), charSet);
br = new BufferedReader(isr);
//使用字符输入处理流
//br = new BufferedReader(new FileReader(filePath));
while ((line = br.readLine()) != null) {
System.out.println((++lineNum) + " " + line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3
package com.wexiao.homework;
import org.junit.jupiter.api.Test;
import java.io.*;
import java.util.Date;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
*/
public class Homework03 {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
// //创建dog.properties
// //创建配置文件内容
// properties.setProperty("name","tom");
// properties.setProperty("age","5");
// properties.setProperty("color","red");
// //储存到配置文件中
// properties.store(new FileOutputStream("src\\dog.properties"),null);
//读取dog.properties
String propertiesPath = "src\\dog.properties";
properties.load(new FileReader(propertiesPath));
String name = properties.get("name") + ""; //Object -> String
int age = Integer.parseInt(properties.get("age") + ""); //Object -> int
String color = properties.get("color") + "";
//Dog dog = new Dog(properties.getProperty("name"), properties.getProperty("age"), properties.getProperty("color"));
Dog dog = new Dog(name, age, color);
System.out.println(dog);
String filePath = "C:\\Users\\46124\\Desktop\\dat.txt";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化对象
oos.writeObject(dog);
//关闭流
oos.close();
System.out.println("dog对象,序列化成功...");
}
/**
* 反序列化dog
* @throws IOException
* @throws ClassNotFoundException
*/
@Test
public void deserialization() throws IOException, ClassNotFoundException {
String serFilePath = "C:\\Users\\46124\\Desktop\\dat.txt";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(serFilePath));
Dog dog = (Dog)ois.readObject();
ois.close();
System.out.println("===反序列化后===");
System.out.println(dog);
}
}
//被序列化的对象,要求实现Serializable接口
class Dog implements Serializable{
private String name;
private int age;
private String color;
public Dog(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
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;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
", color='" + color + '\'' +
'}';
}
}
网络编程
网络的相关概念
网络通信
- 概念 : 两台设备之间通过网络实现数据传输
- 网络通信:将数据通过网络从一台设备传输到另一台设备
- java.net包下提供了一系列的类或接口,供程序员使用,完成网络通信
网络
- 概念:两台或多台设备通过一定物理设备连接起来构成了网络
- 根据网络的覆盖范围不同,对网络进行分类:
局域网 : 覆盖范围最小,仅仅覆盖一个教室或一个机房
城域网 : 覆盖范围较大,可以覆盖一个城市
广域网 : 覆盖范围最大,可以覆盖全国,甚至全球,万维网是广域网的代表
ip地址
- 概念:用于唯一标识网络中的每台计算机/主机
- 查看ip地址: ipconfig
- ip地址的表示形式 : 点分十进制 XX.XX.XX.XX
- 每一个十进制数的范围:0~255
- ip地址的组成=网络地址+主机地址,比如:192.168.16.69
- IPv6是互联网工程任务组设计的用于替代IPv4的下一代IP协议,其地址数量号称可以为全世界的每一
粒沙子编上一个地址。
- 由于IPv4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPv6的使用,不仅能解决网络地址资源数量的问题,而且也解决了多种接入设备连入互联网的障碍
IPv4地址分类
ipv4:
4个字节(32位)表示,所以范围是0~255 0~255 0~255 0~255
域名
- www.baidu.com
- 好处:为了方便记忆,解决记ip的困难
- 概念:将ip地址映射成域名(http协议)
端口号
- 概念:用于标识计算机上某个特定的网络程序
- 表示形式:以整数形式,端口范围0~65535 [2个字节表示端口0~2^16-1]
- 0~1024已经被占用,比如ssh 22, ftp 21, smtp 25 http 80
- 常见的网络程序端口号:
- tomcat :8080
- mysql:3306
- oracle:1521
- sqlserver:1433
网络通信协议
TCP/IP (Transmission ControlProtocol/Internet Protocol)的简写,中文译名为**传输控制协议/因特网互联协议**,又叫网络通讯协议,这个协议是lnternet最基本的协议、Internet国际互联网络的基础,简单地说,就是由网络层的IP协议和传输层的TCP协议组成的。[示意图]
数据进入协议栈时的封装过程:
模型及对应协议
TCP 和 UDP
TCP协议: 传输控制协议
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用”三次握手”方式,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务端
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
UDP协议: 用户数据协议
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 因无需连接,故是不可靠的 (一次握手?)
- 发送数据结束时无需释放资源(因为不是面向连接的),速度快
InetAddress类
InetAddress相关方法
- 获取本机InetAddress对象 getLocalHost
- 根据指定主机名/域名获取ip地址对象 getByName
- 获取InetAddress对象的主机名 getHostName
- 获取InetAddress对象的地址 getHostAddress
package com.wexiao.inetaddress_;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author 微咲
* @version 1.0
* 演示 InetAddress 的使用
*/
public class InetAddress_ {
public static void main(String[] args) throws UnknownHostException {
//获取本机InetAddress对象 getLocalHost
InetAddress inetAddress = InetAddress.getLocalHost();
System.out.println(inetAddress); //wexiao/192.168.137.1
//根据指定主机名/域名获取ip地址对象 getByName
InetAddress host1 = InetAddress.getByName("wexiao");
System.out.println("host1 = " + host1);//host1 = wexiao/192.168.137.1
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println("host2 = " + host2);//host2 = www.baidu.com/14.215.177.38
//获取InetAddress对象的主机名/域名 getHostName
String hostName = host1.getHostName();
System.out.println("host1对应的主机名/域名 = " + hostName);//wexiao
//获取InetAddress对象的地址 getHostAddress
String hostAddress = host1.getHostAddress();
System.out.println("host1对应地址 = " + hostAddress);//192.168.137.1
}
}
Socket
基本介绍
- 套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准。
- 通信的两端都要Socket,是两台机器间通信的端点
- 网络通信其实就是Socket间的通信。
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端
TCP网络通信编程
TCP网络通信编程基本介绍
- 基于客户端,服务端的网络通信
- 底层使用的是TCP/IP协议
- 应用场景举例:客户端发送数据,服务端接受并显示
- 基于Socket的TCP编程
应用案例1(单向发送、接受数据(字节流))
思路
代码实现
SocketTCP01Server
package com.wexiao.socket;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 微咲
* @version 1.0
* 服务端
*/
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
//思路
//1.在本机的9999端口监听,等待连接
// 细节:要求在本机没有其他服务在监听9999
ServerSocket serverSocket = new ServerSocket(9999);
//这个ServerSocket可以通过accept()返回多个Socket[多个客户端连接服务器的]并发
System.out.println("服务端在9999端口监听,等待连接...");
//2.当没有客户端连接9999端口时,程序会阻塞,等待连接
// 如果有客户端连接,则会 返回一个Socket对象,然后程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端socket = " + socket.getClass());
//3.通过socket.getInputStream()读取客户端写入到数据通道的数据,显示
InputStream inputStream = socket.getInputStream();
//4.IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new java.lang.String(buf, 0, readLen));//根据读取到的实际长度,显示内容
}
//5.关闭流和socket
inputStream.close();
socket.close();
serverSocket.close();//关闭源
}
}
SocketTCP01Client
package com.wexiao.socket;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @author 微咲
* @version 1.0
* 客户端,发送 "hello,server~"给服务端
*/
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
//思路
//1.连接服务端(ip,端口)
// 连接本机的9999端口,如果连接成功返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回 :" + socket.getClass());
//2.连接上后,生成Socket,通过socket.getOutputStream()
// 得到和 socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3.通过输出流,写入数据到数据通道
outputStream.write("hello,server~".getBytes());
outputStream.write("hello,server~".getBytes());
//4.关闭流对象和socket,必须关闭
outputStream.close();
socket.close();
System.out.println("客户端退出...");
}
}
应用案例2(双向发送、接受数据(字节流))
- 在使用socket.getOutputStream();后,置结束标志
- socket.shutdownOutput();
package com.wexiao.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 微咲
* @version 1.0
* 服务端接受消息,并向客户端发送消息
*/
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
//思路
//1.在本机的9999端口监听,等待连接
// 细节:要求在本机没有其他服务在监听9999
ServerSocket serverSocket = new ServerSocket(9999);
//这个ServerSocket可以通过accept()返回多个Socket[多个客户端连接服务器的]并发
System.out.println("服务端在9999端口监听,等待连接...");
//2.当没有客户端连接9999端口时,程序会阻塞,等待连接
// 如果有客户端连接,则会 返回一个Socket对象,然后程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端socket = " + socket.getClass());
//3.通过socket.getInputStream()读取客户端写入到数据通道的数据,显示
InputStream inputStream = socket.getInputStream();
//4.IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new java.lang.String(buf, 0, readLen));//根据读取到的实际长度,显示内容
}
//发送消息
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello,client".getBytes());
//设置结束标志
socket.shutdownOutput();
//5.关闭流和socket
inputStream.close();
outputStream.close();
socket.close();
serverSocket.close();//关闭源
}
}
package com.wexiao.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @author 微咲
* @version 1.0
* 客户端,发送 "hello,server~"给服务端,并接收服务端发送的消息
*/
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
//思路
//1.连接服务端(ip,端口)
// 连接本机的9999端口,如果连接成功返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回 :" + socket.getClass());
//2.连接上后,生成Socket,通过socket.getOutputStream()
// 得到和 socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3.通过输出流,写入数据到数据通道
outputStream.write("hello,server~".getBytes());
//设置结束标志
socket.shutdownOutput();
//读取服务端数据
InputStream inputStream = socket.getInputStream();
byte[] bef = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(bef)) != -1) {
System.out.println(new String(bef, 0, readLen));
}
//4.关闭流对象和socket,必须关闭
outputStream.close();
inputStream.close();
socket.close();
System.out.println("客户端退出...");
}
}
应用案例3(双向发送、接受数据(字符流))
思路
代码实现
- bw.newLine();//插入一个换行符,表示写入的内容结束,注意,要求对方使用readLine()!!!!
- bw.flush();// 如果使用的是字符流,需要手动刷新,否则数据不会写入到数据通道
SocketTCP03Server
package com.wexiao.socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 微咲
* @version 1.0
* 字符流
* 服务端接受消息,并向客户端发送消息
*/
public class SocketTCP03Server {
public static void main(String[] args) throws IOException {
//思路
//1.在本机的9999端口监听,等待连接
// 细节:要求在本机没有其他服务在监听9999
ServerSocket serverSocket = new ServerSocket(9999);
//这个ServerSocket可以通过accept()返回多个Socket[多个客户端连接服务器的]并发
System.out.println("服务端在9999端口监听,等待连接...");
//2.当没有客户端连接9999端口时,程序会阻塞,等待连接
// 如果有客户端连接,则会 返回一个Socket对象,然后程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端socket = " + socket.getClass());
//3.通过socket.getInputStream()读取客户端写入到数据通道的数据,使用IO流将其显示
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
System.out.println(br.readLine());
// String line = "";
// while ((line = br.readLine()) != null) {
// System.out.println(line);//根据读取到的实际长度,显示内容
// }
//4.通过socket.getOutputStream();和IO流向主机发送消息
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, "utf-8"));
bw.write("小鸡炖蘑菇");
//设置结束标志
bw.newLine();//插入一个换行符,表示写入的内容结束,注意,要求对方使用readLine()!!!!
bw.flush();// 如果使用的是字符流,需要手动刷新,否则数据不会写入到数据通道
//5.关闭流和socket
br.close();
bw.close();
socket.close();
serverSocket.close();//关闭源
}
}
SocketTCP03Client
package com.wexiao.socket;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* @author 微咲
* @version 1.0
* 字符流
* 客户端,发送 "hello,server~"给服务端,并接收服务端发送的消息
*/
public class SocketTCP03Client {
public static void main(String[] args) throws IOException {
//思路
//1.连接服务端(ip,端口)
// 连接本机的9999端口,如果连接成功返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客户端 socket返回 :" + socket.getClass());
//2.连接上后,通过socket.getOutputStream() 和 IO流 对主机发送消息和接受消息
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, "utf-8"));
bw.write("天王盖地虎");
//设置结束标志
bw.newLine();//插入一个换行符,表示写入的内容结束,注意,要求对方使用readLine()!!!!
bw.flush();// 如果使用的是字符流,需要手动刷新,否则数据不会写入到数据通道
//3.使用socket.getInputStream();和IO流接受服务端数据
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
System.out.println(br.readLine());
// String line = "";
// while ((line = br.readLine()) != null) {
// System.out.println(line);
// }
//4.关闭流对象和socket,必须关闭
bw.close();
br.close();
socket.close();
System.out.println("客户端退出...");
}
}
应用案例4(发送文件)
思路
代码实现
TCPFileUploadServer
package com.wexiao.upload;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 微咲
* @version 1.0
* 文件上传的服务端
*/
public class TCPFileUploadServer {
public static void main(String[] args) throws IOException {
//在本机的8888端口监听,等待连接
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端在8888端口监听,等待连接...");
//等待客户端连接
Socket socket = serverSocket.accept();
System.out.println("客户端已连接~");
//读取客户端发送的数据
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bis);
//将读取到的数据写入到指定路径文件中
String filePath = "C:\\Users\\46124\\Desktop\\socket,tcp文件传输.jpg";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
bos.write(bytes);
//System.out.println("文件已接收~");
//向客服端发送 “收到图片”
//通过socket 获取到输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("已经收到图片~");
bw.flush();//把内容刷新到数据通道
socket.shutdownOutput();//写入结束标志
//关闭
bw.close();
bis.close();
bos.close();
socket.close();
serverSocket.close();
}
}
TCPFileUploadClient
package com.wexiao.upload;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* @author 微咲
* @version 1.0
* 文件上传的客户端
*/
public class TCPFileUploadClient {
public static void main(String[] args) throws IOException {
//连接本机的8888端口,返回socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
System.out.println("连接成功~");
//创建读取文件的字节输入流
String filePath = "F:\\Create one's own\\wallhaven\\wallhaven-q6gq9d.jpg";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
byte[] bytes = StreamUtils.streamToByteArray(bis); //读取filePath文件内容 -> 字节数组
//通过socket获取到输出流,将bytes数据发送到服务端
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(bytes); //将字节数组写入到通道
System.out.println("文件已发送~");
socket.shutdownOutput(); //设置写入数据的结束标志
//接受从服务端回复的消息
InputStream inputStream = socket.getInputStream();
String s = StreamUtils.streamToString(inputStream);
System.out.println(s);
//关闭
socket.close();
bis.close();
bos.close();
}
}
StreamUtils(工具类)
package com.wexiao.upload;
import java.io.*;
/**
* @author 微咲
* @version 1.0
*/
public class StreamUtils {
/**
* 将输入流读取到的文件内容转换成 字节数组
*
* @param is 传入的字节流
* @return 字节数组
* @throws IOException
*/
public static byte[] streamToByteArray(InputStream is) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象,写内容到字节数组中
byte[] b = new byte[1024];
int len;
while ((len = is.read(b)) != -1) { //循环读取b大小的文件
bos.write(b, 0, len); //将读取到的内容写到 bos中
}
byte[] bytesArray = bos.toByteArray(); //将bos读取到的内容转换成字节数组
bos.close();
return bytesArray;
}
/**
* 将输入流读取到的字节内容转换成 字符串
* @param is 传入的字节流
* @return 字符串
* @throws IOException
*/
public static String streamToString(InputStream is) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuilder builder = new StringBuilder();
String line = "";
while ((line = br.readLine()) != null) {
builder.append(line);
}
return builder.toString();
}
}
TCP网络通讯小知识
- 当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是TCP/IP来分配的,是不确定的,是随机的.
- 示意图
- 程序验证+netstat
netstat 指令
- netstat -an可以查看当前主机网络情况,包括端口监听情况和网络连接情况(netstat -anb可查看具体是哪个应用)
- netstat -an | more可以分页显示 (空格查看下一页)
- 要求在dos控制台下执行win+r
说明:
- Listening表示某个端口在监听
- 如果有一个外部程序(客户端)连接到该端口,就会显示一条连接信息.
- 可以输入ctrl +c退出指令
UDP网络通信编程
UDP网络通信编程基本介绍
- 类 DatagramSocket 和 DatagramPacket[数据包/数据报]实现了基于UDP协议网络程序。
- UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到
目的地,也不能确定什么时候可以抵达。 - DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP
地址和端口号。
- UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接
基本流程
- 核心的两个类/对象 DatagramSocket与DatagramPacket
- 建立发送端,接收端(没有服务端和客户端概念)
- 发送数据前,建立数据包/报 DatagramPacket对象
- 调用DatagramSocket的发送、接收方法
- 关闭DatagramSocket
应用案例
代码实现
UDPReceiverA:
package com.wexiao.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/**
* @author 微咲
* @version 1.0
* UDP接收端
*/
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1. 创建一个 DatagramSocket 对象,在9999端口接收数据
DatagramSocket socket = new DatagramSocket(9999);
//2. 构建一个 DatagramPacket 对象,用于接受数据
// UDP协议中的数据包 最大 64kb
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 调用接受方法,将通过网络传输的 DatagramPacket对象填充到packet中
// 当有数据包发送到本机的9999端口时,就会接收到数据
// 如果没有数据包发送到本机的9999端口,就会阻塞等待。
System.out.println("A等待数据报...");
socket.receive(packet);
//4. 将packet拆包,取出数据
int length = packet.getLength();//实际接受到的数据字节长度
byte[] data = packet.getData();//接受到的数据
System.out.println(new String(data, 0, length));
System.out.println("A接受成功~");
//5. 将要发送的数据封装到 DatagramPacket 对象中
byte[] bytes = "hello,too".getBytes();
//new DatagramPacket(字节数组,字节长度,主机(IP),端口)
packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.137.1"), 9998);
//调用send方法,将packet发送出去
socket.send(packet);
System.out.println("A发送成功...");
//6. 关闭资源
socket.close();
}
}
UDPSenderB:
package com.wexiao.udp;
import java.io.IOException;
import java.net.*;
/**
* @author 微咲
* @version 1.0
* UDP发送端
*/
public class UDPSenderB {
public static void main(String[] args) throws IOException {
//1. 创建 DatagramSocket 对象,准备发送和接受数据
DatagramSocket socket = new DatagramSocket(9998);
//2. 将要发送的数据封装到 DatagramPacket 对象中
byte[] bytes = "hello".getBytes();
//new DatagramPacket(字节数组,字节长度,主机(IP),端口)
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.137.1"), 9999);
//3.调用send方法,将packet发送出去
socket.send(packet);
System.out.println("B 发送成功...");
//4. 构建一个 DatagramPacket 对象,用于接受数据
// UDP协议中的数据包 最大 64kb
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
// 调用接受方法,将通过网络传输的 DatagramPacket对象填充到packet中
// 当有数据包发送到端口时,就会接收到数据
// 如果没有数据包发送到端口,就会阻塞等待。
System.out.println("B等待数据报...");
socket.receive(packet);
//将packet拆包,取出数据
int length = packet.getLength();//实际接受到的数据字节长度
byte[] data = packet.getData();//接受到的数据
System.out.println(new String(data, 0, length));
System.out.println("B接受成功~");
//5. 关闭资源
socket.close();
}
}
网络编程Homework
Homework1
Homework01Server
package com.wexiao.homework;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 微咲
* @version 1.0
*/
public class Homework01Server {
public static void main(String[] args) throws IOException {
while (true) {
//1. 创建ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
//2. 通过serverSocket.accept();接受到Socket对象
System.out.println("服务端正在监听中...");
Socket socket = serverSocket.accept();
//3. 接受数据并读取
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String s = br.readLine();
//根据判断接受内容。输出相应的
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
String answer = "";
if ("name".equals(s)) {
answer = "我是wexiao";
} else if (s.equals("hobby")) {
answer = "java";
} else {
answer = "你说鸡儿呢";
}
bw.write(answer);
bw.newLine();
bw.flush();
//socket.shutdownOutput();
//4. 关闭资源
br.close();
bw.close();
socket.close();
serverSocket.close();
}
}
}
Homework01Client
package com.wexiao.homework;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* @author 微咲
* @version 1.0
*/
public class Homework01Client {
public static void main(String[] args) throws IOException {
while (true) {
//1. 创建Socket对象,和服务端连接
Socket socket = new Socket(InetAddress.getLocalHost(), 6666);
//2. 写入数据并发送
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你的问题:");
String question = scanner.next();
bw.write(question);
System.out.println("发送成功~");
bw.newLine();
bw.flush();
//socket.shutdownOutput();
//3. 接受数据并读取
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
String s = br.readLine();
System.out.println(s);
//4. 关闭资源
bw.close();
br.close();
socket.close();
System.out.println("客户端退出...");
}
}
}
Homework03(download)
TCPFileDownloadServer
package com.wexiao.download;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 微咲
* @version 1.0
*/
public class TCPFileDownloadServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("客户端:" + socket + "已连接~");
//接受客户端发来的信息
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String s = StreamUtils.streamToString(bis);
System.out.println("用户想要下载的文件名是:" + s);
String filePath;
if (s.equals("music")) {
filePath = "F:\\Create one's own\\Voice\\Don't Let Me Go.wav";
} else {
filePath = "F:\\Create one's own\\Voice\\.wav音乐\\织风结.wav";
}
byte[] bytes = StreamUtils.streamToByteArray(new FileInputStream(filePath));
//将文件传输给客户端
OutputStream outputStream = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
bos.write(bytes);
bos.flush();
System.out.println("文件发送成功...");
socket.shutdownOutput();
//关闭资源
bos.close();
bis.close();
socket.close();
serverSocket.close();
}
}
TCPFileDownloadClient
package com.wexiao.download;
import sun.net.www.content.audio.wav;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* @author 微咲
* @version 1.0
*/
public class TCPFileDownloadClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
//向服务端发送数据
OutputStream outputStream = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
//向服务端发送用户输入
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要下载的文件名:");
String downloadFileName = scanner.next();
bos.write(downloadFileName.getBytes());
bos.flush();
System.out.println("发送成功...");
socket.shutdownOutput();
//读取服务端发来的文件
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bis);
//将读取的文件写入到本地
String filePath = "C:\\Users\\46124\\Desktop\\ " + downloadFileName + ".wav";
bos = new BufferedOutputStream(new FileOutputStream(filePath));
bos.write(bytes);
System.out.println("下载完成~");
//关闭资源
bos.close();
bis.close();
socket.close();
}
}
反射(reflection)
一个需求引出反射
package com.wexiao.reflection.question;
import com.wexiao.Cat;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 反射问题的引入
*/
public class ReflectionQuestion {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//根据配置文件 re.properties ,创建Cat对象调用hi方法
//传统方式:
Cat cat = new Cat();
System.out.println(cat.getClass());
cat.hi();
//
//使用properties类,读取配置文件
Properties properties = new Properties();
//读取配置文件
properties.load(new FileInputStream("src\\re.properties"));
//通过key获得 value:路径,方法名
String classfullpath = (String) properties.get("classfullpath"); //com.wexiao.Cat
String methodName = properties.get("method").toString(); //hi
System.out.println("classfullpath=" + classfullpath);
System.out.println("methodName=" + methodName);
//new classfullpath() ... 不行所以引出 -> reflection
//使用反射
//(1)加载类,返回Class类型的对象
Class<?> cls = Class.forName(classfullpath);
//(2)通过 cls 得到加载的类 com.wexiao.Cat 的对象实例
Object o = cls.newInstance();
// o的运行类型
System.out.println("o的运行类型:" + o.getClass());
//(3)通过 cls 得到加载的类 com.wexiao.Cat 的 methodName 'hi()' 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4)通过 method1 调用方法 (通过方法对象来实现调用方法)
method1.invoke(o); //传统方法:对象.方法(), 反射机制:方法.invoke(对象)
// 反射意义;
// 可以修改配置文件中的数据,不用修改源码,达到程序的变动
}
}
- Cat
package com.wexiao;
/**
* @author 微咲
* @version 1.0
*/
public class Cat {
private String name = "小龙猫";
public void hi() {
System.out.println(name + "对你撒了个娇~~~");
}
}
- re.properties
classfullpath=com.wexiao.Cat
method=hi
反射机制
Java Reflection简单介绍
- 反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到
- 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射
Java反射机制原理示意图
反射机制可以完成
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
反射相关的类
- java.lang.Class:代表一个类, Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflect.Field:代表类的成员变量,Filed对象表示某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示某个类的构造器
- 加载类,返回Class类型的对象
- 通过 cls 得到加载的类 (com.wexiao.Cat) 的对象实例
package com.wexiao.reflection;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 演示关于reflection的类
*/
@SuppressWarnings({"all"})
public class Reflection01 {
public static void main(String[] args) throws Exception {
//使用反射
//(1)加载类,返回Class类型的对象
Class<?> cls = Class.forName("com.wexiao.Cat");
//(2)通过 cls 得到加载的类 com.wexiao.Cat 的对象实例
Object o = cls.newInstance();
// o的运行类型
System.out.println("o的运行类型:" + o.getClass());
//(3)通过 cls 得到加载的类 com.wexiao.Cat 的 methodName 'hi()' 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod("hi");
//(4)通过 method1 调用方法 (通过方法对象来实现调用方法)
method1.invoke(o); //传统方法:对象.方法(), 反射机制:方法.invoke(对象)
//(5)得到name字段,getField 不能得到私有的属性
//Field name = cls.getField("name");
Field ageField = cls.getField("age");
//传统写法: 对象.字段 反射:字段.get(对象实例)
System.out.println(ageField.get(o));
//(6)得到构造器
Constructor<?> constructor1 = cls.getConstructor(); //无参构造器 ()可放指定参数
System.out.println(constructor1);
Constructor<?> constructor2 = cls.getConstructor(String.class); //String.class就是String的Class对象
}
}
反射优点和缺点
- 优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
- 缺点:使用反射基本是解释执行,对执行速度有影响.
反射调用优化 - 关闭访问检查
- 传统方法调用方法用时:5
- 使用反射调用方法用时:229
- 使用反射调用方法(取消访问检查)用时:203
package com.wexiao.reflection;
import com.wexiao.Cat;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
/**
* @author 微咲
* @version 1.0
* 测试反射调用的性能,和优化方案
*/
public class Reflection02 {
public static void main(String[] args) throws Exception {
m1();
m2();
m3();
}
/**
* 测试使用传统调用方法hi()用时
*/
@Test
public static void m1() {
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("传统方法调用方法用时:" + (end - start));
}
/**
* 测试使用反射调用方法hi()用时
*
* @throws Exception
*/
@Test
public static void m2() throws Exception {
Class<?> cls = Class.forName("com.wexiao.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("使用反射调用方法用时:" + (end - start));
}
public static void m3() throws Exception {
Class<?> cls = Class.forName("com.wexiao.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
hi.setAccessible(true); //在反射调用方法时,取消访问检查
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("使用反射调用方法(取消访问检查)用时:" + (end - start));
}
}
Class类
Class类基本介绍
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,而是系统创建的
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由哪个Class 实例所生成(同一个类的全部实例对应一个Class实例)
- 通过Class对象可以完整地得到一个类的完整结构,通过一系列API
- Class对象是存放在堆的
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)
hotpot java虚拟机Class对象是放在 方法区 还是堆中 ?
package com.wexiao.reflection.class_;
import com.wexiao.Cat;
/**
* @author 微咲
* @version 1.0
* 梳理 Class类 特点
*/
public class Class01 {
public static void main(String[] args) throws ClassNotFoundException {
//1. Class也是类,因此也继承Object类
// [Class类图]
//2. Class类对象不是new出来的,而是系统创建的
//(1) 传统方式
/* ClassLoader类中的loadClass()方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
//Cat cat = new Cat();
//(2) 反射方式
// 没有debug到 ClassLoader类的 loadClass,原因是,没有注销Cat cat = new Cat(), 因为new Cat() 会在前面加载过loadClass
/*
ClassLoader类,仍然是通过ClassLoader类加载Cat类的Class对象
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
Class cls1 = Class.forName("com.wexiao.Cat");
//3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
Class cls2 = Class.forName("com.wexiao.Cat");
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
}
}
Class类体系图
Class类的常用方法
package com.wexiao.reflection.class_;
import com.wexiao.Car;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* @author 微咲
* @version 1.0
* 演示Class类的常用方法
*/
public class Class02 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException {
String classAllPath = "com.wexiao.Car";
//1. 得到Car类对应的 Class 对象
// <?> : 不确定的java类型
Class<?> cls = Class.forName(classAllPath);
//2. 输出cls
System.out.println(cls); //显示cls对象,是哪个类的Class对象 class com.wexiao.Car
System.out.println(cls.getClass()); //cls运行类型 class java.lang.Class (在后面加上.getName()就会没有前面的类型名)
//3. 得到包名
System.out.println(cls.getPackage().getName()); //com.wexiao
//4. 得到类名
System.out.println(cls.getSimpleName()); //Car
System.out.println(cls.getName()); //com.wexiao.Car
//5. 通过cls创建对象实例
//Object o = cls.newInstance();
Car car = (Car) cls.newInstance();
System.out.println(car); //car.toString();
//6. 通过反射获取属性 brand
Field brand = cls.getField("brand");
System.out.println(brand.get(car));//法拉利
//7. 通过反射给属性赋值
brand.set(car, "奔驰");
System.out.println(brand.get(car));//奔驰
//8. 可以得到所有的属性(字段)
System.out.println("=======所有的字段属性=======");
Field[] fields = cls.getFields();
for (Field f : fields) {
System.out.println(f.getName() + " :" + f.get(car));//名称
}
//9. 通过反射获得接口
System.out.println("=======所有的接口=======");
Class<?>[] interfaces = cls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface.getName());
}
//10. 通过反射获得父类
Class<?> superclass = cls.getSuperclass();
System.out.println(superclass);
//11. 通过反射获得该类的类加载器
ClassLoader classLoader = cls.getClassLoader();
System.out.println(classLoader);
//12. 通过反射获得构造器
Constructor<?> constructor = cls.getConstructor(); //()放参数
System.out.println(constructor);
}
}
Car类:
package com.wexiao;
import java.io.Serializable;
/**
* @author 微咲
* @version 1.0
*/
public class Car extends Cat implements Serializable,Runnable {
public String brand = "法拉利";
public int price = 10000;
public String color = "白色";
public Car() {
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
", color='" + color + '\'' +
'}';
}
@Override
public void run() {
}
}
获取 Class 类对象
- 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出
ClassNotFoundException,
实例:Class cls1 =Class.forName( “java.lang.Cat”);
应用场景: 多用于配置文件,读取类全路径,加载类. - 前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高
实例:Class cls2 = Cat.class;
应用场景: 多用于参数传递,比如通过反射得到对应构造器对象. - 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象,
实例:Class cls3=对象.getClass(); //运行类型
应用场景: 通过创建好的对象,获取Class对象. - 其他方式,通过类加载器
ClassLoader cl =对象.getClass().getClassLoader();
Class cls4 = cl.loadClass(“类的全类名”); - 基本数据(int, char,boolean,float,double,byte,long,short)按如下方式得到Class类对象
Class cls =基本数据类型.class - 基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class cls =包装类.TYPE
package com.wexiao.reflection.class_;
import com.wexiao.Car;
import com.wexiao.Cat;
/**
* @author 微咲
* @version 1.0
* 演示得到 Class对象 的方式(6)
*/
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
//1. Class.getName
String classAllPath = "com.wexiao.Cat"; //往往读取配置文件
Class<?> cls1 = Class.forName(classAllPath);
System.out.println(cls1);
//2. 类名.class , 应用场景:用于参数传递
Class<Cat> cls2 = Cat.class;
System.out.println(cls2);
//3. 对象.getClass() , 应用场景,有对象实例
Cat cat = new Cat();
Class<? extends Cat> cls3 = cat.getClass();
System.out.println(cls3);
//4. 通过 类加载器[4] 获得 类的Class对象
//(1)先得到类加载器 实例.getClass.getClassLoader()
ClassLoader classLoader = cat.getClass().getClassLoader();
//(2)通过类加载器得到Class对象 classLoader.loadClass("类名")
Class<?> cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);
//cls1,2,3,4为同一个对象, Cat 类 的Class对象
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());
//5. 基本数据(int, char,boolean,float,double,byte,long,short)按如下方式得到Class类对象
System.out.println(int.class);
System.out.println(char.class);
System.out.println(Integer.class);
System.out.println(String.class);
int bHC = boolean.class.hashCode();
//6. 基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
System.out.println(Integer.TYPE);
System.out.println(Character.TYPE);
//System.out.println(String.TYPE);
//相等 Class对象是同一个
int BHC = Boolean.TYPE.hashCode();
System.out.println(bHC);
System.out.println(BHC);
// class com.wexiao.Cat
// class com.wexiao.Cat
// class com.wexiao.Cat
// class com.wexiao.Cat
// 1554874502
// 1554874502
// 1554874502
// 1554874502
// int
// char
// class java.lang.Integer
// class java.lang.String
// int
// char
// 1846274136
// 1846274136
}
}
具有Class对象的类型
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void
package com.wexiao.reflection.class_;
import java.io.Serializable;
/**
* @author 微咲
* @version 1.0
* 演示哪些类型有Class对象
*/
public class AllTypeClass {
public static void main(String[] args) {
Class<String> cls1 = String.class; //外部类
Class<Serializable> cls2 = Serializable.class; //接口
Class<Integer[]> cls3 = Integer[].class; //数组
Class<float[][]> cls4 = float[][].class; //二维数组
Class<Deprecated> cls5 = Deprecated.class; //注解
Class<Thread.State> cls6 = Thread.State.class; //枚举
Class<Long> cls7 = long.class; //基本数据类型
Class<Void> cls8 = void.class; //void数据类型
Class<Class> cls9 = Class.class; //Class
System.out.println(cls1);
System.out.println(cls2);
System.out.println(cls3);
System.out.println(cls4);
System.out.println(cls5);
System.out.println(cls6);
System.out.println(cls7);
System.out.println(cls8);
System.out.println(cls9);
// class java.lang.String
// interface java.io.Serializable
// class [Ljava.lang.Integer;
// class [[F
// interface java.lang.Deprecated
// class java.lang.Thread$State
// long
// void
// class java.lang.Class
}
}
类加载
类加载基本说明
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。
- 静态加载: 编译时加载相关的类,如果没有该类则报错, 依赖性太强
- 动态加载: 运行时加载需要的类,降低了依懒性。 (编译时即使没有需要的类名,也不会报错,若运行会报错)
类加载时机
- 当创建对象时(new) //静态加载
- 当子类被加载时,父类也加载 //静态加载
- 调用类中的静态成员时 //静态加载
- 通过反射 //动态加载 Class.forName(“com.test.Cat”);
类加载流程图
类加载三个阶段各阶段的任务
加载阶段(Loading)
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为**二进制字节流加载到内存中**,并**生成一个代表该类的java.lang.Class对象**
连接阶段(Linking)
验证(Verification)
- 目的是为了确保 Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 包括 : 文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
.class文件 - 可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
准备(Preparation)
JVM会在该阶段**对静态变量,分配内存并默认初始化** (对应数据类型的默认初始值,如O、OL、null、false等)。这些变量所使用的内存都将在方法区中进行分配
解析(Resolution)
虚拟机将常量池内的符号引用替换为直接引用的过程
初始化(Initialization)
- 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行
()方法的过程。 ()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。 - 虚拟机会保证一个类的
()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 ()方法,其他线程都需要阻塞等待,直到活动线程执行 ()方法完毕[debug源码]
package com.wexiao.reflection.classload_;
/**
* @author 微咲
* @version 1.0
*/
public class ClassLoadInit {
public static void main(String[] args) {
//1. 加载 B 类,并生成 B 的 class 对象
//2. 链接 num = 0
//3. 初始化阶段
// 依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并
/*
clinit() {
System.out.println("B 静态代码块被执行");
//num = 300;
num = 100;
}
合并: num = 100
*/
//new B();//类加载
//System.out.println(B.num);//100, 如果直接使用类的静态属性,也会导致类的加载
//看看加载类的时候,是有同步机制控制
/*
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//正因为有这个机制,才能保证某个类在内存中, 只有一份 Class 对象
synchronized (getClassLoadingLock(name)) {
//....
}
}
*/
B b = new B();
}
}
class B {
static {
System.out.println("B 静态代码块被执行");
num = 300;
}
static int num = 100;
public B() {//构造器
System.out.println("B() 构造器被执行");
}
}
//B 静态代码块被执行
//B() 构造器被执行
通过反射获取类的结构信息
java.lang.Class 类
java.lang.reflect.Field 类
getType:获得是对应的那个属性对应的Class对象
java.lang.reflect.Method 类
java.lang.reflect.Constructor 类
package com.wexiao.reflection;
import org.junit.jupiter.api.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author 微咲
* @version 1.0
* 演示通过关于反射的方法获取类的结构信息
*/
public class ReflectionUtils {
public static void main(String[] args) {
}
@Test
public void api_02() throws ClassNotFoundException { //测试属性相关的方法
//得到Class对象
Class<?> personCls = Class.forName("com.wexiao.reflection.Person");
//getDeclaredFields:获取本类中所有属性
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性:" + declaredField.getName()
+ " 该属性的修饰符值:" + declaredField.getModifiers()
+ " 该属性的类型:" + declaredField.getType());
}
// 本类中所有属性:name 该属性的修饰符值:1 该属性的类型:class java.lang.String
// 本类中所有属性:age 该属性的修饰符值:12 该属性的类型:int
// 本类中所有属性:job 该属性的修饰符值:0 该属性的类型:class java.lang.String
// 本类中所有属性:sal 该属性的修饰符值:2 该属性的类型:double
//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("获取本类中所有方法:" + declaredMethod.getName()
+ " 该方法全名为:" + declaredMethod
+ " 该方法的修饰符值:" + declaredMethod.getModifiers()
+ " 该方法的返回类型:" + declaredMethod.getReturnType());
//输出当前这个方法的形参情况
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该方法的形参类型:" + parameterType);
}
}
// 本类中所有属性:name 该属性的修饰符值:1 该属性的类型:class java.lang.String
// 本类中所有属性:age 该属性的修饰符值:12 该属性的类型:int
// 本类中所有属性:job 该属性的修饰符值:0 该属性的类型:class java.lang.String
// 本类中所有属性:sal 该属性的修饰符值:2 该属性的类型:double
// 获取本类中所有方法:m1 该方法全名为:public void com.wexiao.reflection.Person.m1(java.lang.String,int,double) 该方法的修饰符值:1 该方法的返回类型:void
// 该方法的形参类型:class java.lang.String
// 该方法的形参类型:int
// 该方法的形参类型:double
// 获取本类中所有方法:m2 该方法全名为:protected java.lang.String com.wexiao.reflection.Person.m2() 该方法的修饰符值:4 该方法的返回类型:class java.lang.String
// 获取本类中所有方法:m4 该方法全名为:private void com.wexiao.reflection.Person.m4() 该方法的修饰符值:2 该方法的返回类型:void
// 获取本类中所有方法:m3 该方法全名为:void com.wexiao.reflection.Person.m3(int) 该方法的修饰符值:0 该方法的返回类型:void
// 该方法的形参类型:int
//getDeclaredConstructors:获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("获取本类中所有构造器:" + declaredConstructor
+ " 该构造器的修饰符值:" + declaredConstructor.getModifiers()
+ " 该构造器名:" + declaredConstructor.getName());
//输出当前这个构造器的形参情况
Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该构造器的形参:" + parameterType);
}
}
// 获取本类中所有构造器:private com.wexiao.reflection.Person(java.lang.String,int) 该构造器的修饰符值:2 该构造器名:com.wexiao.reflection.Person
// 该构造器的形参:class java.lang.String
// 该构造器的形参:int
// 获取本类中所有构造器:public com.wexiao.reflection.Person(java.lang.String) 该构造器的修饰符值:1 该构造器名:com.wexiao.reflection.Person
// 该构造器的形参:class java.lang.String
// 获取本类中所有构造器:public com.wexiao.reflection.Person() 该构造器的修饰符值:1 该构造器名:com.wexiao.reflection.Person
}
@Test
public void api_01() throws ClassNotFoundException { //得到类的相关信息
//得到Class对象
Class<?> personCls = Class.forName("com.wexiao.reflection.Person");
//1.getName:获取全类名
System.out.println(personCls.getName()); //com.wexiao.reflection.Person
//2.getSimpleName:获取简单类名
System.out.println(personCls.getSimpleName()); //Person
//3. getFields:获取所有public修饰的属性,包含本类以及父类的
Field[] fields = personCls.getFields();
for (Field field : fields) {
System.out.println("获取所有public修饰的属性,包含本类以及父类的:" + field.getName());
}
//4. getDeclaredFields:获取本类中所有属性
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性:" + declaredField.getName());
}
//5.getMethods:获取所有public修饰的方法,包含本类以及父类的
Method[] methods = personCls.getMethods();
for (Method method : methods) {
System.out.println("获取所有public修饰的方法,包含本类以及父类的:" + method.getName());
}
//6. getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("获取本类中所有方法:" + declaredMethod.getName());
}
//7.getConstructors:获取本类所有public修饰的构造器
Constructor<?>[] constructors = personCls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("获取本类所有public修饰的构造器:" + constructor);
//下面是全名
System.out.println("获取本类所有public修饰的构造器(全名):" + constructor.getName());
}
//8. getDeclaredConstructors:获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("获取本类中所有构造器:" + declaredConstructor);
}
//9. getPackage:以Package形式返回包信息
Package aPackage = personCls.getPackage();
System.out.println("返回包信息:" + aPackage);
//10.getSuperClass:以Class形式返回父类信息
Class<?> superclass = personCls.getSuperclass();
System.out.println("返回父类信息:" + superclass);
//11.getInterfaces:以Class形式返回接口信息
Class<?>[] interfaces = personCls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("接口信息:" + anInterface);
}
//12.getAnnotations:以Annotation[]形式返回注解信息
Annotation[] annotations = personCls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息:" + annotation);
}
// com.wexiao.reflection.Person
// Person
// 获取所有public修饰的属性,包含本类以及父类的:name
// 获取所有public修饰的属性,包含本类以及父类的:hobby
// 本类中所有属性:name
// 本类中所有属性:age
// 本类中所有属性:job
// 本类中所有属性:sal
// 获取所有public修饰的方法,包含本类以及父类的:m1
// 获取所有public修饰的方法,包含本类以及父类的:hi
// 获取所有public修饰的方法,包含本类以及父类的:wait
// 获取所有public修饰的方法,包含本类以及父类的:wait
// 获取所有public修饰的方法,包含本类以及父类的:wait
// 获取所有public修饰的方法,包含本类以及父类的:equals
// 获取所有public修饰的方法,包含本类以及父类的:toString
// 获取所有public修饰的方法,包含本类以及父类的:hashCode
// 获取所有public修饰的方法,包含本类以及父类的:getClass
// 获取所有public修饰的方法,包含本类以及父类的:notify
// 获取所有public修饰的方法,包含本类以及父类的:notifyAll
// 获取本类中所有方法:m1
// 获取本类中所有方法:m2
// 获取本类中所有方法:m4
// 获取本类中所有方法:m3
// 获取本类所有public修饰的构造器:public com.wexiao.reflection.Person(java.lang.String)
// 获取本类所有public修饰的构造器(全名):com.wexiao.reflection.Person
// 获取本类所有public修饰的构造器:public com.wexiao.reflection.Person()
// 获取本类所有public修饰的构造器(全名):com.wexiao.reflection.Person
// 获取本类中所有构造器:private com.wexiao.reflection.Person(java.lang.String,int)
// 获取本类中所有构造器:public com.wexiao.reflection.Person(java.lang.String)
// 获取本类中所有构造器:public com.wexiao.reflection.Person()
// 返回包信息:package com.wexiao.reflection
// 返回父类信息:class com.wexiao.reflection.A
// 接口信息:interface com.wexiao.reflection.IA
// 接口信息:interface com.wexiao.reflection.IB
// 注解信息:@java.lang.Deprecated()
}
}
class A {
public String hobby;
public void hi() {
}
public A() {
}
public A(String name) {
}
}
interface IA {
}
interface IB {
}
@Deprecated
class Person extends A implements IA, IB {
//属性
public String name;
protected static int age; // 4 + 8 = 12
String job;
private double sal;
//构造器
public Person() {
}
public Person(String name) {
}
//私有的
private Person(String name, int age) {
}
//方法
public void m1(String name, int age, double sal) {
}
protected String m2() {
return null;
}
void m3(int num) {
}
private void m4() {
}
}
通过反射创建对象;操作属性;调用方法
通过反射创建对象
- 方式一:调用类中的public修饰的无参构造器
- 方式二:调用类中的指定构造器
- Class类相关方法
- newlnstance : 调用类中的无参构造器,获取对应类的对象
- getConstructor(Class…clazz) : 根据参数列表,获取对应的public构造器对象
- getDecalaredConstructor(Class…clazz) : 根据参数列表,获取对应的所有构造器对象
- Constructor类相关方法
- setAccessible : 暴破 (Accessible ,可到达的,可访问的)
- newlnstance(Object…obj) : 调用构造器
package com.wexiao.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author 微咲
* @version 1.0
* 通过反射机制创建实例
*/
public class ReflectionCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//1. 先获取到User类的Class对象
Class<?> userClass = Class.forName("com.wexiao.reflection.User");
//2. 通过public的无参构造器创建实例
Object o = userClass.newInstance();
System.out.println(o);
//3. 通过public的有参构造器创建实例
/*
constructor 对象:
public User(String name) {//public 的有参构造器
this.name = name;
}
*/
//3.1 先得到对应构造器 public (String.class)
Constructor<?> constructor = userClass.getConstructor(String.class);
//3.2 创建实例,并传入实参
Object wx = constructor.newInstance("wx");
System.out.println(wx);
//4. 通过非public的有参构造器创建实例
//4.1 先得到private构造器(int.class, String.class)
Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
//4.2 创建实例,传参
//暴破,使用反射可以访问private构造器/方法/属性 [因为此构造器是私有的所以要暴破才能创建实例]
// 反射+暴破 面前都是纸老虎
declaredConstructor.setAccessible(true);
Object wssssssss = declaredConstructor.newInstance(20, "wssssssss");
System.out.println(wssssssss);
}
}
class User { //User 类
private int age = 10;
private String name = "wexiaostudy";
public User() {//无参 public
}
public User(String name) {//public 的有参构造器
this.name = name;
}
private User(int age, String name) {//private 有参构造器
this.age = age;
this.name = name;
}
public void sayHi() {
System.out.println("hi");
}
public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}
通过反射操作属性
- 根据属性名获取Field对象
Field f = clazz对象.getDeclaredField(属性名); - 暴破 : f.setAccessible(true); //f是Field
- 访问
f.set(o,值); //o表示对象,设置值
syso(f.get(o)); //o表示对象 - 注意 : 如果是静态属性,则set和get中的参数o,可以写成null
package com.wexiao.reflection;
import java.lang.reflect.Field;
/**
* @author 微咲
* @version 1.0
* 通过反射操作属性
*/
public class ReflectionAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//1. 得到Student类 对应的Class对象
Class<?> stuClass = Class.forName("com.wexiao.reflection.Student");
//2. 创建对象 无参构造器
Object o = stuClass.newInstance();
System.out.println(o.getClass());
System.out.println(o);
//3. 使用反射得到age(public)属性对象
Field age = stuClass.getField("age");
age.set(o, 18); //通过反射操作属性 (操作对象,值)
System.out.println(age.get(o)); //通过反射得到age属性的值
System.out.println(o);
//4. 使用反射得到name(private,static)属性对象
Field name = stuClass.getDeclaredField("name");
// 对name(private) 进行暴破
name.setAccessible(true);
//name.set(o,"wexiao");
//也可以将对象设置为null,因为这个属性是静态的,因为静态的成员是所有对象的
name.set(null,"wexiao");
System.out.println(o);
System.out.println(age.get(null)); //因为age是静态的,所以可以写null,来得到值
}
}
class Student {//类
public int age;
private static String name;
public Student() {//构造器
}
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}
通过反射调用方法
- 根据方法名和参数列表获取Method方法对象: Method m =clazz.getDeclaredMethod(方法名,XX.class);
//得到本类的所有方法 - 获取对象:Object o = clazz.newlnstance);
- 暴破:m.setAccessible(true);
- 访问:Object returnValue = m.invoke(o,实参列表);//o就是对象
- 注意:如果是静态方法,则invoke的参数o,可以写成null!
package com.wexiao.reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author 微咲
* @version 1.0
* 通过反射调用方法
*/
public class ReflectionAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//1. 得到Boss类对应的Class对象
Class<?> bossCls = Class.forName("com.wexiao.reflection.Boss");
//2. 创建对象 无参构造器
Object o = bossCls.newInstance();
//3. 调用hi(String s) public
//getDeclaredMethod(方法名,参数类型)
Method hi = bossCls.getDeclaredMethod("hi", String.class);
hi.invoke(o, "wexiao");
//4. 调用say(int n, String s, char c) private,static
Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
//因为方法是私有的,所以要暴破
say.setAccessible(true);
say.invoke(o, 18, "wexiao", '牛');
say.invoke(null, 22, "wssss", 'n'); //因为是静态方法是全部类对象的,所以对象可以设置成null
//5. 在反射中,如果方法有返回值,统一返回 Object
Method hello = bossCls.getDeclaredMethod("hello");
Object returnValue = hello.invoke(o);
System.out.println("hello()的返回类型:" + returnValue.getClass());
}
}
class Monster {
}
class Boss {//类
public int age;
private static String name;
public Boss() {//构造器
}
public Monster m1() {
return new Monster();
}
private static void say(int n, String s, char c) {//静态方法
System.out.println(n + " " + s + " " + c);
}
public int hello() {
return 0;
}
public void hi(String s) {//普通 public 方法
System.out.println("hi " + s);
}
}
ReflectionHomework
package com.wexiao.reflection.homework;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author 微咲
* @version 1.0
*/
public class HomeWork02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//得到File类的Class对象
Class<?> fileCls = Class.forName("java.io.File");
//File类的全部构造器
Constructor<?>[] declaredConstructors = fileCls.getDeclaredConstructors();
int i = 0;
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("File类的构造器 " + ++i + ":" + declaredConstructor);
}
//创建文件
//得到public java.io.File(java.lang.String)指定构造器
Constructor<?> declaredConstructor = fileCls.getDeclaredConstructor(String.class);
//使用构造器创建对象,传入参数
String filePath = "e:\\reflection.txt";
Object file = declaredConstructor.newInstance(filePath);
//调用方法
Method createNewFile = fileCls.getDeclaredMethod("createNewFile");
createNewFile.invoke(file);
System.out.println("文件创建成功!" + filePath);
}
}
JDBC
JDBC基本介绍
- JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
- Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
- JDBC的基本原理
- 模拟JDBC
Java DataBase Connectivity standard (Java数据库连接)
JDBC原理图
JDBC好处
JDBC API
JDBC快速入门
JDBC 程序编写步骤
前置工作:引入mysql-connector-java.jar包
- 注册驱动 - 加载Driver类
- 获取连接 - 得到Connection
- 执行增删改查 - 发送SQL给mysql执行
- 释放资源 - 关闭相关连接
JDBC第一个程序
package com.wexiao.jdbc;
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 这是第一个Jdbc程序,完成简单的操作
*/
public class Jdbc01 {
public static void main(String[] args) throws SQLException {
//前置工作:mysql-connector-java
//1. 注册驱动 - 加载Driver类 (每个驱动类必须实现的接口)
Driver driver = new Driver(); //创建driver对象
//2. 获取连接 - 得到Connection
//jdbc:mysql:// 规定好表示协议,通过jdbc的方式连接mysql
//localhost 主机;可以是ip地址
//3307:表示mysql监听的端口
//wexiao_db02 连接到 mysql dbms的那个数据库
String url = "jdbc:mysql://localhost:3306/wexiao_db02";
//将 用户名和密码 放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user","root"); //用户
properties.setProperty("password","wexiao"); //密码
//获取连接 是数据库连接到指定的url
Connection connect = driver.connect(url, properties);
//3. 执行增删改查 - 发送SQL给mysql执行
//String sql = "insert into actor values(null , '刘德华' , '男' , '1970-11-11' , '110')";
//String sql = "update actor set name ='周星驰' where id = 1 ";
String sql = "delete from actor where id = 1 ";
// statement 用户执行静态SQL语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql); // 如果是dml语句,返回的就是影响的行数
System.out.println(rows > 0 ? "成功" : "失败");
//4. 释放资源 - 关闭相关连接
statement.close();
connect.close();
}
}
获得数据库连接(connection)的五种方式
在已经创建好的数据库 表中
方式一
方式二
方式三
方式四
方式五
代码演示
package com.wexiao.jdbc;
import com.mysql.jdbc.Driver;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 分析 java 连接 mysql 的 5 种方式
*/
public class JdbcConn {
//方式 1
@Test
public void connect01() throws SQLException {
//创建 driver 对象
Driver driver = new Driver();
String url = "jdbc:mysql://localhost:3306/wexiao_db02";
//将 用户名和密码放入到 Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "wexiao"); //密码
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
//方式 2
@Test
public void connect02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
//使用反射加载 Driver 类 , 动态加载,更加的灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/wexiao_db02";
//将 用户名和密码放入到 Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "wexiao"); //密码
Connection connect = driver.connect(url, properties);
System.out.println("方式 2=" + connect);
}
//方式 3 使用 DriverManager 替代 driver 进行统一管理
@Test
public void connect03() throws IllegalAccessException, InstantiationException, ClassNotFoundException, SQLException {
//使用反射加载 Driver
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
//创建 url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/wexiao_db02";
String user = "root";
String password = "wexiao";
DriverManager.registerDriver(driver);//注册 Driver 驱动
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第三种方式=" + connection);
}
//方式 4: 使用 Class.forName 自动完成注册驱动,简化代码
//这种方式获取连接是使用的最多,推荐使用
@Test
public void connect04() throws ClassNotFoundException, SQLException {
//使用反射加载了 Driver 类
//在加载 Driver 类时,完成注册
/*
源码: 1. 静态代码块,在类加载时,会执行一次.
2. DriverManager.registerDriver(new Driver());
3. 因此注册 driver 的工作已经完成
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*/
Class.forName("com.mysql.jdbc.Driver");
//创建 url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/wexiao_db02";
String user = "root";
String password = "wexiao";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第 4 种方式~ " + connection);
}
//方式 5 , 在方式 4 的基础上改进,增加配置文件,让连接 mysql 更加灵活
@Test
public void connect05() throws IOException, ClassNotFoundException, SQLException {
//通过 Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//建议写上
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式 5 " + connection);
}
}
mysql.properties
user=root
password=wexiao
url=jdbc:mysql://localhost:3306/wexiao_db02
driver=com.mysql.jdbc.Driver
ResultSet [结果集]
基本介绍
- 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
- ResultSet对象保持一个光标指向其当前的数据行。
- 最初,光标位于第一行之前next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回
false,因此可以在while循环中使用循环来遍历结果集(例如:select的结果集)
ResultSet代码演示
package com.wexiao.jdbc.resultset_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 演示select语句返回 ResultSet,取出结果
*/
@SuppressWarnings({"all"})
public class ResultSet_ {
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
//通过 Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//注册驱动
Class.forName(driver);//建议写上
//得到连接
Connection connection = DriverManager.getConnection(url, user, password);
// statement 用户执行静态SQL语句并返回其生成的结果的对象
Statement statement = connection.createStatement();
//组织SQL
String sql = "select id,name,sex,borndate from actor";
ResultSet resultSet = statement.executeQuery(sql);
/*
+----+--------+-----+---------------------+----------+
| id | name | sex | borndate | phone |
+----+--------+-----+---------------------+----------+
| 1 | 华子 | 男 | 1029-12-29 00:00:00 | 12312 |
| 2 | 无棣 | 女 | 1231-12-23 00:00:00 | 12312312 |
+----+--------+-----+---------------------+----------+
*/
//使用while读取
while (resultSet.next()) { //让光标向后移动,如果没有更多行,返回false
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String sex = resultSet.getString(3);
Date date = resultSet.getDate(4);
System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);
}
//关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
Statement(执行静态sql语句)
Statement基本介绍
- Statement对象用于执行静态SQL语句并返回其生成的结果的对象(执行静态sql语句,并获取结果集对象)
- 在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过
- Statement [存在SQL注入]
- PreparedStatement [预处理]
- CallableStatement [存储过程]
- Statement对象执行SQL语句,存在SQL注入风险
- SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。
- 要防范SQL注入,只要用PreparedStatement(从Statement扩展而来)取代Statement就可以了,
SQL注入
使用mysql数据库演示
sql_injection.sql
-- 演示sql 注入
-- 创建一张表
CREATE TABLE admin ( -- 管理员表
NAME VARCHAR(32) NOT NULL UNIQUE,
pwd VARCHAR(32) NOT NULL DEFAULT '') CHARACTER SET utf8;
-- 添加数据
INSERT INTO admin VALUES('tom', '123');
-- 查找某个管理是否存在
SELECT *
FROM admin
WHERE NAME = 'tom' AND pwd = '123'
-- SQL注入 (如果输入一些破坏性的字符串可能导致数据库毁坏)
-- 输入用户名 为 1' or
-- 输入万能密码 为 or '1'= '1
SELECT *
FROM admin
WHERE NAME = '1' OR' AND pwd = 'OR '1'= '1'
SELECT * FROM admin
使用java程序演示
package com.wexiao.jdbc.statement_;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/**
* @author 微咲
* @version 1.0
* 演示Statement的 SQL注入问题
*/
@SuppressWarnings({"all"})
public class Statement_ {
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
//通过 Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//注册驱动
Class.forName(driver);//建议写上
//得到连接
Connection connection = DriverManager.getConnection(url, user, password);
// statement 用户执行静态SQL语句并返回其生成的结果的对象
Statement statement = connection.createStatement();
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String admin_name = scanner.nextLine(); // 如果使用next方法,接受到空格或者 '就会默认(无视?结束?),nextLine()是以回车为结束标识
System.out.print("请输入密码:");
String admin_pwd = scanner.nextLine();
//组织SQL
String sql = "select name,pwd from admin where name='" + admin_name + "' and pwd='" + admin_pwd + "' ";
ResultSet resultSet = statement.executeQuery(sql);
// while (resultSet.next()) {
// String name = resultSet.getString(1);
// String pwd = resultSet.getString(2);
// System.out.println(name + "\t" + pwd);
// }
if (resultSet.next()){
System.out.println("登陆成功");
}else{
System.out.println("登陆失败");
}
//关闭连接
resultSet.close();
statement.close();
connection.close();
}
}
请输入用户名:1' or
请输入密码:or '1'= '1
登陆成功
PreparedStatement
PreparedStatement类图
PreparedStatement基本介绍
- PreparedStatement 执行的SQL语句中的参数用问号 (?) 来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数.
setXxx()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值 - 调用executeQuery(),返回ResultSet对象
- 调用executeUpdate():执行更新,包括增、删、修改,返回int类型(影响的行数)
PreparedStatement预处理的好处
- 不再使用+拼接sql语句,减少语法错误
- 有效的解决了sql注入问题 !
- 大大减少了编译次数,效率较高
演示 PreparedStatement的使用 (查询表)
package com.wexiao.jdbc.preparedstatement_;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/**
* @author 微咲
* @version 1.0
* 演示 PreparedStatement的使用
*/
@SuppressWarnings({"all"})
public class PreparedStatement_ {
public static void main(String[] args) throws Exception {
//通过 Properties 对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
//注册驱动
Class.forName(driver);//建议写上
//得到连接
Connection connection = DriverManager.getConnection(url, user, password);
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String admin_name = scanner.nextLine(); // 如果使用next方法,接受到空格或者 '就会默认(无视?结束?),nextLine()是以回车为结束标识
System.out.print("请输入密码:");
String admin_pwd = scanner.nextLine();
//(1)组织SQL
String sql = "select name,pwd from admin where name= ? and pwd=?";
// statement 用户执行静态SQL语句并返回其生成的结果的对象(执行静态sql语句,并获取结果集对象)
//(2)得到 PreparedStatement
//preparedStatement对象实现了PreparedStatement接口的实现类的对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//(3)给 ? 赋值
preparedStatement.setString(1,admin_name);
preparedStatement.setString(2,admin_pwd);
//(4)使用PreparedStatement执行executeQuery时不用添加sql语句了,因为在上面sql语句已经和preparedStatement关联了
//执行sql语句(查询), 增删更使用executeUptade
ResultSet resultSet = preparedStatement.executeQuery();
// while (resultSet.next()) {
// String name = resultSet.getString(1);
// String pwd = resultSet.getString(2);
// System.out.println(name + "\t" + pwd);
// }
if (resultSet.next()){
System.out.println("登陆成功");
}else{
System.out.println("登陆失败");
}
//关闭连接
resultSet.close();
preparedStatement.close();
connection.close();
}
}
PreparedStatement DML操作
建立JDBC工具类
在jdbc操作中,获取连接和释放资源是经常使用到可以将其封装JDBC连接的工真类JDBCUtils
JDBCUtils |
---|
连接 |
关闭 |
JDBCUtils
package com.wexiao.jdbc.utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* JDBC工具类,完成mysql连接,和关闭资源
*/
public class JdbcUtils {
//定义相关属性
private static String user; //用户名
private static String password; //密码
private static String url; //url
private static String drive; //驱动名
//在static代码块去初始化
static {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关属性的值
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
drive = properties.getProperty("drive");
} catch (IOException e) {
//在实际开发中,我们可以这样处理
//1. 将编译异常转成 运行异常
//2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
} finally {
}
}
/**
* //连接数据库
*
* @return Connection类型
*/
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
//1. 将编译异常转成 运行异常
//2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}
}
//关闭相关资源
/*
1. ResultSet 结果集
2. Statement 或者 PreparedStatement
3. Connection
4. 如果需要关闭资源,就传入对象,否则传入 null
*/
/**
* 关闭资源
* @param set 处理sql返回的结果集
* @param statement (执行静态sql语句)的对象
* @param connection 与sql的连接
*/
public static void close(ResultSet set, Statement statement, Connection connection) {
//判断是否为 null
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
//将编译异常转成运行异常抛出
throw new RuntimeException(e);
}
}
}
JDBCUtils工具类使用
package com.wexiao.jdbc.utils;
import org.junit.jupiter.api.Test;
import java.sql.*;
/**
* @author 微咲
* @version 1.0
* 测试工具类 sqlDML语句 和 Select语句
*/
public class Utils_Use {
public static void main(String[] args) {
}
@Test
public void testDML() {
Connection connection = null;
PreparedStatement preparedStatement = null;
//组织sql语句
String sql = "update actor set name = ? where id = ? ";
try {
//得到连接
connection = JdbcUtils.getConnection();
//得到PreparedStatement对象,处理sql语句
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "李华");
preparedStatement.setInt(2, 1);
//执行sql DML语句
preparedStatement.executeUpdate();
System.out.println("√");
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(null, preparedStatement, connection);
}
}
@Test
public void testSelect() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
//组织sql语句
String sql = "select * from actor where id = ?";
try {
//得到连接
connection = JdbcUtils.getConnection();
//得到PreparedStatement对象,处理sql语句
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 2);
//执行sql select语句
resultSet = preparedStatement.executeQuery();
//遍历结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String sex = resultSet.getString("sex");
Date borndate = resultSet.getDate("borndate");
String phone = resultSet.getString("phone");
System.out.println(id + "\t" + sex + "\t" + borndate + "\t" + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(resultSet, preparedStatement, connection);
}
}
}
将 JDBCUtils 工具类改成 Druid(德鲁伊)实现
package com.wexiao.jdbc.datasource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 基于druid数据库连接池的工具类
*/
public class JDBCUtilsByDruid {
private static DataSource ds;
//在静态代码块完成 ds初始化
static {
//创建 Properties 对象, 读取配置文件
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
//创建一个指定参数的数据库连接池
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取Connection连接
*
* @return Connection连接
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//关闭连接, 老师再次强调: 在数据库连接池技术中,close 不是真的断掉连接
//而是把使用的 Connection 对象放回连接池 //Connection是个接口,connection是根据厂商来重写的,所以close就会不同
/**
* 关闭资源,关闭连接
*/
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
JDBCUtilsDruid_Use
package com.wexiao.jdbc.datasource;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
/**
* @author 微咲
* @version 1.0
*/
public class JDBCUtilsByDruid_Use {
@Test
public void testSelect() {
System.out.println("使用 druid 方式完成");
//1. 得到连接
Connection connection = null;
//2. 组织一个 sql
String sql = "select * from actor where id >= ?";
PreparedStatement preparedStatement = null;
ResultSet set = null;
//3. 创建 PreparedStatement 对象
try {
connection = JDBCUtilsByDruid.getConnection();
System.out.println(connection.getClass());//运行类型 com.alibaba.druid.pool.DruidPooledConnection
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 1);//给?号赋值
//执行, 得到结果集
set = preparedStatement.executeQuery();
//遍历该结果集
while (set.next()) {
int id = set.getInt("id");
String name = set.getString("name");//getName()
String sex = set.getString("sex");//getSex()
Date borndate = set.getDate("borndate");
String phone = set.getString("phone");
System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtilsByDruid.close(set, preparedStatement, connection);
}
}
}
事务
JDBC事务介绍
- JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚
- JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务
- 调用Connection的 setAutoCommit(false) 可以取消自动提交事务
- 在所有的SQL语句都成功执行后,调用Connection 的 commit(); 方法提交事务
- 在其中某个操作失败或出现异常时,调用Connection的rollback();方法回滚事务
事务使用—-转账
CREATE TABLE ACCOUNT(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32) NOT NULL DEFAULT '',
balance DOUBLE NOT NULL DEFAULT 0)
CHARACTER SET utf8;
INSERT INTO ACCOUNT VALUES(NULL, '马云',3000);
INSERT INTO ACCOUNT VALUES(NULL, '马化腾',10000);
SELECT * FROM ACCOUNT
package com.wexiao.jdbc.transaction_;
import com.wexiao.jdbc.utils.JdbcUtils;
import org.junit.jupiter.api.Test;
import java.sql.*;
/**
* @author 微咲
* @version 1.0
* 演示jdbc中事务的使用
*/
public class Transaction_ {
@Test
/**
* 不使用事务进行转账(如果中途出现异常可能会出现数据丢失)
*/
public void noTransaction() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
//组织sql语句
String sql1 = "update account set balance=balance-100 where id = 1";
String sql2 = "update account set balance=balance+100 where id = 2";
try {
//得到连接
connection = JdbcUtils.getConnection();//在默认情况下,connection事务自动提交
//得到PreparedStatement对象,处理sql语句
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.executeUpdate(); //执行第一条sql语句
int i = 1 / 0;//故意出现异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); //执行第二条sql语句
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(null, preparedStatement, connection);
}
}
@Test
/**
* 使用事务进行转账
*/
public void useTransaction() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
//组织sql语句
String sql1 = "update account set balance=balance-100 where id = 1";
String sql2 = "update account set balance=balance+100 where id = 2";
try {
//得到连接
connection = JdbcUtils.getConnection();
//启用事务,将connection 设置为不自动提交
connection.setAutoCommit(false);
//得到PreparedStatement对象,处理sql语句
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.executeUpdate(); //执行第一条sql语句
int i = 1 / 0;//故意出现异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); //执行第二条sql语句
//提交事务
connection.commit();
} catch (SQLException e) {
try {
//这里我们可以进行回滚,即撤销执行的 SQL
//默认回滚到事务开始的状态.
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
} finally {
JdbcUtils.close(null, preparedStatement, connection);
}
}
}
批处理应用
批处理基本介绍
- 当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
- JDBC的批量处理语句包括下面方法:
- addBatch() : 添加需要批量处理的SQL语句或参数
- executeBatch() : 执行批量处理语句;
- clearBatch() : 清空批处理包的语句
- JDBC连接MySQL时,如果要使用批处理功能,请再url中加参数? rewriteBatchedStatements=true
- 批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高
批处理演示
- 批处理所需表
CREATE TABLE admin2(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32) NOT NULL,
PASSWORD VARCHAR(32) NOT NULL);
SELECT * FROM admin2
SELECT COUNT(*) FROM admin2
DROP TABLE admin2
- 批处理演示
package com.wexiao.jdbc.batch_;
import com.wexiao.jdbc.utils.JdbcUtils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author 微咲
* @version 1.0
* 演示批处理
*/
public class Batch_ {
@Test
/**
* 不使用批处理,给表加入数据
*/
public void noBatch() throws Exception {
Connection connection = JdbcUtils.getConnection();
String sql = "insert into admin2 values(null,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行...");
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("传统方式,用时:" + (end - start));//2745
//关闭连接
JdbcUtils.close(null, preparedStatement, connection);
}
@Test
/**
* 批处理,给表加入数据
*/
public void Batch() throws Exception {
Connection connection = JdbcUtils.getConnection();
String sql = "insert into admin2 values (null,?,?)"; //values后要加空格
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行...");
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
/*
addBatch()源码
//1. //第一就创建 ArrayList - elementData => Object[]
//2. elementData => Object[] 就会存放我们预处理的 sql 语句
//3. 当 elementData 满后,就按照 1.5 扩容
//4. 当添加到指定的值后,就 executeBatch
//5. 批量处理会减少我们发送 sql 语句的网络开销,而且减少编译次数,因此效率提高
public void addBatch() throws SQLException {
synchronized(this.checkClosed().getConnectionMutex()) {
if (this.batchedArgs == null) {
this.batchedArgs = new ArrayList();
}
for(int i = 0; i < this.parameterValues.length; ++i) {
this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
}
this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
}
}
*/
//将sql语句加入到批处理包中
preparedStatement.addBatch();
//当有1000条记录时,再批量处理
if ((i + 1) % 1000 == 0){
//执行此1000条
preparedStatement.executeBatch();
//清空处理过的1000条
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("批处理方式,用时:" + (end - start));//77
//关闭连接
JdbcUtils.close(null, preparedStatement, connection);
}
}
addBatch(): 1.5倍自增长
数据库连接池
5k 次连接数据库问题
package com.wexiao.jdbc.datasource;
import com.wexiao.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
/**
* @author 微咲
* @version 1.0
*/
public class ConQuestion {
@Test
/**
* 模拟5k次连接数据库
*/
public void testCon() {
long start = System.currentTimeMillis();
//模拟多连接数据库
for (int i = 0; i < 5000; i++) {
Connection connection = JDBCUtils.getConnection();
//做一些工作,比如得到PreparedStatement,发送sql
//....
//关闭
JDBCUtils.close(null, null, connection);
}
long end = System.currentTimeMillis();
System.out.println(end - start); //15707
}
}
传统获取 Connection 问题分析
- 传统的JDBC数据库连接使用 DriverManaaer来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证IP地址,用户名和密码(0.05s ~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
- 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃。
- 解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)。
数据库连接池基本介绍
- 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中
数据库连接池原理图
数据库连接池种类
- JDBC的数据库连接池使用javax.sqI.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现[提供.jar]
- C3P0数据库连接池,速度相对较慢,稳定性不错(hibernate, spring)
- DBCP数据库连接池,速度相对c3p0较快,但不稳定
- Proxool数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- BoneCP数据库连接池,速度快
- Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3P0、Proxool优点于一身的数据库连接池
使用数据库连接池准备工作
引入jar包
C3P0
引入 c3p0jar.包
引入、更改 xml文件
c3p0-confi
<c3p0-config>
<!-- 数据源名称代表连接池 -->
<named-config name="wexiao">
<!-- 驱动类 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- url-->
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/wexiao_db02</property>
<!-- 用户名 -->
<property name="user">root</property>
<!-- 密码 -->
<property name="password">wexiao</property>
<!-- 每次增长的连接数-->
<property name="acquireIncrement">5</property>
<!-- 初始的连接数 -->
<property name="initialPoolSize">10</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!-- 最大连接数 -->
<property name="maxPoolSize">10</property>
<!-- 可连接的最多的命令对象数 -->
<property name="maxStatements">5</property>
<!-- 每个连接对象可连接的最多的命令对象数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
C3P0演示
package com.wexiao.jdbc.datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.jupiter.api.Test;
import java.beans.PropertyVetoException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 演示c3p0的使用
*/
public class C3P0_ {
//方式一:相关参数,在程序中指定user,password,url...
@Test
public void testC3P0_01() throws IOException, PropertyVetoException, SQLException {
//1.创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//2.通过配置文件mysql.properties获取相关信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关属性的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String drive = properties.getProperty("drive");
//给数据源 comboPooledDataSource 设置相关参数
//注意:连接管理是由 comboPooledDataSource 来管理
comboPooledDataSource.setDriverClass(drive);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
comboPooledDataSource.setJdbcUrl(url);
//设置数据源的初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
//最大连接数
comboPooledDataSource.setMaxPoolSize(50);
//测试C3P0连接池速度
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection();//这个方法是 DataSource 接口实现的
//System.out.println("连接成功...");
connection.close();//回收
}
long end = System.currentTimeMillis();
System.out.println("C3P0 5000次连接mysql耗时:" + (end - start)); //C3P0 5000次连接mysql耗时:360
}
//方式二:使用配置文件模板来完成
//1. 将 c3p0 提供的 c3p0.config.xml 拷贝到 src 目录下
//2. 该文件指定了连接数据库和连接池的相关参数
//更改相应参数
@Test
public void testC3P0_02() throws SQLException {
//写入数据源的名称
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("wexiao");
//获得连接
Connection connection = comboPooledDataSource.getConnection();
System.out.println("方式二连接成功...");
connection.close();
}
}
Druid
引入Druid.jar包
引入、更改druid.properties
druid.properties
#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/wexiao_db02?rewriteBatchedStatements=true
#url=jdbc:mysql://localhost:3306/girls
username=root
password=wexiao
#initial connection Size
initialSize=10
#min idle(空置的) connection size
minIdle=5
#max active connection size
maxActive=20
#max wait time(最大等待时间) (5000 mil seconds)
maxWait=5000
druid演示
- 在数据库连接池技术中,close 不是真的断掉连接,而是把使用的 Connection 对象放回连接池
- Connection是个接口,connection是根据厂商来重写的,所以close就会不同
package com.wexiao.jdbc.datasource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* Druid的使用
*/
public class Druid_ {
@Test
public void testDruid() throws Exception {
//1. 加入 Druid jar 包
//2. 加入 配置文件 druid.properties , 将该文件拷贝项目的 src 目录
//3. 创建 Properties 对象, 读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//4.创建一个指定参数的数据库连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
System.out.println("连接成功");
connection.close();
}
}
将 JDBCU_tils 工具类改成 Druid(德鲁伊)实现
package com.wexiao.jdbc.datasource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 基于druid数据库连接池的工具类
*/
public class JDBCUtilsByDruid {
private static DataSource ds;
//在静态代码块完成 ds初始化
static {
//创建 Properties 对象, 读取配置文件
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
//创建一个指定参数的数据库连接池
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取Connection连接
*
* @return Connection连接
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//关闭连接, 老师再次强调: 在数据库连接池技术中,close 不是真的断掉连接
//而是把使用的 Connection 对象放回连接池 //Connection是个接口,connection是根据厂商来重写的,所以close就会不同
/**
* 关闭资源,关闭连接
*/
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
Apache-DBUtils
ApacheDBUtils引出
表和 JavaBean 的类型映射关系
使用土方法封装ResultSet
// 使用土方法来解决ResultSet 封装=> ArrayList
@Test
public void testSelectToArrayList() {
System.out.println("使用 druid 方式完成");
//1. 得到连接
Connection connection = null;
//2. 组织一个 sql
String sql = "select * from actor where id >= ?";
PreparedStatement preparedStatement = null;
ResultSet set = null;
ArrayList<Actor> list = new ArrayList<Actor>();
//3. 创建 PreparedStatement 对象
try {
connection = JDBCUtilsByDruid.getConnection();
System.out.println(connection.getClass());//运行类型 com.alibaba.druid.pool.DruidPooledConnection
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 1);//给?号赋值
//执行, 得到结果集
set = preparedStatement.executeQuery();
//遍历该结果集
while (set.next()) {
int id = set.getInt("id");
String name = set.getString("name");//getName()
String sex = set.getString("sex");//getSex()
Date borndate = set.getDate("borndate");
String phone = set.getString("phone");
//把得到的resultset 的记录,封装到 Actor对象,放入到list集合
list.add(new Actor(id, name, sex, borndate, phone));
}
System.out.println("list集合:" + list);
for (Actor actor : list) {
System.out.println("id=" + actor.getId() + "\tname=" + actor.getName());
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtilsByDruid.close(set, preparedStatement, connection);
}
//因为ArrayList 和 connection 没有任何关联,所以该集合可以复用
//return list;
}
Apache-DBUtils基本介绍
- commons-dbutils 是 Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量[真的]。
- DbUtils类
- QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理使用
- QueryRunner类实现查询
- ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式
QueryRunner类实现查询
queryRunner.query(connection, sql, new BeanHandler<>(类名.class), 赋值语句);
//(1) query方法 就是实行sql语句,得到resultset -- 封装到 ---- ArrayList集合中
//(2) 返回集合
//(3) connection:连接
//(4) sql:执行的sql语句
//(5) new BeanListHandler<>(Actor.class):将resultset -> actor对象 -> 封装到ArrayList (BeanListHandle:将actor封装成集合) (Actor类中属性对应表中属性)
// 底层使用反射机制,去获得Actor 类的属性,然后进行封装
//(6) 1: 就是sql语句中的 ? 赋值的,可以有个多个值,因为是可变参数Object... params
//(7) 底层得到的resultSet,preparedStatement 会在query方法中关闭
对结果集的处理(处理器)
ArrayHandler:把结果集中的第一行数据转成对象数组。
ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
ColumnListHandler:将结果集中某一列的数据存放到List中。
KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key.
MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
使用DBUtils + 数据池(德鲁伊) 对表 crud
引入DBUtils相关jar包
DBUtils代码演示
查询表,返回多行、单行、单行单列
package com.wexiao.jdbc.datasource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author 微咲
* @version 1.0
*/
public class DBUtils_Use {
//使用Apache-DBUtils工具类 + druid 完成对表的操作
/**
* 演示Apache-DBUtils工具类
* 查询表,返回多个结果 -> 集合
* queryRunner.query()方法中,new BeanListHandler<>(Actor.class),返回多行,返回类型是ArrayList
* @throws SQLException
*/
@Test
public void testQueryMany() throws SQLException {
//1. 得到连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用DBUtils 类和接口,先引入相关的jar包,加入到本项目
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. QueryRunner就可以执行相关的方法,返回ArrayList结果集
String sql = "select * from actor where id >= ? ";
//解读
//(1) query方法 就是实行sql语句,得到resultset -- 封装到 ---- ArrayList集合中
//(2) 返回集合
//(3) connection:连接
//(4) sql:执行的sql语句
//(5) new BeanListHandler<>(Actor.class):将resultset -> actor对象 -> 封装到ArrayList (BeanListHandle:将actor封装成集合) (Actor类中属性对应表中属性)
// 底层使用反射机制,去获得Actor 类的属性,然后进行封装
//(6) 1: 就是sql语句中的 ? 赋值的,可以有个多个值,因为是可变参数Object... params
//(7) 底层得到的resultSet,preparedStatement 会在query方法中关闭
List<Actor> list = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
/**
* 分析 queryRunner.query 方法:
* public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throwsSQLException {
* PreparedStatement stmt = null;//定义 PreparedStatement
* ResultSet rs = null;//接收返回的 ResultSet
* Object result = null;//返回 ArrayList
*
* try {
* stmt = this.prepareStatement(conn, sql);//创建 PreparedStatement
* this.fillStatement(stmt, params);//对 sql 进行 ? 赋值
* rs = this.wrap(stmt.executeQuery());//执行 sql,返回 resultset
* result = rsh.handle(rs);//返回的 resultset --> arrayList[result] [使用到反射,对传入 class 对象处理]
* } catch (SQLException var33) {
* this.rethrow(var33, sql, params);
* } finally {
* try {
* this.close(rs);//关闭 resultset
* } finally {
* this.close((Statement)stmt);//关闭 preparedstatement 对象
* }
* }
*
* return result;
* }
*/
for (Actor actor : list) {
System.out.println(actor);
}
//释放资源,底层得到的resultSet,preparedStatement 会在query方法中关闭所以不在这里关闭了
JDBCUtilsByDruid.close(null, null, connection);
}
/**
* 演示使用DBUtils查询表,返回一条结果
* queryRunner.query()方法中,new BeanHandler<>(Actor.class),返回单行,返回类型是Actor
* @throws SQLException
*/
@Test
public void testQuerySingle() throws SQLException {
//1. 得到连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用DBUtils 类和接口,先引入相关的jar包,加入到本项目
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. QueryRunner就可以执行相关的方法,返回单个对象
String sql = "select * from actor where id = ? ";
Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 1);
System.out.println(actor);
//释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
/**
* queryRunner.query()方法中,new ScalarHandler(),返回单行单列,返回类型是object
* @throws SQLException
*/
@Test
public void testQueryScalar() throws SQLException {
//1. 得到连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用DBUtils 类和接口,先引入相关的jar包,加入到本项目
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. QueryRunner就可以执行相关的方法,返回单个对象
String sql = "select name from actor where id = ? ";
Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 1);
System.out.println(obj);
//释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
}
演示apache-dbutils + druid 完成dml(update,insert,delete)
/**
* 演示apache-dbutils + druid 完成dml(update,insert,delete)
*
* @throws SQLException
*/
@Test
public void testDML() throws SQLException {
//1. 得到连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用DBUtils 类和接口,先引入相关的jar包,加入到本项目
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. 组织sql语句 DML
//String sql = "update actor set name = ? where id = ? ";
//String sql = "insert into actor values (?,?,?,?,?)";
String sql = "delete from actor where id = ?";
//执行DML操作
//(1) 执行 dml 操作是 queryRunner.update()
//(2) 返回的值是受影响的行数 (affected: 受影响)
//int affectedRows = queryRunner.update(connection, sql, "初恋", 1);
//int affectedRows = queryRunner.update(connection, sql, null, "ss", "女", "2001-01-21", 10027);
int affectedRows = queryRunner.update(connection, sql, 2);
System.out.println(affectedRows > 0 ? "执行成功" : "执行没有影响到表");
//释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
Dao
BasicDao引入
apache-dbutils+ Druid简化了JDBC开发,但还有不足;
- SQL语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
- 对于select 操作,如果有返回值,返回类型不能固定,需要使用泛型
- 将来的表很多,业务需求复杂,不可能只靠一个Java类完成
- 引出 => BasicDAO画出示意图,看看在实际开发中,应该如何处理
BasicDAO示意图
DAO基本说明
- DAO:data access object数据访问对象
- 这样的通用类,称为 BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作。
- 在BaiscDao的基础上,实现一张表对应一个Dao,更好的完成功能,
比如 Customer表 - Customer.java类(javabean) - CustomerDao.java
BasicDAO 应用实例
dao演示
dao
BasicDAO.java
package com.wexiao.jdbc.dao_.dao;
import com.wexiao.jdbc.dao_.utils.JDBCUtilsByDruid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* @author 微咲
* @version 1.0
* 开发BasicDAO,是其他DAO的父类,使用到apache-dbutils
*/
public class BasicDAO<T> { //泛型指定具体类型
private QueryRunner qr = new QueryRunner();
//开发通用的DML方法,针对任意表
/**
* 对任意表进行DML操作
*
* @param sql sql语句,可以带有?
* @param parameters ?具体的值,可以传入多个
* @return
*/
public int update(String sql, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
int update = qr.update(connection, sql, parameters);
return update;
} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常 -> 运行异常 抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
//查询表返回多个对象,针对任意表
/**
* 查询表返回多个对象,针对任意表
*
* @param sql sql语句,可以有?
* @param clazz 传入一个类的Class对象,比如 Actor.class
* @param parameters 传入?具体的值,可以是多个
* @return 根据Actor.class 返回对应的 ArrayList集合
*/
public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
// 使用QueryRunner的query()方法,处理器:new BeanListHandler<T>(clazz) 查询表
List<T> query = qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
return query;
} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常 -> 运行异常 抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
/**
* 查询表返回单行,针对任意表
*
* @param sql sql语句,可以有?
* @param clazz 传入一个类的Class对象,比如 Actor.class
* @param parameters 传入?具体的值,可以是多个
* @return 根据Actor.class 返回对应的单行结果
*/
public T querySingle(String sql, Class<T> clazz, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
// 使用QueryRunner的query()方法,处理器:new BeanListHandler<T>(clazz) 查询表
T query = qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);
return query;
} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常 -> 运行异常 抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
/**
* 查询表返回单行单列,针对任意表
*
* @param sql sql语句,可以有?
* @param parameters 传入?具体的值,可以是多个
* @return 根据Actor.class 返回对应的 单行单列
*/
public Object queryScalar(String sql, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
// 使用QueryRunner的query()方法,处理器:new BeanListHandler<T>(clazz) 查询表
Object query = qr.query(connection, sql, new ScalarHandler(), parameters);
return query;
} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常 -> 运行异常 抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
}
ActorDAO.java
package com.wexiao.jdbc.dao_.dao;
import com.wexiao.jdbc.dao_.domain.Actor;
/**
* @author 微咲
* @version 1.0
*/
public class ActorDAO extends BasicDAO<Actor> {
//有BasicDAO 的方法
//根据业务需求,可以编写特有的方法
}
AccountDAO.java
package com.wexiao.jdbc.dao_.dao;
import com.wexiao.jdbc.dao_.domain.Account;
/**
* @author 微咲
* @version 1.0
*/
public class AccountDAO extends BasicDAO<Account> {
//有BasicDAO 的方法
//根据业务需求,可以编写特有的方法
}
domain
Actor.java
package com.wexiao.jdbc.dao_.domain;
import java.util.Date;
/**
* @author 微咲
* @version 1.0
* Actor 对象和 actor表的记录对应
*/
public class Actor {
private Integer id;
private String name;
private String sex;
private Date borndate;
private String phone;
public Actor(Integer id, String name, String sex, Date borndate, String phone) {
this.id = id;
this.name = name;
this.sex = sex;
this.borndate = borndate;
this.phone = phone;
}
public Actor() { //一定要给一个无参构造器[反射需要]
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBorndate() {
return borndate;
}
public void setBorndate(Date borndate) {
this.borndate = borndate;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "\nActor{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", borndate=" + borndate +
", phone='" + phone + '\'' +
'}';
}
}
Account.java
package com.wexiao.jdbc.dao_.domain;
/**
* @author 微咲
* @version 1.0
*/
public class Account {
private Integer id;
private String name;
private Double balance;
public Account(Integer id, String name, Double balance) {
this.id = id;
this.name = name;
this.balance = balance;
}
public Account() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", balance=" + balance +
'}';
}
}
test
TestDAO.java
package com.wexiao.jdbc.dao_.test;
import com.wexiao.jdbc.dao_.dao.AccountDAO;
import com.wexiao.jdbc.dao_.dao.ActorDAO;
import com.wexiao.jdbc.dao_.domain.Account;
import com.wexiao.jdbc.dao_.domain.Actor;
import org.junit.jupiter.api.Test;
import java.util.List;
/**
* @author 微咲
* @version 1.0
*/
public class TestDAO {
//测试ActorDAO 对actor表crud操作
@Test
public void testActorDAO() {
ActorDAO actorDAO = new ActorDAO();
//1. 查询多行记录
List<Actor> actors = actorDAO.queryMulti("select * from actor where id >= ?", Actor.class, 1);
for (Actor actor : actors) {
System.out.println(actor);
}
//2. 查询单行记录
Actor actor = actorDAO.querySingle("select * from actor where id = ? ", Actor.class, 1);
System.out.println(actor);
//3. 查询单行单列
Object o = actorDAO.queryScalar("select name from actor where id = ? ", 1);
System.out.println(o);
//4. DML
// int update = actorDAO.update("insert into actor values (?,?,?,?,?)", 66, "宝贝", "女", "2001-10-21 12:32:56.2", 23124231);
// System.out.println(update > 0 ? "执行成功" : "执行没有影响到表");
int update = actorDAO.update("update actor set id = ? where id = ? ", 666, 66);
System.out.println(update > 0 ? "执行成功" : "执行没有影响到表");
//"delete from actor where id = 666"
}
//测试ActorDAO 对account表crud操作
@Test
public void testAccountDAO() {
AccountDAO accountDAO = new AccountDAO();
//1. 查询多行数据
List<Account> accounts = accountDAO.queryMulti("select * from account where id >= ?", Account.class, 1);
for (Account account : accounts) {
System.out.println(account);
}
//2.DML
int update = accountDAO.update("update account set balance = ? where id = ?", 66666.99, 1);
System.out.println(update > 0 ? "执行成功" : "执行未生效");
}
}
utils
JDBCUtilsByDruid.java
package com.wexiao.jdbc.dao_.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author 微咲
* @version 1.0
* 基于druid数据库连接池的工具类
*/
public class JDBCUtilsByDruid {
private static DataSource ds;
//在静态代码块完成 ds初始化
static {
//创建 Properties 对象, 读取配置文件
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
//创建一个指定参数的数据库连接池
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取Connection连接
*
* @return Connection连接
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//关闭连接, 再次强调: 在数据库连接池技术中,close 不是真的断掉连接
//而是把使用的 Connection 对象放回连接池 //Connection是个接口,connection是根据厂商来重写的,所以close就会不同
/**
* 关闭资源,关闭连接
*/
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
正则表达式(regexp)
regular expression
体验正则表达式
简单地说:正则表达式是对字符串执行模式匹配的技术。
//使用正则的步骤
//1.先创建一个Pattern 对象,悞式对象,叮以理解成就是一个止则表达式对象
//Pattern pattern = Pattern.compile("[a-zA-Z]+");
//Pattern pattern = Pattern.compile("[O-9]+");
//Pattern pattern = Pattern.compile("([0-9]+)|([a-zA-Z]+)");
//Pattern pattern = Pattern.compile("<a target=\"_blank\" title=\"(\\S*)\"");
//2.创建一个匹配器对象
//理解 : 就是 matcher匹配器按照 pattern(模式/样式),到 content文本中去匹配
//找到就返回true,否则就返回false
Matcher matcher = pattern.matcher(content);
//3.开始循环匹配
while(matcher.find()){
//匹配内容、文本、放到m.group(0)、m.group(1)
System.out.println(matcher.group(0));
//System.out.println(matcher.group(1));
}
正则表达式基本介绍
- 一个正则表达式,就是用某种模式去匹配字符串的一个公式。很多人因为它们着上去比较古怪而且复杂所以不敢去使用,不过,经过练习后,就觉得这些复杂的表达式写起来还是相当简单的,而且,一旦你弄懂它们,你就能把数小时辛苦而且易错的文本处理工作缩短在几分钟(甚至几秒钟)内完成
- 特别强调,正则表达式不是只有java才有,实际上很多编程语言都支持正则表达式进行字符串操作! 比如javascript,php,java…
- 正则表达式可以用来做什么?给定一个正则表达式和另一个字符串,我们可以达到如下的目的:
- 检查给定的字符串是否符合正则表达式的过滤逻辑,即能否匹配;
- 可以通过正则表达式的匹配,从字符串中提取或替换特定的部分。
正则表达式底层实现
- 开始匹配,matcher.find()找到要查找元素的索引,记录到int[] groups中,如果是分组的话,groups[0],[1]记录的是全部子字符串的位置,groups[2],[3]记录的是第一组()的子串索引,groups[4],[5]记录的是第二组()的子串索引
- matcher.find()中
- boolean search(int from)方法中
1. for (int i = 0; i < groups.length; i++){ //将groups数组元素全部重置为-1 groups.length = 20
groups[i] = -1;
}
2. boolean result = parentPattern.root.match(this, from, text);通过前置得到的索引,得到下一个所求索引
- boolean search(int from)方法中
- matcher.group(),从content里截取子字符串返回。
group是针对()来说的,group(0)就是指的整个串,group(1) 指的是第一个括号里的东西,group(2)指的第二个括号里的东西
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 分析java的正则表达式的底层实现(重要)
*/
public class RegTheory {
public static void main(String[] args) {
String content = "1998 年 12 月 8 日,第二代 Java 平台的企业版 J2EE 发布。1999 年 6 月,Sun 公司发布了" +
"第二代 Java 平台(简称为 Java2)的 3 个版本:J2ME(Java2 Micro Edition,Java2 平台的微型" +
"版),应用于移动、无线及有限资源的环境;J2SE(Java 2 Standard Edition,Java 2 平台的" +
"标准版),应用于桌面环境;J2EE(Java 2Enterprise Edition,Java 2 平台的企业版),应" +
"用 3443 于基于 Java 的应用服务器。Java 2 平台的发布,是 Java 发展过程中最重要的一个" +
"里程碑,标志着 Java 的应用开始普及 9889 ";
//目标:匹配所有四个数字
//说明
//1. 创建正则表达式的要求内容,\d 表示一个任意的数字
String regStr = "(\\d\\d)(\\d\\d)";
//2. 创建模式对象[即正则表达式对象]
Pattern pattern = Pattern.compile(regStr);
//3. 创建匹配器
// 创建匹配器matcher,按照 正则表达式的规则pattern 去匹配 content
Matcher matcher = pattern.matcher(content);
//4. 开始匹配
while (matcher.find()) {
//小结
//1. 如果正则表达式有(),即分组
//2. group(0) 表示匹配到的子字符串
//3. group(1) 表示匹配到的子字符串的第一组子串
//4. group(2) 表示匹配到的子字符串的第二组子串
//5. 但是分组的数不能越界
// 每次循环都会继承上一次的索引
System.out.println("找到:" + matcher.group(0));
System.out.println("第一组():" + matcher.group(1));
System.out.println("第二组():" + matcher.group(2));
}
/*
matcher.find()完成的任务 考虑分组
boolean search(int from)方法中
for (int i = 0; i < groups.length; i++){ //将groups数组元素全部重置为-1 groups.length = 20
groups[i] = -1;
}
boolean result = parentPattern.root.match(this, from, text);通过前置得到的索引,得到下一个所求索引
1. 根据指定的规则,定位满足规则的子字符串[比如(19)(98)]
2. 找到后,将子字符串的开始的索引记录到matcher对象属性 int[] groups;中
2.1 groups[0] = 0 ,把该子字符串的结束的索引+1的值记录到groups[1] = 4,
2.2 记录1组()匹配到的字符串 groups[2] = 0 groups[3] = 2
2.3 记录2组()匹配到的字符串 groups[4] = 2 groups[5] = 4
2.4 如果有更多的分组...
3. 同时记录 oldLast的值为 子字符串的结束的索引+1的值即4,即下次执行find()方法时,
就从4开始匹配,
matcher.group(0)分析
源码:
public String group(int group) {
if (first < 0)
throw new IllegalStateException("No match found");
if (group < 0 || group > groupCount())
throw new IndexOutOfBoundsException("No group " + group);
if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
return null;
return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
}
1. 根据 groups[0] = 0 和 groups[1] = 4 的记录的位置,从content里截取子字符串返回,
就是[0,4)
2. 如果再次指向 find()方法,仍然按照上面分析执行
*/
}
}
正则表达式语法
- 限定符
- 选择匹配符
- 分组组合和反向引用符
- 特殊字符
- 字符匹配符
- 定位符
元字符
下表包含了元字符的完整列表以及它们在正则表达式上下文中的行为:
字符 | 描述 |
---|---|
\ | 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,’n’ 匹配字符 “n”。’\n’ 匹配一个换行符。序列 ‘\\‘ 匹配 “\“ 而 “\(“ 则匹配 “(“。 |
^ | 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 ‘\n’ 或 ‘\r’ 之后的位置。 |
$ | 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 ‘\n’ 或 ‘\r’ 之前的位置。 |
* | 匹配前面的子表达式零次或多次。例如,zo 能匹配 “z” 以及 “zoo”。 等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,’zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。 |
? | 匹配前面的子表达式零次或一次。例如,”do(es)?” 可以匹配 “do” 或 “does” 。? 等价于 {0,1}。 |
{n} | n 是一个非负整数。匹配确定的 n 次。例如,’o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。 |
{n,} | n 是一个非负整数。至少匹配n 次。例如,’o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。’o{1,}’ 等价于 ‘o+’。’o{0,}’ 则等价于 ‘o*’。 |
{n,m} | m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,”o{1,3}” 将匹配 “fooooood” 中的前三个 o。’o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。 |
? | 当该字符紧跟在任何一个其他限制符 (, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而*默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,’o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。 |
. | 匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用像”(.|\n)“的模式。 |
(pattern) | 匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 ‘(‘ 或 ‘)’。 |
(?:pattern) | 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 “或” 字符 (|) 来组合一个模式的各个部分是很有用。例如, ‘industr(?:y|ies) 就是一个比 ‘industry|industries’ 更简略的表达式。 |
(? =pattern) | 正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,”Windows(?=95|98|NT|2000)”能匹配”Windows2000”中的”Windows”,但不能匹配”Windows3.1”中的”Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。 |
(?!pattern) | 正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如”Windows(?!95|98|NT|2000)”能匹配”Windows3.1”中的”Windows”,但不能匹配”Windows2000”中的”Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。 |
(?<=pattern) | 反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,”(?<=95|98|NT|2000)Windows “能匹配” 2000Windows “中的” Windows “,但不能匹配” 3.1Windows “中的” Windows “。 |
(?<!pattern) | 反向否定预查,与正向否定预查类似,只是方向相反。例如”(?<!95|98|NT|2000)Windows “能匹配” 3.1Windows “中的” Windows “,但不能匹配” 2000Windows “中的” Windows “。 |
x|y | 匹配 x 或 y。例如,’z|food’ 能匹配 “z” 或 “food”。’(z|f)ood’ 则匹配 “zood” 或 “food”。 |
[xyz] | 字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’。 |
[^xyz] | 负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’、’l’、’i’、’n’。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,’[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。 |
[^a-z] | 负值字符范围。匹配任何不在指定范围内的任意字符。例如,’[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。 |
\B | 匹配非单词边界。’er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。 |
\cx | 匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。 |
\d | 匹配一个数字字符。等价于 [0-9]。 |
\D | 匹配一个非数字字符。等价于 [^0-9]。 |
\f | 匹配一个换页符。等价于 \x0c 和 \cL。 |
\n | 匹配一个换行符。等价于 \x0a 和 \cJ。 |
\r | 匹配一个回车符。等价于 \x0d 和 \cM。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
\S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
\t | 匹配一个制表符。等价于 \x09 和 \cI。 |
\v | 匹配一个垂直制表符。等价于 \x0b 和 \cK。 |
\w | 匹配字母、数字、下划线。等价于’[A-Za-z0-9_]’。 |
\W | 匹配非字母、数字、下划线。等价于 ‘[^A-Za-z0-9_]’。 |
\xn | 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,’\x41’ 匹配 “A”。’\x041’ 则等价于 ‘\x04’ & “1”。正则表达式中可以使用 ASCII 编码。 |
\num | 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,’(.)\1’ 匹配两个连续的相同字符。 |
\n | 标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。 |
\nm | 标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。 |
\nml | 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。 |
\un | 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。 |
转义号 \ (在JAVA中)
package com.hspedu.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 韩顺平
* @version 1.0
* 演示转义字符的使用
*/
public class RegExp02 {
public static void main(String[] args) {
String content = "abc$(a.bc(123( )";
//匹配( => \\(
//匹配. => \\. //String regStr = "\\.";
//String regStr = "\\d\\d\\d";
String regStr = "\\d{3}";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到 " + matcher.group(0));
}
}
}
元字符-字符匹配符
匹配符说明
- abc 表示可以匹配abc
- [a-z] 表示可以匹配a-z中任意一个字符
- [^a-z] 表示可以匹配除a-z中的任意一个字符
- java正则表达式默认是区分字母大小写的
- (?i)abc 表示abc都不区分大小写
- a(?i)bc 表示bc不区分大小写
- a((?i)b)c 表示只有b不区分大小写
- Pattern pat = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
- [abcd] 表示可以匹配abcd中的任意一个字符 [.?><:”:L.{:{}:@#%^@&$}] [.] -> \.
- [^abcd] 表示可以匹配不是abcd中的任意一个字符
- \d表示可以匹配0-9的任意一个数字,相当于[0-9].
- \D表示可以匹配不是0-9中的任意一个数字,相当于[ ^ 0-9]
- \w匹配任意英文字符、数字和下划线,相当于[a-zA-Z0-9_ ]
- \W 相当于[ ^a-zA-Z0-9_] 是\w刚好相反.
- \s匹配任何空白字符(空格,制表符等)
- \S 匹配任何非空白字符,和\s刚好相反
- . 匹配出\n之外的所有字符,如果要匹配.本身则需要使用 \.
匹配符演示
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 演示字符匹配符的使用
*/
public class RegExp03 {
public static void main(String[] args) {
String content = "a11dWhB23u PwaZ32A@BC。1¥idu RG8_a bc23`1Xj k `a2Ed9 @8F1_aB.c2M31Zk.Glj3d";
//String regStr = "[a-z]"; //匹配 a-z 之间任意一个字符
//String regStr = "[A-Z]"; //匹配 A-Z 之间任意一个字符
//String regStr = "[a-c]"; //匹配 a-c 之间任意一个字符
//String regStr = "[abc]"; //匹配 a-c 之间任意一个字符
//String regStr = "abc"; //匹配 abc [默认区分大小写]
//String regStr = "(?i)abc"; //匹配 abc [不区分大小写]
//String regStr = "a(?i)bc"; //匹配 abc [bc不区分大小写]
//String regStr = "a((?i)b)c"; //匹配 abc [b不区分大小写]
//String regStr = "[0-9]"; //匹配 0-9之间的字符
//String regStr = "[^a-z]"; //匹配 不在a-z 之间任意一个字符
//String regStr = "\\D"; //匹配 不在0-9 之间任意一个字符
//String regStr = "\\w"; //匹配 字母、数组、下划线
//String regStr = "\\W"; //匹配 除了字母、数组、下划线
//String regStr = "\\s"; //匹配 空白字符
String regStr = "."; //匹配 出\n所有
//String regStr = "\\."; //匹配 .
Pattern pattern = Pattern.compile(regStr);
//Pattern pattern = Pattern.compile(regStr,Pattern.CASE_INSENSITIVE); //不区分大小写
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到:" + matcher.group(0));
}
}
}
元字符-选择匹配符
选择匹配符说明
选择匹配符演示
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 选择匹配符
*/
public class RegExp04 {
public static void main(String[] args) {
String content = "xiao 校长 薇咲 哈哈大笑 ";
String regStr = "xiao|校|咲|笑"; //匹配 x 或 y
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到:" + matcher.group(0));
}
}
}
元字符-限定符
限定符说明
- x{n}:n表示x出现的次数,例如:x{3},//表示匹配xxx
- x{n,m}:表示x最少出现n次最多出现m次
- x?:表示出现x 0次到1次
- x*:表示出现x 0次到任意多次
- x+:表示出现x 1次到任意多次
限定符演示
- java匹配默认贪婪匹配,尽可能匹配多的
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 演示限定符的使用
*/
public class RegExp05 {
public static void main(String[] args) {
String content = "1R1wEd@a2431132aad1Z1131aaaaaaa111w4eW23aaa2132a43da1";
//String regStr = "1{4}"; //表示匹配1111
//String regStr = "\\d{2}"; //表示匹配任意两位数
//java匹配默认贪婪匹配,尽可能匹配多的
//String regStr = "a{3,4}"; //表示匹配aaa或者aaaa
//String regStr = "1{4,5}"; //表示匹配1111或者11111
//String regStr = "\\d{2,5}"; //表示匹配两位数、三位数、四位数或五位数
//String regStr = "1+"; //匹配一个1或者多个1
//String regStr = "1*"; //匹配0个1或者多个1
//String regStr = "1?"; //匹配0个1或者1个1
String regStr = "a1?"; //匹配a 或者 a1
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到:" + matcher.group(0));
}
}
}
元字符-定位符
定位符说明
定位符, 规定要匹配的字符串出现的位置,比如在字符串的开始还是在结束的位置,这个也是相当有用的,必须掌握
定位符演示
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 演示定位符的使用
*/
public class RegExp06 {
public static void main(String[] args) {
String content = "a123abc12";
//以至少1个数字开头,后接任意个小写字母的字符串
//String regStr = "[0-9]+[a-z]*";
//String regStr = "^[0-9]+[a-z]*"; //匹配失败
//以至少1个数字开头,必须以至少一个小写字母结束
//String regStr = "[0-9]+[a-z]+";
String regStr = "^[0-9]+[a-z]+$"; //匹配失败
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到:" + matcher.group(0));
}
}
}
分组
- 分组
我们可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作是一个子表达式/一个分组。 - 捕获
把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。组0代表的是整个正则式
捕获匹配
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 分组
*/
public class RegExp07 {
public static void main(String[] args) {
String content = "yuguy321321i32ui1h3421h3222jk1h45ui3h4322i5h1134i3uhi@u6hi5j7";
//非命名分组
//说明:
//1. matcher.group(0) 得到匹配到的字符串
//2. matcher.group(1) 得到匹配到的字符串的第1个分组内容
//3. matcher.group(2) 得到匹配到的字符串的第2个分组内容
// ...
//String regStr = "(\\d\\d)(\\d\\d)";
//命名分组:即可以给分组取名
String regStr = "(?<g1>\\d\\d)(?<g2>\\d\\d)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("得到匹配到的字符串" + matcher.group(0));
System.out.println("第1个分组内容" + matcher.group(1));
System.out.println("第1个分组内容" + matcher.group("g1")); //通过组名
System.out.println("第2个分组内容" + matcher.group(2));
System.out.println("第2个分组内容" + matcher.group("g2")); //通过组名
}
}
}
非捕获匹配
非捕获匹配的(),不能matcher.group(1) ''不是分组.''
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 演示非捕获分组
*/
public class RegExp08 {
public static void main(String[] args) {
String content = "hello 韩顺平教育 jack 韩顺平老师 韩顺平同学 hello 韩顺平学生";
// 找到 韩顺平教育 、韩顺平老师、韩顺平同学 子字符串
//String regStr = "韩顺平教育|韩顺平老师|韩顺平同学";
//上面的写法可以等价非捕获分组, 注意:不能 matcher.group(1)
//String regStr = "韩顺平(?:教育|老师|同学)";
//找到 韩顺平 这个关键字,但是要求只是查找韩顺平教育和 韩顺平老师 中包含有的韩顺平
//下面也是非捕获分组,不能使用 matcher.group(1)
//String regStr = "韩顺平(?=教育|老师)";
//找到 韩顺平 这个关键字,但是要求只是查找 不是 (韩顺平教育 和 韩顺平老师) 中包含有的韩顺平
//下面也是非捕获分组,不能使用 matcher.group(1)
String regStr = "韩顺平(?!教育|老师)";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到: " + matcher.group(0));
}
}
}
非贪婪匹配
? : 当该字符紧跟在任何一个其他限制符 (, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而*默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,’o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* ? 非贪婪匹配
*/
public class RegExp09 {
public static void main(String[] args) {
String content = "hello111111 ok";
//String regStr = "\\d+"; //默认是贪婪匹配
// String regStr = "\\d+?"; //非贪婪匹配
String regStr = "\\d+?"; //非贪婪匹配
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到: " + matcher.group(0));
}
}
}
正则表达式应用实例
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 正则表达式的应用实例
*/
public class RegExp10 {
public static void main(String[] args) {
String content = "13588889999";
// 汉字
//String regStr = "^[\u4e00-\u9fa5]+$";
// 邮政编码
// 要求:是 1-9 开头的一个六位数. 比如:123890
//String regStr = "^[1-9]\\d{5}$";
// QQ 号码
// 要求: 是 1-9 开头的一个(5 位数-10 位数) 比如: 12389 , 1345687 , 187698765
//String regStr = "^[1-9]\\d{4,9}$";
// 手机号码
// 要求: 必须以 13,14,15,18 开头的 11 位数 , 比如 13588889999
String regStr = "^1[3|4|5|8]\\d{9}$";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
if(matcher.find()) {
System.out.println("满足格式");
} else {
System.out.println("不满足格式");
}
}
}
使用正则表达式判断url是否正确
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 使用正则表达式判断url是否正确
*/
public class RegExp11 {
public static void main(String[] args) {
String content = "https://www.runoob.com/regexp/regexp-mwww.etachar.html";
/*
思路:
1. 先确定url的开始部分 https:// | http://
2. 然后通过 ([\w\-]+\.)+[\w]+ 匹配 www.bilibili.com
3. /?aldtype=16047#en/zh/regular%20expression 匹配
*/
String regStr = "^((https|http)://)?([\\w-]+\\.)+[\\w]+([\\w-?=&;#/%.]*)$";
/*
((https|http)://)? https://,可能没有http
[abcd] 表示可以匹配abcd中的任意一个字符
\\. 就是.
([\\w\\-]+\\.)+ 域名
[\\w]+$ com
([\w-?=&#/%.]*) 最后杂乱
注意:[.?&]表示匹配就是.本身
*/
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
System.out.println("满足格式~");
} else {
System.out.println("不满足格式~");
}
}
}
正则表达式三个常用类
Pattern类
- pattern对象是一个正则表达式对象。Pattern 类没有公共构造方法。要创建一个 Pattern对象,调用其公共静态方法,它返回一个 Pattern对象。该方法接受一个正则表达式作为它的第一个参数,比如:Pattern r=Pattern.compile(pattern);
- 整体匹配:
**Pattern.matches(regStr, content);**
//返回值为boolean 尝试将整个区域与模式匹配
Matcher类
- Matcher 对象是对输入字符串进行解释和匹配的引擎。与Pattern类一样,Matcher 也没有公共构造方法。你需要调用Pattern对象的matcher方法来获得一个 Matcher对象
PatternSyntaxException
PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。
反向引用
- 分组
我们可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分我们可以看作是一个子表达式/一个分组。 - 捕获
把正则表达式中子表达式/分组匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用,从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。组0代表的是整个正则式 - 反向引用
圆括号的内容被捕获后。可以在这个括号后被使用,从而写出一个比较实用的匹配模式,这个我们称为反向引用,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部,内部反向引\分组号,外部反向引用$分组号
反向引用案例
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 反向引用案例
*/
public class RegExp12 {
public static void main(String[] args) {
String content = "h1234el9876lo33333 j12324-333999111a1551ck14 tom11 jack22 yyy12345 xxx";
//要匹配两个连续的相同数字 : (\\d)\\1
//String regStr = "(\\d)\\1";
//要匹配五个连续的相同数字: (\\d)\\1{4}
//String regStr = "(\\d)\\1{4}";
//要匹配个位与千位相同,十位与百位相同的数 5225 , 1551 (\\d)(\\d)\\2\\1
//String regStr = "(\\d)(\\d)\\2\\1";
/**
* 请在字符串中检索商品编号,形式如:12321-333999111 这样的号码,
* 要求满足前面是一个五位数,然后一个-号,然后是一个九位数,连续的每三位要相同
*/
String regStr = "\\d{5}-(\\d)\\1{2}(\\d)\\2{2}(\\d)\\3{2}";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到 " + matcher.group(0));
}
}
}
结巴去重案例
package com.wexiao.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 微咲
* @version 1.0
* 结巴去重案例
*/
public class RegExp13 {
public static void main(String[] args) {
String content = "我....我要....学学学学....编程 jjjjavaaa!";
//1. 去掉所有的.
Pattern pattern = Pattern.compile("\\.");
Matcher matcher = pattern.matcher(content);
content = matcher.replaceAll("");
// System.out.println("content=" + content);
//2. 去掉重复的字 我我要学学学学编程 java!
// 思路
//(1) 使用 (.)\\1+
//(2) 使用 反向引用$1 来替换匹配到的内容
// 注意:因为正则表达式变化,所以需要重置 matcher
pattern = Pattern.compile("(.)\\1+"); //分组的捕获内容记录到$1
matcher = pattern.matcher(content);
while (matcher.find()) {
System.out.println("找到重复内容=" + matcher.group(0));
}
//使用 反向引用$1 来替换匹配到的内容
content = matcher.replaceAll("$1");
System.out.println("替换匹配content=" + content);
//3. 使用一条语句 去掉重复的字 我我要学学学学编程 java!
content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");
System.out.println("content=" + content);
}
}
String类使用正则表达式
替换功能
String 类 public String replaceAll(String regex,String replacement)
判断功能
String 类 public boolean matches(String regex){} //尝试将整个区域与模式匹配
分割功能
String 类 public String[] split(String regex)
package com.wexiao.regexp;
/**
* @author 微咲
* @version 1.0
* String类中使用正则
*/
public class StringReg {
public static void main(String[] args) {
String content = "2000 年 5 月,JDK1.3、JDK1.4 和 J2SE1.3 相继发布,几周后其" +
"获得了 Apple 公司 Mac OS X 的工业标准的支持。2001 年 9 月 24 日,J2EE1.3 发" +
"布。" +
"2002 年 2 月 26 日,J2SE1.4 发布。自此 Java 的计算能力有了大幅提升";
//使用正则表达式方式,将 JDK1.3 和 JDK1.4 替换成 JDK
content = content.replaceAll("JDK1\\.3|JDK1\\.4", "JDK");
System.out.println(content);
//要求 验证一个 手机号, 要求必须是以 138 139 开头的
content = "13888889999";
if (content.matches("1(38|39)\\d{8}")) {
System.out.println("验证成功");
} else {
System.out.println("验证失败");
}
//要求按照 # 或者 - 或者 ~ 或者 数字 来分割
System.out.println("===================");
content = "hello#abc-jack12smith~北京";
String[] split = content.split("#|-|~|\\d+");
for (String s : split) {
System.out.println(s);
}
}
}
常用java正则表达式大全
java正则表达式大全(参考).java.java)
一、校验数字的表达式
1 数字:[1]
3 至少n位的数字:^\d{n,}
5 零和非零开头的数字:^(0|[1-9][0-9])%2B(.%5B0-9%5D%7B1%2C2%7D)%3F#card=math&code=%0A6%20%E9%9D%9E%E9%9B%B6%E5%BC%80%E5%A4%B4%E7%9A%84%E6%9C%80%E5%A4%9A%E5%B8%A6%E4%B8%A4%E4%BD%8D%E5%B0%8F%E6%95%B0%E7%9A%84%E6%95%B0%E5%AD%97%EF%BC%9A%5E%28%5B1-9%5D%5B0-9%5D%2A%29%2B%28.%5B0-9%5D%7B1%2C2%7D%29%3F&id=mP6hl)
7 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})?%3F%5Cd%2B(%5C.%5Cd%2B)%3F#card=math&code=%0A8%20%E6%AD%A3%E6%95%B0%E3%80%81%E8%B4%9F%E6%95%B0%E3%80%81%E5%92%8C%E5%B0%8F%E6%95%B0%EF%BC%9A%5E%28%5C-%7C%5C%2B%29%3F%5Cd%2B%28%5C.%5Cd%2B%29%3F&id=tuPlT)
9 有两位小数的正实数:[2]+(.[0-9]{2})?%3F#card=math&code=%0A10%20%E6%9C%891~3%E4%BD%8D%E5%B0%8F%E6%95%B0%E7%9A%84%E6%AD%A3%E5%AE%9E%E6%95%B0%EF%BC%9A%5E%5B0-9%5D%2B%28.%5B0-9%5D%7B1%2C3%7D%29%3F&id=dwf8j)
11 非零的正整数:[3]\d$ 或 ^([1-9][0-9]){1,3}$ 或 ^+?[1-9][0-9]![](https://g.yuque.com/gr/latex?%0A12%20%E9%9D%9E%E9%9B%B6%E7%9A%84%E8%B4%9F%E6%95%B4%E6%95%B0%EF%BC%9A%5E%5C-%5B1-9%5D%5B%5D0-9%22#card=math&code=%0A12%20%E9%9D%9E%E9%9B%B6%E7%9A%84%E8%B4%9F%E6%95%B4%E6%95%B0%EF%BC%9A%5E%5C-%5B1-9%5D%5B%5D0-9%22%2A&id=MnqAL) 或 ^-[1-9]\d 或 [4]\d|0 或 ^((-\d+)|(0+))%3F#card=math&code=%0A15%20%E9%9D%9E%E8%B4%9F%E6%B5%AE%E7%82%B9%E6%95%B0%EF%BC%9A%5E%5Cd%2B%28%5C.%5Cd%2B%29%3F&id=lsB7p) 或 [5]\d.\d|0.\d[1-9]\d|0?.0+|0%3F)%7C(0%2B(%5C.0%2B)%3F))#card=math&code=%0A16%20%E9%9D%9E%E6%AD%A3%E6%B5%AE%E7%82%B9%E6%95%B0%EF%BC%9A%5E%28%28-%5Cd%2B%28%5C.%5Cd%2B%29%3F%29%7C%280%2B%28%5C.0%2B%29%3F%29%29&id=kkrKL) 或 ^(-([1-9]\d.\d|0.\d[1-9]\d))|0?.0+|0 或 ^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))#card=math&code=%0A18%20%E8%B4%9F%E6%B5%AE%E7%82%B9%E6%95%B0%EF%BC%9A%5E-%28%5B1-9%5D%5Cd%2A%5C.%5Cd%2A%7C0%5C.%5Cd%2A%5B1-9%5D%5Cd%2A%29&id=kkyyG) 或 ^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))(%5C.%5Cd%2B)%3F#card=math&code=%0A19%20%E6%B5%AE%E7%82%B9%E6%95%B0%EF%BC%9A%5E%28-%3F%5Cd%2B%29%28%5C.%5Cd%2B%29%3F&id=ZLmEl) 或 ^-?([1-9]\d.\d|0.\d[1-9]\d|0?.0+|0)$二、校验字符的表达式
1 汉字:[6]{0,} 或 [7]{4,40}
4 由26个英文字母组成的字符串:[8]+
6 由26个小写英文字母组成的字符串:[9]+
8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}
10 中文、英文、数字但不包括下划线等符号:[10]+$ 或 [11]{2,20}“等字符:[^%&’,;=?$\x22]+
12 禁止输入含有的字符:[^\x22]+三、特殊需求表达式
1 Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)%2B%2F.%3F%0A3%20InternetURL%EF%BC%9A%5Ba-zA-z%5D%2B%3A%2F%2F%5B%5E%5Cs%5D%20%E6%88%96%20%5Ehttps%3A%2F%2F(%5B%5Cw-%5D%2B%5C.)%2B%5B%5Cw-%5D%2B(%2F%5B%5Cw-.%2F%3F%25%26%3D%5D)%3F#card=math&code=%0A2%20%E5%9F%9F%E5%90%8D%EF%BC%9A%5Ba-zA-Z0-9%5D%5B-a-zA-Z0-9%5D%7B0%2C62%7D%28%2F.%5Ba-zA-Z0-9%5D%5B-a-zA-Z0-9%5D%7B0%2C62%7D%29%2B%2F.%3F%0A3%20InternetURL%EF%BC%9A%5Ba-zA-z%5D%2B%3A%2F%2F%5B%5E%5Cs%5D%2A%20%E6%88%96%20%5Ehttps%3A%2F%2F%28%5B%5Cw-%5D%2B%5C.%29%2B%5B%5Cw-%5D%2B%28%2F%5B%5Cw-.%2F%3F%25%26%3D%5D%2A%29%3F&id=qCjkZ)
4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}%EF%BC%9A%5E(%5C(%5Cd%7B3%2C4%7D-)%7C%5Cd%7B3.4%7D-)%3F%5Cd%7B7%2C8%7D#card=math&code=%0A5%20%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81%28%22XXX-XXXXXXX%22%E3%80%81%22XXXX-XXXXXXXX%22%E3%80%81%22XXX-XXXXXXX%22%E3%80%81%22XXX-XXXXXXXX%22%E3%80%81%22XXXXXXX%22%E5%92%8C%22XXXXXXXX%29%EF%BC%9A%5E%28%5C%28%5Cd%7B3%2C4%7D-%29%7C%5Cd%7B3.4%7D-%29%3F%5Cd%7B7%2C8%7D&id=I631Z)
6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
7 身份证号:
15或18位身份证:^\d{15}|\d{18}%7C(1%5B0-2%5D))((%5B0%7C1%7C2%5D%5Cd)%7C3%5B0-1%5D)%5Cd%7B3%7D#card=math&code=%0A%09%0915%E4%BD%8D%E8%BA%AB%E4%BB%BD%E8%AF%81%EF%BC%9A%5E%5B1-9%5D%5Cd%7B7%7D%28%280%5Cd%29%7C%281%5B0-2%5D%29%29%28%28%5B0%7C1%7C2%5D%5Cd%29%7C3%5B0-1%5D%29%5Cd%7B3%7D&id=GjsCm)
18位身份证:[12]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}%EF%BC%9A%5E(%5B0-9%5D)%7B7%2C18%7D(x%7CX)%3F#card=math&code=%0A8%20%E7%9F%AD%E8%BA%AB%E4%BB%BD%E8%AF%81%E5%8F%B7%E7%A0%81%28%E6%95%B0%E5%AD%97%E3%80%81%E5%AD%97%E6%AF%8Dx%E7%BB%93%E5%B0%BE%29%EF%BC%9A%5E%28%5B0-9%5D%29%7B7%2C18%7D%28x%7CX%29%3F&id=IIkMd) 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?%EF%BC%9A%5E%5Ba-zA-Z%5D%5Ba-zA-Z0-9%5D%7B4%2C15%7D#card=math&code=%0A9%20%E5%B8%90%E5%8F%B7%E6%98%AF%E5%90%A6%E5%90%88%E6%B3%95%28%E5%AD%97%E6%AF%8D%E5%BC%80%E5%A4%B4%EF%BC%8C%E5%85%81%E8%AE%B85-16%E5%AD%97%E8%8A%82%EF%BC%8C%E5%85%81%E8%AE%B8%E5%AD%97%E6%AF%8D%E6%95%B0%E5%AD%97%E4%B8%8B%E5%88%92%E7%BA%BF%29%EF%BC%9A%5E%5Ba-zA-Z%5D%5Ba-zA-Z0-9%5D%7B4%2C15%7D&id=I5bI9)
10 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):[13]\w{5,17}%EF%BC%9A%5E(%3F%3D.%5Cd)(%3F%3D.%5Ba-z%5D)(%3F%3D.%5BA-Z%5D).%7B8%2C10%7D#card=math&code=%0A11%20%E5%BC%BA%E5%AF%86%E7%A0%81%28%E5%BF%85%E9%A1%BB%E5%8C%85%E5%90%AB%E5%A4%A7%E5%B0%8F%E5%86%99%E5%AD%97%E6%AF%8D%E5%92%8C%E6%95%B0%E5%AD%97%E7%9A%84%E7%BB%84%E5%90%88%EF%BC%8C%E4%B8%8D%E8%83%BD%E4%BD%BF%E7%94%A8%E7%89%B9%E6%AE%8A%E5%AD%97%E7%AC%A6%EF%BC%8C%E9%95%BF%E5%BA%A6%E5%9C%A88-10%E4%B9%8B%E9%97%B4%29%EF%BC%9A%5E%28%3F%3D.%2A%5Cd%29%28%3F%3D.%2A%5Ba-z%5D%29%28%3F%3D.%2A%5BA-Z%5D%29.%7B8%2C10%7D&id=XZ6WU)
12 日期格式:^\d{4}-\d{1,2}-\d{1,2}
13 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])%EF%BC%9A%5E((0%3F%5B1-9%5D)%7C((1%7C2)%5B0-9%5D)%7C30%7C31)#card=math&code=%0A14%20%E4%B8%80%E4%B8%AA%E6%9C%88%E7%9A%8431%E5%A4%A9%2801%EF%BD%9E09%E5%92%8C1%EF%BD%9E31%29%EF%BC%9A%5E%28%280%3F%5B1-9%5D%29%7C%28%281%7C2%29%5B0-9%5D%29%7C30%7C31%29&id=osfGj)
15 钱的输入格式:
16 1.有四种钱的表示形式我们可以接受:”10000.00” 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:[14][0-9]$
17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0”不通过,所以我们采用下面的形式:^(0|[1-9][0-9])$
18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9])$
19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:[15]+(.[0-9]+)?$
20 5.必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的:[16]+(.[0-9]{2})?$
21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:[17]+(.[0-9]{1,2})?$
22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:[18]{1,3}(,[0-9]{3})(.[0-9]{1,2})?$
23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3}))(.[0-9]{1,2})?$
24 备注:这就是最终结果了,别忘了”+”可以用”“替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
25 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+.[x|X][m|M][l|L])%0A28%20%E7%A9%BA%E7%99%BD%E8%A1%8C%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%EF%BC%9A%5Cn%5Cs%5Cr%20(%E5%8F%AF%E4%BB%A5%E7%94%A8%E6%9D%A5%E5%88%A0%E9%99%A4%E7%A9%BA%E7%99%BD%E8%A1%8C)%0A29%20HTML%E6%A0%87%E8%AE%B0%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%EF%BC%9A%3C(%5CS%3F)%5B%5E%3E%5D%3E.%3F%7C%3C.%3F%20%2F%3E%20(%E7%BD%91%E4%B8%8A%E6%B5%81%E4%BC%A0%E7%9A%84%E7%89%88%E6%9C%AC%E5%A4%AA%E7%B3%9F%E7%B3%95%EF%BC%8C%E4%B8%8A%E9%9D%A2%E8%BF%99%E4%B8%AA%E4%B9%9F%E4%BB%85%E4%BB%85%E8%83%BD%E9%83%A8%E5%88%86%EF%BC%8C%E5%AF%B9%E4%BA%8E%E5%A4%8D%E6%9D%82%E7%9A%84%E5%B5%8C%E5%A5%97%E6%A0%87%E8%AE%B0%E4%BE%9D%E6%97%A7%E6%97%A0%E8%83%BD%E4%B8%BA%E5%8A%9B)%0A30%20%E9%A6%96%E5%B0%BE%E7%A9%BA%E7%99%BD%E5%AD%97%E7%AC%A6%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%EF%BC%9A%5E%5Cs%7C%5Cs*#card=math&code=%0A26%20%E4%B8%AD%E6%96%87%E5%AD%97%E7%AC%A6%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%EF%BC%9A%5B%5Cu4e00-%5Cu9fa5%5D%0A27%20%E5%8F%8C%E5%AD%97%E8%8A%82%E5%AD%97%E7%AC%A6%EF%BC%9A%5B%5E%5Cx00-%5Cxff%5D%20%28%E5%8C%85%E6%8B%AC%E6%B1%89%E5%AD%97%E5%9C%A8%E5%86%85%EF%BC%8C%E5%8F%AF%E4%BB%A5%E7%94%A8%E6%9D%A5%E8%AE%A1%E7%AE%97%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E9%95%BF%E5%BA%A6%28%E4%B8%80%E4%B8%AA%E5%8F%8C%E5%AD%97%E8%8A%82%E5%AD%97%E7%AC%A6%E9%95%BF%E5%BA%A6%E8%AE%A12%EF%BC%8CASCII%E5%AD%97%E7%AC%A6%E8%AE%A11%29%29%0A28%20%E7%A9%BA%E7%99%BD%E8%A1%8C%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%EF%BC%9A%5Cn%5Cs%2A%5Cr%20%28%E5%8F%AF%E4%BB%A5%E7%94%A8%E6%9D%A5%E5%88%A0%E9%99%A4%E7%A9%BA%E7%99%BD%E8%A1%8C%29%0A29%20HTML%E6%A0%87%E8%AE%B0%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%EF%BC%9A%3C%28%5CS%2A%3F%29%5B%5E%3E%5D%2A%3E.%2A%3F%7C%3C.%2A%3F%20%2F%3E%20%28%E7%BD%91%E4%B8%8A%E6%B5%81%E4%BC%A0%E7%9A%84%E7%89%88%E6%9C%AC%E5%A4%AA%E7%B3%9F%E7%B3%95%EF%BC%8C%E4%B8%8A%E9%9D%A2%E8%BF%99%E4%B8%AA%E4%B9%9F%E4%BB%85%E4%BB%85%E8%83%BD%E9%83%A8%E5%88%86%EF%BC%8C%E5%AF%B9%E4%BA%8E%E5%A4%8D%E6%9D%82%E7%9A%84%E5%B5%8C%E5%A5%97%E6%A0%87%E8%AE%B0%E4%BE%9D%E6%97%A7%E6%97%A0%E8%83%BD%E4%B8%BA%E5%8A%9B%29%0A30%20%E9%A6%96%E5%B0%BE%E7%A9%BA%E7%99%BD%E5%AD%97%E7%AC%A6%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%EF%BC%9A%5E%5Cs%2A%7C%5Cs%2A&id=RcAVS)或(^\s)|(\s$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
31 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
32 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
33 IP地址:\d+.\d+.\d+.\d+ (提取IP地址时有用)
正则练习
package com.hspedu.regexp;
/**
* @author 韩顺平
* @version 1.0
*/
public class Homework02 {
public static void main(String[] args) {
//要求验证是不是整数或者小数
//提示: 这个题要考虑正数和负数
//比如: 123 -345 34.89 -87.9 -0.01 0.45 等
/**
* 老师的思路
* 1. 先写出简单的正则表达式
* 2. 在逐步的完善[根据各种情况来完善]
*/
String content = "-0.89"; //
String regStr = "^[-+]?([1-9]\\d*|0)(\\.\\d+)?$";
if(content.matches(regStr)) {
System.out.println("匹配成功 是整数或者小数");
} else {
System.out.println("匹配失败");
}
}
}
package com.hspedu.regexp;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author 韩顺平
* @version 1.0
*/
public class Homework03 {
public static void main(String[] args) {
String content = "http://www.sohu.com:8080/abc/xxx/yyy/////inde@#$%x.htm";
//因为正则表达式是根据要求来编写的,所以,如果需求需要的话,可以改进.
String regStr = "^([a-zA-Z]+)://([a-zA-Z.]+):(\\d+)[\\w-/]*/([\\w.@#$%]+)$";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
if(matcher.matches()) {//整体匹配, 如果匹配成功,可以通过group(x), 获取对应分组的内容
System.out.println("整体匹配=" + matcher.group(0));
System.out.println("协议: " + matcher.group(1));
System.out.println("域名: " + matcher.group(2));
System.out.println("端口: " + matcher.group(3));
System.out.println("文件: " + matcher.group(4));
} else {
System.out.println("没有匹配成功");
}
}
}