编程思想
一个思想:编程思想
两种能力:1.自我学习的能力
2.自我解决问题的能力
是什么?为什么?怎么用?
程序是用来模拟现实世界,解决现实问题的一系列指令集合
数据类型与操作符
1.1 数据类型
基本数据类型:
整型:byte short int long
浮点型:float double
字符型:char
布尔型:boolean(true或false)
引用数据类型:String
byte范围为 -128~127
大类型转小类型避免报错
Java系统默认数值的数据类型为int型和double型
long类型和float类型须在数值后面加L和f(大小写没有影响)
字符型char只能为一个字符
1.2 转义字符
\n 代表换行
\t 代表水平制表符
\” 代表”不为字符串的标记而是双引号本身
如:
System.out.println(“三\”国\”志”);//运行结果为 三”国”志
1.3 算数运算符
相同数据类型的数值进行运算,结果不会改变类型
不同数据类型的数值进行运算,结果为大类型
byte < short < int < long
int < double
赋值运算:+= -= *= /= %=
num1 += 3;//num1 = num1 +3
//当类型不相同的时候 num += 1 就不等于num = num + 1
如:
short num1 = 3;
short num2 = 4;
num2 = num2 + 1;(此处num2为short类型,1默认为整型int,为大类型加小类型,结果为大类型,大类型再赋值给num2,而num2为short类型,属于大类型赋值给小类型,最终结果会报错
改正需要强制转换成short,正确为:num2 = (short)(num2 + 1);
关系运算符:!= == <= >= < >
int num1 = 1;
int num2 = 2;
//!=
System.out.println(num1!=num2);//true
//==
System.out.println(num1==num2);//false
一元运算符:++ –
前置++ 自加一后赋值
后置++ 先赋值后加一
int num1 = 1;
int num2 = 2;
//前置++ 自加一后使用
System.out.println(++num1);//2
//后置++ 先使用后自加一
System.out.println(num2++);//2
System.out.println(num2);//3
三元运算符
关系表达式?表达式1:表达式2
当关系表达式运行结果为true时,总结果为表达式1;当关系表达式运行结果为false时,总结果为表达式2
如:
int num1 = 2;
int num2 = 3;
int max = num2 > num1 ? num2 : num1;
//当num2 > num1返回true的时候
// int max = num2;
//当num2 >num1 返回false的时候
// int max = num1;
分支语句
1.1 逻辑运算符
运算两边运行的结果必须为布尔类型结果
& 与 并且 有一个假则为假
| 或 或者 有一个真则为真
&& 短路与 运算符两边,只要有一个false,结果则为false
|| 短路或 运算符两边,只要有一个true,结果则为true
! 非 取反 非false为true,非false为true
^ 异或 判断运算符两边是否不同,不同结果为true,反之为false
& 与 运算符两边,只要有一个false,结果则为false,并且当运算符左边为false时,继续运行运算符右边的代码
如:
boolean flag1 = true;
boolean flag2 = true;
System.out.println(false & (flag1 = false));//false
System.out.println(“flag1:”+flag1);//false
&& 短路与 运算符两边,只要有一个false,结果则为false,并且当运算符左边为false时,将不再运行右边的代码,直接返回结果
如:
boolean flag1 = true;
boolean flag2 = true;
System.out.println(false && (flag2 = false));//false
//运算符左边为false,(flag2 = false)不运行,所以flag2还是原来的值为true
System.out.println(“flag2:”+flag2);//true
| 逻辑或 运算符两边,只要有一个true,结果则为true 或者
如:
boolean flag1 = true;
boolean flag2 = true;
System.out.println(true | (flag1 = true));//true
System.out.println("flag1:"+flag1);//true
|| 短路或 运算符两边,只要有一个true,结果则为true 或者
如:
boolean flag1 = true;
boolean flag2 = true;
System.out.println(true || (flag2 = false));//ture
//运算符左边为true,(flag2 = false)不运行,所以flag2还是原来的值为true
System.out.println("flag2:"+flag2);//true
1.2 if语句
if(关系表达式){
语句体
}
程序运行到if语句时,当关系表达式返回true则执行语句体
当关系表达式返回false则直接结束if语句
if(关系表达式){
语句体1
}else{
语句体2
}
当程序运行到if语句时
关系表达式返回true,则运行语句体1然后结束if语句
关系表达式返回false,则运行语句体2然后结束if语句
if(关系表达式1){
语句体1
}else if(关系表达式2){
语句体2
}else if
...
else if(关系表达式n){
语句体n
}else{
语句体n+1
}
程序运行到if语句
进行关系表达式1的判断,如果返回true,则执行语句体1然后结束if语句
如果关系表达式1返回false,则进行关系表达式2的判断,关系表达式2如果返回true,则执行语句体2并结束if语句
如果关系表达式2返回false…
如果所有关系表达式都返回false,则执行else里的语句体n+1,如果没有else则结束if语句
1.3 导包 – 工具箱的来源
如:
import java.util.Scanner;
import java.util.Random;
控制台录入
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
随机
Random ran = new Random();
int num = ran.nextInt();
随机生成1-8
nextInt(1) -- [0,1) 随机生成0~1的数,包含0不包含1
nextInt(8) -- [0,8) 随机生成0~8的数,包含0不包含8
nextInt(8) + 1 随机生成1~9的数,包含1不包含9
int num = ran.nextInt(8)+1; //ran.nextInt(8)随机生成0~7(不包括8), ran.nextInt(8)+1则为随机生成1~8
nextInt(98) -- [0,98)
nextInt(98)+2 -- [2,100) -- [2,99]
数据类型 变量名 = 初始值;
对象类型 对象名 = new 对象类型();
switch语句
switch(表达式){
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
case 值3:
语句体3;
break;
...
default:
语句体n+1;
break;
}
程序运行到switch的时候,表达式与值1进行等值判断,如果相等则执行语句体1,遇到break结束switch语句
如果不相等则与值2进行等值判断,如果判断相等则执行语句体2,遇到break结束switch语句
…
如果表达式与所有case的值比较都不相等,则执行default里的语句体n+1
- default是否一定要放在最后
语法结构完整的情况下可以在switch语句的任意位置
- default是否可以省略不写
语法结构完整的情况下可以省略不写
- break是否可以省略不写
可以,根据需求来,如case内容相同的情况下
如:
switch(num){
case 1:
//System.out.println("工作日");
//break;
case 2:
//System.out.println("工作日");
//break;
case 3:
//System.out.println("工作日");
//break;
case 4:
//System.out.println("工作日");
//break;
case 5:
System.out.println("工作日");
break;
case 6:
//System.out.println("休息日");
//break;
case 7:
System.out.println("休息日");
switch表达式允许的类型:
byte、short、int、char、String(J D K 1.7的新特性)
不允许的类型:
long、float、double、boolean
switch表达式允许的String类型:
String str = "a";
//switch表达式默认为int类型
switch(str){
case "a":
System.out.println("一");
break;
case "b":
System.out.println("二");
break;
default:
break;
}
char和int互转:
//根据ASCII
//char类型转换成int类型
char ch = '0';
int num1 = ch;
System.out.println("num1:"+num1);
System.out.println("------------");
//int类型转换成char类型
int num2 = 65;
char ch1 = (char)(num2+3);
System.out.println("ch1:"+ch1);
循环语句
已经定义过的变量,如果想再重新用而不带原来的数据可以通过重新赋值来使用
如下面的count:
public class Demo03{
public static void main(String[] args){
//1.打印10-1
for(int i = 10;i > 0;i--){
System.out.println(i);
}
```
//2.统计1-100之间的偶数
//记录次数
int count = 0;
for(int i = 1;i <= 100;i++){
//判断是否为偶数
if(i%2 == 0){
//偶数次数+1
count++;
}
}
System.out.println("1-100之间的偶数个数:"+count);
//3.统计1-100之间能被3整除的数的个数
//记录次数
count = 0;
for(int i = 1;i <= 100;i++){
//判断是否能被3整除
if(i%3 == 0){
count++;
}
}
System.out.println("1-100之间能被3整除的个数:"+count);
//4.统计1-100之间能被3整除但不能被2整除的数的个数
count = 0;
for(int i = 1;i <= 100;i++){
//判断能被3整除但是不能被2整除的数
if(i%3 == 0 && i%2 != 0){
count++;
}
}
System.out.println("1-100之间能被3整除但是不能被2整除的个数:"+count);
}
```
}
1.1 for循环语句
for(初始化条件;条件判断语句;条件控制语句){
循环体语句
}
初始化条件:控制循环的开始
条件判断语句:控制循环的结束(范围)
条件控制语句:控制循环如何从开始到结束
程序运行到for循环的时候,
先执行初始化条件,然后经过条件判断语句
如果返回false则直接结束for循环
如果判断语句返回true则执行循环体语句,再执行条件控制语句
再回到条件判断语句,如果返回false结束for循环
如果返回true则执行循环体语句,再执行条件控制语句
注意的点:
-
for循环里定义的变量,循环结束则销毁,生命周期为for循环
-
在main方法中定义的变量,生命周期为main方法
嵌套循环中,外层循环控制行数,内层循环控制列数
外层for循环不是自己控制行数的,需放入println();才能实现换行
内层for循环也不是自己控制列数的,需把 println();的换行去掉才能实现,即只留下print();
例:
public static void main(String[] args){
/*
****
****
****
****
*/
//i代表的是行数,i <= 4控制行数
for(int i = 1;i <= 4;i++){
//j代表的是列数;j <= 4控制列数
for(int j = 1;j <= 4;j++){
System.out.print("*");
}
//每行打印结束进行换行
System.out.println();
}
System.out.println("-------------");
1.2 while语句
初始化语句
while(条件判断语句){
循环体语句(条件控制语句)
}
当条件判断语句返回true时则执行循环体语句,返回false则结束循环
//初始化语句
int i = 1;
while(i <= 154){
//条件控制语句
System.out.println("来千锋的第"+(i++)+"天");
}
如何区分什么情况下用for循环,什么情况下用while循环?
循环次数确定的时候使用for循环,循环次数不确定的时候使用while循环
如下例(循环次数不确定)
public class Demo07{
//循环次数确定的时候使用for循环,循环次数不确定的时候使用while循环
public static void main(String[] args){
//我有个梦想,我完成人生的小目标,加入月收入15K,一个月存10K,问多久完成小目标
//月份
int month = 0;
//存款(K)
int money = 0;
//每次循环相当于一次存钱的月份
while(money < 100000){
//存钱
money += 10;
//月份+1
month++;
}
System.out.println("需要:"+month+"月,"+month/12+"年");
}
}
1.3 do while语句
初始化语句
do{
循环体语句(条件控制语句)
}while(条件判断语句);
当条件判断语句返回true时继续执行循环体语句,返回false则结束循环
do{
System.out.println("请在"+min+"-"+max+"之间猜");
//获取用户猜的数字
guess = sc.nextInt();
/*
随机数为60
猜50
最小值改变为50
50-100
*/
//猜的数小于随机数
if(guess < ranNum){
//最小值等于猜的数字
min = guess;
}else if(guess > ranNum){//猜的数大于随机数
//最大值等于猜的数
max = guess;
}
}while(guess != ranNum);
如何区分什么情况用while,什么情况用do while?
do while 在还没有开始循环就执行了一次循环体语句,如果需要必须执行一次循环语句就用do while,大部分情况下用while比较多,除非情况特殊。
1.3 死循环
for死循环
for(;;){
System.out.println(1);
}
*/
//条件判断语句有误
/*for(int i =1;i > 0;i++){
System.out.println(i);
}
*/
/*while死循环
while(true){
System.out.println(1);
}
1.4 break、continue、return
break:循环执行中,遇到break,则退出整个循环结构
多重循环中,break语句只向外跳一层
System.out.println("------借助标号的break----------");
//借助标号我们实现结束任意一层的循环
T:
for(int i = 1;i < 5;i++){
//内层for循环控制的是列数 -- 局数
for(int j = 1;j < 5;j++){
if(j == 3){
//结束标号K的循环
break T;
}
System.out.print(j);
}
System.out.println();
}
System.out.println();
continue:循环执行中,遇到continue,则跳过此次,进入下一次循环
return:结束方法,后面程序将不运行
System.out.println("-------return---------");
for(int i = 1;i < 5;i++){
//内层for循环控制的是列数 -- 局数
for(int j = 1;j < 5;j++){
if(j == 3){
//直接结束return所在的方法
return;
}
System.out.print(j);
}
System.out.println();
}
System.out.println("程序即将结束");
}
方法(函数)
1.1方法
main方法是程序的主入口
无参:
访问修饰符 返回值类型 方法名(){
方法体
}
有参:
访问修饰符 返回值类型 方法名(参数类型 参数名 ...){
方法体
}
static修饰的方法为静态方法,静态方法只能调用静态方法
调用方法时,方法存在多少个形参,就需要传递多少个实参,类型、顺序要一致
public class Demo02{
//static修饰的方法为静态方法,静态方法只能调用静态方法
public static void main(String[] args){
//调用方法时,方法存在多少个形参,就需要传递多少个实参,类型、顺序要一致
nineNine(7,"*");
System.out.println("-----------------------------------------------------------------------");
nineNine(9,"x");
System.out.println("-----------------------------------------------------------------------");
//调用方法要传递2个参数。一个int类型,一个String类型
int num = 8;
String str = "x";
nineNine(num,str);
System.out.println("-----------------------------------------------------------------------");
}
//打印九九乘法表
public static void nineNine(int num,String str){
for(int i=1;i<=num;i++){
for(int j=1;j<=i;j++){
System.out.print(j+str+i+"="+j*i+"\t");
}
System.out.println();
}
}
}
public class Demo05{
public static void main(String[] args){
//接收返回值 -- 接老板找的零钱
int sum = add(1,3);
System.out.println("sum:"+sum);
```
//不接收返回值 -- 不用找
//只调用方法不需要返回值
add(1,5);
}
/*接收2个int类型的数据,打印出2个数据之和
public static void add(int num1,int num2){
System.out.println(num1+num2);
}
*/
//接收2个int类型的数据,计算之和并返回结果
public static int add(int num1,int num2){
System.out.println("add方法被调用");
int sum = num1 + num2;
//在结束方法的时候把sum返回给调用方
return sum;
}
```
}
1.1.1 返回值的方法
public static void main(String[] args){
//add(3,4)既调用add方法,同时将num发送给add(3,4)
int sum = add(3,4);
System.out.println("sum:"+sum);
}
//接受两个数之和
public static int add(int num1,num2){
int num = num1 + num2;
System.out.println(num);
retrun num;
}
方法的定义:
1.思考创建一个解决什么问题
2.确定方法名
3.方法需不需要由调用方决定的变量,如果需要则设置对应的参数
4.调用方是否需要从方法中获取数据,需要则在方法体重返回需要的数据,以及在方法中设置对应的返回值
1.1.2 方法的重载
方法的重载:是方法的衍生(扩展)
1.发生在同一个类中
2.方法名相同
3.参数列表必须不一致(参数的数量、参数类型、参数类型顺序)
4.与返回值类型无关
方法重载的作用
方法重载的主要好处就是不用为了对不同的参数类型或参数个数,而写多个函数。多个函数用同一个名字,但参数表,即参数的个数或(和)数据类型可以不同,调用的时候,虽然方法名字相同,但根据参数表可以自动调用对应的函数
1.1.3 递归
1! 1
2! 1*2 (2-1)!*2
3! 1*2*3 (3-1)!*3
4! 1*2*3*4 (4-1)!*4
1.方法调用方法本身
2.递归拥有出口(结束方法的代码)
public class Demo07{
public static void main(String[] args){
int sum = factorial(4);
System.out.println("sum:"+sum);
}
```
//使用递归完成阶乘
public static int factorial(int sum){
if(sum == 1){
return 1;
}
//return (sum-1)!*sum;
return factorial(sum-1)*sum;
}
//传递一个整数,返回对应的阶乘
public static int factorial1(int num){
int sum = 1;
for(int i = 1;i <= num;i++){
sum *= i;
}
return sum;
}
```
}
数组
1.1 数组的特点
1.存储的数据称为元素
2.定义后长度固定
3.拥有索引(下标),从0开始,最大值为长度-1
4.存储相同类型的数据
1.2 数组的初始化
数组初始化 – 动态初始化
数组的数据类型[] 数组名 = new 数组的数据类型[数组长度];
数组的数据类型 数组名[] = new 数组的数据类型[数组长度];
String[] books = new String[4];
String[]是数据类型,books是数组名,4为数组的长度
赋值 – 通过索引进行赋值,索引不能与长度相等或者大于长度,否则将会溢出
取值 -- 通过索引进行取值
books.length获取books这个数组的长度
数组的静态初始化
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3…};
数据类型 数组名[] = new 数据类型[]{元素1,元素2,元素3…};
//String books[] = new String[]{"三国演义","水浒传","西游记","红楼梦"};
数据类型[] 数组名;
数组名 = new 数据类型[]{元素1,元素2,元素3…};
数据类型 数组名[];
数组名 = new 数据类型[]{元素1,元素2,元素3…};
//String[] books;
//books = new String[]{"红楼梦","水浒传","西游记","三国演义"};
数据类型[] 数组名 = {元素1,元素2,元素3…};
数据类型 数组名[] = {元素1,元素2,元素3…};
//String[] books = {"西游记","水浒传","红楼梦","三国演义"};
//错误的写法
//String[] books;
//books = {"西游记","三国演义","红楼梦","水浒传"};
1.3 数组的遍历
遍历String类型的数组
public static void printArray1(String[] arrays){
//数组的遍历 -- for循环
for(int i = 0;i < arrays.length;i++){
System.out.println(arrays[i]);
}
}
数组的遍历 – foreach(增强for循环)
在循环的时候,循环值自动获取数组里的元素,循环值类型与数组中的元素类型一致
for(循环值类型 循环值:数组名){
}
//遍历String类型的数组
public static void printArray1(String[] arrays){
//数组的遍历 -- for循环
for(int i = 0;i < arrays.length;i++){
System.out.println(arrays[i]);
}
}
通过for循环改变数组的元素
//通过for循环改变数组的元素
public static void printArray3(String[] arrays){
for(int i = 0;i < arrays.length;i++){
//把数组的元素统一改变成"书籍"
arrays[i] = "书籍";
}
}
通过foreach改变数组的元素(无法改变)
//通过foreach改变数组的元素
public static void printArray4(String[] arrays){
for(String str:arrays){
//把"书本"赋值给循环值
//在循环过程中,改变循环值是无法改变数组里的元素
str = "书本";
}
}
1.4 二维数组
//定义一维数组
int[] nums = new int[3];
//定义二维数组 -- 3代表的是一共有3个一维数组 4代表的是一维数组的长度为4
//动态初始化
int[][] nums = new int[3][4];
int nums[][] = new int[3][4];
//赋值
nums.length为二维数组的长度
nums[i].length为一维数组的长度
//静态初始化
int[][] nums = {{11,12},{21,22,23,24},{31,32,33}};
int nums[][] = {{11,12},{21,22,23,24},{31,32,33}};
遍历
//遍历 -- i为二维数组的索引 j为一维数组的索引
for(int i = 0;i < nums.length;i++){
for(int j = 0;j < nums[i].length;j++){
System.out.println(nums[i][j]);
}
}
定义二维数组的长度为3
public class Demo07{
public static void main(String[] args){
//定义二维数组的长度为3
int[][] nums = new int[3][];
//定义一个长度为2的一维数组并赋值给二维数组的第一个元素
nums[0] = new int[2];
nums[1] = new int[5];
nums[2] = new int[4];
}
}
/*
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
```
每行的第一个跟最后一个为1,剩下的数等于上一行同一列的数加上一行前一列的数
```
*/
public class Demo08{
public static void main(String[] args){
int[][] nums = new int[8][8];
//i为行数
for(int i = 0;i < nums.length;i++){
//j为列数 内层循环一次为一行数据
for(int j = 0; j <= i;j++){
//赋值
//每行的第一个跟最后一个为1
if(j == 0 || j == i){
nums[i][j] = 1;
}else{
//nums[i-1][j] 为上一行同一列
//nums[i-1][j-1] 为上一行前一列
nums[i][j] = nums[i-1][j] + nums[i-1][j-1];
}
//打印
System.out.print(nums[i][j]+"\t");
}
System.out.println();
}
}
}
排序与面向对象
1.1 排序算法
1.1.1 冒泡排序
相邻的两个数进行两两对比,符合条件的交换位置,找出最大或者最小值,剩下的数再从头开始进行两两对比,继续找出剩下的数的最大值或者最小值,直到只剩一个数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XxmR7KiU-1653492706181)(E:\培训\第一阶段\总结\Picture\wps12.png)]
public class Demo02 {
public static void main(String[] args) {
int[] nums = {8,6,5,4,73,8,9,3,2,4,5,67,4,3};
```
/*冒泡排序*/
//外层for循环控制的是轮数
for(int i = 0;i < nums.length - 1;i++){
//内层for循环控制的是每轮对比的次数
for(int j = 0;j < nums.length - 1-i;j++){
//下个元素小于当前数则进行交换
if(nums[j+1] < nums[j]) {
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
System.out.println("第"+(i+1)+"轮结束:"+ Arrays.toString(nums));
}
*冒泡排序
*选择排序
*快速排序
*插入排序
*堆排序
*归并排序
*希尔排序
*计数排序
*基数排序
*桶排序
1.1.2选择排序
/*选择排序
*/
public class Demo03 {
public static void main(String[] args) {
int[] nums = {3,1,5,2};
//外层for循环控制轮数
for (int i = 0; i < nums.length - 1; i++) {
//1.定义最小值的索引
int min = i;
//2.进行比较,j为被对比数的索引
for(int j = i+1;j < nums.length;j++){
//判断最小值索引所在的元素是否比被对比数大
if(nums[min] > nums[j]){
//记录最小值的索引
min = j;
}
}
//3.查看min是否发生改变
if(min != i){
int temp = nums[min];
nums[min] = nums[i];
nums[i] = temp;
}
//alt+回车自动导包
System.out.println("第"+(i+1)+"轮:"+ Arrays.toString(nums));
}
}
}
每一轮的每一次按元素的索引所对应的数值进行比较,每一次记录最小值的索引,每一轮结束再根据索引是否改变进行交换位置
选择排序最妙的地方就是for循环里面的j=i+1
/*下列循环可以实现i:0 1 2 3 4 5 6 7 8
j循环次数依次减少,次数为9 8 7 6 5 4 3 2 1 */
for(int i=0;i<nums.length-1;i++){//nums.length为10
for(int j=i+1;j<nums.length;j++){
}
1.2 面向对象
创建对象的步骤:
- 创建一个类 – 抽象(职业)
类属性(名词) – 特征
类方法(动词) – 行为
- 实例化对象(创建对象) – 具体的(员工)
类名 对象名 = new 类名();
也可以:
类名 对象名;
对象名 = new 类名();
对象名.属性 – 调用对象的属性
对象名.方法 – 调用对象的方法
面向过程:一个类中执行完所有的程序
面向对象:把一个类中的事拆分由其他类完成
面向过程和面向对象相对于是解决一个现实问题的两种方法
用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭
蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香
盖浇饭的好处就是"菜"“饭"分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是"可维护性"比较好,“饭” 和"菜"的耦合度比较低。蛋炒饭将"蛋”“饭"搅和在一起,想换"蛋”"饭"中任何一种都很困难,耦合度很高,以至于"可维护性"比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在3个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性
创建一个类:
//java程序员
public class JavaEngineer {
//类属性 -- 特征
String name;
```
//类方法 -- 行为
public void javaDevelopment(){
System.out.println(name+"在进行后台开发");
}
}
创建对象:
public class Demo02 {
public static void main(String[] args) {
/*面向对象*/
//创建对象(实例化对象) -- 请人
//类名 对象名 = new 类名();
JavaEngineer je1 = new JavaEngineer();
//通过对象名调用属性进行赋值
je1.name = "小明";
//通过对象名调用方法
je1.javaDevelopment();
}
面向对象:封装、继承、多态
方法的封装和属性的封装
属性的封装:使属性更加合理
-
属性私有化
-
提供get/set方法来进行取值和赋值
set():拥有参数,没有返回值 – 针对单个属性进行赋值、修改
get():没有参数,有返回值 – 针对单个属性进行的取值
//电脑类
public class Computer {
//品牌
String brand;
//价格
//private 代表私有的访问修饰符
//只允许在当前类中使用
private double price;
```
//设置价格的方法 -- 赋值
public void setPrice(double p){
if(p < 14000){
price = 14000;
}else{
price = p;
}
}
//通过方法取值
public double getPrice(){
return price;
}
//观看
public void watch(){
System.out.println("使用价值"+price+"的"+brand+"电脑观看学习视频");
}
```
}
/*避免价格不合理 -- 每创建一个对象需要写一次*/
double price = 100;
if(price < 14000){
computer.price = 14000;
}else{
computer.price = price;
}
1.在对象的外部,给对象的属性赋值,可能存在非法数据的录入,在公共的访问方法内部,添加逻辑判断,进而过滤掉非法数据,以保证数据安全
2.每创建一个对象都要设置一个判断价格合不合理,如果有n个对象,那就太麻烦了,所以就写在类里面,不可能说有一个价格为1000块钱的或者价格100块钱的一手电脑品牌,下例是没有价格低于14000的外星人品牌电脑
public class Computer {
//品牌
String brand;
//价格
//private 代表私有的访问修饰符
//只允许在当前类中使用
private double price;
```
//设置价格的方法 -- 赋值
public void setPrice(double p){
if(p < 14000){
price = 14000;
}else{
price = p;
}
}
//通过方法取值
public double getPrice(){
return price;
}
类中的私有化,如果要在main方法或者其他地方使用就要通过方法get/set方法来取值、赋值,get/set方法是外界访问对象私有属性的唯一通道
面向对象
1.1 构造方法
构造方法:不能有返回值类型,方法名必须与类名一致,创建对象
访问修饰符 方法名(){ //方法名必须与类名一致
}
- 无参构造方法默认存在的,但是当类里存在有参构造方法后默认提供无参构造的方法则消失,如需使用必须手动实现
无参构造方法 – 创建对象
public class User {
private String name;
private int age;
```
/*
访问修饰符 方法名(){
}
*/
public User(){
System.out.println("无参构造方法");
}
//有参构造方法 -- 2个参数
public User(String n,int a){
name = n;
age = a;
}
public class Demo01 {
public static void main(String[] args) {
/*User user = new User();
user.setName("zs");
System.out.println(user.getName());*/
```
//User user = new User();
//调用了无参构造方法 -- 创建对象
User user1 = new User();
//通过toString()获取对象信息
System.out.println(user1.toString());
//调用有参构造方法 -- 创建对象同时进行属性的赋值
User user2 = new User("ls",20);
System.out.println(user2.toString());
}
}
- 有参构造方法,创建对象的同时进行属性赋值
有参构造方法 – 创建对象的同时,一个或者多个属性的赋值
public class User {
private String name;
private int age;
```
/*
访问修饰符 方法名(){
}
*/
public User(){
System.out.println("无参构造方法");
}
//有参构造方法 -- 2个参数
public User(String n,int a){
name = n;
age = a;
}
public class Demo01 {
public static void main(String[] args) {
/*User user = new User();
user.setName("zs");
System.out.println(user.getName());*/
```
//User user = new User();
//调用了无参构造方法 -- 创建对象
User user1 = new User();
//通过toString()获取对象信息
System.out.println(user1.toString());
//调用有参构造方法 -- 创建对象同时进行属性的赋值
User user2 = new User("ls",20);
System.out.println(user2.toString());
}
}
set – 针对单个属性赋值、修改
get – 针对单个属性的取值
toString – 返回对象的信息
//返回对象的属性
public String toString(){
return "User {name = "+name+",age="+age+"}";
}
System.out.println(user2.toString());
用 类名 对象名 = new 对象名();创建对象和用无参构造方法创建对象没有区别
用 类名 对象名 = new 对象名(); 创建对象需要先定义对象名然后再进行赋值
User user = new User();
user.setName("zs");
System.out.println(user.getName());
使用无参构造方法也一样,它是默认存在的,但当有有参构造方法存在则需要手动实现
public User(){
System.out.println("无参构造方法");
}
//有参构造方法 -- 2个参数
public User(String n,int a){
name = n;
age = a;
}
1.2 全局属性和局部属性
类属性 – 成员变量,成员变量,全局变量,全局属性,类里面的属性就是全局变量
全局属性:定义在类中,拥有初始化值,拥有访问修饰符,全局属性生命周期为整个类
局部属性:定义在方法中,必须手动初始化才可以使用,不能有访问修饰符,局部属性生命周期为所在的方法
1.2.1 全局属性 vs 局部属性
-
全局属性定义在类中,局部属性定义在方法中
-
全局属性拥有初始值,局部属性必须手动初始化才可以使用
-
全局属性拥有访问修饰符,局部属性不能有访问修饰符
-
全局属性的生命周期为整个类,局部属性的生命周期为所在的方法
当全局属性和局部属性同名的情况下使用局部属性
1.2.2 this
this:代表当前类对象
this.属性 – 调用当前类对象的属性
this.方法 – 调用当前类对象的方法
this() – 调用当前类对象的无参构造方法
this(实参) – 调用当前类对象的有参构造方法
给user这个对象传递name、age这两个属性,加上this的意思就是传递的是属性,不然两个东西不可能赋值在同一个对象上面,而且还是不同类型,只有属性可以这样
public class Student {
private String name;
private int age;
```
public Student() {
//调用当前类对象的有参构造方法
this("a",1);
System.out.println("无参");
}
public Student(String name, int age) {
System.out.println("有参");
this.name = name;
this.age = age;
}
//普通方法无法调用构造方法
/*public void test(){
this();
}*/
普通方法无法调用构造方法
1.2.3 普通方法 vs 构造方法
结构不同,普通方法:
public void test(){
}
构造方法的方法名必须与类名一样:
public class Student {
public Student() {
System.out.println("无参");
}
public Student(String name, int age) {
System.out.println("有参");
this.name = name;
this.age = age;
}
}
通过this调用构造方法必须在构造方法的第一行
1.3 静态变量
static:静态的,被static所修饰的代码会随着字节码文件优先于对象加载进内存
static可以修饰成员方法,成员变量
特点:
- 被类的所有对象共享,这也是我们判断是否使用静态关键字的条件
例如所有学生对象的大学都为清华大学,那么我们就可以在学生类的大学属性设置成static,让所有学生共享,而不用每一个学生都输入清华大学
- 静态变量可以通过类名直接调用,可以把它当作全局变量(拥有初始值、访问修饰符、生命周期为整个类)
public class Test {
String name;
//静态变量
static int age;
}
/*static:静态的,被static所修饰的代码会随着字节码文件优先于对象加载进内存
*/
public class Demo01 {
public static void main(String[] args) {
Test test1 = new Test();
test1.name = "zs";
test1.age = 18;
```
Test test2 = new Test();
test2.name = "ls";
test2.age = 20;
System.out.println("--------test1------------");
//name属于对象的,每个对象拥有一份name属性
//age为静态变量,不属于对象,属于类,所有对象共享一份数据
System.out.println("name:"+test1.name+",age:"+test1.age);
System.out.println("--------test2------------");
System.out.println("name:"+test2.name+",age:"+test2.age);
//静态变量通过类名直接调用
System.out.println("age:"+Test.age);
```
}
}
name属于对象的,每个对象拥有一份name属性(不同值)
age为静态变量,不属于对象,属于类,所有对象共享一份数据,最终的值为最后赋值的20(相同值)
1.4 静态方法
静态的成员方法的特点:
- 静态的成员方法允许直接访问静态成员
- 静态的成员方法不能直接访问非静态成员
- 静态的成员方法中不允许使用this或者super关键字
- 静态的成员方法可以继承,不能重写、没有多态
- 静态的成员方法只能直接调用静态方法和静态属性
为什么静态方法不能直接访问非静态成员?
1.静态变量在类加载的时候,就初始化static的成员,在此时,static已经分配内存空间,所以可以被直接访问
2.非静态变量在通过new创建对象而初始化,所以在对象创建之前,是不可以访问非静态变量的
2.总结:静态变量属于类,不需要生成对象就存在了,而非静态变量需要生成对象才能产生,所以静态变量不能直接访问。
非静态的成员方法
- 能访问静态的成员变量
- 能访问非静态的成员变量
- 能访问静态的成员方法
- 能访问非静态的成员方法
//先定义的属性无法使用后定义的属性 -- 相当于静态方法
int num1 = num2;
//后定义的属性可以使用先定义的属性 -- 普通方法
int num2 = num1;
public class Test {
private String name;
private static int age;
```
//普通方法
public void method(){
System.out.println("这是个平平无奇的方法");
}
//静态方法
public static void staticMethod(){
System.out.println("静态方法");
//静态方法不能调用非静态的方法,只能调用静态方法
//method();
//静态方法可以调用静态属性
System.out.println(age);
//静态方法不能调用非静态属性
//System.out.println(name);
}
}
工具类 – 方法都为静态方法
/*工具类 -- 方法都为静态方法
*/
public class MyArrays {
//遍历数组
public static void printArray(int[] arrays){
for (int i = 0; i < arrays.length; i++) {
System.out.println(arrays[i]);
}
}
//数组的排序 -- 从小到大
}
普通方法可以通过对象调用对象方法,静态方法可以直接通过类名调用
public class Test {
private String name;
private static int age;
```
//普通方法
public void method(){
System.out.println("这是个平平无奇的方法");
}
//静态方法
public static void staticMethod(){
System.out.println("静态方法");
//静态方法不能调用非静态的方法,只能调用静态方法
//method();
//静态方法可以调用静态属性
System.out.println(age);
//静态方法不能调用非静态属性
//System.out.println(name);
}
}
/* 静态方法只能直接调用静态方法和静态属性
*
*/
public class Demo01 {
public static void main(String[] args) {
//创建对象
Test test = new Test();
//通过对象调用对象方法
test.method();
//直接通过类名调用
Test.staticMethod();
int[] nums = {3,4,5,6};
MyArrays.printArray(nums);
int[] nums2 = {3,4,6,3};
//先定义的属性无法使用后定义的属性 -- 相当于静态方法
//int num1 = num2;
//后定义的属性可以使用先定义的属性 -- 普通方法
//int num2 = num1;
}
}
1.5 构造代码块
构造代码块:
在每次对象创建之前优先执行的代码块
static修饰构造代码块:
类被使用的第一次,优先执行静态代码块
优先级:静态属性 > 静态代码块 > 构造代码块 > 构造方法
父类静态代码块 → 子类静态代码块 → 父类初始化块 → 父类构造函数 → 子类初始化块 → 子类构造函数
静态构造代码块和构造代码块可以存在多个(按顺序)
public class Demo01 {
public static void main(String[] args) {
Test test1 = new Test();
test1.name = "大明";
test1.age = 200000;
```
Test test2 = new Test();
test1.name = "小明";
test1.age = 100000;
}
public class Test {
String name;
static int age;
//普通方法
public void eat(){
System.out.println("吃");
}
//静态方法
public static void sleep(){
//name ="小小";静态方法不能直接访问非静态变量
age = 1;
System.out.println("睡觉");
}
static{
System.out.println("静态代码块2");
}
//public static Test(){} -- static不能修饰构造方法
//无参构造方法
public Test(){
System.out.println("无参构造方法");
}
//构造代码块
{
System.out.println("构造代码块2");
}
{
System.out.println("构造代码块1");
}
static{
System.out.println("静态代码块1");
}
}
运行结果:
静态代码块2
静态代码块1
构造代码块2
构造代码块1
无参构造方法
构造代码块2
构造代码块1
无参构造方法
总结:
1.在创建一个对象的过程中,三者的执行顺序是:静态代码块 --> 构造代码块 --> 构造方法
2.每创建一个对象,构造代码块和构造方法就会执行一次;而不管创建多少个对象,静态代码块仅仅执行一次
3.静态的总是优先于非静态的执行
4.构造代码块和构造方法是捆绑执行,即构造代码块与构造方法总是按顺序一起执行,没有中断
继承
1.1 继承
继承就是保持已有类的特性而构造新类的过程,继承后,子类能够利用父类中定义的变量和方法,就像它们属于子类本身一样
继承:extends – 多个类拥有相同的属性和方法,把相同的属性和方法抽取到一个父类中,子类继承父类
语法:
class 子类 extends 父类{
}
public class Dog extends Pet{
```
/*类中添加了有参构造方法,无参构造方法则消失,需要手动创建
如果继承了父类的无参构造方法就不需要手动创建可以直接使用
在Demo01中使用无参构造方法创建dog对象会报错
所以证明子类无法继承父类的无参构造方法*/
/*public Dog(String name,int age){
}*/
```
public Dog(){
//this("str");
//默认存在 -- 调用父类的无参构造方法,无法与this调用构造方法同时存在
//super();
}
public Dog(String name){
//默认存在
//super();
}
}
-
子类继承父类,只能继承一个类,不能继承多个类,但可以多级继承,属性和方法逐级叠加
-
子类不能继承父类的私有属性和私有方法
-
子类不能继承父类的构造方法(有参构造方法和无参构造方法)
-
子类的构造方法中默认存在super(),子类对象构造之前先创建父类对象
public Dog(){
//this("str");
//默认存在 -- 调用父类的无参构造方法,无法与this调用构造方法同时存在
//super();
}
1.2 super
super:当前类的父类的对象
super.属性 – 调用父类对象的属性
super.方法() – 调用父类的方法
super() – 调用父类的无参构造方法
this和super调用构造方法都必须在构造方法中的第一行,所以二者不能同时在一个构造方法中调用构造方法
当子类构造中使用了this()或this(实参),即不可在同时书写super()或super(实参),会由this()指向构造方法完成super()调用
继承中变量的访问特点
在子类方法中访问一个变量
- 子类局部范围找
- 子类成员范围找
- 父类成员范围找
- 如果都没有就报错(不考虑父类的父类…)
当子类局部,子类成员,父类成员都有相同的变量时,想要在子类方法中访问子类的成员变量则使用this关键字,想要访问父类成员变量则使用super关键字
继承中成员方法的访问特点
通过子类对象访问一个方法
- 子类成员范围找
- 父类成员范围找
- 如果都没有就报错(不考虑父类的父类…)
1.2.1 This vs super
1.属性的区别:
(1)this访问本类中的属性,如果本类没有这个属性则访问父类中的属性
(2)super访问父类中的属性
2.方法的区别:
(1)this访问本类中的方法,如果本类没有这个方法则访问父类中的方法
(2)super访问父类中的方法
3.构造的区别:
(1)this调用本类构造构造,必须放在构造方法的首行
(2)super调用父类构造,必须放在子类构造方法首行
(3)其他区别:this表示当前对象。super不能表示当前对象
this.变量和super.变量
(1)this.变量:调用的当前对象的变量
(2)super.变量:直接调用的是父类中的变量
this(参数)和super(参数)方法
(1)this(参数):调用(转发)的是当前类中的构造器
(2)super(参数):用于确认要使用父类中的哪一个构造器
注意点:
(1)在对拥有父类的子类进行初始化时,父类的构造方法也会执行,且优先于子类的构造函数执行;因为每一个子类的构造函数中的第一行都有一条默认的隐式语句super()
(2)this() 和super()都只能写在构造函数的第一行
(3)this() 和super() 不能存在于同一个构造函数中
1.this()和super()都必须写在构造函数的第一行
2.this()语句调用的是当前类的另一个构造函数而这个另一个构造函数中必然有一个父类的构造器,再使用super()又调用一次父类的构造器, 就相当于调用了两次父类的构造器,编译器不会通过
(4)this和super不能用于static修饰的变量,方法,代码块因为this和super都是指的是对象(实例)
1.3 访问修饰符
public:公开的 – 都可以使用
protected:受保护的 – 当前包可以使用,不同包只有子类可以使用
default:默认的 – 只能当前包使用
private:私有的 – 只能在当前类中使用
1.4 重写
重写:父类的方法满足不了子类的需求
-
发生在继承关系中
-
方法名必须相同
-
参数列表必须一致
-
返回值类型必须相同
-
子类重写的方法访问修饰符必须比父类的更大
-
子类重写父类方法后,调用时优先执行子类重写后的方法
public > protected > default > private
方法的重载:是方法的衍生(扩展)
1.发生在同一个类中
2.方法名相同
3.参数列表必须不一致(参数的数量、参数类型、参数类型顺序)
4.与返回值类型无关
1.4.1 重载 vs 重写
-
方法的重写是方法的覆盖,方法的重载是方法扩展
-
方法的重写是发生在继承关系中,方法的重载是发生在同一个类中
-
重写的参数列表必须一致,重载的参数列表必须不一致
-
重写的访问修饰符不能比父类的更严谨,重载与访问修饰符无关
-
重写的返回值必须一致,重载与返回值无关
1.5 final
final:最终的,可以修饰成员方法,成员变量,类
- 修饰变量:final修饰的变量为常量,常量名为大写,多个单词使用“_”进行拼接,一般与static一起使用当做是状态码,需要手动初始化
- 修饰方法:可以重载但是无法被子类重写
- 无法修饰构造方法
- 修饰类:不能被继承
final修饰局部变量
- 变量是基本数据类型:final修饰指的是基本类型的数据值不能发生改变
- 变量是引用类型:final修饰指的是引用类型的地址值不能发生改变,但是地址里面的内容是可以发生改变的
多态
1.1 多态
多态:父类对象引用子类对象(引用类型中的“=”的含义为引用)
二者具有直接或间接的继承关系时,父类引用可指向子类对象,即形成多态
父类引用仅可调用父类所声明的属性和方法,不可调用子类独有的属性和方法
多态可以分为变量的多态,方法的多态,类的多态,多态的表现形式有方法重载,方法改写,多态变量和泛型
使用场景:
场景一:使用父类作为方法形参实现多态,使方法参数的类型更为宽泛
场景二:使用父类作为方法返回值实现多态,使方法可以返回不同子类对象
问题:
有一个新的动物,医生需要重载多一个打针的方法
解决办法:
医生一个打针方法完成对所有动物的打针行为
创建一个Animal类,猫,狗等等的宠物继承Animal,医生创建一个接收Animal对象的方法
调用方可以传递Animal对象或者是Animal子类的对象
/*使用多态创建对象,调用的属性为父类属性,
使用父类对象调用方法先看子类是否有重写,有重写则调用子类的重写方法
没有重写则调用父类的方法
*/
public class Demo01 {
public static void main(String[] args) {
//父类对象引用子类对象
Animal animal = new Cat();
//打印的是父类的属性 -- 类型为父类类型
System.out.println(animal.name);
//打印的是子类重写的方法 -- 子类重写了父类的方法,所以调用的是子类重写的方法
animal.method();
}
}
多态:
1、使用父类类型的引用指向子类的对象
2、该引用只能调用父类中定义的方法和变量
3、如果子类中重写(覆盖)了父类中的一个方法,那么在调用这个方法的时候,将会调用子类中的这个方法
4、变量不能被重写(覆盖),重写只针对方法,如果在子类中重写了父类的变量,编译时会报错
面向对象的设计原则:开口合理最单依
开闭原则,接口分离原则,合成复用原则,里氏替换原则,最少原则(迪米特原则),单一原则,依赖原则
开闭原则:
open:对拓展代码持开放状态
close:对内部修饰的代码持关闭状态
1.1.1 多态的好处和弊端
-
多态的好处:提高了程序的扩展性
具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作
-
多态的弊端:不能使用子类的特有功能
1.1.2 向上向下转型(解决多态的弊端)
引用数据类型进行向下转型,强制转换有可能出现异常,通过instanceof避免异常出现
Instanceof:对象名 instanceof 类名,判断关键字左边的对象是否为右边的类型,是则返回true,否则false
public class Demo01 {
public static void main(String[] args) {
/*
//----------------基本数据类型---------------
byte num1 = 1;
int num2 = 2;
//小类型赋值给大类型 -- 默认转换,向上转型
num2 = num1;
//大类型赋值给小类型 -- 强制转换,向下转型
num1 = (byte)num2;
```
//----------------引用数据类型---------------
//子类转成父类 -- 默认转换,向上转型
Animal animal1 = new Cat();
//父类转成子类
Animal animal2 = new Animal();
//父类转成子类 -- 强制转换,向下转换
Cat cat = (Cat)animal2;
*/
//Animal animal = new Cat();
//通过多态形式创建的对象,只能调用从父类继承的方法或者重写的方法
//需要调用子类独有的方法必须强转为子类的类型
//animal.catMethod();
//强转转换会出现异常,animal本质为Cat对象,强转为Dog会报异常
//Dog dog =(Dog)animal;
Cat cat = new Cat();
method(cat);
System.out.println("------------");
Dog dog = new Dog();
method(dog);
}
public static void method(Animal animal){
//强转为dog对象,为了调用dog独有的方法,如果传递进行的是Cat对象会报异常
//((Dog)animal).dogMethod();
//((Cat)animal).catMethod();
//判断animal是引用Cat还是Dog的对象,再强转为对应的类型避免出现异常
if(animal instanceof Cat){
((Cat)animal).catMethod();
}else if(animal instanceof Dog){
((Dog)animal).dogMethod();
}
}
```
}
1.2 抽象类
abstract:抽象的
- abstract修饰的方法为抽象方法,抽象方法没有方法体,必须存在抽象类中(abstract修饰的类)
- 普通子类继承抽象类必须重写抽象方法 – 起到约束作用
- 抽象类拥有抽象方法和普通方法,可以没有抽象方法
- 抽象类拥有构造方法,但不能被实例化
- 抽象子类继承抽象父类,可以有选择地重写父类的抽象方法
抽象类 – 模板
抽象类不能实例化
抽象类如何实例化呢?参照多态的方式,通过子类对象实例化,这叫抽象类多态
1.3 接口
接口:特殊的抽象类,接口定义:interface 实现接口:implement
-
接口中的方法都是公开的抽象方法
-
接口不存在构造方法,不能被实例化
-
接口的属性为公开静态常量
-
类可以多实现接口
-
在JDK1.8以后接口允许有static和default修饰的方法(新特性)
-
接口不能实现接口
-
接口可以多继承接口
-
接口不允许有代码块
接口 – 额外功能的添加
接口不能实例化
接口如何实例化呢?
参照多态的方式,通过实现类对象实例化,这叫接口多态
多态的形式:具体类多态,抽象类多态,接口多态
多态的前提:
有继承或者实现关系
有方法重写
有父(类/接口)引用指向(子/实现)类对象
接口的实现类
要么重写接口中的所有抽象方法
要么是抽象类
jump的接口
实现类cat重写jump方法
参照多态的方式,通过实现类cat的对象j实例化
成员方法
只能是抽象方法
默认修饰符:public abstract
1.3.1 接口 vs 抽象类
相同点:
1.接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他的类实现和继承
2.接口和抽象类都是可以包含抽象方法的,实现接口或是继承抽象类的普通子类都必须实现这些抽象方法
不同点:
1.接口只能包含抽象方法,不能包含已经提供实现的方法;抽象类则完全可以包含普通的方法
2.接口不能定义静态方法;抽象类可以定义静态方法
3.接口里面不能够包含初始化块;但是抽象类里面则完全可以包含初始化块
4.一个类最多只能有一个直接父类,包括抽象类;但是一个类可以直接实现多个接口,通过实现多个接口可以弥补Java中的单继承的不足
5.实现抽象类和接口的类必须实现其中的所有方法。抽象类中可以有非抽象方法。接口中则不能有实现方法
1.4 形参和返回值
1.4.1 类名作为形参和返回值
- 方法的形参是类名,其实需要的是该类的对象
- 方法的返回值是类名,其实返回的是该类的对象
1.4.2 抽象类名作为形参和返回值
- 方法的形参是抽象类名,其实需要的是该抽象的子类对象
- 方法的返回值是抽象类名,其实返回的是该抽象类的子类对象
1.4.3 接口名作为形参和返回值
- 方法的形参是接口名,其实需要的是该接口的实现类对象
- 方法的返回值是接口名,其实返回的是该接口的实现类对象
内存和异常
1.1 内存
栈:栈帧、局部变量
堆:创建的对象
方法区:字节码文、常量池、static静态修饰的内容
普通方法在栈中,静态方法在方法区中
1.2 异常
Throwable:所有异常和错误的父类
Error:错误
Exception:异常
- 受检性异常(一般异常)
编译的时候会出现的异常
- 非受检性异常(运行时异常)
程序运行时才会出现的异常
处理异常:
- 捕获异常:当知道如何处理异常或者不希望影响后续代码
try{
可能会出现异常的代码块
}catch(异常类型 异常对象){
处理异常的方式
}
try里的代码块如果出现异常,则结束try的代码块,
进入catch块处理异常,不影响后续代码的执行
//try块里的异常,catch块没有捕获
private static void method5(String str) {
try {
System.out.println(str.equals(""));
FileInputStream fis = new FileInputStream("c://c");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("方法结束");
}
这里只捕获了文件不存在异常,没有捕获空指针异常,Excepion是所有异常的父类
//一个catch块处理所有的异常
private static void method4(String str) {
try {
System.out.println(str.equals(""));
FileInputStream fis = new FileInputStream("c://c");
} catch (Exception e) {
System.out.println("有异常");
}
}
一个catch块处理多个异常 – 多个异常相同的处理方式
//一个catch块处理多个异常 -- 多个异常相同的处理方式
private static void method3(String str) {
try {
System.out.println(str.equals(""));
FileInputStream fis = new FileInputStream("c://c");
} catch (FileNotFoundException | NullPointerException | ClassCastException e) {
System.out.println("有异常");
}
}
//一个异常进行捕获处理
public static void method1(){
try{
FileInputStream fis = new FileInputStream("c://c");
System.out.println("try块里的代码");
}catch (FileNotFoundException e){
System.out.println("catch块代码");
//打印异常信息
e.printStackTrace();
}
System.out.println("捕获异常后的代码");
}
}
- 声明(抛出)异常类型,关键词:throws
当出现异常,程序会终止
不知道如何处理异常或者需要由调用方处理的时候
/*声明异常
*/
public class Demo03 {
public static void main(String[] args) throws FileNotFoundException {
//调用方需要处理异常
method1();
System.out.println("调用方法后");
}
//声明异常类型
private static void method1() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("c://c");
System.out.println("创建对象后");
}
}
- 抛出(声明)异常对象,关键字:throw
抛出异常对象
自定义异常说明
/*抛出异常类型
*/
public class Demo04 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("c://c");
} catch (FileNotFoundException e) {
//抛出异常对象
try {
//抛出异常对象
throw new MyFileException("Demo04第15行代码报错");
} catch (MyFileException e1) {
e1.printStackTrace();
}
}
```
try{
String str = null;
System.out.println(str.equals(""));
}catch (Exception e){
throw new RuntimeException("空指针异常");
}
```
}
}
解决异常:
-
明白异常类型
-
了解异常说明
-
确定异常位置
finally:一定会执行的代码块,除非虚拟机关闭
/*finally:一定会执行的代码块
*/
public class Demo05 {
public static void main(String[] args) {
//method1();
method2();
}
//finally搭配try{}catch(){}
public static void method1(){
FileInputStream fis = null;
try{
fis = new FileInputStream("c://c");
} catch (FileNotFoundException e){
e.printStackTrace();
}finally {
System.out.println("一定执行的代码");
}
}
public static void method2(){
String str = null;
try{
System.out.println(str.equals(""));
}finally {
System.out.println("finally的代码");
}
System.out.println("finally外的代码");
}
}
常用类
1.1 == vs equals()
-
== 是运算符,equals()是方法
-
== 运算基本数据类型是比较数值,运算引用数据类型是比较内存地址
-
equals()只能通过对象调用,没有重写的equals()是调用object的equals(),比较的是两个对象的内存地址,如果重写的equals(),则可能会比较存储的数据
1.1.1什么时候需要重写类的equals()
自定义对象如需比较内容,需要重写equals(),因为Object中默认的equals方法,内部还是使用“==”来比较对象的内存地址,当我们需要重新定义两个对象是否相等的条件时,需要进行重写。比如通常情况下,我们认为两个不同对象的某些属性值相同时,就认为这两个对象是相同的
重写equals()
/*自定义对象如需比较内容,需要重写equals()
*/
public class Demo02 {
public static void main(String[] args) {
User user1 = new User(10);
User user2 = new User(10);
System.out.println(user1.equals(user2));
}
}
public class User {
private int num;
```
public User(int num) {
this.num = num;
}
/*重写equals()*/
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(obj instanceof User){
User user = (User)obj;
if(user.getNum() == this.getNum()){
return true;
}
}
return false;
}
```
}
this单独使用的情况下,指的是全局变量,在方法或者函数中使用时,指的是调用方法或者函数的对象
如上图,this是在方法equals()中的,user1.equals()是user1在调用,所以这里的this指的是user1
public class Human {
private String name;
private int age;
private char sex;
private double height;
```
public Human() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Human human = (Human) o;
return age == human.age &&
sex == human.sex &&
Double.compare(human.height, height) == 0 &&
Objects.equals(name, human.name);
}
String里面的equals()方法
下图为equals()的底层代码的运用以及执行过程解释
public class Demo01 {
public static void main(String[] args) {
String str1 = new String("abc");
String str2 = new String("abc");
String str3 = "abc";
String str4 = "abc";
//false
System.out.println(str1 == str2);
//false
System.out.println(str1 == str3);
//true
System.out.println(str3 == str4);
//true
System.out.println(str1.equals(str4));
/*1.判断是否相同的内存地址
2.判断类型是否相等
3.判断长度是否相等
4.判断内容是否相等*/
//anObject为str4
public boolean equals(Object anObject) {
//this代表调用方法的当前对象str1
//判断str1和str4是否同一个内存地址
if (this == anObject) {
return true;
}
//判断的对象参数是否为String类型
if (anObject instanceof String) {
//把参数对象强转为String类型 -- 需要调用到子类独有的方法
//anotherString为str4
String anotherString = (String)anObject;
//value为str1的value -- value为存储str1数据的char类型数组{'a','b','c'}
//value.length为str1里char类型数组的长度3
int n = value.length;
//判断str1和str4的char类型数组长度是否一致
if (n == anotherString.value.length) {
//v1为str1存储数据的字符数组
char v1[] = value;
//v2为str4存储数据的字符数组
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
//判断字符数组的元素是否不相等
if (v1[i] != v2[i])
return false;
i++;
}
//2个字符数组的元素都相等
return true;
}
}
//传递的参数不为String类型返回false
//长度不一致
return false;
}
String:不可变字符序列
/*String:不可变字符序列
*/
public class Demo03 {
public static void main(String[] args) {
String str = "abcdefg";
//通过索引返回指定的字符
char ch = str.charAt(3);
System.out.println("ch:"+ch);
```
//末尾追加 -- abcdefg123
str = str.concat("123");
System.out.println("str:"+str);
//判断是否包含指定字符串
boolean flag = str.contains("bc");
System.out.println("判断是否包含指定字符串:"+flag);
//判断是否以指定字符串结尾
flag = str.endsWith("g123");
System.out.println("判断是否以指定字符串结尾:"+flag);
//判断是否以指定字符串开头
flag = str.startsWith("abc");
System.out.println("判断是否以指定字符串开头:"+flag);
//indexOf()
//isEmpty()
//lastIndexOf()
//replace()
//replaceFirst()
//split()
//substring()
//toCharArray()
//toLowerCase()
//toUpperCase()
//trim()
//valueOf()
```
}
}
StringBuffer:线程安全,可变的字符序列
public class Demo04 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("abc");
//末尾追加
sb.append("d");
System.out.println("末尾追加:"+sb);
//中间插入
sb.insert(2,"123");
System.out.println("中间插入:"+sb);
//头部插入
sb.insert(0,"1");
System.out.println("头部插入:"+sb);
```
//反转
sb.reverse();
System.out.println("反转:"+sb);
//删除单个
sb.deleteCharAt(3);
System.out.println("删除单个:"+sb);
//删除多个
sb.delete(1,4);
System.out.println("删除多个:"+sb);
//删除全部
//sb.delete(0,sb.length());
//System.out.println("删除全部:"+sb);
//replace()
//indexOf()
//lastIndexOf()
//substring()
//转换成字符串
String str = sb.toString();
System.out.println(str);
}
```
}
StringBuilder:线程不安全,可变字符序列
/*StringBuilder
*/
public class Demo05 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("abc");
//末尾追加
sb.append("d");
System.out.println("末尾追加:"+sb);
//中间插入
sb.insert(2,"123");
System.out.println("中间插入:"+sb);
//头部插入
sb.insert(0,"1");
System.out.println("头部插入:"+sb);
```
//反转
sb.reverse();
System.out.println("反转:"+sb);
//删除单个
sb.deleteCharAt(3);
System.out.println("删除单个:"+sb);
//删除多个
sb.delete(1,4);
System.out.println("删除多个:"+sb);
//删除全部
//sb.delete(0,sb.length());
//System.out.println("删除全部:"+sb);
//replace()
//indexOf()
//lastIndexOf()
//substring()
//转换成字符串
String str = sb.toString();
System.out.println(str);
```
}
}
Date:时间类
SimpleDateFormat:格式化类
/*Date:时间类
SimpleDateFormat:格式化类
*/
public class Demo01 {
public static void main(String[] args) {
//存储当前系统时间
Date date = new Date();
//Wed Jul 28 15:35:47 CST 2021 -- 格林威治时间格式
System.out.println("格林威治时间格式:"+date);
//2021-07-28 15:35:47
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化时间对象
String str1 = sdf.format(date);
System.out.println("北京时间:"+str1);
```
//在基准时间上添加一个小时
//1970-01-01 08:00:00(东八区)
Date date1 = new Date(1000 * 60 * 60);
String str2 = sdf.format(date1);
System.out.println("时间:"+str2);
```
}
}
BigDecimal:精准运算的类
BigInteger:大长度的Int类型包装类
/*BigDecimal:精准运算的类
BigInteger:大长度的Int类型包装类
*/
public class Demo02 {
public static void main(String[] args) {
double num1 = 0.01;
double num2 = 0.09;
System.out.println(num1+num2);
//精准运算
BigDecimal bd1 = new BigDecimal(num1+"");
BigDecimal bd2 = new BigDecimal(num2+"");
BigDecimal bd3 = bd1.add(bd2);
System.out.println(bd3);
```
Integer integer1 = 2147483647;
Integer integer2 = 2147483647;
Integer integer3 = new Integer(integer1 + integer2);
System.out.println(integer3);
BigInteger bi1 = new BigInteger(integer1 + "");
BigInteger bi2 = new BigInteger(integer2 + "");
BigInteger bi3 = bi1.add(bi2);
System.out.println(bi3);
```
}
}
装箱:基本数据类型数据转换成对应的包装类对象
拆箱:包装类对象转换成对应的基本数据类型
/*装箱: 基本数据类型数据转换成对应的包装类对象
拆箱: 包装类对象转换成对应的基本数据类型
*
*/
public class Demo01 {
public static void main(String[] args) {
//-----------手动装拆箱-----------
//基本数据类型转化成包装类
//基本数据类型
int num = 10;
//转换成int对应的包装类 -- 装箱
Integer integer = new Integer(num);
System.out.println("integer:"+integer);
```
String str = "123";
//把String类型的数值转换成int类型的数据
int num2 = Integer.parseInt(str);
System.out.println("num2:"+num2);
//包装类转换成基本数据类型 -- 拆箱
int num3 = integer.intValue();
System.out.println("num3:"+num3);
//-----------JDK1.5以后自动装拆箱----------
int num4 = 5;
//自动装箱
Integer integer1 = num4;
//自动拆箱
int num5 = integer1;
```
}
}
正则表达式、内部类
1.1 正则表达式
正则表达式:一串描述规则的字符串
public class Demo01 {
public static void main(String[] args) {
String str = "手机号码:15364738295," +
"手机号码:17498507371," +
"手机号码:18746839473";
str = str.replaceAll("[1][1-9][0-9]{9}","***********");
System.out.println(str);
}
}
public class Demo02 {
public static void main(String[] args) {
String str = "17364384951";
System.out.println("原生判断:"+checkPhone(str));
boolean flag = str.matches("[1][1-9][0-9]{9}");
System.out.println("正则表达式判断:"+flag);
}
public static boolean checkPhone(String phone){
//1.长度是否为11位
if(phone.length() != 11){
return false;
}
//2.不是以1开头
if(!phone.startsWith("1")){
return false;
}
//3.是否都为数字
char[] chars = phone.toCharArray();
for (char ch:chars) {
if(ch < 48 || ch >57){
return false;
}
}
return true;
}
}
1.1.1 元字符
元字符是构造正则表达式的一种基本元素。
. :匹配除换行符以外的任意字符
w:匹配字母或数字或下划线或汉字
s:匹配任意的空白符
d:匹配数字
b:匹配单词的开始或结束
^:匹配字符串的开始
$:匹配字符串的结束
匹配有abc开头的字符串:abc或者^abc
匹配8位数字的QQ号码:^dddddddd$
匹配1开头11位数字的手机号码:^1dddddddddd$
1.1.2 重复限定符
*:重复零次或更多次
+:重复一次或更多次
?:重复零次或一次
{n}:重复n次
{n,}:重复n次或更多次
{n,m}:重复n到m次
有了这些限定符之后,我们就可以对之前的正则表达式进行改造了,比如:
匹配8位数字的QQ号码:^d{8}$
匹配1开头11位数字的手机号码:^1d{10}$
匹配银行卡号是14~18位的数字:^d{14,18}$
匹配以a开头的,0个或多个b结尾的字符串^ab*$
1.1.3 分组()
限定符是作用在与他左边最近的一个字符,那么问题来了,如果我想要ab同时被限定那怎么办呢?
正则表达式中用小括号()来做分组,也就是括号中的内容作为一个整体。
因此当我们要匹配多个ab时,我们可以这样。
如匹配字符串中包含0到多个ab开头:^(ab)*
1.1.4 转义
正则提供了转义的方式,也就是要把这些元字符、限定符或者关键字转义成普通的字符,做法很简答,就是在要转义的字符前面加个斜杠,也就是\即可。
匹配字符串中包含0到多个ab开头:^((ab))*
1.1.5 条件或 |
回到我们刚才的手机号匹配,我们都知道:国内号码都来自三大网,它们都有属于自己的号段,比如联通有130/131/132/155/156/185/186/145/176等号段,假如让我们匹配一个联通的号码,那按照我们目前所学到的正则,应该无从下手的,因为这里包含了一些并列的条件,也就是“或”,那么在正则中是如何表示“或”的呢?
正则用符号 | 来表示或,也叫做分支条件,当满足正则里的分支条件的任何一种条件时,都会当成是匹配成功。
那么我们就可以用或条件来处理这个问题
^(130|131|132|155|156|185|186|145|176)\d{8}$
1.1.6 区间[ ]
看到上面的例子,是不是看到有什么规律?是不是还有一种想要简化的冲动?
实际是有的
正则提供一个元字符中括号 [] 来表示区间条件。
限定0到9 可以写成[0-9]
限定A-Z 写成[A-Z]
限定某些数字 [165]
那上面的正则我们还改成这样:
^((13[0-2])|(15[56])|(18[5-6])|145|176)\d{8}$
上面几个只是一部分
1.2 内部类
内部类:(定义在类里的类)
-
成员内部类
-
局部内部类
-
静态内部类
-
匿名内部类
1.2.1 成员内部类
格式:
public class 类名{
修饰符 class 类名{
}
}
public class Demo01 {
public static void main(String[] args) {
//1.实例化外部类
//Outter outter = new Outter();
//2.通过外部类对象来创建内部类对象
//Outter.Inner inner1 = outter.new Inner();
```
//直接实例化成员内部类
Outter.Inner inner = new Outter().new Inner();
}
```
}
成员内部类,外界如何创建使用呢?
格式:
外部类名.内部类名 对象名 = 外部类对象.内部类对象;
范例:
Outer.Inner oi = new Outer().new Inner();
1.2.2 局部内部类
局部内部类是在方法中定义的类,所以外界是无法直接使用,要在方法内部创建对象并使用该类可以直接访问外部类的成员,也可以访问方法内部的局部变量
局部内部类用法 – 方法中存在多个属性或者方法,把属性和方法封装成一个对象,只能在方法中实例化并使用
1.2.3 静态内部类
静态属性 – 直接通过类名调用,多个对象同享一份数据
内部类 vs 静态内部类
1.静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法
2.静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法
3.静态内部类可以单独初始化
静态内部类使用场景
当外部类需要使用内部类,而内部类无需外部资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计
public class Demo01 {
public static void main(String[] args) {
//调用静态属性 -- 直接通过类名调用
Outter.num = 10;
```
//调用静态内部类的方法
Outter.Inner inner = new Outter.Inner();
inner.innerMethod();
}
}
//外部类
public class Outter {
//静态属性 -- 直接通过类名调用,多个对象同享一份数据
public static int num;
```
//静态内部类
public static class Inner{
public void innerMethod(){
System.out.println("静态内部类的方法");
}
}
1.2.4 匿名内部类
匿名内部类:减少了类的创建,只适用于对象创建一次或者少次
前提:存在一个类或者接口,这里的类可以是具体类也可以是抽象类
本质:是一个继承了该类或者实现了该接口的子类匿名对象
格式:
new 类名或者接口名(){
重写方法;
}
/*匿名内部类:
减少了类的创建,只适用于对象只创建一次或者少次
*/
public class Demo01 {
public static void main(String[] args) {
//实例化子类
//Cat cat = new Cat();
//cat.eat();
```
//多态
//需要调用eat方法,必须创建一个实现类实现接口,重写方法,再实例化实现类,通过对象调用方法
IAnimal animal = new Cat();
animal.eat();
//使用匿名内部类
/*IHuman human = new IHuman(){
@Override
public void eat() {
System.out.println("吃饭");
}
};
human.eat();*/
/*
//创建匿名内部类对象
IHuman human = new IHuman(){
@Override
public void eat() {
System.out.println("吃饭");
}
};
method(human);*/
/*上面几步演变到的最终结果*/
method(new IHuman(){
@Override
public void eat() {
System.out.println("吃饭");
}
});
```
}
public static void method(IHuman human){
human.eat();
}
}
集合
1.1 List集合
数组的扩容
//数组的扩容
public class Demo01 {
public static void main(String[] args) {
int[] nums = new int[3];
nums[0] = 1;
nums[1] = 2;
nums[2] = 3;
//进行数组容量判断
//进行数组扩容
nums = grow(nums);
nums[3] = 4;
System.out.println(Arrays.toString(nums));
}
private static int[] grow(int[] nums) {
//int[] newNums = new int[nums.length/2 + nums.length]
//创建一个新数组
int[] newNums = new int[(nums.length >> 1) +nums.length];
//把旧数组的元素存放到新数组中
for (int i = 0; i < nums.length; i++) {
newNums[i] = nums[i];
}
//返回新数组
return newNums;
}
```
}
ArrayList:
底层是动态数组,通过无参构造方法创建对象,当添加第一个对象的时候,初始容量为10,当底层动态数组存满对象,再添加对象会进行扩容
LinkedList:
底层是双向链表结构,添加原理是链表追加对象,first属性指向第一个节点,不存在索引,理论上无限
ArrayList vs LinkedList
- ArrayList底层是动态数组,LinkedList底层是双向链表结构
- ArrayList查询比LinkedList快
- 如果不需要进行扩容,ArrayList末尾追加比LinkedList快
- 修改ArrayList比LinkedList快
- 插入和删除不同情况效率不一致
- ArrayList长度有限,LinkedList长度理论上无限
- 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据
1.1.1 ArrayList
public class Demo01 {
public static void main(String[] args) {
//通过无参构造方法创建对象,给elementData赋予空数组
ArrayList list = new ArrayList();
//末尾添加 Integer
list.add(1);
//末尾添加 String
list.add("abc");
list.add("a");
list.add("b");
//指定位置添加 -- 把对象添加到指定的索引处
list.add(2,"3");
System.out.println("-----for循环遍历------");
//size()获取存储的对象个数
for (int i = 0; i < list.size(); i++) {
//通过索引获取对象
Object obj = list.get(i);
System.out.println(obj);
}
//修改 -- 通过索引进行修改
list.set(1,"bcd");
System.out.println("------foreach遍历-----");
for(Object obj:list){
System.out.println(obj);
}
//删除 -- 根据索引进行删除
//list.remove(1);
//删除 -- 根据对象进行删除,如果需要删除Integer类型,必须传递对象,而不是int类型的数据
list.remove(new Integer(1));
System.out.println("------删除过后-----");
//foreach遍历
for(Object obj:list){
System.out.println(obj);
}
}
}
1.1.2 LinkedList
public class Demo02 {
public static void main(String[] args) {
//通过无参构造方法创建对象,给elementData赋予空数组
LinkedList list = new LinkedList();
//末尾添加 Integer
list.add(1);
//末尾添加 String
list.add("abc");
list.add("a");
list.add("b");
//指定位置添加 -- 把对象添加到指定的索引处
list.add(2,"3");
System.out.println("-----for循环遍历------");
//size()获取存储的对象个数
for (int i = 0; i < list.size(); i++) {
//通过索引获取对象
Object obj = list.get(i);
System.out.println(obj);
}
//修改 -- 通过索引进行修改
list.set(1,"bcd");
System.out.println("------foreach遍历-----");
for(Object obj:list){
System.out.println(obj);
}
//删除 -- 根据索引进行删除
//list.remove(1);
//删除 -- 根据对象进行删除,如果需要删除Integer类型,必须传递对象,而不是int类型的数据
list.remove(new Integer(1));
System.out.println("------删除过后-----");
for(Object obj:list){
System.out.println(obj);
}
}
}
1.1.3 迭代器
只迭代Collection接口的集合
/* 迭代器:只迭代Collection接口的集合
*/
public class Demo05 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
System.out.println("------foreach-------");
for(Object obj:list){
System.out.println(obj);
}
System.out.println("------迭代器--------");
Iterator it = list.iterator();
//hasNext()判断下一个对象是否存在
while(it.hasNext()){
//next()获取指向的元素对象并指向下一个元素
Object obj = it.next();
System.out.println(obj);
//System.out.println(it.next());
}
System.out.println("-------lambda-------");
list.forEach(item -> System.out.println(item));
}
}
1.1.4 Vector
元老级的集合,底层是数组,线程安全的集合
/*Vector:元老级别的集合,底层是数组,线程安全的集合
*/
public class Demo03 {
public static void main(String[] args) {
Vector vector = new Vector();
vector.addElement("a");
vector.addElement("b");
vector.addElement("c");
//遍历数据
Enumeration elements = vector.elements();
//判断是否还有元素
while(elements.hasMoreElements()){
//获取下一个元素
System.out.println(elements.nextElement());
}
}
}
1.1.5 有限通配符
<? extends E>
<? extends E> 是 Upper Bound(上限) 的通配符,用来限制元素的类型的上限
List<? extends People> list_1 = null;
//表示集合中的元素上限是People,即只能是People或People的子类
<? super E>
<? super E> 是 Lower Bound(下限) 的通配符 ,用来限制元素的类型下限
List<? super Man> list_4 = null;
//该表示给出了集合中元素的下限是Man,即只能为Man或者Man的父类,而不能是Man的子类
有限通配符很多时候都是用它来当作方法中的形参
1.1.6 Stack
模拟栈的集合,特点是后进先出(LIFO),类似于一个箱子先放进去的东西会压在箱底,后放进去的东西在箱顶,箱顶先出来,箱底后出来,用完即删(主要学习栈的思想)
/*Stack:模拟栈的集合,特点是后进先出(LIFO),用完即删
*/
public class Demo04 {
public static void main(String[] args) {
Stack stack = new Stack();
//添加
stack.push("a");
stack.add("b");
stack.add("c");
/* System.out.println(stack.size());
//获取栈顶的对象
System.out.println(stack.pop());
System.out.println(stack.size());*/
//遍历
while(!stack.isEmpty()){
//pop()获取栈顶对象并删除栈顶对象
System.out.println(stack.pop());
}
}
}
1.1.7 泛型
泛型:约束集合的存储对象类型
/*泛型:约束集合的存储对象类型
*/
public class Demo06 {
public static void main(String[] args) {
ArrayList<User> list = new ArrayList();
list.add(new User("zs",18));
//list.add(1);
```
//创建对象,泛型为Integer,代表使用到泛型的地方,只能传递Integer类型
Person<Integer> person = new Person<>();
person.method(1);
Integer num = person.method2(1);
Person.method3("a");
```
}
}
public class Person<T> {
//属性类型为泛型
private T t;
//普通方法 -- 参数为泛型
public void method(T t){
System.out.println(t);
}
//普通方法 -- 返回值类型为泛型
public T method2(T t){
return t;
}
//静态方法 -- 需要在返回值类型前加<T>
public static <T> T method3(T t){
return t;
}
}
1.2 Set集合
Set:无序不可重复(有序:存储时对象的顺序与取出时的顺序一致,无序则不一致)
1.2.1 TreeSet
TreeSet:无序可排序,不能存储null对象,使用TreeSet存储自定义对象,对象的类必须使用自然排序,或者在创建TreeSet对象时,通过构造方法传递比较器
TreeSet:红黑树(平衡二叉树)
set.add()的底层代码:
public class Demo01 {
public static void main(String[] args) {
TreeSet<Integer> set = new TreeSet<>();
set.add(5);
set.add(3);
set.add(2);
set.add(8);
set.add(9);
set.add(4);
//重复的对象
set.add(5);
//for遍历 -- 没有get() 因为没有索引
/*for (int i = 0; i < set.size(); i++) {
System.out.println(set.get);
}*/
//----foreach循环遍历----
for (Integer integer :set){
System.out.println(integer);
}
}
```
}
自然排序:存储进TreeSet的对象必须实现Comparable接口,重写compareTo(),在compareTo()实现比较规则,返回正数,存储的对象则在右子树,返回负数,则在左子树,返回0,则不存储
重写compareTo()在相应对象的类中
public class Demo02 {
public static void main(String[] args) {
Set<User> set = new TreeSet<>();
set.add(new User(4));
set.add(new User(3));
set.add(new User(6));
set.add(new User(1));
set.add(new User(4));
set.forEach(user -> System.out.println(user));
}
}
public class User implements Comparable<User>{
private int id;
//this为要存储进集合的对象
//参数user为已存储进集合的对象
@Override //重写底层代码
public int compareTo(User user) {
//从小到大
//return this.getId() - user.getId();
//从大到小
return user.getId()-this.getId();
}
}
实现下列功能的重写compareTo()
/* 使用TreeSet存储Person对象,id从小到大排序,
* id相等的按age从大到小排序
* 属性值完全一样的不存储
*/
public class Demo03 {
public static void main(String[] args) {
TreeSet<Person> set = new TreeSet<>();
set.add(new Person(5,"zs",18));
set.add(new Person(3,"ls",48));
set.add(new Person(2,"ww",34));
set.add(new Person(5,"zs",18));
set.add(new Person(6,"zs",16));
set.add(new Person(5,"z6",20));
set.add(new Person(5,"z7",20));
//----lambda遍历集合----
set.forEach(item -> System.out.println(item));
//删除 -- 会调用equals判断是否相等
set.remove(new Person(5,"z7",20));
System.out.println("-------删除对象");
set.forEach(item -> System.out.println(item));
//修改 -- 先删除后添加
}
}
public class Person implements Comparable<Person>{
private int id;
private String name;
private int age;
@Override
public int compareTo(Person person) {
//3个属性值都相等
if(this.equals(person)){
return 0;
}
//id不相等
if(this.getId() != person.getId()){
return this.getId() - person.getId();
}
//id相等,age不相等
if(this.getAge() != person.getAge()){
return person.getAge() - this.getAge();
}
//id和age相等,name不等
return 1;
//最后的return 1;也可以是return -1;id和age相等就存进去,名字默认排序
}
}
比较器:在TreeSet创建对象时,通过构造方法传递实现了Comparator接口类的对象重写compare(),实现比较规则
Comparator是接口,需要实现类重写Compare()
/*实现了Comparator接口的类为比较器
*/
public class MyComparator implements Comparator<User> {
//user1为要存储进集合的对象
//user2是已存储进集合的对象
@Override
public int compare(User user1, User user2) {
return user2.getId()-user1.getId();
}
}
通过构造方法传递比较器,使用匿名内部类当做比较器:
public class Demo01 {
public static void main(String[] args) {
//通过构造方法传递比较器
//TreeSet<User> set = new TreeSet<>(new MyComparator());
```
/*
* 匿名内部类
*
* new 接口(){
* 重写接口中的抽象方法
* }
*
*
* */
//使用匿名内部类当做是比较器
TreeSet<User> set = new TreeSet<>(new Comparator<User>() {
@Override
public int compare(User user1, User user2) {
return user2.getId()-user1.getId();
}
});
set.add(new User(1,"zs"));
set.add(new User(5,"zs"));
set.add(new User(3,"zs"));
set.forEach(user -> System.out.println(user));
}
```
}
比较器 vs 自然排序
-
自然排序需要改变存储对象类的结构
-
比较器会额外创建多一个类
-
当比较器与自然排序相存在的时候,使用比较器规则
1.2.2 HashSet
HashSet:无序不可重复的集合,可以存储null对象,存储自定义对象需要重写hashCode()和equals()
JDK1.7:hash表(不可变)+链表
JDK1.8:hash表(可变数组)+链表+红黑树
Hash表是两层结构,外层是一个数组,数组的每一个元素是一个链表或红黑树,初始化的时候元素是链表,当链表的长度达到一个值以后就会转换为红黑树
通过HashSet存储对象,会根据对象的hashCode()得到hash值,根据hash值找到hash表的位置,如果位置上没有对象,则直接存储,如果存在对象,则通过equals()进行内容比较,如果相同则不存储,如果不同则根据链表查询下一个对象进行判断
当hash表的总存储对象超过容量的负载因子(0.75),则会扩容成原来的两倍,当其中一条链表超过8个对象时,则转化成红黑树
public class Demo01 {
public static void main(String[] args) {
HashSet<Integer> set = new HashSet<>();
set.add(11);
set.add(88);
set.add(66);
set.add(33);
set.add(55);
set.add(0);
//不可重复
set.add(8);
//set.add(null);
/*迭代器遍历*/
Iterator<Integer> it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
使用HashSet存储自定义对象
如果存在对象相同属性需要去重需求,需要重写对象的equals()和hashCode(),如果不重写hashCode()则所有对象存放在hash表同一位置
public class Demo02 {
public static void main(String[] args) {
HashSet<User> set = new HashSet<>();
set.add(new User(1,"zs"));
set.add(new User(2,"ls"));
set.add(new User(1,"ls"));
set.add(new User(1,"zs"));
/*foreach遍历集合*/
set.forEach(user -> System.out.println(user));
}
}
public class User {
private int id;
private String name;
//重写equals()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id &&
Objects.equals(name, user.name);
}
//重写hashCode()
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
解决哈希冲突
哈希冲突的解决办法有好几种,其中两种方法,一个是开放寻址法,一个是拉链法
开放寻址法其实简单来说就是,既然位置被占了,那就另外再找个位置不就得了,怎么找其他的位置呢?这里其实也有很多的实现,我们说个最基本的就是既然当前位置被占用了,我们就看看该位置的后一个位置是否可用,也就是1的位置被占用了,我们就看看2的位置,如果没有被占用,那就放到这里呗,当然,也有可能2的位置也被占用了,那咱就继续往下找,看看3的位置,直到找到空位置
拉链法也是比较常用的,HashMap就是使用了这种方法。和开放寻址法不同,还是在该位置,解决办法就是链表,这时候这个1的位置存放的不单单是之前的那个Entry了,此时的Entry还额外的保存了一个next指针,这个指针指向数组外的另外一个位置,将张尚安排在这里,然后张三那个Entry中的next指针就指向张尚的这个位置,也就是保存的这个位置的内存地址,这样就形成了一个链表;这时候又有一个新元素来了,hash后也是在这个位置,那就把这个新的Entry放在一个新位置上,然后张尚的Entry中的next指向它
拉链法会产生什么问题?
如果冲突过多的话,这块的链表会变得比较长,怎么处理呢?举个例子,拿java8集合类中的HashMap来说,如果链表长度大于等于8的话,链表就会转换成树结构;如果长度小于等于6的话,就会还原链表。以此来解决链表过长导致的性能问题
开放寻址法会产生什么问题
有一定量的位置被占了的时候就会发生扩容
哈希表读取数据
比如现在要通过zhangshang来查找好友。首先通过哈希函数利用zhangshang得出位置1,然后去位置1拿数据,拿到这个Entry之后我们得看看这个Entry的key是不是zhangshang,一看是zhangshan,这不是我们要的key,然后根据这个Entry的next找到下一给位置,再比较key,成功找到张尚
开放寻址那种也是这个思路,先确定到这个位置,然后再看这个位置上的key是不是我们要的,如果不是就看看下一个位置
1.3 queue
Queue:先进先出(FIFO),用完即删(了解先进先出的思想,看底层代码)
/*Queue:先进先出(FIFO),用完即删
*/
public class Demo01 {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.offer("小明");
queue.offer("小芳");
queue.offer("小飞");
queue.offer("小东");
queue.offer("琳达");
queue.offer("尼克");
System.out.println(queue.size()+"个人进入凤凰花开");
System.out.println("到钟了,"+queue.poll()+"出来了");
System.out.println("凤凰花开里剩下"+queue.size()+"个人");
}
}
1.4 collection
Collections – 针对Colletion的工具类
/*Collections -- 针对Collection的工具类
*/
public class Demo02 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
//通过工具类完成多个对象添加
Collections.addAll(list,"a","b","c","d");
System.out.println(list);
```
ArrayList<Integer> list1 = new ArrayList<>();
Collections.addAll(list1,43,6,3,1,4,76,8,99,5,3,3);
System.out.println("-------未排序---------");
System.out.println(list1);
System.out.println("--------排序后------");
Collections.sort(list1);
System.out.println(list1);
```
}
}
几种集合的特点:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-km5QGv4b-1653492706200)(E:\培训\第一阶段\总结\Picture\wps109.jpg)]
Map
1.1 Map
Map:以key-value形式存储数据,key是唯一的,value可重复的
1.1.1 HashMap
HashMap:底层是可变hash表+链表+红黑树
HashMap 数据结构为 数组+链表,其中:链表的节点存储的是一个 Entry 对象,每个Entry 对象存储四个属性(hash,key,value,next)
它的数据结构:
整体是一个数组;
数组每个位置是一个链表;
链表每个节点中的Value即我们存储的Object;
工作原理
首先,初始化 HashMap,提供了有参构造和无参构造,无参构造中,容器默认的数组大小 initialCapacity 为 16,加载因子loadFactor 为0.75。容器的阈(yu)值为 initialCapacity * loadFactor,默认情况下阈值为 16 * 0.75 = 12
然后,这里我们拿 PUT 方法来做研究:
第一步:通过 HashMap 自己提供的hash 算法算出当前 key 的hash 值
第二步:通过计算出的hash 值去调用 indexFor 方法计算当前对象应该存储在数组的几号位置
第三步:判断size 是否已经达到了当前阈值,如果没有,继续;如果已经达到阈值,则先进行数组扩容,将数组长度扩容为原来的2倍。
请注意:size 是当前容器中已有 Entry 的数量,不是数组长度。
第四步:将当前对应的 hash,key,value封装成一个 Entry,去数组中查找当前位置有没有元素,如果没有,放在这个位置上;如果此位置上已经存在链表,那么遍历链表,如果链表上某个节点的 key 与当前key 进行 equals 比较后结果为 true,则把原来节点上的value 返回,将当前新的 value替换掉原来的value。如果遍历完链表,没有找到key 与当前 key equals为 true的,就把刚才封装的新的 Entry中next 指向当前链表的始节点,也就是说当前节点现在在链表的第一个位置,简单来说即,先来的往后退
扩容机制:
HashMap 使用 “懒扩容” ,只会在 PUT 的时候才进行判断,然后进行扩容
- 将数组长度扩容为原来的2 倍
- 将原来数组中的元素进行重新放到新数组中
需要注意的是,每次扩容之后,都要重新计算原来的 Entry 在新数组中的位置
public class Demo01 {
public static void main(String[] args) {
//HashSet<Object> set = new HashSet<>();
//底层调用HashMap的put方法,把存储的对象传递到put方法的key
//set.add(1);
```
HashMap<String, Integer> map = new HashMap<>();
//添加
map.put("zs",20);
map.put("ls",18);
map.put("ww",null);
map.put(null,18);
//当key相等的时候,value会覆盖
map.put("zs",18);
//System.out.println(map);
//for遍历数据 -- 不可用
/*for (int i = 0; i < map.size(); i++) {
//get()根据key获取value
System.out.println(map.get(i));
}*/
//foreach -- 不可用
//迭代器 -- 不可用
System.out.println("--------使用keySet--------");
//1.把map集合中的key存放到collection底下的集合中
Set<String> set = map.keySet();
//2.迭代存放key的collection集合,根据key获取value
Iterator<String> it = set.iterator();
while(it.hasNext()){
String key = it.next();
Integer value = map.get(key);
System.out.println("key:"+key+",value:"+value);
}
//修改key -- 先删后加
//修改value -- 添加一个key相同,value不同的对象
//删除 -- 根据key删除key-value
System.out.println("------根据key删除key-value---------");
Integer num = map.remove("ls");
System.out.println("被删除的key的value:"+num);
//删除 -- 根据key和value一起删除
System.out.println("------根据key-value删除key-value---------");
boolean flag = map.remove("zs", 18);
System.out.println("flag:"+flag);
System.out.println("-------使用EntrySet-----");
//1.把map集合的映射关系(key-value)存放到set中
Set<Map.Entry<String, Integer>> set1 = map.entrySet();
//2.迭代存放映射关系的set集合
Iterator<Map.Entry<String, Integer>> it1 = set1.iterator();
while(it1.hasNext()){
Map.Entry<String, Integer> entry = it1.next();
System.out.println("key:"+entry.getKey()+",value:"+entry.getValue());
}
}
```
}
1.1.2 Hashmap vs Hashtable
1.底层数据结构不同:jdk1.7底层都是数组+链表,但jdk1.8 HashMap加入了红黑树
2.Hashtable 是不允许键或值为 null 的,HashMap 的键值则都可以为 null
3.添加key-value的hash值算法不同:HashMap添加元素时,是使用自定义的哈希算法,而HashTable是直接采用key的hashCode()
4.实现方式不同:Hashtable 继承的是 Dictionary类,而 HashMap 继承的是 AbstractMap 类
5.初始化容量不同:HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75
6.扩容机制不同:当已用容量>总容量 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 +1
7.支持的遍历种类不同:HashMap只支持Iterator遍历,而HashTable支持Iterator和Enumeration两种方式遍历
8.迭代器不同:HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。而Hashtable 则不会
9.部分API不同:HashMap不支持contains(Object value)方法,没有重写toString()方法,而HashTable支持contains(Object value)方法,而且重写了toString()方法
- 同步性不同: Hashtable是同步(synchronized)的,适用于多线程环境,而hashmap不是同步的,适用于单线程环境。多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的
1.2 Treemap
Treemap:底层是红黑树,使用TreeMap存储自定义对象key需要实现自然排序或者传递比较器,当自然排序的排序方法compareTo()返回0,则value覆盖旧的value并返回旧的value或者比较器的排序方法compare()返回0,则覆盖旧的value并返回旧的value
TreeMap是可以自动排序的,默认情况下comparator为null,这个时候按照key的自然顺序进行排序,然而并不是所有情况下都可以直接使用key的自然顺序,有时候我们想让Map的自动排序按照我们自己的规则, 这个时候就需要传递Comparator的实现类
key不可以存储null对象
Value可以存储null对象
public class Demo02 {
public static void main(String[] args) {
//TreeSet<String> set = new TreeSet<>();
//底层通过调用TreeMap的put方法,把存储对象当做key值存储
//存储的对象,需要实现自然排序或者传递比较器
//set.add("a");
```
/*
TreeMap<Integer, Integer> map = new TreeMap<>();
map.put(1,2);
//key相等,value覆盖并返回旧的value
Integer num = map.put(1, 10);
System.out.println(num);
*/
TreeMap<User,Integer> map = new TreeMap<>();
map.put(new User(1,"zs",18),1);
map.put(new User(2,"ls",54),3);
map.put(new User(3,"ww",32),2);
map.put(new User(5,"z6",44),2);
map.put(new User(4,"z7",19),2);
map.put(new User(1,"zs",18),10);
map.put(new User(1,"zs",20),9);
Set<User> set = map.keySet();
Iterator<User> it = set.iterator();
while(it.hasNext()){
User key = it.next();
Integer value = map.get(key);
System.out.println(key+":"+value);
}
}
```
}
public class User implements Comparable<User>{
private int id;
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id &&
age == user.age &&
Objects.equals(name, user.name);
}
//id从大到小排序
//age从小到大排序
@Override
public int compareTo(User user) {
//3个属性都相等
if(this.equals(user)){
return 0;
}
//id不相等
if(this.getId() != user.getId()){
return user.getId() - this.getId();
}
//年龄不相等
if(this.getAge() != user.getAge()){
return this.getAge() - user.getAge();
}
return 1;
}
}
1.3 HashMap vs TreeMap
(1)HashMap:适用于在Map中插入、删除和定位元素
(2)Treemap:适用于按自然顺序或自定义顺序遍历键(key)
(3)HashMap通常比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在需要排序的Map时候才用TreeMap
(4)HashMap 非线程安全 TreeMap 非线程安全
(5)HashMap的结果是没有排序的,而TreeMap输出的结果是排好序的
I/O框架(1)
1.1 File
File:文件和目录的抽象路径表示
/*File:文件和目录的抽象路径表示
*/
public class Demo01 {
public static void main(String[] args) {
//指定目录
//File file = new File("E:\\File");
//指定文件必须加上后缀名
//File file = new File("E:\\File\\cls.jpg");
//多种拼接方式
//File file = new File("E:\\File","cls.jpg");
File prentFile = new File("E:\\File");
File file = new File(prentFile,"newFile1");
```
//相对于当前项目的路径
System.out.println("相对路径:"+file.getPath());
//路径从盘符开始
System.out.println("绝对路径:"+file.getAbsolutePath());
System.out.println("文件是否可读:"+file.canRead());
System.out.println("文件是否可写:"+file.canWrite());
System.out.println("文件是否隐藏:"+file.isHidden());
//返回文件的字节大小
System.out.println("文件的长度(大小):"+file.length());
System.out.println("文件(目录)是否存在:"+file.exists());
System.out.println("判断是否为文件:"+file.isFile());
System.out.println("判断是否为目录:"+file.isDirectory());
//查看目录里的内容
File[] files = file.listFiles();
System.out.println(Arrays.toString(files));
System.out.println("文件(目录)最后修改时间:"+file.lastModified());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(file.lastModified());
System.out.println("文件(目录)最后修改时间:"+time);
```
}
}
删除目录的注意事项:
- 如果一个目录中有内容(目录、文件),不能直接删除。应该先删除目录中的内容,最后才能删除目录
遍历目录
/*1.删除指定目录
2.学习listFiles()重载的方法
3.拷贝指定目录*/
1.2 单元测试
1.方法上添加单元测试注解@Test
2.访问修饰符不能为private
3.方法不能为静态的
4.当前包下不能有Test类
public class Demo02 {
//1.父目录存在
@Test
public void test01() throws IOException {
File file = new File("E:\\File\\io4.txt");
if(!file.exists()){
file.createNewFile();
}
}
//2.父目录不存在
@Test
public void test02() throws IOException {
File file = new File("E:\\File\\newFile\\io1.txt");
//先获取父目录的file对象
File parentFile = file.getParentFile();
//先创建父目录
if(!parentFile.exists()){
//创建目录
parentFile.mkdir();
}
//判断文件是否存在
if(!file.exists()){
//创建文件
file.createNewFile();
}
}
//3.多层父目录不存在
@Test
public void test03() throws IOException {
File file = new File("E:\\File\\newFile1\\newFile2\\io1.txt");
//先获取父目录的file对象
File parentFile = file.getParentFile();
//先创建父目录
if(!parentFile.exists()){
//创建多层父目录
parentFile.mkdirs();//mkdir()和mkdirs()一个是创建一层,一个是多层
}
//判断文件是否存在
if(!file.exists()){
//创建文件
file.createNewFile();
}
}
//删除目录 -- 空目录
@Test
public void test04(){
File file = new File("E:\\File\\newFile");
file.delete();
}
//删除文件
@Test
public void test05(){
File file = new File("E:\\File\\newFile\\io1.txt");
file.delete();
}
~~~
public class Demo03 {
//遍历出指定目录下的所有文件名 -- 不包含子目录
@Test
public void test01(){
File file = new File("E:\\File");
//获取目录里所有文件和目录的File对象
File[] files = file.listFiles();
for (File f:files) {
//判断是否为文件
if(f.isFile()){
//输出文件名
System.out.println(f.getName());
//输出文件的绝对路径
System.out.println(f.getAbsolutePath());
}
}
}
//遍历出指定目录下的所有文件的绝对路径 -- 包含子目录
public static void main(String[] args) {
File file = new File("E:\\File");
method(file);
}
/**
*
* @param file 根目录
*/
private static void method(File file) {
//获取根目录里所有目录和文件的File对象
File[] files = file.listFiles();
for (File f:files) {
//判断是否为目录
if(f.isDirectory()){
//E:\File\newFile1
//递归
method(f);
}else{
//输出文件的绝对路径
System.out.println(f.getAbsolutePath());
}
}
}
}
I/O框架(2)
1.1 字节流
按方向: – 以程序为参照物(可以把程序当成大脑,文件为书本)
输入流:将<存储设备>中的内容读入到<内存>中 文件 —> 程序 读取
输出流:将<内存>中的内容写入到<存储设备>中 程序 —> 文件 写出
按单位:
字节流:以字节为单位,可以读写所有数据
字符流:以字符为单位,只能读写文本数据
两种流都在什么情况下使用呢?
- 如果数据通过window自带的记事本软件打开,我们还可以读懂里面的内容,就使用字符流,否则使用字节流。如果你不知道该使用哪种类型的流,就使用字节流
public class Demo01 {
//使用字节输入流读取单个字节
@Test
public void test01(){
//文件字节输入流
FileInputStream fis = null;
try {
fis = new FileInputStream(new File("E:\\File\\io4.txt"));
//读取文件中下一个字节
int read = fis.read();
System.out.println((char)read);
} catch (IOException e) {
e.printStackTrace();
}finally {
//null对象调用方法会报异常,需要先进行非空判断
if(fis != null){
try {
//关闭资源
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
```
//使用字节输入流每次读取单个字节,读取完整个文件
@Test
public void test02(){
FileInputStream fis = null;
try {
fis = new FileInputStream("E:\\File\\io4.txt");
//读取 -- 判断 -- 打印
//定义变量
int read;
//读取数据然后判断是否为-1,-1则读到文件末尾,不再继续打印数据
while((read = fis.read()) != -1){
//打印数据
System.out.print((char)read);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
```
}
//写出单个字节 -- 每一次实例化字节输出流对象会清空原有的数据
@Test
public void test01(){
FileOutputStream fos = null;
try {
//会自动创建文件
fos = new FileOutputStream("File\\io1.txt");
//输出26个字母
for (int i = 0; i < 26; i++) {
fos.write(97+i);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//末尾追加 -- 创建FileOutputStream对象时,通过构造方法传递2个参数,第一个参数是路径,第二个参数为true则为末尾追加
@Test
public void test02(){
FileOutputStream fos = null;
try {
//会自动创建文件
fos = new FileOutputStream("File\\io1.txt",true);
//输出26个字母
for (int i = 0; i < 26; i++) {
fos.write(97+i);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
```
}
//拷贝文本文件 -- 每次读写一个字节
@Test
public void test01(){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("File\\io1.txt");
fos = new FileOutputStream("File\\io2.txt");
//实现拷贝
//读取数据
int read;
while((read = fis.read()) != -1){
fos.write(read);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//拷贝文本文件 -- 每次读写一个数组长度的数据
@Test
public void test02(){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("File\\io1.txt");
fos = new FileOutputStream("File\\io2.txt");
//实现拷贝
//新建数组
byte[] b = new byte[1024];
//定义长度
int len;
//一次性读取数组长度的数据,len是具体读取到数组中的字节长度
//b为存储数据的数组
while((len = fis.read(b)) != -1){
/*
* 第一个参数为存放数据的数组
* 第二个参数为偏移量
* 第三个为具体存放到数组中数据的长度
* */
fos.write(b,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(fis,fos);
}
}
}
public class IOUtils {
public static void closeAll(Closeable ... closeables){
for (Closeable closeable:closeables) {
if(closeable != null){
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
1.2 字符流
字符流:针对文本文件
Reader – FileReader:字符输入流
Writer – FileWriter:字符输出流
//一次读取单个字符
@Test
public void test01(){
//字符输入流
FileReader fr = null;
try {
fr = new FileReader("IO1.txt");
int read = fr.read();
System.out.println((char)read);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(fr);
}
}
//一次读取多个字符
@Test
public void test02(){
//字符输入流
FileReader fr = null;
try {
fr = new FileReader("IO1.txt");
char[] c = new char[1024];
int len;
while((len = fr.read(c)) != -1) {
System.out.println(new String(c, 0, len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(fr);
}
}
//输出字符
@Test
public void test03(){
//字符输出流
FileWriter fw = null;
try {
fw = new FileWriter("IO2.txt");
fw.write("78号技师你好");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(fw);
//fw.close();
}
}
@Test
public void test04(){
//字符输入流
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("IO2.txt");
fw = new FileWriter("IO3.txt");
char[] c = new char[1024];
int len;
while((len = fr.read(c)) != -1){
fw.write(c,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(fr,fw);
}
}
1.3 带缓冲的字节流
按功能区分:
基础流(节点流):直接去磁盘交互
包装流(处理流):建立在节点流的基础上,通过缓冲区进行读写
带缓冲区的字节输出流 – 缓冲区默认大小为8192字节
关流(刷新)会使缓冲区的内容到达目的源或者是缓冲区满了后会自动刷新
@Test
public void test01() throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("IO4.txt"));
bos.write("有道无术".getBytes());
bos.close();
}
指定缓冲区大小 – 印证缓冲区满了自动刷新
@Test
public void test02() throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("IO5.txt"),12);
bos.write("术尚可求".getBytes());
}
带缓冲区的字节输入流
//带缓冲区的字节输入流
@Test
public void test03() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("IO5.txt"));
byte[] b = new byte[1024];
int len;
while((len = bis.read(b)) != -1){
System.out.println(new String(b,0,len));
}
bis.close();
}
1.4 带缓冲区的字符流
BufferedReader – readLine()为独有的方法(读取一行字符串)
BufferedWriter – newLine()为独有的方法(换行)
//使用带缓冲区的字符流进行拷贝
@Test
public void test01() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("IO5.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("IO6.txt"));
char[] c = new char[1024];
int len;
while((len = br.read(c)) != -1){
bw.write(c,0,len);
}
IOUtils.closeAll(br,bw);
}
//使用带缓冲区的字符流独有的方法进行拷贝
@Test
public void test02() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("IO5.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("IO6.txt"));
String str;
//readLine()读取一行字符串
while((str = br.readLine()) != null){
bw.write(str);
//换行
bw.newLine();
}
IOUtils.closeAll(br,bw);
}
1.5 转换流
转换流:字节通向字符的桥梁,本质上是字符流
InputStreamReader:把文件中的字节转换成程序的字符
OutputStreamWriter:把程序的字符转换成文件的字节
1.字节与字符的转换
2.读写不同的编码格式文件
//转换输出流
@Test
public void test01() throws IOException {
//字节输出流 -- 通过方法获取字节流(目的源)
FileOutputStream fos = new FileOutputStream("IO7.txt");
//通过输出转换流把字节转换成字符对象,创建带缓冲区的字符流,构造方法传递字符对象
BufferedWriter bw= new BufferedWriter(new OutputStreamWriter(fos));
bw.write("有道无术,术尚可求,有术无道,止于术");
bw.close();
}
//转换输入流
@Test
public void test02() throws IOException {
//先创建字节输入流,再把对象传递到转换输入流的构造方法中获取转换输入流的对象
//再把转换输入流的对象传递带缓冲区的字符输入流的构造方法中
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("IO7.txt")));
String str = br.readLine();
System.out.println(str);
br.close();
}
//使用带缓冲区的字符流读取文本文件 -- 编码格式为GBK
//会出现中文乱码
@Test
public void test03() throws IOException {
BufferedReader br = new BufferedReader(new FileReader("E:\\File\\io5.txt"));
String str = br.readLine();
System.out.println(str);
br.close();
}
//使用带缓冲区的字符流(转换流)读取文本文件 -- 编码格式为GBK
//使用指定的编码格式来读取文件
@Test
public void test04() throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\File\\io5.txt"),"GBK");
BufferedReader br = new BufferedReader(isr);
String str = br.readLine();
System.out.println(str);
br.close();
}
1.6 对象流
对象流:
-
ObjectInputStream:对象输入流
-
ObjectOutputStream:对象输出流
1.6.1 写出
使用对象流把对象写入到文件中 – 单个对象
对象的类需要实现Serializable接口
//使用对象流进行对象的读写操作需要对象的类实现Serializable接口
public class User implements Serializable {
}
//使用对象流把对象写入到文件中 -- 单个对象
//对象的类需要实现Serializable接口
@Test
public void test02() throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user2.txt"));
User user = new User(1, "ls");
oos.writeObject(user);
oos.close();
}
//使用对象流把对象写入到文件中 -- 多个对象
//在存入对象前存入对象的个数 -- 个数与对象不匹配
@Test
public void test03() throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user3.txt"));
oos.writeInt(3);
oos.writeObject(new User(1,"zs"));
oos.writeObject(new User(2,"ls"));
oos.writeObject(new User(3,"ww"));
oos.close();
}
//使用对象流把对象写入到文件中 -- 多个对象
//写入对象后写入null
@Test
public void test04() throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user3.txt"));
oos.writeObject(new User(1,"zs"));
oos.writeObject(new User(2,"ls"));
oos.writeObject(new User(3,"ww"));
oos.writeObject(null);
oos.close();
}
// 把对象存放到集合中,再通过对象流把集合写到文件中
@Test
public void test05() throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user3.txt"));
ArrayList<User> list = new ArrayList<>();
list.add(new User(1,"zs"));
list.add(new User(2,"ls"));
list.add(new User(3,"ww"));
oos.writeObject(list);
oos.close();
}
1.6.2 读取
//使用对象流读取存储到文件的对象 -- 单个对象
@Test
public void test02() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user2.txt"));
User user = (User)ois.readObject();
System.out.println(user.getId()+":"+user.getName());
ois.close();
}
//使用对象流读取存储到文件的对象 -- 多个对象
//在读取对象前读取对象个数
@Test
public void test03() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user3.txt"));
int num = ois.readInt();
User user = null;
for (int i = 0; i < num; i++) {
user = (User)ois.readObject();
System.out.println(user);
}
ois.close();
}
//使用对象流读取存储到文件的对象 -- 多个对象
//在读取对象读取到null则结束
@Test
public void test04() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user3.txt"));
User user;
while((user = (User)ois.readObject()) != null){
System.out.println(user);
}
ois.close();
}
//使用对象流读取存储到文件的对象 -- 多个对象
//读取集合对象
@Test
public void test05() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user3.txt"));
ArrayList<User> list = (ArrayList<User>)ois.readObject();
list.forEach(user -> System.out.println(user));
ois.close();
}
本质上读写操作是在进行序列化和反序列化
写入对象到文件中序列化操作
读取对象为反序列化
私有属性的静态属性不会被序列化
私有的transient属性不会被序列化
//不会被序列化
private static int age;
private transient int age;
//会被序列化
private int age;
private String str;
public int age;
public String str;
public final int AGE = 1;
private final int AGE = 1;
public static int age;
1.7 内存流
ByteArrayOutputStream:程序 —> 内存
ByteArrayInputStream:内存 —> 程序
特点:1.操作数量小,效率高
2.无法关闭的流
3.操作临时文件
public class Demo01 {
public static void main(String[] args) throws IOException {
//内存输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write("有道无术".getBytes());/*write(byte[] b, int off, int len) 从指定的字节数组写入 len长度的字节,从偏移量为 off开始,输出到这个字节数组输出流。*/
bos.close();
```
//从内存中读取数据
byte[] bytes = bos.toByteArray();/*public byte[] toByteArray()创建一个新分配的字节数组。 其大小是此输出流的当前大小,缓冲区的有效内容已被复制到其中,*/
System.out.println(new String(bytes));
/*
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}输出的是该字节数组的内容,从偏移量0开始,输出到数组的长度*/
//把内存中的数据写到指定文件中
bos.writeTo(new FileOutputStream("IO1.txt"));
bos.close();
}
```
}
1.8 随机访问流
RandomAccessFile:随机访问流
-
1.只有一个类,由一个类创建的不同对象完成写入和读取
-
2.拥有指针(偏移量)
-
3.每次实例化后,偏移量为0
-
4.每次实例化对象不会清空文件,只会从偏移量开始处进行覆盖写入
-
5.可以在文件指定位置进行读写操作
//写入单个字节
@Test
public void test01() throws IOException {
RandomAccessFile rw = new RandomAccessFile("IO2.txt", "rw");
/*
RandomAccessFile(File file, String mode)
创建一个随机访问文件流从File参数指定的文件中读取,并可选地写入文件
模式mode参数是等于 "r"一个 ,"rw","rws",或 "rwd"
*/
//每次都从文件的头部开始写入,如果存在数据则进行覆盖
rw.write(98);//写入b,98为b的ASC码
rw.close();
}
//写入字节并获取偏移量
@Test
public void test02()throws Exception{
RandomAccessFile rw = new RandomAccessFile("IO3.txt", "rw");
System.out.println("第一次获取偏移量:"+rw.getFilePointer());//0
rw.write("a".getBytes());
System.out.println("写入a后获取偏移量:"+rw.getFilePointer());//1
rw.write("bc".getBytes());
System.out.println("写入bc后获取偏移量:"+rw.getFilePointer());//3
rw.close();
}
//通过指针指定位置进行写入
@Test
public void test03() throws Exception{
RandomAccessFile rw = new RandomAccessFile("IO3.txt", "rw");
//设置偏移量
rw.seek(2);
rw.write("123".getBytes());
/*在偏移量为2的地方写入123,但偏移量之后的内容会被覆盖*/
rw.close();
}
解决偏移量写入会被覆盖的问题(先读后写入)
//在文件中加入数据
@Test
public void test04() throws Exception{
//指定偏移量
int pointer = 1;
//指定插入的数据
String str1 = "123";
RandomAccessFile r = new RandomAccessFile("IO4.txt", "r");//IO4.txt:abc
RandomAccessFile rw = new RandomAccessFile("IO4.txt", "rw");
//1.读取指定偏移量后的数据
//设置读取的偏移量
r.seek(pointer);
/*
seek(long pos)
设置文件指针偏移,从该文件的开头测量,发生下一次读取或写入
*/
byte[] b = new byte[1024];
int len;
String str;
StringBuffer sb = new StringBuffer();//线程安全的可变字符序列
//先加入需要插入的数据
sb.append(str1);
while ((len = r.read(b)) != -1){
str = new String(b,0,len);//这里的偏移量从之前的1算起,读取指定偏移量后的数据
//读取的数据添加到StringBuffer中
sb.append(str);//末尾追加 sb:123bc
}
//设置写入的偏移量
rw.seek(pointer);
rw.write(sb.toString().getBytes());//sb.toString():123bc
r.close();
rw.close();
}
//断点续传
@Test
public void test05() throws Exception{
RandomAccessFile r = new RandomAccessFile("E:\\File\\QQ1.mp4", "r");
RandomAccessFile rw = new RandomAccessFile("E:\\File\\QQ2.mp4", "rw");
//1.设置偏移量
r.seek(rw.length());
rw.seek(rw.length());
byte[] b = new byte[2];
int len;
while((len = r.read(b)) != -1){
rw.write(b,0,len);
}
r.close();
rw.close();
}
1.9 打印流
打印流:只有输出没有输入,直接打印数据
-
PrintStream:打印字节流
-
PrintWriter:打印字符流
@Test
public void test01() throws Exception {
FileOutputStream fos = new FileOutputStream("IO5.txt");
fos.write(97);//a
fos.close();
PrintStream ps = new PrintStream("IO6.txt");
ps.print(97);//97
/*
public void print(int i) {
write(String.valueOf(i));
}
*/
ps.close();
}
//输出重定向 -- 改变输出的目的源
public static void main(String[] args) throws Exception{
System.out.println("有道无术");
//改变目的源
System.setOut(new PrintStream("IO7.txt"));
System.out.println("噜啦噜啦嘞");
}
/*改变目的源,原先是写入(输出)到控制台,改变目的源之后写入到IO7.txt*/
多线程
1.1 多线程的创建
进程:程序执行的基本单位,由一个或者多个进程组合而成
线程:CPU调度的基本单位
多线程的本质:
线程的执行是由CPU调度,多线程是CPU不断地在来回切换调度不同线程的表现
多线程的创建方式:
1.自定义线程类继承Thread类,重写run(),实例化线程类对象,通过对象调用start()启动线程
2.自定义任务类实现Runnable接口,重写run(),实例化任务类对象,再实例化Thread类,并在构造方法中传递任务类对象,通过Thread类对象调用start()启动线程
3.自定义类实现Callable接口,重写call(),拥有返回值
4.通过线程池启动线程
并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
并发:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事
获取线程名称:
getName()
Thread.currentThread().getName()
start方法的作用:
1.启动当前线程
2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法
线程的5种状态:创建、就绪、运行、阻塞、销毁
/*
自定义线程类继承Thread类,重写run(),实例化线程类对象,通过对象调用start()启动线程
*/
public class Demo01 {
//主线程
public static void main(String[] args) {
//创建线程对象
MyThread myThread = new MyThread();
//调用方法
//myThread.run();
//启动线程
myThread.start();
for (int i = 0; i < 10000; i++) {
System.out.println("煮烫水第"+i+"毫秒");
}
}
}
//自定义线程类
class MyThread extends Thread{
//重写run()
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("刷牙第"+i+"毫秒");
}
}
}
/*
自定义任务类实现Runnable接口,重写run(),实例化任务类对象,再实例化Thread类,并在构造方法中传递任务类对象,通过Thread类对象调用start()启动线程
*/
public class Demo02 {
public static void main(String[] args) {
/*
//实例化任务类对象
Task task = new Task();
//实例化线程对象
Thread thread = new Thread(task);
thread.start();
*/
```
//使用匿名内部类完成多线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("运动第"+i+"毫秒");
}
}
}).start();
for (int i = 0; i < 10000; i++) {
System.out.println("看学习资料第"+i+"毫秒");
}
}
```
}
//自定义任务类
class Task implements Runnable{
//重写run方法
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("吃饭第"+i+"毫秒");
}
}
}
1.2 进程的休眠
public class Demo01 {
public static void main(String[] args) {
new MyThread1().start();
new MyThread2().start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
if(i == 9000){
System.out.println("兔子睡着了");
try {
sleep(3000);//进程休眠3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("----兔子在跑----"+i);
}
System.out.println("----兔子冲过终点----");
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
System.out.println("乌龟在爬"+i);
}
System.out.println("乌龟爬过终点");
}
}
/*
利用进程的休眠实现控制台打印时间
*/
public class Demo02 {
public static void main(String[] args) throws InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while(true){
String time = sdf.format(new Date());
System.out.println(time);
Thread.sleep(1000);
}
}
}
1.3 临界资源
临界资源:多个线程共享一个数据
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new MyThread().start();
Thread.sleep(10);
}
}
}
class MyThread extends Thread{
//临界资源
private static int num = 0;
```
@Override
public void run() {
for (int i = 0; i < 100; i++) {
num++;
}
System.out.println(Thread.currentThread().getName()+":"+num);
}
```
}
1.4 线程安全问题
线程安全问题:当多个线程访问异步临界资源,可能会出现数据混乱
线程安全问题:指多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题
同步锁:synchronized
1. 同步代码块
把需要进行同步的代码放在代码块里,添加同步锁和同步监视器 要求多个线程访问同步监视器必须是同一个对象,当线程进入同步代码块中
必须执行完同步代码块的代码,其他线程才能进入
2. 同步方法
同步锁所修饰的方法为同步方法,同步监视器为方法本身
当其中一条线程执行到同步方法时,其他线程等待同步方法执行完毕在调用同步方法
要求方法为静态方法或者同一个对象
1.4.1 同步代码块
在同步代码块中,只能存在一个线程
创建十个华为手机专卖店卖P50手机,总的手机台数为1000台
1.卖手机过程中,出现了手机重卖的情况(手机被反复的卖出,phone未被减少时就打印出了)
2.问题出现的原因:当某个线程操作手机的过程中,尚未完成操作时,其他线程参与进来,也来操作手机(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)
生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。
3.如何解决:当一个线程在操作phone时,其他线程不能参与进来,直到此线程的生命周期结束
4.在java中,我们通过同步机制,来解决线程的安全问题
/*
把需要进行同步的代码放在代码块里,添加同步锁和同步监视器 要求多个线程访问同步监视器必须是同一个对象,当线程进入同步代码块中
必须执行完同步代码块的代码,其他线程才能进入
*/
public class Demo01 {
public static void main(String[] args) {
Task task = new Task();
for (int i = 1; i <= 10; i++) {
new Thread(task,"第"+i+"家华为手机专卖店").start();
}
}
}
class Task implements Runnable{
private int phone = 0;
//静态对象
private static final Object obj = new Object();
```
@Override
public void run() {
sellPhone();
}
//出售手机的方法
private void sellPhone() {
while(true){
//obj为同步监视器,要求每个线程的同步监视器必须是同一个对象
synchronized(obj){
if(phone < 1000){
/*
第5家华为手机专卖店正在出售第21部手机
第1家华为手机专卖店正在出售第25部手机
第6家华为手机专卖店正在出售第24部手机
第7家华为手机专卖店正在出售第23部手机
第3家华为手机专卖店正在出售第22部手机
第2家华为手机专卖店正在出售第21部手机
*/
System.out.println(Thread.currentThread().getName()+"正在出售第"+(++phone)+"部手机");
}else{
System.out.println(Thread.currentThread().getName()+"售罄");
break;
}
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
```
}
1.4.2 同步方法
/*
同步锁所修饰的方法为同步方法,同步监视器为方法本身
当其中一条线程执行到同步方法时,其他线程等待同步方法执行完毕在调用同步方法
要求方法为静态方法或者同一个对象
*/
public class Demo02 {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
new MyThread("第"+i+"家华为手机店").start();
}
}
}
class MyThread extends Thread{
private static int phone = 0;
public MyThread(String str){
setName(str);
}
```
@Override
public void run() {
boolean flag = true;
while(flag){
flag = sellPhone();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//同步方法
private synchronized static boolean sellPhone() {
if(phone < 1000){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(++phone)+"部手机");
}else{
System.out.println(Thread.currentThread().getName()+"售罄");
return false;
}
return true;
}
```
}
-
验证其中一条线程调用到同步方法时,其他线程的状态
-
其他线程正常运行,直到调用同步方法的时候才会进入阻塞状态
public class Demo03 { public static void main(String[] args) throws InterruptedException { for (int i = 1; i <= 10; i++) { new MyThread2("第"+i+"家华为手机店").start(); Thread.sleep(1000); } } } class MyThread2 extends Thread{ private static int phone = 0; public MyThread2(String str){ setName(str); } ``` @Override public void run() { boolean flag = true; while(flag){ System.out.println(Thread.currentThread().getName()+"准备出售手机"); //调用同步方法 flag = sellPhone(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } //同步方法 private synchronized static boolean sellPhone() { int num = 0; if(phone < 1000){ System.out.println(Thread.currentThread().getName()+"正在出售第"+(++phone)+"部手机"); }else{ System.out.println(Thread.currentThread().getName()+"售罄"); return false; } for (int i = 0; i < 1000000000; i++) { for (int j = 0; j < 10000000; j++) { for (int k = 0; k < 100000000; k++) { num++; } } } return true; } ``` }
1.5 Locke接口
Locke:显示获取锁释放锁
ReentrantLock:可重入锁,需要通过方法来获取锁释放锁
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
for (int i = 1; i <= 10; i++) {
new Thread(task,"第"+i+"家华为手机专卖店").start();
//创建线程的间隔加长
Thread.sleep(2);
}
}
}
class Task implements Runnable{
private int phone = 0;
//实例化可重入锁,当通过构造方法传递true时,ReentrantLock则为公平锁
private Lock myLock = new ReentrantLock(true);
@Override
public void run() {
sellPhone();
}
```
//出售手机的方法
private void sellPhone() {
while(true){
//获取锁
myLock.lock();
try{
if(phone < 1000){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(++phone)+"部手机");
}else{
System.out.println(Thread.currentThread().getName()+"售罄");
break;
}
//加长同步时间
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < 10000; j++) {
for (int k = 0; k < 10; k++) {
}
}
}
}finally {
//释放锁
myLock.unlock();
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
```
}
Synchronized vs lock
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活)
优先使用顺序:
LOCK ——> 同步代码块 ——> 同步方法
1.6 wait
wait() vs sleep()
1.wait()来自Object类,sleep()来自Thread类
2.当线程在同步区域中进入休眠状态,不会释放资源
当线程在同步区域中进入等待状态,会释放资源
3.sleep()会自动唤醒,wait(),wait()需要调用notify()或者notifyAll()进行唤醒,如果没有唤醒则进入死锁状态
4.wait()需要在同步区域里使用,必须通过同步监视器的对象来调用
public class Demo01 {
public static void main(String[] args) {
Task task = new Task();
for (int i = 1; i < 11; i++) {
new Thread(task,"第"+i+"家华为手机店").start();
}
}
}
class Task implements Runnable{
private final Object obj = new Object();
private int phone = 0;
```
@Override
public void run() {
try {
sellPhone();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void sellPhone() throws InterruptedException {
while(true){
synchronized (this){
if(phone < 1000){
System.out.println(Thread.currentThread().getName()+"正在出售"+(++phone)+"部P50手机");
}else{
System.out.println(Thread.currentThread().getName()+"已售罄");
break;
}
this.wait();
//不会释放资源
/* try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//会释放资源
/*try {
//使当前线程进入等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
}
```
}
1.7 notify and notifyAll
public class Demo02 {
public static final Object obj = new Object();
```
public static void main(String[] args) throws InterruptedException {
//创建第一条线程 -- 会进入阻塞状态
for (int i = 0; i < 10; i++) {
new MyThread1("A"+i).start();
}
Thread.sleep(3000);
//创建第二天线程 -- 会唤醒第一条线程
new MyThread2("唤醒线程B").start();
}
```
}
class MyThread1 extends Thread{
public MyThread1(String name){
super(name);
}
@Override
public void run() {
synchronized (Demo02.obj){
System.out.println(getName()+"进入同步代码块前");
try {
//使当前线程进入阻塞状态
Demo02.obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"进入同步代码块后");
}
}
}
class MyThread2 extends Thread{
public MyThread2(String name){
super(name);
}
@Override
public void run() {
synchronized (Demo02.obj){
System.out.println(getName()+"进入同步代码块前");
//唤醒等待的线程 -- 只唤醒一条线程
//Demo02.obj.notify();
//唤醒等待的线程 -- 唤醒所有的线程,先等待的线程后唤醒
Demo02.obj.notifyAll();
System.out.println(getName()+"进入同步代码块后");
}
}
}
1.8 线程池
通过线程池创建线程
public class Demo01 {
public static void main(String[] args) {
//1.创建只有一条线程的线程池
/*ExecutorService pool = Executors.newSingleThreadExecutor();
Task task = new Task();
//把任务提交给线程池里的线程 -- 一条线程完成10个线程任务
for (int i = 0; i < 10; i++) {
pool.submit(task);
}
//关闭线程池
pool.shutdown();*/
```
//2.创建固定线程的线程池
/*ExecutorService pool = Executors.newFixedThreadPool(3);
Task task = new Task();
//把任务提交给线程池里的线程 -- 3条线程完成10个线程任务
for (int i = 0; i < 10; i++) {
pool.submit(task);
}
//关闭线程池
pool.shutdown();*/
//3.创建一个带缓冲区的线程池
ExecutorService pool = Executors.newCachedThreadPool();
Task task = new Task();
for (int i = 0; i < 10; i++) {
pool.submit(task);
}
pool.shutdown();
}
```
}
class Task implements Runnable{
```
@Override
public void run() {
for (int i = 0; i < 10; i++) {
/*try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println(Thread.currentThread().getName()+"--------"+i);
}
}
```
}
1.9 守护线程
main方法是程序的主线程,垃圾回收机制是一条守护线程
守护线程:所有非守护线程运行结束,守护线程跟着销毁
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
//在线程启动前设置为守护线程
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 100; i++) {
Thread.sleep(10);
System.out.println("主线程:"+i);
}
}
}
//守护线程
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 500; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程:"+i);
}
}
}
1.10 线程合并
关键字:join
/*
线程合并
线程A执行到50的时候,让线程B先执行完再执行线程A
*/
public class Demo01 {
public static void main(String[] args) {
//线程B的对象
MyThread2 thread2 = new MyThread2();
MyThread1 thread1 = new MyThread1(thread2);
thread1.start();
thread2.start();
}
}
class MyThread1 extends Thread{
private Thread thread;
public MyThread1(Thread thread){
this.thread = thread;
}
```
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
if(i == 50){
//使用线程B对象调用join()
thread.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A:"+i);
}
}
```
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----线程B:"+i);
}
}
}
1.11 线程的礼让
当线程调用yield(),当前线程会从运行状态回到就绪状态
public class Demo01 {
public static void main(String[] args) {
new MyThread("线程A").start();
new MyThread("B").start();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(name+":"+i);
if("B".equals(name) && i == 50){
System.out.println(name+":进行礼让了");
Thread.yield();
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.12 线程的优先级
线程的优先级不影响CPU的调度,在资源紧缺的时候,CPU才有概率优先调用优先级别高的线程
public class Demo01 {
public static void main(String[] args) {
MyThread thread1 = new MyThread("线程A");
MyThread thread2 = new MyThread("B");
//线程A优先级别最低
thread1.setPriority(Thread.MIN_PRIORITY);
//线程B优先级别最高
thread2.setPriority(Thread.MAX_PRIORITY);
//线程A先启动
thread1.start();
//线程B后启动
thread2.start();
```
}
```
}
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+":"+i);
}
}
}
1.13 读写分离的集合
CopyOnWriteArrayList:读写分离的集合
public class Demo01 {
public static void main(String[] args) {
//List<String> list = new ArrayList<>();
List<String> list = new CopyOnWriteArrayList();
list.add("a");
list.add("b");
list.add("c");
for (String str:list) {
System.out.println(str);
list.add("1");
}
System.out.println("---------");
list.forEach(str -> System.out.println(str));
}
}
反射和设计模式
1.1 反射
反射:在运行状态中能够动态地获取类的属性和调用类的方法
//获取反射对象的三种方式
public class Demo02 {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class.forName("完整类路径")
Class<?> clazz1 = Class.forName("com.qf.ran.refilect1.User");
//2.对象名调用getClass()
User user = new User();
Class<? extends User> clazz2 = user.getClass();
//3.通过类名.class
Class<User> clazz3 = User.class;
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
}
}
通过反射修改对象的私有属性
//通过反射修改对象的私有属性
public class Demo03 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
User user = new User(1, "ls", 22);
System.out.println("反射前:"+user);
//1.获取反射对象
Class<? extends User> clazz = user.getClass();
//2.通过反射对象获取属性对象
Field field = clazz.getDeclaredField("age");
//3.通过属性对象打开访问权限
field.setAccessible(true);
//4.通过属性对象修改属性值 -- 要修改的对象,修改的属性值
field.set(user,20);
System.out.println("反射后:"+user);
}
}
```
调用对象私有方法
~~~java
/*
调用对象私有的方法
*/
public class Demo04 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
//1.获取反射对象
Class<User> clazz = User.class;
//2.通过反射对象获取私有方法的对象 -- 没有参数没有返回值
Method method1 = clazz.getDeclaredMethod(
"privateMethod1");
//3.通过私有方法的对象打开访问权限
method1.setAccessible(true);
//4.通过私有方法的对象调用方法 -- 调用方法的实例对象
method1.invoke(user);
```
//1.通过反射对象获取私有方法的对象 -- 有参数有返回值
Method method2 = clazz.getDeclaredMethod(
"privateMethod2", String.class, int.class);
//2.通过私有方法的对象打开访问权限
method2.setAccessible(true);
//3.通过私有方法的对象调用方法 -- 调用方法的实例对象,方法的实参
int num = (int) method2.invoke(user,"zs",123);
System.out.println("num:"+num);得到的类型为object类型,需要强转为int类型
}
}
public class User {
private int id;
private String username;
private int age;
//没有参数没有返回值
private void privateMethod1(){
System.out.println("私有方法1");
}
//有参数有返回值
private int privateMethod2(String str,int num){
System.out.println(str+":"+num);
System.out.println("私有方法2");
return 1;
}
}
通过反射调用构造方法实例化对象
/*
通过反射调用构造方法实例化对象
*/
public class Demo05 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//1.通过完整类路径获取反射对象、构造方法私有化无法实例化对象、只能通过完整类路径获取反射
Class<?> clazz = Class.forName("com.qf.ran.refilect1.Human");
//2.通过反射对象获取无参构造方法的对象
Constructor<?> constructor1 = clazz.getDeclaredConstructor();
//3.打开访问权限
constructor1.setAccessible(true);
//4.通过无参构造方法对象创建实例对象
Human human1 = (Human)constructor1.newInstance();
```
//直接通过反射对象创建实例对象 -- 要求构造方法非私有
//Human human2 = (Human) clazz.newInstance();
//通过反射对象获取有参构造方法的对象
Constructor<?> constructor2 = clazz.getDeclaredConstructor(int.class, String.class, double.class);
// 打开访问权限
constructor2.setAccessible(true);
Human human3 = (Human)constructor2.newInstance(1, "zs", 188);
System.out.println(human3);
}
}
public class Human {
private int id;
private String name;
private double height;
private Human() {//构造方法私有化无法
System.out.println("私有的无参构造方法");
}
private Human(int id, String name, double height) {
this.id = id;
this.name = name;
this.height = height;
System.out.println("私有的有参构造方法");
}
通过反射对象获取类名
/*
通过反射对象获取类名
*/
public class Demo06 {
public static void main(String[] args) {
Class<Human> clazz = Human.class;
//获取完整的类路径
String name1 = clazz.getName();
//获取类名
String name2 = clazz.getSimpleName();
System.out.println(name1);
System.out.println(name2);
}
}
/*
结果:com.qf.ran.refilect1.Human
Human
*/
1.2 设计模式
设计模式:一套程序员长期累积规划的设计思想
1.创建型模式(5):只关注对象的创建,用于解耦对象的实例
单例模式,工厂方法模式,抽象工厂模式,建造者模式,原型模式
2.结构型模式(7):只关注对象的结构,把类或者对象结合起来形成更强大的结构
装饰者模式,适配器模式,组合模式,代理模式,亨元模式,外观模式,桥接模式
3.行为型模式(11):只关注对象的行为,关注类和对象如何进行交互以及划分职责和算法
观察者模式,策略模式,模板模式,解析器模式,状态模式,备忘录模式,中介者模式
命令模式,责任链模式,访问模式,迭代器模式
单例模式
public class Demo01 {
public static void main(String[] args) {
//饿汉式 -- 线程安全
Single1 instance1 = Single1.getInstance();
Single1 instance2 = Single1.getInstance();
System.out.println(instance1 == instance2);
```
//懒汉式 -- 不会在一开始的时候占用内存资源
Single2 instance3 = Single2.getInstance();
Single2 instance4 = Single2.getInstance();
System.out.println(instance3 == instance4);
}
}
饿汉式
//饿汉式 -- 类被加载的时候创建对象
class Single1{
//2.私有化静态属性,类型为当前类
private static Single1 single1 = new Single1();
//1.私有化构造方法
private Single1(){
}
//3.提供外界一个获取属性对象的静态方法
/*
为什么用static静态的修饰,因为这里的构造方法是私有的,不能通过创建对象实例化,只能通过类名调用,只有静态的方法才能用类名调用
*/
public static Single1 getInstance(){
return single1;
}
}
懒汉式
//懒汉式 -- 调用方法的时候才创建对象
class Single2{
//私有化静态属性,类型为当前类
private static Single2 single2;
```
//私有化构造方法
private Single2(){
}
//给外界提供一个获取当前对象的静态方法
public static Single2 getInstance(){
if(single2 == null){
single2 = new Single2();
}
return single2;
}
}
JSON-枚举-注解-lambda-stream
1.1 JSON
1.1.1 json
JSON:传输的数据格式,本质上是字符串
解析json格式的对象
/*
解析json格式的对象
*/
public class Demo01 {
public static void main(String[] args) throws JSONException {
//json格式的对象
String json = "{id:1,name:'zs'}";
//解析json转换成对象
JSONObject obj = new JSONObject(json);
//根据属性名获取属性值
int id = obj.getInt("id");
String name = obj.getString("name");
User user = new User(id, name);
System.out.println(user);
}
}
解析复杂的json对象(对象中有对象)
/*
解析复杂的json对象
*/
public class Demo02 {
public static void main(String[] args) throws JSONException {
String json = "{id:1,name:'zs',score:{javaScore:88.8,mysqlScore:66.6}}";
JSONObject jsonObj = new JSONObject(json);
//根据属性名获取属性值
int id = jsonObj.getInt("id");
String name = jsonObj.getString("name");
//根据对象名获取对象
JSONObject obj = jsonObj.getJSONObject("score");
double javaScore = obj.getDouble("javaScore");
double mysqlScore = obj.getDouble("mysqlScore");
Student stu = new Student(id, name, new Score(javaScore, mysqlScore));
System.out.println(stu);
}
}
解析多个对象的json格式数据
/*
解析多个对象的json格式数据
*/
public class Demo03 {
public static void main(String[] args) throws JSONException {
String json = "[{id:1,name:'zs'},{id:2,name:'ls'},{id:3,name:'ww'}]";
JSONArray array = new JSONArray(json);
ArrayList<User> list = new ArrayList<>();
//遍历对象
for (int i = 0; i < array.length(); i++) {
//根据索引获取对象
JSONObject obj = array.getJSONObject(i);
//根据属性名获取属性值
int id = obj.getInt("id");
String name = obj.getString("name");
User user = new User(id, name);
/*
需要集合存储每一个对象
*/
list.add(user);
}
System.out.println(list);
}
}
把对象封装成json格式数据
//把对象封装成json格式数据
public class Demo04 {
public static void main(String[] args) {
//将一个对象封装成json格式
User user = new User(1, "zs");
JSONObject obj = new JSONObject(user);
System.out.println(obj.toString());
//将对象中有对象的对象封装成json格式
Student stu = new Student(2, "ls", new Score(99, 88));
JSONObject obj2 = new JSONObject(stu);
System.out.println(obj2.toString());
}
```
}
将集合转换成json格式数据
//将集合转换成json格式数据
public class Demo05 {
public static void main(String[] args) {
ArrayList<User> list = new ArrayList<>();
list.add(new User(1,"zs"));
list.add(new User(3,"z4"));
list.add(new User(5,"z5"));
JSONArray obj = new JSONArray(list);
System.out.println(obj.toString());
}
}
1.1.2 gson
使用gson解析json格式对象
//使用gson解析json格式对象
public class Demo01 {
public static void main(String[] args) {
//json格式的对象
String json1 = "{id:1,name:'zs'}";
//实例化Gson对象
Gson gson = new Gson();
//通过Gson对象解析
User user = gson.fromJson(json1, User.class);
System.out.println(user);
//解析对象中有对象json的对象
String json2 = "{id:1,name:'zs',score:{javaScore:88.8,mysqlScore:66.6}}";
Student stu = gson.fromJson(json2, Student.class);
System.out.println(stu);
}
```
}
使用gson解析json格式的数组
//使用gson解析json格式的数组
public class Demo02 {
public static void main(String[] args) {
String json = "[{id:1,name:'zs'},{id:2,name:'ls'},{id:3,name:'ww'}]";
Gson gson = new Gson();
//ArrayList<User> list = gson.fromJson(json, ArrayList.class);
//System.out.println(list);
/* for (User user:list) {
System.out.println(user);
}*/
```
TypeToken<ArrayList<User> > typeToken = new TypeToken<ArrayList<User>>(){};
ArrayList<User> list = gson.fromJson(json, typeToken.getType());
for (User user:list) {
System.out.println(user);
}
}
```
}
1.1.3 fastjson
使用fastjson解析json格式对象
//使用fastjson解析json格式对象
public class Demo01 {
public static void main(String[] args) {
String json1 = "{id:1,name:'zs'}";
User user = JSON.parseObject(json1, User.class);
System.out.println(user);
```
String json2 = "{id:1,name:'zs',score:{javaScore:88.8,mysqlScore:66.6}}";
Student stu = JSON.parseObject(json2, Student.class);
System.out.println(stu);
}
```
}
使用fastjson解析数组形式的json格式对象
//使用fastjson解析数组形式的json格式对象
public class Demo02 {
public static void main(String[] args) {
String json = "[{id:1,name:'zs'},{id:2,name:'ls'},{id:3,name:'ww'}]";
//fastjson是静态的,可通过类名直接调用,不用实例化
List<User> list = JSON.parseArray(json, User.class);
for (User user:list) {
System.out.println(user);
}
}
}
使用fastjson把对象封装成json格式数据
//使用fastjson把对象封装成json格式数据
public class Demo03 {
public static void main(String[] args) {
//封装一个对象
User user = new User(1, "zs");
String json1 = JSON.toJSONString(user);
System.out.println(json1);
//封装多个对象
ArrayList<User> list = new ArrayList<>();
list.add(new User(1,"zs"));
list.add(new User(2,"z4"));
list.add(new User(3,"z5"));
String json2 = JSON.toJSONString(list);
System.out.println(json2);
}
```
}
1.1.4 MyFastJson
public class Demo01 {
public static void main(String[] args) {
String json = "id:19,name:'ls'}";
User user = MyFastJson.parseObject(json, User.class);
System.out.println(user);
String json2 = "{username:'ww',age:11,password:'123'}";
Person person = MyFastJson.parseObject(json2, Person.class);
System.out.println(person);
}
}
public class MyFastJson {
public static <T> T parseObject(String json,Class<T> clazz){
//判断是否为json格式数据 --前后为"{}"
if(!json.startsWith("{") || !json.endsWith("}")){
throw new RuntimeException("不是json格式数据");
}
T t = null;
try {
//通过反射对象创建实例对象
t = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//截取除了"{}"之外的数据
//id:1,name:'zs'
json = json.substring(1,json.length()-1);
//以","为分隔符进行分割
//["id:1","name:'zs'"]
String[] split = json.split(",");
for (String str:split) {
//["id","1"]
String[] split1 = str.split(":");
try {
//根据属性名获取属性对象
Field field = clazz.getDeclaredField(split1[0]);
//打开访问权限
field.setAccessible(true);
Class<?> type = field.getType();
//属性值
String value = split1[1];
//判断当前属性类型
if(type == int.class){
//给指定对象进行属性赋值
field.set(t,Integer.parseInt(value));
}else if(type == String.class){
//去除头尾的单引号
value = value.substring(1,value.length()-1);
field.set(t,value);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return t;
}
}
1.2 枚举
使用枚举可以替代静态常量当状态值
public class Demo01 {
public static void main(String[] args) {
MyEnum man = MyEnum.OLD_MAN;
switch (man){
case MAN:
System.out.println("男人");
break;
case WOMEN:
System.out.println("女人");
break;
case OLD_MAN:
System.out.println("糟老头子");
break;
default:
break;
}
/*
运行结果:糟老头子
*/
public enum MyEnum {
MAN,
WOMEN,
OLD_MAN
}
枚举的特点
枚举既然是一个类,那么枚举是否有构造方法,成员方法,静态方法,静态变量,成员变量,抽象方法?
有的话,有意义吗?
1.枚举中所有的成员,必须出现在枚举对象的下面
2.如果枚举类中有一个成员,那么枚举对象最后不能省略分号
3.枚举中构造方法必须私有
4.抽象方法有意义 - 可以用来描述某个枚举成员的信息,提高程序的可读性
5.枚举也是switch语句 的常量形式之一
如果打算自定义自己的方法,那么必须在enum实例序列的最后添加一个分号
//重新定义一个日期枚举类,带有desc成员变量描述该日期的对于中文描述,同时定义一个getDesc方法,返回中文描述内容,自定义私有构造函数,在声明枚举实例时传入对应的中文描述
public enum Day2 {
//相当于构造方法的一个个实例
MONDAY("星期一"),
TUESDAY("星期二"),
WEDNESDAY("星期三"),
THURSDAY("星期四"),
FRIDAY("星期五"),
SATURDAY("星期六"),
SUNDAY("星期日");//记住要用分号结束
private String desc;//中文描述
/**
* 私有构造,防止被外部调用
* @param desc
*/
private Day2(String desc){
this.desc=desc;
}
/**
* 定义方法,返回描述,跟常规类的定义没区别
* @return
*/
public String getDesc(){
return desc;
}
public static void main(String[] args){
for (Day2 day:Day2.values()) {
System.out.println("name:"+day.name()+
",desc:"+day.getDesc());
}
}
/**
输出结果:
name:MONDAY,desc:星期一
name:TUESDAY,desc:星期二
name:WEDNESDAY,desc:星期三
name:THURSDAY,desc:星期四
name:FRIDAY,desc:星期五
name:SATURDAY,desc:星期六
name:SUNDAY,desc:星期日
*/
}
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
//以上是枚举的成员,必须先定义,而且使用分号结束
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 普通方法
public static String getName(int index) {
for (Color c : Color.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
// get set 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
1.3 注解
注解:元数据,一种代码级别的说明,增强代码的作用性(可以用标签来理解)
注解通过 @interface 关键字进行定义,它的形式跟接口很类似,不过前面多了一个 @ 符号
/*
自定义注解
*/
@Target(ElementType.TYPE_USE)
/*
指定被修饰的注解不能修饰无返回值的方法,即MyAnnotation这个元注解不能修饰无返回值的方法,其他的都可以
*/
public @interface MyAnnotation {
//元素类型 元素名()
//String str();
}
-
@Override:标记方法为重写方法,检测方法是否为重写方法
-
@FuncitionlInterface:标记接口只能存在一个抽象方法
元注解
元注解:作用在注解上的注解
元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面
元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的
元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种
@Target
Target:指定被修饰的注解的生命周期(应用场景)
Target 是目标的意思,@Target 指定了注解运用的地方。当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等
-
ElementType.TYPE 指定被修饰的注解只能修饰类与接口
-
ElementType.FIELD 指定被修饰的注解只能修饰全局属性
-
ElementType.METHOD 指定被修饰的注解只能修饰非构造的方法
-
ElementType.PARAMETER 指定被修饰的注解只能修饰方法中的形参
-
ElementType.CONSTRUCTOR 指定被修饰的注解只能修饰构造方法
-
ElementType.LOCAL_VARIABLE 指定被修饰的注解只能修饰局部变量
-
ElementType.ANNOTATION_TYPE 指定被修饰的注解只能修饰注解
-
ElementType.TYPE_PARAMETER 指定被修饰的注解只能修饰泛型
-
ElementType.TYPE_USE 指定被修饰的注解不能修饰无返回值的方法
@Retention
Retention:修饰注解在什么情况下保留(Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间)
-
RetentionPolicy.SOURCE 指定被修饰的注解只保留在源代码中
-
RetentionPolicy.CLASS 指定被修饰的注解保留在字节码文件中
-
RetentionPolicy.RUNTIME 指定被修饰的注解在运行时也保留
@Documented
顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去
@Inherited
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的元注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解
@Repeatable
Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
什么样的注解会多次应用呢?通常是注解的值可以同时取多个
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型
下面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
在使用的时候,我们应该给它们进行赋值。赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用 ,隔开,需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
注解中属性可以有默认值,默认值需要用 default 关键值指定
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
//TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi,它可以这样应用
@TestAnnotation()/*因为有默认值,所以无需要再在 @TestAnnotation 后面的括号里面进行赋值了*/
public class Test {}
还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内
public @interface Check {
String value();
}
//应用时,下面两种效果相同
@Check("hi")
int a;
@Check(value="hi")
int a;
还需要注意的一种情况是一个注解没有任何属性
public @interface Perform {}
//在应用这个注解的时候,括号都可以省略
@Perform
public void testMethod(){}
只存在一个抽象方法的接口称为函数式接口
public interface ITest{
void method();
}
1.4 lambda
lambda表达式
-
特殊的匿名内部类,允许把函数当做是方法的参数进行传递
-
简化了方法重写的代码
public class Demo02 {
public static void main(String[] args) {
//-----------匿名内部类的写法-------------
ITest test1 = new ITest() {
@Override
public void method() {
System.out.println("匿名内部类");
}
};
test1.method();
```
//-------------lambda表达式----------------
/*
()代表的是方法
->指向方法的主体
当方法只存在一行代码可以省略{}
*/
ITest test2 = () -> System.out.println("单行lambda表达式");
test2.method();
//方法中存在多行代码
ITest test3 = () -> {
System.out.println("多行lambda表达式1");
System.out.println("多行lambda表达式2");
};
test3.method();
}
}
使用lambda表达式调用只有一个参数的方法
//使用lambda表达式调用只有一个参数的方法
public class Demo03 {
public static void main(String[] args) {
//str相当于形参
ITest1 test1 = str ->
System.out.println("带参数的方法"+str);
test1.method("abc");
```
method(test1);
System.out.println("----------------");
//接口回调
method(str ->System.out.println("带参数的方法"+str));
}
public static void method(ITest1 test1){
test1.method("ac");
}
}
/* 带一个参数的方法
*/
public interface ITest1 {
void method(String str);
}
使用lambda表达式调用多个参数的方法
//使用lambda表达式调用多个参数的方法
public class Demo04 {
public static void main(String[] args) {
ITest2 test = (str,num) ->
System.out.println("str:"+str+",num:"+num);
test.method("a",1);
}
}
/* 多个参数的抽象方法
*/
public interface ITest2 {
void method(String str,int num);
}
使用lambda表达式重写带返回值的方法
//使用lambda表达式重写带返回值的方法
public class Demo05 {
public static void main(String[] args) {
//方法体拥有多行代码
ITest3 test = (str,num) ->{
System.out.println("str:"+str+",num:"+num);
return 1;
};
int num = test.method("a",2);
System.out.println("num:"+num);
```
//方法体只有一行代码
//1为返回值,必须省略return
ITest3 test1 = (str,num1) -> 1;
System.out.println(test1.method("a",18));
}
}
/* 带返回值的方法
*/
public interface ITest3 {
int method(String str, int num);
}
1.5 stream
stream:高级迭代器
public class Demo01 {
public static void main(String[] args) {
ArrayList<User> list = new ArrayList<>();
list.add(new User("剑锋",0));
list.add(new User("叶辉",10000));
list.add(new User("智邦",-10000));
list.add(new User("海铭",15000));
list.add(new User("知友",-18888));
list.add(new User("叶辉",10000));
//获取流的方式
Stream<User> stream = list.parallelStream();
//stream.forEach(str -> System.out.println(str));
//采取多线程的方式遍历
//stream.forEach(System.out::println);
```
//-----------中间操作-----------
//1.过滤 -- 打印0以上的
System.out.println("----------filter---------");
list.stream().filter(user ->user.getMoney() > 0).forEach(System.out::println);
//2.限制 -- 截取需要的对象
System.out.println("----------limit---------");
list.stream().limit(2).forEach(System.out::println);
//3.跳过 -- 指定跳过几个对象
System.out.println("----------skip-----------");
list.stream().skip(2).forEach(System.out::println);
//4.去重 -- 去除重复的对象,需要重写equals()和hashCode()
System.out.println("---------distinct--------");
list.stream().distinct().forEach(System.out::println);
//5.排序 -- 从小到大 -- 需要对象实现自然排序
System.out.println("---------sorted----------");
//list.stream().sorted().forEach(System.out::println);
list.stream().sorted((user1,user2) -> Integer.compare((int)(user1.getMoney()),(int)(user2.getMoney()))).forEach(System.out::println);
```
```
//----------------末尾操作---------------
//1.foreach
//2.min
System.out.println("---------min-----------");
Optional<User> min = list.stream().min((user1, user2) -> Integer.compare((int) (user1.getMoney()), (int) (user2.getMoney())));
User user = min.get();
System.out.println(user);
//3.max
System.out.println("--------max----------");
Optional<User> max = list.stream().max((user1, user2) -> Integer.compare((int) (user1.getMoney()), (int) (user2.getMoney())));
User user1 = max.get();
System.out.println(user1);
}
}
网络编程
1.1 OSI参考模型
OSI(Open System Interconnect),即开放式系统互联。
每层功能:
- 第七层:应用层负责文件访问和管理、可靠运输服务、远程操作服务。(HTTP、FTP、SMTP)
- 第六层:表示层负责定义转换数据格式及加密,允许选择以二进制或ASCII格式传输
- 第五层:会话层负责使应用建立和维持会话,使通信在失效时继续恢复通信。(断点续传)
- 第四层:传输层负责是否选择差错恢复协议、数据流重用、错误顺序重排。(TCP、UDP)
- 第三层:网络层负责定义了能够标识所有网络节点的逻辑地址。(IP地址)
- 第二层:链路层在物理层上,通过规程或协议(差错控制)来控制传输数据的正确性。(MAC)
- 第一层:物理层为设备之间的数据通信提供传输信号和物理介质。(双绞线、光导纤维)
1.2 常见协议
1.2.1 TCP
TCP协议:Transmission Control Protocol 传输控制协议:
- 是一种面向连接的、可靠的、基于字节流的传输层通信协议。数据大小无限制。建立连接的过程需要三次握手,断开连接的过程需要四次挥手
三次握手
-
1.客户端发送SYN包(同步序列编号)给服务器,客户端进入SYN_SENT状态,等待服务器确认
-
2.服务器接收到客户端发送的SYN包,同时自己发送SYN+ACK包,服务进入SYN_RECV状态
-
3.客户端接收到服务器发送的SYN+ACK包,向服务器发送确认包ACK包
/*服务器
*/
public class Server {
public static void main(String[] args) throws IOException, InterruptedException {
//建立服务器的套接字
ServerSocket serverSocket = new ServerSocket(8888);
//System.out.println("创建服务端");
//System.out.println("等待客户端连接");
//等待客户端连接 -- 程序的阻塞
Socket socket = serverSocket.accept();
//System.out.println("客户端连接成功");
```
//模拟第二次握手 -- 服务器接收到客户端的数据
InputStream is = socket.getInputStream();
byte[] b= new byte[1024];
//等待客户端发送数据 -- 进入阻塞
int len = is.read(b);
System.out.println(new String(b,0,len));
Thread.sleep(10000);
OutputStream os = socket.getOutputStream();
os.write("服务器:收到!".getBytes());
b = new byte[1024];
//等待客户端发送数据 -- 进入阻塞
len= is.read(b);
System.out.println(new String(b,0,len));
is.close();
os.close();
socket.close();
serverSocket.close();
```
}
}
/* 客户端
*/
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
//创建客户端的套接字 -- 连接上服务器,需要服务器的ip地址和端口号
Socket socket = new Socket("127.0.0.1", 8888);
//模拟第一次握手 客户端发送数据给服务器
OutputStream os = socket.getOutputStream();
os.write("客户端:喂?".getBytes());
//模拟第三次握手 客户端接收到服务器的数据并返回
InputStream is = socket.getInputStream();
byte[] b = new byte[1024];
//等待服务端发送数据 -- 进入阻塞
int len = is.read(b);
System.out.println(new String(b,0,len));
Thread.sleep(5000);
os.write("客户端:OK!".getBytes());
os.close();
is.close();
socket.close();
}
}
使用多线程在客户端和服务器之间通信,一对一无限制不间断通讯
/* 服务器
*/
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
//等待连接
Socket socket = serverSocket.accept();
//启动输出数据的线程
new WriterThread(socket,"罗翔").start();
//启动读取数据的线程
new ReaderThread(socket).start();
}
}
//用户
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
//启动输出的线程
new WriterThread(socket,"法外狂徒").start();
//启动接收数据的线程
new ReaderThread(socket).start();
}
}
/* 读取数据的线程
*/
public class ReaderThread extends Thread {
private InputStream is;
private Socket socket;
public ReaderThread(Socket socket){
try {
this.socket = socket;
is = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try{
while(true){
byte[] b = new byte[1024];
int len = 0;
try {
len = is.read(b);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new String(b,0,len));
}
}finally {
IOUtils.closeAll(is,socket);
}
}
}
/* 输出的线程
*/
public class WriterThread extends Thread {
private OutputStream os;
private Socket socket;
//标记客户端还是服务器
private String name;
private Scanner sc = new Scanner(System.in);
public WriterThread(Socket socket,String name){
try {
this.name = name;
this.socket = socket;
os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
String str = null;
try{
while (true){
//获取控制台录入的数据 -- 阻塞
str = sc.next();
//equalsIgnoreCase()忽略大小写的判断
if("esc".equalsIgnoreCase(str)){
break;
}
os.write((name+":"+str).getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(sc,os,socket);
}
}
}
带缓冲的io流多线程实现一对一无限制不间断通讯
/*读取数据的线程
*/
public class ReaderThread extends Thread {
private BufferedReader br;
//private InputStream is;
private Socket socket;
public ReaderThread(Socket socket){
try {
this.socket = socket;
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try{
while(true){
String str = br.readLine();
if(str != null){
System.out.println(str);
}
}
```
/*while(true){
byte[] b = new byte[1024];
int len = 0;
try {
len = is.read(b);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new String(b,0,len));
}*/
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(br,socket);
//IOUtils.closeAll(is,socket);
}
```
}
}
/* 输出的线程
*/
public class WriterThread extends Thread {
//private OutputStream os;
private BufferedWriter bw;
private Socket socket;
//标记客户端还是服务器
private String name;
//private Scanner sc = new Scanner(System.in);
private BufferedReader br;
public WriterThread(Socket socket,String name){
try {
this.name = name;
this.socket = socket;
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
br = new BufferedReader(new InputStreamReader(System.in));
//os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
String str = null;
try{
while(true){
//获取控制台录入的数据 -- 阻塞
str = br.readLine();
//equalsIgnoreCase()忽略大小写的判断
if("esc".equalsIgnoreCase(str)){
break;
}
bw.write(name+":"+str);
bw.newLine();
bw.flush();
}
/*while (true){
//获取控制台录入的数据 -- 阻塞
str = sc.next();
//equalsIgnoreCase()忽略大小写的判断
if("esc".equalsIgnoreCase(str)){
break;
}
os.write((name+":"+str).getBytes());
}*/
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(br,bw,socket);
//IOUtils.closeAll(sc,os,socket);
}
}
}
1.2.2 UDP
UDP协议:User Datagram Protocol 用户数据报协议:
- 是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,每个包的大小64KB
//用户1
public class Client1 {
public static void main(String[] args) throws IOException {
//1.创建一个包含端口号的UDP套接字
DatagramSocket socket = new DatagramSocket(8888);
//2.发送数据
//把传输的数据转换成byte类型数组
byte[] b = "客户端1:有道无术".getBytes();
//创建一个数据报包,包中有要发送的数据,发送的地址和端口
DatagramPacket packet = new DatagramPacket(b,b.length, InetAddress.getByName("127.0.0.1"),9999);
//发送
socket.send(packet);
```
b = new byte[1024*64];
packet = new DatagramPacket(b,b.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
```
}
//用户2
public class Client2 {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(9999);
byte[] b = new byte[1024*64];
//创建一个包接受数据,这个包可以接b数组类型的,最大长度为b数组长度的数据
DatagramPacket packet = new DatagramPacket(b, b.length);
//造成阻塞
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
b = "客户端2:天王盖地虎".getBytes();
packet = new DatagramPacket(b,b.length, InetAddress.getByName("127.0.0.1"),8888);
socket.send(packet);
socket.close();
}
}
t = serverSocket.accept();
//System.out.println("客户端连接成功");
//模拟第二次握手 -- 服务器接收到客户端的数据
InputStream is = socket.getInputStream();
byte[] b= new byte[1024];
//等待客户端发送数据 -- 进入阻塞
int len = is.read(b);
System.out.println(new String(b,0,len));
Thread.sleep(10000);
OutputStream os = socket.getOutputStream();
os.write("服务器:收到!".getBytes());
b = new byte[1024];
//等待客户端发送数据 -- 进入阻塞
len= is.read(b);
System.out.println(new String(b,0,len));
is.close();
os.close();
socket.close();
serverSocket.close();
```
}
}
/* 客户端
*/
public class Client {
public static void main(String[] args) throws IOException, InterruptedException {
//创建客户端的套接字 -- 连接上服务器,需要服务器的ip地址和端口号
Socket socket = new Socket("127.0.0.1", 8888);
//模拟第一次握手 客户端发送数据给服务器
OutputStream os = socket.getOutputStream();
os.write("客户端:喂?".getBytes());
//模拟第三次握手 客户端接收到服务器的数据并返回
InputStream is = socket.getInputStream();
byte[] b = new byte[1024];
//等待服务端发送数据 -- 进入阻塞
int len = is.read(b);
System.out.println(new String(b,0,len));
Thread.sleep(5000);
os.write("客户端:OK!".getBytes());
os.close();
is.close();
socket.close();
}
}
使用多线程在客户端和服务器之间通信,一对一无限制不间断通讯
/* 服务器
*/
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
//等待连接
Socket socket = serverSocket.accept();
//启动输出数据的线程
new WriterThread(socket,"罗翔").start();
//启动读取数据的线程
new ReaderThread(socket).start();
}
}
//用户
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
//启动输出的线程
new WriterThread(socket,"法外狂徒").start();
//启动接收数据的线程
new ReaderThread(socket).start();
}
}
/* 读取数据的线程
*/
public class ReaderThread extends Thread {
private InputStream is;
private Socket socket;
public ReaderThread(Socket socket){
try {
this.socket = socket;
is = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try{
while(true){
byte[] b = new byte[1024];
int len = 0;
try {
len = is.read(b);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new String(b,0,len));
}
}finally {
IOUtils.closeAll(is,socket);
}
}
}
/* 输出的线程
*/
public class WriterThread extends Thread {
private OutputStream os;
private Socket socket;
//标记客户端还是服务器
private String name;
private Scanner sc = new Scanner(System.in);
public WriterThread(Socket socket,String name){
try {
this.name = name;
this.socket = socket;
os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
String str = null;
try{
while (true){
//获取控制台录入的数据 -- 阻塞
str = sc.next();
//equalsIgnoreCase()忽略大小写的判断
if("esc".equalsIgnoreCase(str)){
break;
}
os.write((name+":"+str).getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(sc,os,socket);
}
}
}
带缓冲的io流多线程实现一对一无限制不间断通讯
/*读取数据的线程
*/
public class ReaderThread extends Thread {
private BufferedReader br;
//private InputStream is;
private Socket socket;
public ReaderThread(Socket socket){
try {
this.socket = socket;
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try{
while(true){
String str = br.readLine();
if(str != null){
System.out.println(str);
}
}
```
/*while(true){
byte[] b = new byte[1024];
int len = 0;
try {
len = is.read(b);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new String(b,0,len));
}*/
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(br,socket);
//IOUtils.closeAll(is,socket);
}
```
}
}
/* 输出的线程
*/
public class WriterThread extends Thread {
//private OutputStream os;
private BufferedWriter bw;
private Socket socket;
//标记客户端还是服务器
private String name;
//private Scanner sc = new Scanner(System.in);
private BufferedReader br;
public WriterThread(Socket socket,String name){
try {
this.name = name;
this.socket = socket;
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
br = new BufferedReader(new InputStreamReader(System.in));
//os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
String str = null;
try{
while(true){
//获取控制台录入的数据 -- 阻塞
str = br.readLine();
//equalsIgnoreCase()忽略大小写的判断
if("esc".equalsIgnoreCase(str)){
break;
}
bw.write(name+":"+str);
bw.newLine();
bw.flush();
}
/*while (true){
//获取控制台录入的数据 -- 阻塞
str = sc.next();
//equalsIgnoreCase()忽略大小写的判断
if("esc".equalsIgnoreCase(str)){
break;
}
os.write((name+":"+str).getBytes());
}*/
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeAll(br,bw,socket);
//IOUtils.closeAll(sc,os,socket);
}
}
}
1.2.2 UDP
UDP协议:User Datagram Protocol 用户数据报协议:
- 是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,每个包的大小64KB
//用户1
public class Client1 {
public static void main(String[] args) throws IOException {
//1.创建一个包含端口号的UDP套接字
DatagramSocket socket = new DatagramSocket(8888);
//2.发送数据
//把传输的数据转换成byte类型数组
byte[] b = "客户端1:有道无术".getBytes();
//创建一个数据报包,包中有要发送的数据,发送的地址和端口
DatagramPacket packet = new DatagramPacket(b,b.length, InetAddress.getByName("127.0.0.1"),9999);
//发送
socket.send(packet);
```
b = new byte[1024*64];
packet = new DatagramPacket(b,b.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
socket.close();
}
```
}
//用户2
public class Client2 {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(9999);
byte[] b = new byte[1024*64];
//创建一个包接受数据,这个包可以接b数组类型的,最大长度为b数组长度的数据
DatagramPacket packet = new DatagramPacket(b, b.length);
//造成阻塞
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
b = "客户端2:天王盖地虎".getBytes();
packet = new DatagramPacket(b,b.length, InetAddress.getByName("127.0.0.1"),8888);
socket.send(packet);
socket.close();
}
}