1.java概述
1.语言发展史
2.Java语言跨平台原理
Java-----JVM(翻译)----windows,mac,linux
在需要运行Java应用程序的操作系统上,安装一个与操作系统对应的Java虚拟机(JVM Java Virtual Machine)即可
3.JRE和JDK
3.1 JRE(Java Runtime Environment)
是java程序的运行时环境,包含JVM和运行时所需要的核心类库.
3.2 JDK(Java Development Kit)
是java程序开发工具包,包含JRE和开发人员使用的工具.
其中的开发工具:编译工具(javac.exe)和运行工具(java.exe)
开发一个全新的Java程序,必须安装jdk
3.3JDK,JRE,和JVM的关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KDJbO5vP-1617883920689)(D:\Pictures\批注 2021-01-07 230248.png)]
4.JDK的下载和安装
2.第一个程序
1.常用DOS命令
win+r 打开命令提示符窗口,输入cmd
操作 | 说明 |
---|---|
盘符名称 | 盘符切换 |
dir | 查看当下路径下的内容 |
cd目录 | 进入单级目录 |
cd… | 回退到上一级目录 |
cd 目录1\目录2… | 进入多级目录 |
cd\ | 回退到盘符目录 |
cls | 清屏 |
exit | 退出命令行窗口 |
2.Path环境变量的配置
3.HelloWorld案例
4.Notepad软件的安装和使用
5.IDE
IDE:集成开发环境 integrted development environment
开发工具:Ecilpse idea MyEclipse
5.2 IDEA
在项目中,使用package统一管理源码文件
包名规范全部小写. www.baidu.com com/cn/org/edu.baidu.项目名.模块名.其他
3.基础语法
1.注释
不参与程序运行,仅起到说明作用
- 单行注释 //注释信息
- 多行注释 /注释信息/
- 文档注释 /**注释信息 */
2.关键字
就是被Java语言赋予了特定含义的单词
特点:
- 字母全部小写
- 代码编辑器对关键字会有特殊的颜色标记
3.常量
在程序运行过程中,其值不可以发生改变的量
常量类型 | 说明 | 举例 |
---|---|---|
字符串常量 | 用双引号括起来的内容 | “helloworld”,“我是谁” |
整数常量 | 不带小数的数字 | 666,-88 |
小数常量 | 带小数的数字 | 13.14,-5.21 |
字符常量 | 用单引号括起来的内容 | ‘A’,‘0’,‘我’ |
布尔常量 | 布尔值表示真假 | 只有两个值true,false |
空常量 | 一个特殊的值,空值 | null |
4.数据类型
4.1计算机存储单元
最小信息单元-位(bit) -比特位-b;
最小存储单元-字节(byte)-B-由连续的8个位组成
1B=8bit 1KB=1024B 1MB=1024KB 1GB=1024MB 1TB=1024GB
4.2数据类型
Java语言对每一种数据给出了明确的数据类型,不同的数据类型非陪了不同的内存空间,表示的数据大小不同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3fY9Eb36-1617883920691)(D:\Pictures\批注 2021-01-07 233206.png)]
4.3数据类型内存占用和取值范围
数据类型 | 关键字 | 内存占用 | 取值范围 |
---|---|---|---|
整数 | byte | 1 | -128~127 |
short | 2 | -32768~32767 | |
int (默认) | 4 | -2的31次方到2的31次方-1 | |
long | 8 | -2的63次方到2的63次方-1 | |
浮点数 | float | 4 | 负数:-3.402823E+38到-1.40298E-45 正数:1.40298E-45到3.402823E+38 |
double (默认) | 8 | 负数:-1.797693E+308到-49000000E-304 正数:49000000E-304到1.797693E+308到 | |
字符 | char | 2 | 0-65535 |
布尔 | boolean | 1 | true,false |
说明;E+38是表示是乘以10的38次方,同样E-45表示乘以10的负45次方
5.变量
在程序运行中,值可以发生改变的量(内存上的一小块区域);
格式:数据类型 变量名=变量值; int a=10;
变量的使用:取值(a)和修改值(a=10;)
注意事项:
- 名字不能重复
- 变量未赋值不能使用
- long类型的变量定义时,为了防止整数过大,后面要加L
- float类型的变量定义时,为了防止类型不兼容后面要加F
6.标识符
就是给类,方法,变量等起名字的符号,
定义规则:
- 由数字,字母,下划线(_)和美元符($)组成
- 不能以数字开头
- 不能是关键字
- 区分大小写
常见约定:
小驼峰命名法: 方法,变量
- 标识符是一个单词时,首字母小写(name)
- 标识符有多个单词组成时,第一个单词首字母小写,其他单词首字母大写(firstName)
大驼峰命名法: 类
- 标识符为一个单词时,首字母大写(Student)
- 标识符有多个单词组成的时候,首字母大写(GoodStudent)
7.类型转换
自动类型转换:
把一个表示数据范围小的数值或者变量赋值给另一个表示数据范围大的变量
范例:double d =10;
byte–>short–>int–>long–>float–>double
char–>int–>long–>float–>double
强制类型转换
把一个表示数据范围大的数值或变量赋值给另一个表示数据范围小的变量
- 格式:目标数据类型 变量名 = (目标数据类型)值或变量;
- 范例:int k =(int)88.88;
//自动类型转换
double d = 10;
System.out.println(d);
//定义byte类型的变量
byte b = 10;
short s = b;
int i = b;
//char c = b;类型不兼容
//强制类型转换;(但可能会导致数据丢失)
int k = (int)88.88;
System.out.println(k);
4.运算符
1.算数运算符
1.1运算符和表达式
int a = 10;
int b = 10;
int c = a + b;
其中"+“是运算符,并且是算数运算符;
a+b是表达式因为”+"是算数运算符,所以这个表达式是算术表达式
1.2算数运算符
int a = 4;
int b = 6;
System.out.println(a+b);
System.out.println(a-b);
System.out.println(a*b);
System.out.println(b/a);
//除法得到的是商,取余得到余数
//整数相除只能得到整数,要想得到小数,必须有浮点数的参与
System.out.println(6.0/4);
1.3 字符"+"操作
拿字符在计算机底层对应的数值来进行计算的
‘A’---------> 65 A-Z是连续的
‘a’----------> 97 a-z是连续的
‘0’----------> 48 0-9是连续的
算数表达式中包含多个基本数据类型的值的时候,整个算术表达式的类型会自动进行提升
提升规则:
-
byte类型,short类型和char类型将被提升到int类型
-
整个表达式的类型自动提升到表达式中最高等级操作数同样的类型
等级顺序:byte,short,char->int->long->float->double;
int i=10;
char c='A'; //'A'=65
c ='a'; //'a'=97
c ='0'; //'0'=48
System.out.println(i+c);
//char ch = i+c;
//char类型会自动提升为int类型
int j = i + c;
System.out.println(j);
//int k = 10 + 13.14;(13.14属于高于int类型的double类型)
double d =10 + 13.14;
1.4字符串的’+'操作
System.out.println("it"+"黑马");
System.out.println("it"+666);
//当"+"操作中出现字符串时,表示字符串连接符,而不是算数运算符
System.out.println(666+"it");
System.out.println("it"+6+66);
System.out.println(1+99+"年黑马");
//在"+"操作中,如果出现了字符串,就是连接运算符,否则就是算数运算,当连续进行"+"运算时,从左往右逐个执行
2.赋值运算符
2.1赋值运算符
符号 | 作用 | 说明 |
---|---|---|
= | 赋值 | a=10;将10赋值给变量a |
+= | 加后赋值 | a+=b,将a+b的值赋值给a |
-= | 减后赋值 | a-=b,将a-b的值赋值给a |
*= | 乘后赋值 | a*=b,将a*b的值赋值给a |
/= | 除后赋值 | a/=b,将a/b的值赋值给a |
%= | 取余后赋值 | a%=b,将a%b的值赋值给a |
//把10赋值给int类型的变量;
int i=10;
System.out.println("i:"+i);
//+= 把左边和右边的数据做加法操作,结果赋值给左边;
i+=20;
//i=i+20;
System.out.println("i:"+i);
//注意:扩展的赋值运算符底层隐含了强制类型转换
short s = 10;
s+=20;
//s=s+20; s属于short类型,20属于int类型,所以报错
//s=(short)(s+20);需强制转换为short类型
System.out.println("s:"+s);
注意:
扩展的赋值运算隐含了强制类型转换
3.自增自减运算符
3.1自增自减运算符
符号 | 作用 | 说明 |
---|---|---|
++ | 自增 | 变量的值+1 |
– | 自减 | 变量的值-1 |
//定义变量
int i=10;
System.out.println("i:"+i);
//单独使用
/*
i++;
//++i;
System.out.println("i:"+i);
i--;
//--i;
System.out.println("i:"+i);
*/
//参与操作使用
//int j = i++;
int j = ++i;
System.out.println("i:"+i);
System.out.println("j:"+j);
注意事项:
- ++和–可以放在变量的前边或后边
- 单独使用时,++和–放在前后结果是一样的
- 参与操作时,如果放在变量前边,先拿变量做++或–,后拿变量参与操作;如果放在变量后边,则先拿变量参与操作,后拿变量做++或–;
最常见的用法:单独使用.
4.关系运算符
4.1关系运算符
符号 | 说明 |
---|---|
== | a==b,判断a和b的值是否相等,成立为true,不成立为false |
!= | a!=b,判断a,b的值是否不相等,成立为true,不成立为false |
> | a>b,判断a是否大于b,成立为true,不成立为false |
>= | a>=b,判断a是否大于等于b,成立为true,不成立为false |
< | a<b,判断a是否小于b,成立为true,不成立为false |
<= | a<=b,判断a是否小于等于b,成立为true,不成立为false |
//定义变量
int i=10;
int j = 20;
int k = 10;
//==
System.out.println(i==j);
System.out.println(i==k);
System.out.println("----------");
//!=
System.out.println(i!=j);
System.out.println(i!=k);
System.out.println("----------");
//>
System.out.println(i>j);
System.out.println(i>k);
System.out.println("----------");
//>=
System.out.println(i>=j);
System.out.println(i>=k);
System.out.println("----------");
//注意:不小心把==写成=
//把j的值赋值给了i,然后输出i的值
System.out.println(i=j);
注意事项:
关系运算符的结果都是boolean类型,要么是true,要么是false.
千万不要把"==“写成”=";会变成赋值操作
5.逻辑运算符
5.1逻辑运算符说明
如:数学中:3<x<6;在java中表示为x<3&&x<6;
逻辑运算符是用来连接关系表达式的运算符,也可以直接连接布尔类型的常量或变量;
5.2逻辑运算符
符号 | 作用 | 说明 |
---|---|---|
& | 与 | a&b,a和b都是true,结果为true,否则为false |
| | 或 | a|b,a和b都是false,结果为false,否则为true |
^ | 异或 | a^b, a和b结果不同为true,相同为false |
! | 非 | !a,结果和a的结果正好相反 |
//定义变量
int i=10;
int j =20;
int k =30;
// & 有false则false;
// | 有true则true;
// ^ 相同为false,不同为true
// ! 结果相反
System.out.println((i>j)&(i>k));//f&f
System.out.println((i<j)&(i>k));//t&f
System.out.println((i>j)&(i<k));//f&t
System.out.println((i<j)&(i<k));//t&t
5.3短路逻辑运算符
符号 | 作用 | 说明 |
---|---|---|
&& | 短路与 | 作用与&相同,但是有短路效果 |
|| | 短路或 | 作用与|相同,但是有短路效果 |
//定义变量
int i=10;
int j =20;
int k =30;
//&&
System.out.println((i>j)&&(i>k));//f&&f
System.out.println((i<j)&&(i>k));//t&&f
System.out.println((i>j)&&(i<k));//f&&t
System.out.println((i<j)&&(i<k));//t&&t
System.out.println("-------");
//||
System.out.println((i>j)||(i>k));//f||f
System.out.println((i<j)||(i>k));//t||f
System.out.println((i>j)||(i<k));//f||t
System.out.println((i<j)||(i<k));//t||t
System.out.println("-------");
//&和&&
// System.out.println((i++ > 100)&(j++ >100));//false&false
System.out.println((i++ > 100)&&(j++ > 100));//false&&false
System.out.println("i:"+i);
System.out.println("j:"+j);
注意事项:
逻辑与**&,无论左边真假,右边都要执行,短路与&&,如果左边为真,右边执行,如果左边为假,右边不执行**;
逻辑或**|,无论左边真假,右边都要执行,短路或||,如果左边为假,右边执行,如果左边为真,右边不执行**;
6.三元运算符
6.1三元运算符
- 格式:关系表达式?表达式1:表达式2;
- 范例:a>b?a:b;
计算规则:
- 首先计算关系表达式的值,
- 如果值为true,表达式1的值就是运算结果
- 如果值为false,表达式2的值就是运算结果
//定义两个变量
int a=10;
int b=20;
//获取两个数据中的较大值
int max= a>b?a:b;
//输出结果
System.out.println("max:"+max);
案例:
两只老虎
有两只老虎体重分别为180kg,200kg;请用程序实现比较这两只老虎体重是否一样重.
//定义两个变量用于保存老虎的体重,单位为Kg,这里仅仅体现数值即可
int weight1=180;
int weight2=200;
//用三元运算符实现老虎体重的判断,体重相同返回true,否则返回false
boolean b =weight1==weight2?true:false;
//输出结果
System.out.println(b);
三个和尚
寺庙有三个和尚身高分别为150cm,210cm,165cm;用程序实现获取三个和尚的最高身高.
//1.定义三个变量用于保存三个和尚的身高,单位为cm,这里仅仅体现数值即可
int height1=150;
int height2=210;
int height3=165;
//2.用三元运算符获取前两个和尚的较高身高值,并用临时身高变量保存下来
int tempHeight =height1>height2?height1:height2;
//3.三元运算符获取临时身高和第三个和尚身高较高值,并用最大身高变量保存
int maxHeight =tempHeight>height3 ?tempHeight:height3;
//4.输出结果
System.out.println(maxHeight);
5.数据输入
1.数据输入
1.1数据输入概述
1.2Scanner使用的基本步骤
1.导包
import java.util.Scanner;
//导包的动作必须出现在类定义的上边
2.创建对象
Scanner sc = new Scanner(System.in);
//上比那这个格式里面,只有sc是变量名,可以变,其他的都不允许变.
3.接收数据
int i = sc.nextInt();
//上面这个格式里面,只有i是变量名,可以变,其他的都不允许变.
案例
三个和尚
寺庙三个和尚他们的身高必须经过测量得出并输入,请用程序实现获取最高身高
import java.util.Scanner;//导包
public class helloworld{
public static void main(String[] args){
//身高未知,采用键盘输入实现,首先导包,然后创建对象
Scanner sc = new Scanner(System.in);//创建对象
//键盘录入三个身高分别赋值给三个变量;
System.out.println("请输入第一个和尚的身高");
int height1=sc.nextInt();
System.out.println("请输入第二个和尚的身高");
int height2=sc.nextInt();
System.out.println("请输入第三个和尚的身高");
int height3=sc.nextInt();
//接收数据
//2.用三元运算符获取前两个和尚的较高身高值,并用临时身高变量保存下来
int tempHeight =height1>height2?height1:height2;
//3.三元运算符获取临时身高和第三个和尚身高较高值,并用最大身高变量保存
int maxHeight =tempHeight>height3 ?tempHeight:height3;
//4.输出结果
System.out.println("三个和尚最高的身高为:"+maxHeight);
}
}
6.分支语句
1.流程控制
1.1流程控制语句概述
1.2流程控制语句分类
- 顺序结构
- 分支结构(if,switch)
- 循环结构(for,while,do…while)
1.3顺序结构
程序中最简单最基本的流程控制,没有特定语法结构,按照代码先后顺序,依此执行.
2.if语句
2.1 if语句格式
if(关系表达式){
语句体;
}
执行流程:首先计算关系表达式的值,如果关系表达式的值为true就执行语句体,如果关系表达式的值为false就不执行预语句体,继续执行后面的语句内容
int a=1;
//int b=2;
int b=1;
if(a==b){
System.out.println("a=b");
}
System.out.println("结束:");
2.2 if语句格式2
if(关系表达式){
语句体1;
}else{
语句体2;
}
执行流程:首先计算关系表达式的值,如果关系表达式的值为true就执行语句体1,如果关系表达式的值为false就执行预语句体2,继续执行后面的语句内容
int a=1;
//int a=3;
int b=2;
if(a>b){
System.out.println("a大于b");
}else{
System.out.println("a不大于b");
}
System.out.println("结束:");
案例:奇偶数
import java.util.Scanner;
public class helloworld{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
System.out.println("请输入一个整数:");
int a=sc.nextInt();
if(a%2==0){
System.out.println(a+"是偶数");
}else{
System.out.println(a+"是奇数");
}
}
}
2.3 if语句格式3
格式:
if(关系表达式1){
语句体1;
}else if(关系表达式2){
语句体2;
}
else if(关系表达式3){
语句体3;
}
...
else{
语句体n+1;
}
执行流程:首先计算关系表达式的值,如果值为true就执行语句体1,如果的值为false就计算关系表达式2的值,如果值为true就执行语句体2,如果的值为false就计算关系表达式3的值…
import java.util.Scanner;
public class helloworld{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
System.out.println("请输入一个星期数:");
int week=sc.nextInt();
if(week==1){
System.out.println("今天是星期"+week);
}else if(week==2){
System.out.println("今天是星期"+week);
}
else if(week==3){
System.out.println("今天是星期"+week);
}
else if(week==4){
System.out.println("今天是星期"+week);
}
else if(week==5){
System.out.println("今天是星期"+week);
}
else if(week==6){
System.out.println("今天是星期"+week);
System.out.println("祝您周末愉快");
}
else if(week==7){
System.out.println("今天是星期"+week);
System.out.println("祝您周末愉快");
}else {
System.out.println("输入错误");
}
}
}
案例:考试奖励
期末考试用程序实现根据不同的考试缠成绩,送不同的礼物,并在控制台输出
import java.util.Scanner;
public class helloworld{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
System.out.println("请输入期末考试分数:");
int score=sc.nextInt();
if(score>=95&&score<=100){
System.out.println("山地自行车一辆");
}else if(score>=85&&score<95){
System.out.println("游乐场玩一次");
}
else if(score>=75&&score<85){
System.out.println("变形金刚玩具");
}
else if(score>=60&&score<75){
System.out.println("文具盒一个");
}
else if(score>=0&&score<60){
System.out.println("五年高考三年模拟一份");
}
else{
System.out.println("你输入的成绩有误");
}
}
}
3…switch语句
3.1 switch语法格式
格式:switch(表达式)
case值1:
语句体1;
break;
case值2:
语句体2;
break;
...
default:
语句体n+1;
[break;]
}
格式说明:
- 表达式:取值为byte,short,int,char,JDK5以后可以是枚举,JDK7以后可以是string
- case:后面要跟的是要和表达式进行比较的值.
- break:表示中断,结束的意思,用来结束switch语句.
- default:表示所有的情况都不匹配的时候,执行该处的内容,和if语句的else相似.
import java.util.Scanner;
public class helloworld{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
System.out.println("请输入星期数:");
int week=sc.nextInt();
switch(week){
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("星期日");
break;
default:
System.out.println("你输入的星期数有误");
break;
}
}
}
案例:春夏秋冬
根据输入的月分判断属于哪个季节
import java.util.Scanner;
public class helloworld{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个月份:");
int month = sc.nextInt();
switch(month){
case 1:
case 2:
case 3:
System.out.println("春季");
break;
case 4:
case 5:
case 6:
System.out.println("夏季");
break;
case 7:
case 8:
case 9:
System.out.println("秋季");
break;
case 10:
case 11:
case 12:
System.out.println("冬季");
break;
default:
System.out.println("您输入的月份有误");
}
}
}
7.循环语句
7.1for循环语句
//初始化语句,给循环变量赋初值;
//判断条件语句,返回true,执行循环体,否则跳出循环体;
//控制语句,修改循环变量。执行一次循环体后执行。
for(初始化语句;判断条件语句;控制语句){
循环体语句块;
}
7.2while循环语句
珠穆朗玛峰:
public class helloworld{
public static void main(String[] args){
//世界最高峰珠穆朗玛峰高度为8848.43米=8848430毫米,有一张足够大的纸,厚度为u0.1毫米,折叠多少次可以折成珠穆柯朗玛峰的高度
//定义一个计数器
int count=0;
//定义纸张厚度
double paper = 0.1;
//定义珠穆柯朗玛峰的高度
int zf = 8848430;
//while循环
while(paper<=zf)
{
//纸张厚度加倍
paper*=2;
//纸张次数累加
count++;
}
System.out.println(count);
}
}
7.3do…while循环语句
先执行循环体,再进行条件判断
do{
//第一次无条件执行,后续每次判断语句返回true,执行一次
循环体语句块;
//修改循环变量。
控制语句;
//返回true,执行循环体,否则跳出循环体;
} while(判断条件语句) ;
7.3.1循环语句格式
基本格式:
do{
循环体语句;
}while(条件判断语句);
完整格式
初始化语句;
do{
循环体语句;
条件控制语句;
}while(条件判断语句);
执行流程
- 执行初始化语句
- 执行循环体语句
- 执行条件控制语句
- 执行条件判断语句判断为false循环结束
- 判断为true继续执行;回到2继续
int i = 1 ;
do{
System.out.println("HelloWorld");
j++;
}while(j<=5);
7.3.2三种循环的区别
//执行顺序不一样
for(int i = 3;i<3;i++){
System.out.println("HelloWorld");
}
System.out.println("i");
//
int j = 3;
while(j<3){
System.out.println("HelloWorld");
j++;
}
System.out.println("j");
//
int k = 3 ;
do{
System.out.println("HelloWorld");
k++;
}while(k<3);
/*死循环
for(;;){
System.out.println("for");
}
while(true){
System.out.println("while");
}
do{
System.out.println("do....while");
}while(true);
*/
ctrl+c结束死循环.
for循环和while循环先判断条件是否成立,然后再执行循环体(先判断后执行)
do…while循环先执行一次循环体判断条件是否成立,是否执行循环体(先执行后判断)
条件控制语句所控制的自增变量,归属于for循环的语法结构中,就不能再次访问了,但并不归属while循环的语法结构中,在循环结束后变量还可以访问.
7.4控制跳转语句
continue 用在循环中,基于条件控制,跳过某次循环体内容的执行,继续下一次的执行
break 用在循环中,基于条件控制,终止循环体内容的执行,也就是结束当前的整个循环
for(int i = 1;i<=5;i++){
if(i%2 ==0){
//continue;
//break;
}
System.out.println(i);
}
7.5循环嵌套
语句结构:
- 顺序语句:以分号结尾,表示一句话的结束
- 分支语句:一对大括号表示if的整体结构,整体表示一个完整的if语句或者一对大括号表示switch的整体结构,整体表示一个完整的switch语句
- 循环语句:for;while;do…while
for(int hour=0;hour<24;hour++){
for(int minute=0;minute<60;mintune++){
System.out.println(hour+"时"+minute+"分");
}
}
//内循环控制分钟的范围;外循环控制小时的范围
7.6Random
作用:用于产生一个随机数
使用步骤:
1.导包:import java.util.Random;(导包的动作必须出现在类定义的上面)
2.创建对象:Random r = new Random();(这个格式里边,r是变量名,可以变,其他的不允许变)
3.获取随机数:int number = r.nextInt(10);
//获取数据的范围:(0,10)包括0,不包括10
import java.util.Random;
public class helloworld{
public static void main(String[] args){
//创建对象:
Random r = new Random();
//获取随机数
int number = r.nextInt(10);
System.out.println(number);
//需求:获取一个1-100之间的随机数
int x = r.nextInt(100)+1;
System.out.println(x);
}
}
案例:猜数字
import java.util.Random;
import java.util.Scanner;
public class helloworld{
public static void main(String[] args){
Random r = new Random();
//获取随机数1-100
int number = r.nextInt(100)+1;
while(true){
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要猜的数字:");
int quessNumber = sc.nextInt();
//比较输入的数字和系统产生的数据,使用分支语句
if(quessNumber>number){
System.out.println("你猜的大了");
}else if(quessNumber<number){
System.out.println("您猜小了");
}else{
System.out.println("您猜对了");
break;
}
}
}
}
8.数组
8.1数组定义格式
- 一次性声明大量的用于存储数据的变量
- 要存储的数据通常是同数据类型
- 数组是一种用于存储多个相同类型数据的存储模型
- 格式1:数据类型[]变量名 int[] arr 定义了一个int类型的数组,数组名是arr
- 格式2:数据类型 变量名[] int arr[] 定义了一个int类型的变量,变量名是arr数组
8.2数组初始化之动态初始化
动态初始化:只指定数组长度,由系统为数组分配初始值
格式:数据类型[]变量名 = new 数据类型[数组长度];
范例:int [] arr = new int[3];
package com.heima_01;
public class ArrayDemo {
public static void main(String[] args){
int[] arr= new int[3];
/*
左边:int确定数据类型
[]代表这是一个数组
arr数组的名称
右边:
new:为数组申请内存空间
int: 说明数组中的元素类型是int型
[]:说明这是一个数组
3:数组长度,其实就是数组中元素个数
*/
}
}
8.3一维数组
1.1数组简介:
保存一组相同数据类型的数的集合
特性:
- 数组是引用数据类型
- 长度一经确定不能改变
- 数组里面存储的是数组元素(Element)(数组元素的类型可以是任意类型)
- 数组是一块连续的内存空间
- 数组的索引由0开始
1.2数组声明
int[] scores;//数组是引用类型 引用类型资至少占两块内存
int scores2[];//等价(但不推荐使用)
double[] arr1;
float[] arr2;
String[] arr3;
1.3数组初始化
1.数组初始化,长度为6的数组 0是int默认值;double 0.0;char “ ” boolean false
scores=new int[6];
// 2. 长度就是4
int[] arr = new int[]{6,7,8,9};
// 3.
int []arr1 = {12,3,4,5};
l 获得数组元素内容
System.out.println(Arrays.toString(scores));
l 对数组元素重新赋值
int[] arr ={1,2,3,4,5};
int[] arr1=arr;//数组的赋值,引用的赋值
int[] arr ={1,2,3,4,5};
int[] arr1=arr;
// 值赋值
int[] arr2 = new int[arr.length];
for (int i=0;i<arr.length;i++){
arr2[i]=arr[i];
}
l 遍历数组元素内容
for (int i=0;i<arr2.length;i++){
System.out.println(arr2[i]);
}
//增强for循环遍历
for(float a : arr2){
System.out.println(a);
l 动态录入数据存储数组
int num = sc.nextInt();
//初始化了一个长度为学生人数的数组
int[]scores = new int[num];
//给数组元素赋值
for (int i=0;i<num;i++){
System.out.println("录入第"+(i+1)+"个人学生成绩");
scores[i]=sc.nextInt();
}
l 获得数组元素最值
// 求最大值最小值
int[] sort = {66,9,12,54,3,5,7,99};
int max =0;
int min = 0;
for (int i = 1; i <sort.length; i++) {
if( sort[max]<sort[i]) {
max = i;
}
//最值
/* int max =sort[0];
for(int a:sort){
if(max<a){
max=a
}
max = max >a?max:a;
}*/
if( sort[min]>sort[i]) {
min = i;
}
}
System.out.println(sort[max]);
System.out.println(sort[min]);
}
l 复制数组的元素到一个新数组中
//数组扩容
int[] arr2 = Arrays.copyOf(arr1, arr1.length*2);
System.out.println(Arrays.toString(arr2));
//值的复制
int[] arr3 = Arrays.copyOf(arr1,arr1.length);
l 比较两个数组数据是否一致
//判断两个数组的数组元素是否一一对应
boolean boo = Arrays.equals(arr1,arr2);
//引用类型使用双等号比较的是地址空间是否相等
System.out.println(arr1==arr2);
l 对数组元素排序
//对数组进行排序 按自然顺序排序
Arrays.sort(arr1);
Arrays.sort(str);
System.out.println(Arrays.toString(arr1));
System.out.println(Arrays.toString(str));
8.4二维数组
数组元素是一维数组
声明:int[][]a;
初始化二维数组:
int(数据类型)[][] 变量名 = new 数据类型[m][n];
其中:m表示这个二维数组有多少个一维数组
n表示每一个一维数组中有多少个元素
例如: int[][] arr = new int[3][2];
定义了一个二维数组arr,有3个一维数组元素,每个一维数组里面有2个元素
int(数据类型)[][] 变量名= new 数据类型[m][];
这次没有直接给出一维数组的元素个数,可以动态的给出
例如:int[][] arr = new int[3][];
arr[0]= new int[2];arr[1]= new int[3];arr[2]= new int[1];
数据类型[][]变量名 = {{元素},{元素},{元素}};
例:int[][] arr = {{1,2,3},{1,2},{4,5,6}};
8.5Arrays
l 操作数组元素的工具类。在java.util.Array 使用此类,需要导入此类所在的包。
Arrays.toString(数组名); 将数组内容转换成字符串进行输出
- Arrays.copyOf(源数组,新数组长度); 复制数组元素
- Arrays.equals(数组1,数组2);
Arrays.sort(数组)---->字面量类型数组 默认升序
9.方法
- 为了完成某个功能的一段代码的实现
- 实现代码重复利用,减少冗余
- 把某个功能代码封装到方法里
int a = 1;
int b =2;
//调用方法的时候
max(a,b);
System.out.println(max(a,b));
}
/**
* 求最值
* 方法的定义签名
* public是访问权限修饰符 表示哪些地方可以访问这个方法
* static 表示静态的他不是必须出现的
* void/数据类型 表示的是方法的返回值类型,如果此方法不需要有返回值使用void
* 方法名 遵守驼峰标志 首字母小写 第二个单词首字母大写 见名知意
* 方法名后边紧跟的小括号(数据类型 参数1,数据类型 参数2...)
* 参数是定义好需要传参的类型
* return表示返回,只有方法签名处不是void 就一定要加return 返回和方法声明处保持一致的数据类型
* 形参 实参
*有参有返回值/有参无返回值
**/
public static int max(int a ,int b) {
return (a>b?a:b);
}
方法的调用:
只要见到小括号就是方法的调用
System.out.println(max(a, b));
MethodDemo.max(10, 20);
int[] ar = {1, 2, 4, 5};
// 参数不对 编译报错
String str = Arrays.toString(ar);
方法的重载:
同一个类中方法名相同,但是参数列表不同:
- 参数个数不同
- 参数类型不同
- 参数类型顺序不同
public static void task(int a ,double b){
}
public static void task(double a ,int b){
}
重载和重写的区别:
10.排序算法:十大经典排序算法(面试必看)
冒泡排序:
public static void bubbleSort(int[] sort) {
int[] arr ={5,4,3,2,1};
int temp;
for (int i = 1 ;i<arr.length;i++){
for (int j = 0;j<arr.length - i;j++){
if (arr[j]>arr[j+1]){
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]= temp;
}
}
System.out.println(Arrays.toString(arr));
}
选择排序
/**
* 选择排序
* @param sort
*/
public static void selectSort(int[] sort) {
int minIndex, temp;
for (int i = 0; i < sort.length - 1; i++) {
minIndex = i;
for (int j = i + 1; j < sort.length; j++) {
if (sort[minIndex] > sort[j]) {
minIndex = j;
}
}
// 交换位置
if (i != minIndex) {
temp = sort[i];
sort[i] = sort[minIndex];
sort[minIndex] = temp;
}
System.out.println(Arrays.toString(sort));
}
}
11.类和对象
类:类是对现实生活中一类具有共同属性和行为的事物的抽象
特点:
类是对象的数据类型
类是相同属性和行为的一组对象的集合
属性:对象具有的各种特征,每个对象的属性都具有特定的值
对象的行为:
对象能够执行的操作
类和对象的关系:
类是现实生活中一类具有共同属性和行为的事物的抽象
对象是能够看得到摸得着真实存在的实体
11.1类的定义:
- :是java程序的基本组成单位;
- :由属性和行为组成
属性在类中由成员变量来体现(类中方法外的变量)
行为在类中通过成员方法来体现(相比之前方法去掉static关键字即可)
public class 类名{
//成员变量(具有默认值)
数据类型 变量;
...
//成员方法
方法;
...
}
11.2 对象
创建对象
//类名 对象名 = new 类名();
Student a = new Student();
使用对象
//对象名.变量名
a.name
使用成员方法
//对象名.方法名();
a.call();
11.3成员变量和局部变量
成员变量:
- 类中的变量
- 堆内存
- 随着对象的存在而存在,随着对象的消失而消失
- 有默认值
局部变量:
-
方法中的变量,或方法声明上
-
在栈内存
-
没有默认初始值,必须先定义,赋值,才能使用
11.4封装
private
-
是一个权限值
-
可以修饰成员(成员变量和成员方法)
-
作用是保护成员不被别的类使用,被private修饰的成员只在本类中才能访问
-
一个标准类的编写:
把成员变量用private修饰
提供相应的get/set方法
针对private修饰的成员变量如果被其他类使用,提供相应的操作:
get变量名();
//用于获取成员变量变量值,方法用public修饰
set变量名(参数)
//用于设置成员变量的值,方法用public修饰
private int age;
//设置获取成员变量的值的方法getAge()
public int getAge(){
return age;
}
//设设置成员变量的值的方法setAge()
public void setAge(int a){
age = a;
//设置更改成员变量限制
if(a<0||a>100){
System.out.println("你输入的年龄有误");
}
}
----------------------------------------------------------------------
//在类中调用方法
//创建对象
Student s = new Student();
s.setAge(20);
s.getAge();
this
-
修饰的变量用于指代成员变量
-
方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,不是成员变量
-
方法的形参没有成员变量同名,不带this修饰的是成员变量
-
解决局部变量隐藏成员变量
-
代表所在类的对象引用方法被哪个对象调用,this就代表哪个对象
-
本质上是局部变量
-
在构造方法中调用本类的其他构造方法,this要在首行,且只能调用一次
static
static修饰了成员变量和方法,则为静态成员变量或静态方法
static 修饰的成员变量表示该类的对象共享此变量,仅一份
static修饰的方法,静态方法只能访问静态成员(成员方法和变量)
static方法中不能出现this关键字
凡是static修饰的都表示只有一份,成员是属于类的,而不属于某个对象了
static修饰的成员可以直接类名.成员名直接访问成员变量(看权限修饰符是否允许)
static还可以修饰代码块 静态代码块
整个类加载过程和运行中只执行一次
优先于构造方法的,只会执行一次
package/import
包机制是为了解决类名冲突的问题,同一个包下类名不能冲突,不同包下可以;
给包起名字 公司域名倒过来写
package必须是整个源文件的首行
import表示引入的导入包
同个包下的类不用导入
不同包下面类必须导入
java.lang*所有类JVM都会自动导入,不需要程序员显示导入
封装
是面向对象三大特征之一
封装:解觉代码安全问题,
继承;解决代码冗余问题,
多态:解决程序扩展问题
是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的
将类的某些属性信息隐藏,不被外部程序直接访问,而是通过该类提供的方法(get/set)访问成员变量private
通过方法控制成员变量的操作提高了代码的安全性
把代码用方法封装,提高了代码的复用性
11.5 构造方法
:用于创建对象;完成对象的初始化
public class 类名{
修饰符 类名(参数){
}
}
11.6标准类制作
成员变量:
- 使用private修饰
构造方法
- 提供一份无参构造方法
- 提供一个带多个参数的构造方法
成员方法
- 提供每个成员变量对应的set()/get()方法
- 提供一个显示对象的show()
创建对象并为其成员变量赋值的两种方式
- 无参构造方法创建对象后使用set()赋值
- 使用带参构造方法直接创建带有属性值的对象
12.继承
- 类与类之间当有一定关系(例:狗属于动物)时可以用继承extends(Dog extends Animal)
- 子类(派生类)继承父类(基类)后集成父类的所有成员(成员变量和方法),方法公有(权限修饰符)
- java一个子类只能有一个父类,不能继承多个类
- 子类构造方法执行之前会先执行父类构造但没有创建父类对象
- 虚拟机会自动先调用父类无参构造再执行子类构造;但父类如果没有无参构造,子类中必须显示调用父类构造
- super()在子类构造方法的首行;
- 自定义的类会默认继承Object类,Object类是整个继承体系的根基类
员工类
public class Employee {
private int id;
private String name;
private char gender;
private double salary;
public Employee(){
System.out.println("父类构造执行了");
}
public Employee(int id, String name, char gender, double salary) {
this.id = id;
this.name = name;
this.gender = gender;
this.salary = salary;
System.out.println("Father: " + this);
}
//.....getter setter方法
}
程序员类继承了员工类
public class SoftwareEngineer extends Employee {
private int hot;
public SoftwareEngineer() {
System.out.println("子类构造了");
}
public SoftwareEngineer(int id, String name, char gender, double salary, int hot) {
super(id, name, gender, salary);
this.hot = hot;
System.out.println("son: " + this);
}
public void show() {
System.out.println(" 编号是" + getId() + " 姓名是 " + getName() + " 工资是 " + getSalary());
}
public int getHot() {
return hot;
}
public void setHot(int hot) {
this.hot = hot;
}
}
super关键字
- this指代当前对象,super指代父类
- 在子类中显示调用父类构造方法的时候出现在首行
- 子类成员和父类成员重名时(子类再次声明了同样的成员),使用super指代子类成员
public class Dog extends Animal {
private String color;
private int age;
public void run() {
System.out.println("年龄:" + age);
//使用super指代父类的方法
System.out.println("继承下的年龄:" + super.getAge());
}
public Dog() {
}
public Dog(String name, int age, String color) {
//使用super调用父类构造
super(name, age);
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
方法的重写
- 子类对于继承下来的方法实现不满意的时候可以重写
- overwrite重写(注释)
- 重写注意点:子类的方法名,参数列表,返回值类型和父亲一模一样
- 父类的方法权限修饰符比子类更加严格
- 方法重载在指本类中 方法名相同参数列表不同;而重写发生在继承关系前提下
权限修饰符
- 修饰成员变量和方法的修饰符 public protected 缺省 private
- 修饰class 使用public 缺省
那些地方可以访问
修饰符 | 类内部 | 同一个包 | 子类 |
---|---|---|---|
private | Y | ||
缺省的(不写) | Y | Y | |
protected | Y | Y | Y |
public | Y | Y | Y |
13.多态
解决的是代码扩展问题(符合开闭原则)
开闭原则-对修改关闭,对新增开放
- 实现多态 继承 重写 父类引用指向子类对象
- 在运行过程中,根据实际的对象去调用该对象的方法
- 父类引用指向子类对象是向上转型,反之成为向下转型
- 父类引用可以指向子类对象,不能调用子类新增方法
马戏团案例
父类Animal
public class Animal {
private String name;
private int age;
public void play(){
System.out.println("表演");
}
}
子类 重写父类方法
public class Elephant extends Animal {
@Override
public void play() {
System.out.println("大象表演喷水");
}
}
马戏团类
public class Circus {
public void show(Animal animal) {
animal.play();
}
}
测试类
public static void main(String[] args) {
Circus circus = new Circus();
// 向上转型 父类引用指向子类对象
Animal animal = new Monkey();
// 运行过程中去执行此对象的方法 .class
animal.play();
//编译器在报错 .class
// animal.jump();
//向下转型 加强制转换符
Monkey monkey = (Monkey) animal;
/* Elephant elephant = new Elephant();
Tiger tiger = new Tiger();
circus.show(monkey);
circus.show(elephant);
circus.show(tiger);*/
}
抽象类&抽象方法
- 有些类是不需要实例化的,这些类就可以定义为抽象类,抽象类不可以new新对象
- 有些类定义出来就是用来被继承的,这些类可以声明为抽象类
- 有些方法定义出来就是被重写的,这些方法就可以生声明为抽象办法
- 使用abstract关键字声明一个抽象类,使用abstract去声明一个方法为抽象办法;
- 抽象方法只能被抽象类被引用
- 当含有抽象方法的抽象类被继承时,抽象方法必须被重写
/**
* 抽象类 是不能有实例(对象)的
*/
public abstract class Animal {
private String name;
private int age;
/**
* 抽象方法 不需要有方法实现体
*/
public abstract void play();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
13.接口
面型对象:封装 继承 多态
1.1final关键字
- final可以修饰类,,成员变量, 方法,方法的形参
- 凡是被final修饰的类就不能继承 常见的String Math 基本数据类型的包装类都是fianal类
- 凡是final修饰的成员变量一旦被赋初始值后就不可更改
- final修饰的方法不可被重写
- 形参前加final,不可被修改
1.2接口
- 弥补java单继承,子类只能继承一个父类
- 接口是一个非常特殊的抽象类,使用interface关键字定义
接口的特性
- 成员变量默认全由public static final修饰(定义时不需要写)
- 接口中的方法都是public static abstract修饰,都是抽象方法
- 类与接口由implements实现 是实现的关系,一个类可以实现多个接口
- 接口与接口之间是继承关系 ,且可以多继承(主要集成方法)
- 接口同样可以体现出来多态没,也是父类引用指向子类对象
public interface Inter2 extends Inter1,Inter3{
void method();
}
jdk8版本之后的可以在接口中允许有普通方法
普通方法用default修饰
default void method2(){
system.out.println("普通方法");
}
接口的定义
public interface Player {
int a = 0;
/**
* 表演的方法
*/
void play();
}
实现类的定义
/**
* @author Anne
* @date 2021/3/18 10:52
* 表演的功能
* 类具有某个接口角色的时候
* 能用实现的地方就不要用继承(尽量使用接口)
*/
public class Monkey extends Animal implements Player, Jumper {
@Override
public void play() {
System.out.println("猴子表演骑单车");
}
@Override
public void eat() {
}
@Override
public void sleep() {
}
@Override
public void jump() {
}
}
多态的体现
public class Circus {
public void show(Player player) {
player.play();
}
}
- 接口同样可以体现出来多态,也是父类引用指向子类对象
public interface Inter2 extends Inter1, Inter3 {
void method2();
}
- 能用实现的地方就不要用继承,(尽量使用接口)
- 继承:只要两个类通过IS-A测试就可以使用继承
- 抽象类:是一组类的模板,切不需要实例的时候
- 接口:担任的是一种角色(此角色应该具有功能(方法))
13.3instanceof关键字
判断某个对象是否是某个类型的对象
QQ qq = new QQ();
Plugin plguin = QQ.creat(1);//new Music
//向上转型
if(pligin instanceof Music){
Music plguin = (Music) plugin;
//向下转型
}
14.异常
java强壮性:异常处理机制
14.1面试题
常见的一场有哪些(5个)
- 空指针异常nulllpointerxception,
- 算数异常ArithmeticException,
- 数组越界异常,
- 类型转换异常NumberFormatException
- IO
影响程序正常运行的事件分为 错误(Error)和异常(Exception)
错误:堆内存溢出(OutOfMeoryError)栈内存溢出(stackoverflow)方法区内存溢出 堆内存泄漏
当出现错误时妖媚改源码要么改机器配置
14.2异常体系
-
Throwable类是Error类和Exception类的父类
-
Exception是所有异常类的父类;把程序中的异常通常分为两大类:
运行时异常 (RuntimeExcepion)/非运行时异常(编译异常)
-
运行时异常通常指的是运行期间发生的异常,都是RuntimeException的子类;编译肯定通过
-
非运行时异常,一定要处理 编译才能通过
14.3异常的处理
- 捕捉异常,抓到后处理;程序就能往下继续执行
- try…catch…finally
14.3.1try语句块
- try块中放的是有可能发生异常的代码
- 一旦有异常出现之后就会跳出try语句块
14.3.2catch语句块
- catch代码块是当try中有异常出现时才有可能执行的
- catch是捕捉,用来抓异常对象的,catch里小括号里面的类类型一定要和异常对象类型匹配(或者父子关系)才能抓到
- catch代码块是抓到异常之后进行处理,通常是记录到log(日志)中方便后续追踪;
- catch语句块可以有很多个;小类型写在上边,大类型写在下面
public static void div(int a ,String b){
int c=0;
try{
c= a /Integer.parseInt(b);
System.out.println("18line +++++++++");
}catch(ArithmeticException e){
System.out,println(e);
System.out.println(e.getMessage());
//处理
System.out.println("捕捉到异常了");
}catch (NumberFormatException e) {
System.out.println("捕捉到的是数值转换异常");
} catch (Exception e) {
//其他异常
System.out.println("");
}
System.out.println(c);
}
}
14.3.3finally
- 不是必须的
- 里面的代码是一定会执行的代码块
- 异常出现或者不出现都会执行
- try或者catch中出现return也会执行
- 文件资源(资源的关闭)时都会在finally语句块中执行
14.4抛异常
- throws throw 关键字
- throw用在放法体中后面跟的是异常对象,表示要抛出的异常对象
public static void addBike(ShareBike shareBike) throws Exception {
if (shareBike == null) {
// return;
//抛 异常对象
throw new RuntimeException("共享单车传参是null");
}
System.out.println("id " + shareBike.getId());
System.out.println("name " + shareBike.getName());
System.out.println("======");
}
14.5自定义异常
自定义一个异常类型,只需要写两个构造方法即可
public class CustomException extends RuntimeException {
public CustomException() {
}
public CustomException(String message) {
super(message);
}
}
15.常用类
1.Object
- 是所有类的根基类
- 位于java.long包下面的类 不需要import语句
- 工具类 ,为了优雅的解决使用Object类方法时会出现的空指针问题
- 内部的方法都是静态方法
public static void main(String[] args) {
String str = null;
//1,比较对象是否相等
System.out.println(Objects.equals("", null));
//3, 获取hashcode值
System.out.println(Objects.hashCode(str));
int[] ints1 = {1, 2, 3, 45};
int[] ints2 = {1, 2, 3, 45};
System.out.println("数组相等:" + Objects.equals(ints1, ints2));
// 4,deepEquals()常用来比较数组元素是否一一相等
System.out.println(Objects.deepEquals(ints1, ints2));
// 5, isNull 是否为null 如果为null返回结果时true,否则返回false
boolean isNull = Objects.isNull("");
}
面试题-java中四种引用类型
- 从JDK1.2开始,Java中的引用类型分为四种,分别是:
- ①强引用(StrongReference)
- ②软引用(SoftRefernce)
- ③弱引用(WeakReference)
- ④虚引用(PhantomReference)
Reference,引用表示存放的是另外一块内存的起始地址,引用指向某个对象
不同的引用类型对对象生命周期有影响,GC回收不一致
1,强引用(有用并且必需的)
当内存空间不足时,JVM宁可报错(OOM OutOfMemoryError内存溢出)也不会回收此引用引用着的对象空间(当方法没有执行完的时候)
2,软引用 (SoftRefernce 有用非必需)
当内存空间不足时,GC开始回收此类引用引用着的对象空间;如果空间充足不回收
public static void main(String[] args) {
int _1M = 1024 * 1024;
SoftReference<byte[]> by1 = new SoftReference<>(new byte[_1M * 3]);
System.out.println(by1.get().length);
SoftReference<byte[]> by2 = new SoftReference<>(new byte[_1M * 4]);
System.out.println(by1.get());
}
3,弱引用(WeakReference)(有用非必需)
弱引用引用着的对象活不到下一次GC,
public static void main(String[] args) {
int _1M = 1024 * 1024;
WeakReference<byte[]> by1 = new WeakReference<>(new byte[_1M * 3]);
System.out.println(by1.get().length);
WeakReference<byte[]> by2 = new WeakReference<>(new byte[_1M * 2]);
//通知GC开始回收
System.gc();
System.out.println(by1.get());
System.out.println(by2.get());
}
4,虚引用(PhantomReference)
- 虚引用主要用来跟踪对象被垃圾回收器回收的活动
2.方法
1.to string()方法
返回对象的字符串表示类的全路径+"@"+对象hash值
子类中可以重写
public String toString(){
return getClass().getName()+"@"+Integer.toHexString(hashCode());
}
测试类
public static void main(String[] args){
Stu stu= new Stu("tom",12);
System.out.println(stu);//等价与 System.out.println(stu.toString());
//String类对toString放进行了重写
String str = "abc";
System.out.println(str);
}
}
2.equals()方法
- 自反性 x.equals(x)结果为true
- 对称性x.equals(y)结果与y.equals(x)结果一致
- 传递性 x.equals(y)为true 且y.equals(z)也为true那么此时x.equals(z)为true
- 一致性 返回的结果应该始终一致
hashcode 哈希码
- 每个对象都有唯一的hash值 能够唯一代表此对象
- 两个对象equals 那么这两个对象的hash值必须一样,在集合中使用
- 如果要重写equals方法 一定要重写hashcode方法
Stu类重写
public class Stu {
private String name;
private int age;
public Stu(String name, int age) {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/* @Override
public boolean equals(Object obj) {
Stu op;
if (obj instanceof Stu) {
op = (Stu) obj;
return this.name.equals(op.getName()) && this.age == op.getAge();
}
return false;
}
@Override
public int hashCode() {
return this.name.hashCode() + this.getAge();
}*/
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public static void main(String[] args) {
Object stu1 = new Stu("tom", 12);
Stu stu2 = new Stu("tom", 12);
System.out.println(stu1.equals(stu2));
System.out.println(stu1 == stu2);
System.out.println(stu1.hashCode());
System.out.println(stu2.hashCode());
System.out.println(stu1 == stu2);
String str1 = new String("abc");
String str2 = new String("abc");
// String重写了equals方法 比较的是字符内容是否相等
System.out.println(str1.equals(str2));
System.out.println(str1 == str2);
}
}
3.hashCode()方法
- hash值能够唯一的代表此对象
- 两个对象equals 那么这两个对象的hash值必须一样,重写equals一定要重写hashcode方法
- 两个对象不相等hashCode值应该不同
- native表示本地方法 方法实现java实现的,而是C/C++来实现的
- publi native int hashCode();
4.finalize()方法
当GC开始回收这个对象的时候会自动调用
5.clone()方法
克隆对象
public class ObjectDemo implements Cloneable {
private int num;
// 如果是引用类型,并未对成员变量进行克隆 指向的是同一个对象
private Stu stu;
public static void main(String[] args) {
ObjectDemo objectDemo = new ObjectDemo();
Object clone = null;
try {
objectDemo.num = 10;
objectDemo.stu = new Stu();
clone = objectDemo.clone();
objectDemo.stu.setAge(20);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(((ObjectDemo) clone).num);
System.out.println(((ObjectDemo) clone).stu.getAge());
System.out.println(clone);
System.out.println(objectDemo.hashCode());
System.out.println(clone.hashCode());
System.out.println(clone.equals(objectDemo));
}
}
2.String类
- 是一个字符串类,是一个不可变的字符串
1.构造方法
String s1 = new String();
String s2 = new String();
char[] chars = {'9','中','c','e'};
String s3 = new String(chars,2,3);
byte[] bytes = {65,66,67,68,97,98,99,100};
String s4 = new String(bytes);//字符的ASCII码值
System.out.println(s4);
2.常见的字符编码
- ASCII每个字符都占用一个字节 128
- ISO-859-1,欧洲用到的字符是远远多于128个的,对ASCII编码进行扩展 两个字节可以存储655536字符
- GBK /GB2312 针对于中文的字符编码 中文汉字一般占用2个字节
- 乱码 字符的编码和解码用的字符编码不一致
- UTF 定义了一套规范 统一全球的编码规范
- UTF-8 中文汉字一般占用3个字节
- UTF-16
3.比较判断方法
- length()返回字符串字符序列长度
- equals()返回boolean值
- equalsIgnoreCase()返回boolean值
- startsWith()返回boolean值
- endsWith()返回boolean值
4.字符串搜索
- indexOf()
- lastIndexOf()
- 返回字符或者字符串出现的索引值
- 如果找不到 返回值都是-1
String s1 = new String("javasm");
char[] str = {'j','a','v','a'};
String s2 = new String("JAVASM");
int length = s1.length();
// length() 字符串中字符的长度
System.out.println("字符串长度:"+length);
// 1,equals()比较的是字符序列是否一一相等
System.out.println("使用equals进行比较" + flag1);
// 2,equalsIgnoreCase()比较的是字符序列是否一一相等
boolean flag2 = s1.equalsIgnoreCase(s2);
System.out.println("使用equalsIgnoreCase忽略大小写进行比较" + flag2);
// 3,startsWith() 是否以某个字符串开头
boolean flag3 = s1.startsWith("Java", 0);
System.out.println("是否以某个字符串开头" + flag3);
// 4,endsWith()是否已某个字符串结束
boolean flag4 = s2.endsWith("SM");
System.out.println("是否以某个字符串结束" + flag4);
String a= "javaweb";
//5.字符首次出现的索引值
int index = a.indexOf('e');
System.out.println("字符首次出现的索引值:"+index);
//6.字符最后出现索引值
int lastindex = a.lastIndexOf('a');
System.out.println("字符最后出现的索引值:"+lastindex);
//char->int
//字符串首次或者最后出现的索引值
System.out.println(a.indexOf(97));
int strindex = a.indexOf("va");
int strlastindex = a.lastIndexOf("va");
System.out.println("字符串首次出现的索引值"+strindex);
System.out.println("字符串最后出现的索引值"+strlastindex);
5.提取字符串
- char charAt()返回char
- String trim()去除前后空格 中间空格无效
- replace()替换
- replaceAll()替换所有
public static void main(String[] args) {
String s1 = "javasunoracle";//char[] {j,a,v,a}
/* System.out.println(s1.hashCode());
s1 = "sm";
System.out.println(s1.hashCode());*/
// 返回指定的索引对应的字符
char c = s1.charAt(1);// return
System.out.println(c);
String s2 = s1.trim();
System.out.println(s1);
System.out.println("去除前后空格" + s2);
String s3 = s1.replace('a', 'w'); //jwvw
String s4 = s1.replaceAll("av", "wb");
System.out.println(s3);
System.out.println("替换所有" + s4);
System.out.println(s1);// jwvw
// concat 拼接
String s5 = s1.concat("oracle");
System.out.println("拼接 " + s5);
// substring 截取字串
String s6 = s1.substring(4);
// 前闭后开 [)
String s7 = s1.substring(4, 7);
System.out.println(s6);
System.out.println(s7);
// 切分
String[] strs = s1.split("a");
System.out.println(Arrays.toString(strs));
}
// 按指定符号进行拼接字符串
String join = String.join("&", "java", "sun","oa");
System.out.println(join);
6.大小写转换
public static void main(String[] args) {
String s1 = "javaSUNoracle";//char[] {j,a,v,a}
String s2 = s1.toUpperCase();
System.out.println(s2);
String s3 = s1.toLowerCase();
System.out.println(s3);
}
扩展点: **
//可变长参数 传0或者多个都可以 本质上是一个数组来接收
public static String join(CharSequence delimiter, CharSequence... elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
// Number of elements not likely worth Arrays.stream overhead.
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
7.格式转换
public static void main(String[] args) throws UnsupportedEncodingException {
String s1 = "中国abc";
//1,转字节数组,默认使用UTF-8进行编码解码
byte[] bytes = s1.getBytes("GBK");
System.out.println(Arrays.toString(bytes));
String string = new String(bytes,"GBK");
System.out.println(string);
//2,转字符数组
char[] chars = s1.toCharArray();
System.out.println(Arrays.toString(chars));
}
3.字符串常量池
- JVM为了节省内存空间 提高性能 提供了一个字符串常量池
- 在给String引用赋值的时候 会首先查看常量池中是否有此字符序列 如果没有就创建 如果有直接返回内存地址
- 使用+连接字符串的时候 如果都是字符串常量 也是先去常量池中查找;如果+ 旁边有变量 此时生成的字面量会放入一个临时缓冲区中
public static void main(String[] args) {
String s1 = "java";
String s2 = "java";
String s3 = new String("java");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
String s4 = "javasm";
s1 = s1 + "sm";
//s1 = "java" + "sm";
System.out.println(s1 == s4);//true
}
4.StringBuffer/StringBuilder
1.1. StringBuffer类
-
StringBuffer是线程安全的,性能较低
-
StringBuilder线程不安全,性能较高
常用方法
public static void main(String[] args) {
StringBuilder stringBuilder2 = new StringBuilder();
StringBuilder stringBuilder1 = new StringBuilder("ja");
System.out.println(stringBuilder1.hashCode());
//append是追加 在原串后面添加字符串
stringBuilder1.append("va");
stringBuilder1.append("sun");
System.out.println(stringBuilder1);
// 获取长度
System.out.println(stringBuilder1.length());
// 颠倒字符序列 reverse会改动源字符序列
// StringBuilder reverse = stringBuilder1.reverse();
System.out.println("源SB:" + stringBuilder1);
// System.out.println(reverse);
// 索引: [)前开后闭
stringBuilder1.delete(2, 4);
System.out.println("delete :" + stringBuilder1);
//指定索引位 进行插入
stringBuilder1.insert(1, "oracle");
System.out.println("insert:" + stringBuilder1);
}
5.正则表达式 regex
- 常用来 校验用户的输入 是否合法(和提前定义的模式是否匹配)
1,匹配子表达式出现的次数
public static void main(String[] args) {
String str = "javaaaaaaa";
// * 前面的子表达式 出现0或者多次
boolean flag1 = str.matches("java*");
//+ 至少出现一次
boolean flag2 = str.matches("java+");
// ? 0或1
boolean flag3 = str.matches("java?");
// {n} 匹配确定的 n 次。
boolean flag4 = str.matches("java{3}");
// {n,} 至少出现n次
boolean flag5 = str.matches("java{3,}");
//{n,m} 至少出现 n次且小于等于m次
boolean flag = str.matches("java{3,6}");
System.out.println(flag);
}
6.日期类
1.Date
- Date类对象用来表示时间和日期;该类提供一系列操纵日期和时间各组成部分的方法
- Date类最多的用途是获取系统当前的日期和时间。
public static void main(String[] args) {
// 获取当前时间
Date date = new Date();
System.out.println("今天的日期为:" + date);// 今天的日期为:Wed Mar 24 21:19:04 CST 2021
// 获得毫秒数 自1970年1月1日起以毫秒为单位的时间(GMT)
long time = date.getTime();
System.out.println("自1970年1月1日起以毫秒为单位的时间(GMT):" + time);
// 截取字符串中表示时间的部分
String strDate = date.toString();
String strTime = strDate.substring(11, (strDate.length() - 4));//11正好是时间所在索引值,length-4 恰好是年份前的索引值
System.out.println(strTime);// 21:19:04 CST
}
2.Calendar
-
Calendar类也是用来操作日期和时间的类,但它可以以整数形式检索类似于年、月、日之类的信息;
-
Calendar类是抽象类,无法实例化,要得到该类对象只能通过调用getInstance方法来获得;
-
Calendar对象提供为特定语言或日历样式实现日期格式化所需的所有时间字段
-
Jdk1.8之前推荐使用Calendar替换Date。
-
// 创建包含有当前系统时间的Calendar对象 无法实例化只能借助getInstance()方法 Calendar cal = Calendar.getInstance(); // 打印Calendar对象的各个组成部分的值 System.out.print("当前系统时间:"); System.out.print(cal.get(Calendar.YEAR) + "年"); System.out.print((cal.get(Calendar.MONTH) + 1) + "月"); System.out.print(cal.get(Calendar.DATE) + "日 "); System.out.print(cal.get(Calendar.HOUR) + ":");//小时 System.out.print(cal.get(Calendar.MINUTE) + ":");//分钟 System.out.println(cal.get(Calendar.SECOND));//秒 // 将当前时间添加30分钟,然后显示日期和时间 cal.add(Calendar.MINUTE, 30); Date date = cal.getTime(); System.out.println("将当前时间添加30分钟后的时间:" + date); }}
3.localDate/locaDateTime/locaTime
获得当前系统时间 线程安全且不可变
private static void test1() { LocalDate localDate = LocalDate.now(); System.out.println(localDate);// 2019-03-14 System.out.println(localDate.getYear()); System.out.println(localDate.getDayOfWeek());// THURSDAY localDate = localDate.minusMonths(1); System.out.println(localDate); LocalDate date1 = LocalDate.of(2018, 3, 20); System.out.println(date1); LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime);//2019-12-12T14:54:00.375 LocalTime localTime = LocalTime.now(); System.out.println(localTime);// 14:48:12.525 }
4.格式化类DateFormat
- l DateFormat属于抽象类,我们使用子类SimpleDateFormat ,只针对于Date类型数据进行格式化操作。
16.包装类&Math
-
为了让基本数据类型具有对象的性质 丰富了基本数据的操作
-
包装类都是首字母大写 除了Integer Character
-
四类八种基本类型的包装类
1.常用方法
1.Integer常用方法
public static void main(String[] args){
//构造方法
Integer in1 = new Integer(12);
Integer in2 = new Integer("12");
int i = in2 + 12;
System.out.println(i);
//1,parseInt() 把数值字符串转成对应的int值 64+16+4+1 使用2进制转换
Integer i1 = Integer.parseInt("01010101", 2);
System.out.println(i1);
//2,valueOf() 把数值字符串转成对应的Integer值
int i2 = Integer.valueOf("00100",2); // 4
System.out.println(i2);
//valueOf()会做封装,parseInt则直接返回int值, 一般用parseInt 除非非要返回Integer值,要不然会有封装拆箱,性能会有浪费;
// compare比较 返回值是正数表示第一个值大 0表示相等 负数表示第二个值大
int compare = Integer.compare(10, 20);
System.out.println("compare " + compare);
// int的表数范围的最大值
System.out.println(Integer.MAX_VALUE);
// int的表数范围的最小值
System.out.println(Integer.MIN_VALUE);
}
2.包装类型缓冲池
- 整数 byte short int long -128-127
- 字符型 所有字符都在里面
- 不包含任何浮点数
public static void constantPool() {
Integer in1 = new Integer(12);
Integer in2 = new Integer(12);
// -128-127
Integer in3 = 127;
Integer in4 = 127;
Long l1 = (long) 12;
Long l2 = (long) 12;
//在Integer范围内 -128-127 所有都在包装类型缓冲池中
System.out.println(l1 == l2);//true
System.out.println("in3VS in4 " + (in3 == in4));//true
//in1 in2都重新new了新对象,都有属于自己的指向位置 ==比较的地址,所以不相等
System.out.println(in1 == in2);//false
Character c1 = 'a';
Character c2 = 'a';
Float f1 = (float) 1.0;
Float f2 = (float) 1.0;
//字符型,所有字符都在里面
System.out.println("字符是否相等" + (c1 == c2));//true
//包装类型缓冲池中不包括任何浮点数所以为false
System.out.println(f1 == f2);
}
3.不可变的对象
public static void method() {
Integer in1 = 10;
// 一旦改值 就是新的对象
in1 = 20;
System.out.println(in1);
}
4.进制和位移
int b = 24;// 00011000
// 位移运算符 0011 11000
System.out.println(b << 3);//向右移动三位 00011000-->00000111相当于乘以 2的三次方
System.out.println(b >> 3); //向左移动三位 00011000-->11000000相当于除以 2 的三次方
//十进制
System.out.println(12);
// 八进制表现形式 0
System.out.println(052);// 2+5*8 =42
// 十六进制 0X/x 10-A 11-B 15-F F 1111
System.out.println(0XFA);//250 10+15*16=250
// 二进制 0B/b
System.out.println(0B101);//2的0次方+2的2次方 = 5
int a = 0x52;
System.out.println(a);//2+5*16=82
}
5.Character类
public static void wrapCharacter() {
Character ch = 'a';//0-9
boolean flag1 = Character.isDigit(ch);//判断是否数字
boolean flag2 = Character.isUpperCase(ch);//A-z 是否大写
boolean flag3 = Character.isLetter(ch);//是否是字母
//转小写
char c = Character.toLowerCase(ch);
System.out.println(flag1);
System.out.println(flag2);
System.out.println(flag3);*/
}
2.Math
- 内部定义了和数学运算相关的方法 都是静态方法static
public static void main(String[] args) {
// π
double pi = Math.PI;
System.out.println(pi);//3.1415926
// 3的4次方
double pow = Math.pow(3,4);
System.out.println(pow);//81
// 开跟号
double sqrt = Math.sqrt(16);
System.out.println(sqrt);//4.0
// 求绝对值
int abs = Math.abs(-12);
System.out.println(abs);//12
// sin 求正弦 cos 求余弦
Math.random();//随机数
// 向上取最小整
double ceil = Math.ceil(4.5);
System.out.println(ceil);//5.0
// 向下取整
double floor = Math.floor(4.5);
System.out.println(floor);//4.0
// 四舍五入
long round = Math.round(4.6);
System.out.println(round);//5.0
// 最大值和最小值
double max = Math.max(2.3, 2.4);
double min = Math.min(2.3, 2.4);
System.out.println(max);//2.4
System.out.println(min);//2.3
}
3.值传递
Student student = new Student();
// 值传递 引用传递
method2(student);
System.out.println(student.getComputer());
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9arYGdHf-1617883920697)(F:\笔记\值传递引用传递.png)]
17.集合
- 数组是定长的 长度一旦确定不能更改 集合是不限长的
- 数组元素类型是一致的, 集合中不限制类型 可以放任意数据类型(集合中不能有基本数据类型,只能放对象)
- 集合中提供了较多的数据结构,方便编程 提高效率
1.集合体系
1.Collection 接口
- 只放单值
public static void collectionMethods() {
Collection collection = new ArrayList();
Collection collection2 = new ArrayList();
//isEmpty()判断集合是否为空
System.out.println("集合是否无元素" + collection.isEmpty());//true
//int-->Integer add()添加元素 E K T V 泛型
boolean add = collection.add(123);
boolean add3 = collection2.add(123);
boolean add4 = collection2.add(66);
boolean add2 = collection.add("abc");
System.out.println("添加是否成功" + add);//true
//添加另一个集合addAll()
collection.addAll(collection2);
System.out.println(collection);//[123,456,66,abc]
//元素个数,集合的size()方法类似于数组的length()
int num = collection.size();
System.out.println("元素个数:" + num);//4
//contain()是否包含某个对象
boolean contain = collection.contains(123);
System.out.println("是否包含这个元素" + contain);//true
//集合是否包含另一个集合 containAll()
boolean containall = collection.containsAll(collection2);
System.out.println("集合是否包含另一个集合" + containall);//true
//集合转数组
Object[] objects = collection.toArray();
System.out.println("转数组" + Arrays.toString(objects));//[123, abc, 123, 66]
//remove()移除 removeAll 移除一个集合
boolean remove = collection.remove("abc");
System.out.println("移除" + remove);//ture
System.out.println(collection);//[123, 123, 66]
System.out.println(collection2);//[123, 66]
//retainAll()保留与某个集合相等的元素
collection.retainAll(collection2);
System.out.println(collection);//[123, 123, 66]
//clear()清空集合
collection.clear();
System.out.println(collection);//[]
}
常用方法
public static void collectionsMethods() {
/*ArrayList<Student> integers = new ArrayList<>();
integers.add(new Student());
integers.add(new Student());
integers.add(new Student());
integers.add(new Student());
// 存取有顺
System.out.println(integers);
// 1, sort() 对list排序
Collections.sort(integers);
Collections.sort(integers, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return 0;
}
});
System.out.println(integers);*/
ArrayList<String> arrayList = new ArrayList<>();
ArrayList<String> arrayList2 = new ArrayList<>();
arrayList.add("avb");
arrayList.add("oracle");
arrayList.add("Sun");
arrayList.add("java");
arrayList2.add("Sun");
arrayList2.add("java");
String max = Collections.max(arrayList);
String min = Collections.min(arrayList);
System.out.println("max " + max);
System.out.println("min " + min);
// Collections.reverse(arrayList);
System.out.println("颠倒顺序 " + arrayList);
// Collections.fill(arrayList, "sm");
System.out.println("集合内容 " + arrayList);
int i = Collections.indexOfSubList(arrayList, arrayList2);
System.out.println("子集合第一次出现索引位置 " + i);
//对list重新洗牌
Collections.shuffle(arrayList);
System.out.println(arrayList);
}
1.List接口
- list中数据是可以重复的
- list中数据存取有序
public static void listMethod(){
List list = new ArrayList();
list.add("nba");
list.add("abc");
list.add("sdf");
list.add("ghk");
//get()指定索引获取对象 索引值不能超过size大小
Object o = list.get(2);
System.out.println(o);//sdf
System.out.println("原始值:"+list);//[nba, abc, sdf, ghk]
//指定索引位置替换对象
Object set = list.set(0,666);
System.out.println("指定索引位置替换对象后"+list);//[666, abc, sdf, ghk]
//指定索引插入元素,剩下元素依次往后
list.add(1,999);
System.out.println("指定索引插入元素后:"+list);//[666, 999, abc, sdf, ghk]
//指定索引删除元素
list.remove(0);
System.out.println("指定索引删除元素后:"+list);//[999, abc, sdf, ghk]
//indexOf()指定对象首次出现索引值 lastIndexOf()指定对象最后出现的索引值
int sdf = list.indexOf("sdf");
System.out.println("指定对象首次出现索引值:"+sdf);//2
//subList()截取字符串 前闭后开
List list1=list.subList(0,1);
System.out.println("截取子串"+list1);//[999]
System.out.println(list);//[999, abc, sdf, ghk]
}
list接口实现类
数据结构 算法
实现类ArratList
- 底层是数组 空间连续 查询效率高,但在指定索引新增或者删除是性能较低
- 如果调用的是无参构造,数组其实就是空数组 在第一次add的时候开始进行扩容 生成一个默认长度为10的数组
实现类LinkedList
底层是链表 是双向链表
查询性能较低 新增/删除性能高
2.面试题:
ArrayList和LinkedList的区别
- ArrayList底层是数组,内存空间连续 在进行删除和添加的时候会进行数组动态扩容和数组元素的移动;LinkedList底层是双向链表
- ArrayList查询性能高是因为地址空间连续,可以直接根据索引值确定元素位置,在指定位置新增和删除的时候会有大批元素的移动(遍历数组,让数组索引值更改,元素移动)所以性能低
- LinkedList查询是要从链表从头或者从尾进行便利,所以性能低;在新增元素和删除元素的时候只需要改动前后两个元素的指向内容,所以性能高
实现类Vector
- 底层也是数组
- 方法内部加了线程安全
对List进行遍历
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("hello");
arrayList.add("java");
arrayList.add("oracle");
// System.out.println(arrayList.size());
System.out.println("-----1普通for循环遍历-----");
// 0- size-1
for (int i = 0; i < arrayList.size(); i++) {
System.out.println(arrayList.get(i));
}
System.out.println("------2 增强for循环遍历-----");
for (String str : arrayList) {
System.out.println(str);
}
System.out.println("-------3 迭代器进行遍历");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("------4 forEach()-----");
// 匿名内部类 一次性使用
arrayList.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
// System.out.println(s);
}
});
// jdk8以后 lambda表达式 JS箭头函数 ()=>{}
arrayList.forEach((s)->{
System.out.println(s);
});
// arrayList.forEach(new CustomConsumer());
}
3.Set接口
- 数据不可以重复
- 存取无序 没有索引值
Set实现类:
- HashSet底层是HashMap(哈希表 数组 +链表)
- LinkedHashSet底层是LinkedHashMap
- TreeSet底层是TreeMap 底层是红黑树 平衡树(树节点要么黑色,要么红色)元素要实现comparable接口,或者treeset构造方法中提供比较器
public static void main(String[] args) {
//HashSet 底层是hashMap
Set<String> str1 = new HashSet<>();
//不能放重复元素 存取无序
str1.add("abc");
str1.add("www");
str1.add("abc");
System.out.println(str1);
//无get()方法 存取无序,需遍历取出,但无法使用for循环遍历
//增强for循环
for (String s : str1) {
System.out.println(s);
}
//迭代器遍历 iterator
Iterator<String> interator = str1.iterator();
while (interator.hasNext()) {
System.out.println(interator.next());
}
//forEach
str1.forEach((str) -> {
System.out.println(str);
});
//底层是linkedHashMap
Set<String> str2 = new LinkedHashSet<>();
//底层是TreeMap是红黑树是一棵平衡树(每个树节点都有颜色)
Set<String> str3 = new TreeSet<>();
}
2.Map接口
- 放的是成对的值key-value
- key不能重复
Map接口中的方法
public static void main(String[] args) {
//string代表key类型 integer代表value类型
Map<String,Integer> map = new HashMap();
//HashMap LinkeHashMap
map.put("a",1);
map.put("b",2);
map.put("a",2);
map.put("c",6);
//key不能重复,会被覆盖
System.out.println(map);
//长度
System.out.println(map.size());
//是否包含某个key
System.out.println(map.containsKey("a"));
//返回key对应的value值
System.out.println(map.get("b"));
//按指定key移除map元素
map.remove("a");
//按照制定key和value移除
map.remove("b",2);
//key和value的集合
Set<String> strings = map.keySet();
System.out.println("key的集合" + strings);
Collection<Integer> integers = map.values();
System.out.println("value的集合"+integers);
//指定key 替换value
map.replace("c", 6, 5);
System.out.println(map);
}
1.Map实现类
HashMap
-
底层使用的是哈希表(hash表/映射表)
-
哈希表是一种数据结构,其中使用了hash算法
-
hash算法:算法是用来把一个无限的数据映射到一个有限的范围内:
-
抽屉原理: 把无限的数据尽量分散平均放在有限范围内
会出现不同的数据使用hash算法之后映射到了同一个值上面,这个情况就是hash冲突,hash算法是为了让数据尽量分散,然而hash冲突是避免不了的,只能尽量减少
-
HashMap内部使用数组+链表,实现哈希表 JDK8之后加入了红黑树(提高搜索性能)
数组+链表+红黑树
-
Hash表出现中和了数组和链表的优势
将元素通过hash算法得出索引值,映射到hash表上,当索引值相同时出现hah冲突,元素链接在链表上,当hash冲突高时,即索引值相同比较多 数组会变成链表,链表长度达到8时升级为红黑树 数组–>链表–>红黑树
遍历方式
public static void method1() {
HashMap<String, String> stringHashMap = new HashMap<>();
stringHashMap.put("a", "java");
stringHashMap.put("b", "sun");
stringHashMap.put("c", "oracle");
System.out.println("------1遍历----");
Set<Map.Entry<String, String>> entries = stringHashMap.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
System.out.println("-------2--------");
// 取出所有的key 放到set中
Set<String> keys = stringHashMap.keySet();
for (String key : keys) {
System.out.println(key + " = " + stringHashMap.get(key));
}
System.out.println("----3遍历------");
stringHashMap.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String s, String s2) {
System.out.println(s + " : " + s2);
}
});
//lambda表达式
stringHashMap.forEach((k, v) -> {
System.out.println(k + " = " + v);
});
}
put()代码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 局部变量
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
// 如果数组初始为null就扩容 新数组长度赋值为 n=16
n = (tab = resize()).length;
// 0-15 20 % 16 取模 4 4 20; hash & (n-1) 与hash%n 结果等价(前提条件是 n是2的幂次方)
if ((p = tab[i = hash % n]) == null)
// 数组元素赋值
tab[i] = newNode(hash, key, value, null);
else {
// 表示索引对应的数组元素有值
Node<K, V> e;
K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// hash冲突 TreeNode是红黑树节点类型
else if (p instanceof TreeNode)
// 向红黑树中新增元素
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
else {
//
for (int binCount = 0; ; ++binCount) {
// 表示p节点后面没有节点了
if ((e = p.next) == null) {
// p的next指向新节点
p.next = newNode(hash, key, value, null);
// 目前是否需要把链表转红黑树 链表节点个数达到8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 找到是待替换的
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//下一个节点
p = e;
}
}
// 当hash值相同 并且两个key对象 equals时候 其实就是value的替换
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 记录被改动多少次
++modCount;
if (++size > threshold) {
// hash数组扩容
resize();
}
afterNodeInsertion(evict);
return null;
}
get()内部实现
final Node<K, V> getNode(int hash, Object key) {
//
Node[] tab;
Node first, e;
int n;
Object k;
//
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
//直接找到的是第一个map元素
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 找到的元素虽然索引一样 单不是要找到 元素
e = first.next;
if (e != null) {
// 判断节点是否是 红黑树节点
if (first instanceof TreeNode)
return ((TreeNode<K, V>) first).getTreeNode(hash, key);
// 表示此节点是链表节点
do {
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
return e;
}
e = e.next;
} while (e != null);
}
}
return null;
}
resize()内部实现 hash表
final Node<K, V>[] resize() {
//
Node[] oldTab = table;
// oldCap 代表旧数组长度
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 旧的临界值
int oldThr = threshold;
// newCap新数组长度
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
// 新数组的长度是 旧数组长度2倍
} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {
// 给新的临界值赋值
newThr = oldThr << 1; // double threshold
}
} else if (oldThr > 0) // initial capacity was placed in threshold
//
newCap = oldThr;
else { // zero initial threshold signifies using defaults
// 给新数组长度 赋值 16
newCap = DEFAULT_INITIAL_CAPACITY;
// 给新的临界值 赋值
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float) newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
// 给成员变量 临界值 赋值 24
threshold = newThr;
@SuppressWarnings({"rawtypes", "unchecked"})
// 第一次数组扩容 长度是16
Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
// 成员变量 hash表 赋值引用地址
table = newTab;
// 判断是否为 首次扩容
if (oldTab != null) {
// 遍历旧数组
for (int j = 0; j < oldCap; ++j) {
Node<K, V> e;
// 遍历到的数组元素不为null 的时候
if ((e = oldTab[j]) != null) {
//为了GC回收对象
oldTab[j] = null;
// 此节点没有兄弟节点
if (e.next == null)
// 3 3+16
/**
*11111
*00011 3
* -----
* 3 在新数组中的索引位置 有可能发生改变
*/
newTab[e.hash & (newCap - 1)] = e;
//判断此节点是否为树节点
else if (e instanceof TreeNode)
((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
// 后面是链表
else { // preserve order
// 低位的头 低位的尾部 15
Node<K, V> loHead = null, loTail = null;
// 高位的 31
Node<K, V> hiHead = null, hiTail = null;
Node<K, V> next;
do {
// 看高位是否为0 如果为0继续放在低位 否则放入到高位
if ((e.hash & oldCap) == 0) {
//
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
} else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
next = e.next;
} while ((e = next) != null);
//
if (loTail != null) {
//
loTail.next = null;
//给数组数组元素赋值
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
// 给高位 数组元素赋值
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
LinkedHashMap
- 是hashmap子类
- 能够保证插入和遍历的顺序 双向链表和hash表结合
public static void linkedHashMapMethod1() {
/* LinkedHashMap<String, String> stringLinkedHashMap = new LinkedHashMap<>();
stringLinkedHashMap.put("a", "java");*/
LinkedHashMap<String, String> stringHashMap = new LinkedHashMap<>();
stringHashMap.put("c", "java");
stringHashMap.put("cc", "sun");
stringHashMap.put("ccc", "oracle");
// String cc = stringHashMap.get("cc");
System.out.println(stringHashMap);
}
HashTable/concurrentHashMap
- 底层也是hash表,只不过在多线程情况下使用
- HashTable 是线程安全的
public static void hashTableMethod(){
/**
* 01111111 11111111 11111111 11111111
*/
ConcurrentHashMap<String, String> stringConcurrentHashMap = new ConcurrentHashMap<String, String>();
stringConcurrentHashMap.get("");
Hashtable<String, String> stringHashtable = new Hashtable<>();
stringHashtable.put("a","a");
}
TreeMap
底层是红黑树
- 遍历出来的结果是有顺序的 ,按照key进行排序
- TreeMap的key要实现接口Comparable 或者在TreeMap构造方法中直接提供比较器
public static void treeMapMethod() {
TreeMap<Money, String> stringTreeMap = new TreeMap<>((o1, o2) -> {
return o1.getValue() - o2.getValue();
});
stringTreeMap.put(new Money(10), "sm");
stringTreeMap.put(new Money(120), "sun");
stringTreeMap.put(new Money(50), "java");
System.out.println(stringTreeMap);
stringTreeMap.put(new Money(50), "oracle");
System.out.println(stringTreeMap);
}
2.匿名内部类&lambda表达式
1.匿名内部类
- 可以用于抽象类或者接口中,和普通类定义使用并无区别 只是一个无名的实现类 一次性使用 没有名字 所以是匿名内部类
- 匿名内部类中只需要重写抽象方法即可
- 常用于线程,集合
- 抽象类或接口定义
抽象类或接口定义
public abstract class Inter {
public abstract void method1();
public abstract void method2();
}
使用类
public class Demo {
public void demo(Inter inter) {
inter.method1();
inter.method2();
}
}
测试
public static void main(String[] args) {
Demo demo = new Demo();
//匿名内部类
demo.demo(new Inter() {
@Override
public void method1() {
System.out.println("method1 invoked ");
}
@Override
public void method2() {
System.out.println("method1 invoked ");
}
});
}
2 lambda表达式
- 仅用于接口中只有一个抽象方法时,匿名内部类可以简化为lambda表达式
- @FunctionalInterface 函数式接口 凡是接口上面有这种注解的都表示里面只有一个抽象方法 都可以使用lambda表达式
- jdk8以后
接口定义
public interface Inter {
void method1(Integer a, String b);
}
使用类
public class Demo {
public void demo(Inter inter) {
inter.method1(16, "abc");
}
}
测试类
public static void main(String[] args) {
Demo demo = new Demo();
demo.demo((a, b) -> {
System.out.println(a);
System.out.println(b);
}
);
}
2.Stream
注意点:
在对集合遍历的过程中,不能直接去调用集合对象的增加和删除方法,会抛异常;如果需要在遍历的过程中对集合元素进行删除 可以使用迭代器
- stream流丰富了对集合的操作,提供了较多方法
生成流的方式
public static void streamCreateMethod(){
ArrayList<String> arrayList = new ArrayList<>();
// 1
Stream<String> stream = arrayList.stream();
// 2
Stream<String> a = Stream.of("a", "b", "c", "d");
// 3,
Stream<String> stream1 = Arrays.stream(new String[]{"1", "2"});
}
中间操作(方法返回值类型都是Stream):
sorted() 对流中元素进行排序
map() 对流中的数据进行相同功能的操作
filter() 使用过滤规则来过滤元素
limit() 截取指定长度的流
终止操作(返回值不是Stream)
只能出现一次而且必须在最后
count() 返回流中元素个数
max() 根据规则得到流中最大值
min() 根据规则得到流中最小值
reduce() 归纳 把流中数据集成为一个数
collect() 收集 常用类转集合,把流中数据转到集合中
public static void streamMethods() {
//数据源头
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(10);
arrayList.add(1);
arrayList.add(4);
arrayList.add(3);
arrayList.add(6);
arrayList.add(12);
// 如何生成流
Stream<Integer> stream1 = arrayList.stream();
Stream<Integer> stream2 = stream1.sorted();
// 对流中的数据进行相同功能的操作
Stream<Integer> stream3 = stream2.map((a) -> {
return a * 2;
});
// 过滤元素
Stream<Integer> stream4 = stream3.filter((a) -> {
if (a > 6) {
// 符合规则的保留下来 即返回true
return true;
}
return false;
});
//限制元素个数
Stream<Integer> stream5 = stream4.limit(3);
/* long count = stream5.count();
System.out.println("count " + count);*/
//
/*Optional<Integer> max = stream3.min((o1, o2) -> {
return o1 - o2;
});
System.out.println("max " + max.get());*/
/* Optional<Integer> reduce = stream3.reduce((a, b) -> {
return a + b;
});
System.out.println("reduce " + reduce.get());*/
List<Integer> collect = stream3.collect(Collectors.toList());
/* List<Integer> collect = stream5.collect(Collectors.toList());
System.out.println(collect);
System.out.println(arrayList);*/
/* Stream<Integer> limit = stream1.sorted().map((a) -> {
return a * 2;
}).filter((a) -> {
if (a > 56) {
return true;
}
return false;
}).limit(3);*/
}
;
}
}
System.out.println(arrayList);
}
public static void streamMethod2() {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(3);
arrayList.add(2);
arrayList.add(6);
//1.生成流
Stream<Integer> stream = arrayList.stream();
/*//sort()排序,map()映射
Stream<Integer> sorted = stream.sorted();
//对流中的数据进行相同功能的操作
Stream<Integer> integerStream = sorted.map((a) -> {
return a * 2;
});
//filter()过滤元素
Stream<Integer> integerStream1 = integerStream.filter((a) -> {
if (a>6){
//符合规则的保留下来,返回true
return true;
}
return false;
});
//limit()截取元素
Stream<Integer> integerStream2 = integerStream.limit(3);*/
//sort()排序;map()映射;filter()筛选;limit()截取
Stream<Integer> integerStream = stream.sorted().map((a) -> {
return a * 2;
}).filter((b) -> {
if (b > 4) {
return true;
}
return false;
}).limit(1);
//终止流操作 count(); collect();max();reduce();
List<Integer> collect = integerStream.collect(Collectors.toList());
System.out.println(collect);
System.out.println(arrayList);
/* stream.map((a) -> {
return a * 2;
});
18.IO/File
1.FIle
代表的是文件或者目录(有可能不存在 当路径写错的时候)
文件路径
//路径的写法 绝对路径 相对路径 相对于当前项目所在路径 ./表示当前项目所在路径 ../返回到上级路径
File file1 = new File("./resource\\a.txt");
File file2 = new File("E:/io/Hello.txt");//Hello.txt
File file3 = new File("../klay");
System.out.println("文件或目录是否存在:"+file1.exists());
System.out.println(file2.exists());
System.out.println(file3.exists());
构造方法
/**
* File类构造方法
*/
public static void fileConstructorMethods() {
// 1, 传参是文件或者目录的 路径
File file = new File("./resources/a.txt");
// 2, 传参 父级路径 和子级路径
File file1 = new File("./resources", "a.txt");
// 创建父级对象
File file2 = new File("./resources");
// 3, 第三种构造 传父级对象 和子级路径
File file3 = new File(file2, "a.txt");
System.out.println("exists " + file3.exists());
}
常用方法
/**
* 常用方法
*/
public static void fileMethods() {
File file = new File("./resources/dir2/dir23/dir231");
System.out.println("是否存在 " + file.exists());
// 文件的字节大小
System.out.println("文件大小 " + file.length());
System.out.println("是否为文件 " + file.isFile());
System.out.println("是否为目录 " + file.isDirectory());
System.out.println("文件/目录 名称" + file.getName());
System.out.println("绝对路径: " + file.getAbsolutePath());
// 但是 目录下面没有子级的时候才会删除
// System.out.println("f删除文件/目录 " + file.delete());
// mkdir只能创建单级目录
System.out.println("创建目录 " + file.mkdir());
System.out.println("创建级联目录" + file.mkdirs());
/*try {
//当file代表的文件不存在的时候会创建新文件 否则不创建返回false
System.out.println("创建文件 " + file.createNewFile());
} catch (IOException e) {
e.printStackTrace();
}*/
}
获取文件列表
内部递归调用
ublic static void fileMethods1(File file, String ope) {
// 返回的是直接子系 3
File[] files = file.listFiles();
for (File file1 : files) {
if (file1.isFile()) {
System.out.println(ope + file1.getName());
} else {
//如果是目录的话
System.out.println(ope + file1.getName());
//递归调用
fileMethods1(file1, ope + " |");
}
}
}
测试类
public static void main(String[] args) {
File file = new File("./resources");
fileMethods1(file, " |");
}
获取文件列表时加过滤规则
File[] files = file.listFiles((path) -> {
if (path.isDirectory()) {
return true;
}
return false;
});
System.out.println(files.length);
2.IO
I:input 输入 O:output 输出 (在程序角度看 是输入还是输出)
按流向分为 输入流 输出流
按处理数据的基本单位不同 字节流 字符流(字符编码 / 字符解码)
1.字节流(InputStream/OutputStream)
构造方法
public static void inputStreamMethod() {
try {
FileInputStream fileInputStream = new FileInputStream("./resources/a.txt");
File file = new File("./resources/a.txt");
FileInputStream fileInputStream1 = new FileInputStream(file);
System.out.println(fileInputStream);
} catch (FileNotFoundException e) {
// 打印详细的堆栈报错信息
e.printStackTrace();
System.out.println(e.getMessage());
}
}
读取文件内容
public static void inputStreamMethod1() {
// try新语法种只能放实现了Closeable的实现类,会自动调用其close方法
try (FileInputStream fileInputStream = new FileInputStream("./resources/a.txt")) {
int num;
// 返回值是-1的时候表示读到文件末尾了 读完了
while ((num = fileInputStream.read()) != -1) {
System.out.println("数据 " + num);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
2.字节输出流FileOutputStream
write()方法
/**
*文件输出流
*/
public static void outputStreamMethod() {
// 输出流会自动创建文件(如果文件不存在) 构造方法中 true表示追加在文件尾部继续添加
try (FileOutputStream fileOutputStream = new FileOutputStream("./resources/b.txt")) {
fileOutputStream.write(97);
fileOutputStream.write(98);
fileOutputStream.write(99);
fileOutputStream.write(100);
fileOutputStream.write("程序".getBytes());
// fileOutputStream.write(101);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
字节流实现文件包拷贝
/**
*图片拷贝
*/
public static void fileCopyMethod() {
try (FileInputStream fileInputStream = new FileInputStream("./resources/sl.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("./resources/sl2.jpg")) {
int num;
while ((num = fileInputStream.read()) != -1) {
fileOutputStream.write(num);
}
System.out.println("copy finished");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
3.带缓冲区的字节流
实现文件拷贝
public static void copyBuffer() {
//高效缓存流
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("./resource/dir2/2.jpg"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("./resource/dir2/dir5/3.jpg"))) {
int num;
while ((num = bufferedInputStream.read()) != -1) {
//只是把数据放在了数组里,当数组元素满了或者调用了close()方法的时候开始真的写入
bufferedOutputStream.write(num);
}
System.out.println("拷贝成功!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
4.字符流
子字节流的基础上 加上了字符的编码和解码
常用来处理文字文本
字符流不能处理二进制文件(图片 视频 音频 只能使用字节流处理)
5文件字符流
public static void readerMethod() {
try (FileReader fileReader = new FileReader("./resources/a.txt");
FileWriter fileWriter = new FileWriter("./resources/test.txt")) {
int num;
fileWriter.write("hello world");
char[] chars = "你好".toCharArray();
fileWriter.write(chars);
/*while ((num = fileReader.read()) != -1) {
fileWriter.write(num);
// System.out.println(((char) num));
}*/
} catch (FileNotFoundException e) {
} catch (IOException e) {
e.printStackTrace();
}
}
6.高效字符流
public static void bufferReaderMethod() {
try (BufferedReader bufferedReader = new BufferedReader(new FileReader("./resources/a.txt"));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("./resources/c.txt"))) {
String str;
//一次读取一行 当读到流尾部的时候返回null
/*while ((str = bufferedReader.readLine()) != null) {
System.out.println(str);
}*/
bufferedWriter.write("aaa");
bufferedWriter.write("");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
7.转换流
- 把字节输入流转为字符输入流
- 把字符输出流转为字节输出流
public static void changeStreamMethod() {
// InputStreamReader把字节输入流转成字符输入流
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
try (BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
String str;
while (!(str = bufferedReader.readLine()).equals("quit")) {
System.out.println(str);
}
System.out.println("退出了");
} catch (IOException e) {
e.printStackTrace();
}
}
转换流
public static void changeStreamMethod1() {
//
try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out))) {
bufferedWriter.write("hello");
bufferedWriter.newLine();
bufferedWriter.write("bye bye");
} catch (IOException e) {
e.printStackTrace();
}
}
8.对象序列化
- 对象的序列化 把内存中的对象写入到底层存储介质中
- 对象的反序列化 把底层存储介质中数据恢复到内存中
- 注意点 对象类型要实现接口 Serializable 是个标记接口 给JVM看的,真正的序列化技术 由JVM来实现
- 其中的成员变量类型必须都是可序列化的
- transient用来修饰成员变量表示不对此成员变量进行序列化;那么在反序列化的时候会得到的是其数据类型的零值;
- 自定义类型中添加versionID 如果序列化之后源码发生了改动那么反序列化就会失败 因为版本不一致
- 类的定义
public class Student implements Serializable {
private static final long serialVersionUID = 5404764234087337539L;
private String name;
private Integer age;
private transient Car car;
//trandient表示该成员变量不被序列化
public Student() {
}
public Student(String name, Integer age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
/* @Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}*/
}
测试类
public class SeriaExercise {
public static void main(String[] args) {
// seMethod();
unSeMethod();
}
public static void unSeMethod() {
try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("./resources/obj.txt"))) {
//反序列化
Object object = objectInputStream.readObject();
if (object instanceof Student) {
Student student = (Student) object;
System.out.println("name " + student.getName());
System.out.println("age " + student.getAge());
System.out.println("car "+student.getCar());
}
// System.out.println(object);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 对象的序列化 把内存中的对象写入到底层存储介质中
* 注意点 对象类型要实现接口 Serializable
*/
public static void seMethod() {
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./resources/obj.txt"))) {
Student tom = new Student("tom", 12, new Car());
//对象序列化
objectOutputStream.writeObject(tom);
System.out.println(" 序列化完成 ");
} catch (IOException e) {
e.printStackTrace();
}
}
}
9.数据流
- 操作基本数据类型的流对象
- 先写后读
- 写入顺序和读取顺序保持一致
写入数据
public static void dataIoMethod() {
try (DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("./resources/data.txt"))) {
dataOutputStream.writeInt(100); // int 4
dataOutputStream.writeLong(100L);// 8
dataOutputStream.writeUTF("java");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
读出数据
public static void dataIoMethod1() {
try (DataInputStream dataInputStream = new DataInputStream(new FileInputStream("./resources/data.txt"))) {
int i = dataInputStream.readInt();
long v = dataInputStream.readLong(); // 4
String s = dataInputStream.readUTF();
System.out.println(" int " + i);
System.out.println(" long " + v);
System.out.println(" str " + s);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
19.反射
反射是框架的灵魂
反射也是动态化代理的基础
在运行期间 动态的获取类的信息以及对类进行操作的机制(获取编译后类的class字节码文件)
把每一个class文件放大来看 那么其中的每个组成部分都是一个又一个的对象
一个类由 具有相同属性和行为的对象组成
一个class文件看成一个类,每个类里边都有成员变量 方法 构造方法可以被看成属性
1.获取class对象
public static void getClassMothods() {
//1.获取class对象
//1
Class<Car> carClass = Car.class;
//2
Car car = new Car();
final Class<? extends Car> aClass = car.getClass();
//3
try {
final Class<?> aClass1 = Class.forName("com.klay.sm.reflect.Car");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
2.获取成员变量信息
public static void getFieldsMothods(){
// 全路径
try {
Class<?> aClass = Class.forName("com.klay.sm.reflect.Car");
Object o = aClass.newInstance();
System.out.println(o);
//获取成员变量 getFields()只能获取public修饰的属性 包括从父类继承的属性
final Field[] fields = aClass.getFields();
System.out.println(fields.length);
for (Field field:fields){
System.out.println(field);
System.out.println("==========");
System.out.println(field.getName());
}
//getDeclaredFields()只获取本类声明的属性 public private修饰的都可以
aClass.getDeclaredFields();
System.out.println(fields.length);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
3.获取方法对象 并且调用
public static void getMethods() {
try {
//调用类的class文件
Class<?> aClass = Class.forName("com.qy30.sm.reflect.Car");
// aClass.getConstructor()
//实例化
Object obj = aClass.newInstance();
// 调用public方法(包括继承的)
Method[] methods = aClass.getMethods();
/* for (Method method : methods) {
System.out.println(method.getName());
}*/
// 调用本类的方法 public和private
Method[] declaredMethods = aClass.getDeclaredMethods();
/* for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName());
}*/
//1,根据方法名和形参类型获取指定的方法对象
Method declaredMethod = aClass.getDeclaredMethod("run");
// 把方法改为可访问的public
declaredMethod.setAccessible(true);
// 通过反射调用方法 传对象以及实参 调用方法invoke
declaredMethod.invoke(obj);
// 2,拿到有参数的 方法对象
Method addOil = aClass.getDeclaredMethod("addOil", String.class, Integer.class);
addOil.invoke(obj, "abc", 10);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
4,练习题
public static void exercise() {
try {
//调用class文件
Class<?> stuClass = Class.forName("com.qy30.sm.reflect.Student");
//实例化
Object student = stuClass.newInstance();
//调用本类方法
Method setCar = stuClass.getDeclaredMethod("setCar", Car.class);
// 通过配置文件传入进来
Class<?> aClass = Class.forName("配置文件(args[]或者读取配置文件)");
//车的实例化
Object o = aClass.newInstance();
//invoke()调用方法 传入参数
setCar.invoke(student, o);
System.out.println(student);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
5,构造器的使用
public static void getConstructor() {
try {
Class<?> aClass = Class.forName("com.qy30.sm.reflect.BmwCar");
// 默认会调用无参构造 没有无参构造就不能用这种办法
// Object o = aClass.newInstance();
// 获取构造器 public
Constructor<?>[] constructors = aClass.getConstructors();
// 所有的构造器
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
//一个构造器
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Double.class);
//更改修饰符
declaredConstructor.setAccessible(true);
//创建构造器对象
Object o1 = declaredConstructor.newInstance(6.6);
System.out.println(o1);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
6.类加载器
按需加载 使用到的时候才会加载
字节码文件只会被加载一次,不会重复加载
静态代码块在类加载到内存的那一刻执行 并且只能加载一次
java–>complier编译为.class文件–>由类加载器加载到内存–>字节码校验文件–>解释器解释 成机器指令–>操作系统
java中的加载器
-
启动类加载器 bootstrap class loader
核心类的加载器 核心类 不是java代码写的
-
扩展类加载器 extend class loader
\jre\lib\ext 加载内部的类
-
应用类加载器 application class loader
加载用户自定义类
-
其他类加载器
双亲委派机制
当一个类加载器收到了类加载的请求 他不会自己去尝试加载这个类 而是把这个请求委派给父类加载器去完成 每一个层次的类加载器都是如此 因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载,从而保证每个类只会被加载一次
20.线程–>Thread
- 进程 程序 进程是资源(cpu,内存 硬盘空间)分配的基本单位 静态的
- 每个进程至少会有一个线程 线程是任务调度的基本单位 是真正干活的
1.1多线程的概念
并发: 同时发生了 说明服务支持并发
并行:同时处理 cpu高速切换 认为好像在同时进行一样
串行: 按顺序 进行 一个一个进行
同步: 一个的任务的开始必须等待上一个任务的结束
异步: 任务之间互不影响 无需等待别的任务的反馈结果
1.2线程的创建
自定义一个类去继承Thread 重写run()方法
public class CustomThread extends Thread {
/**
* run是线程真正执行的任务
*/
@Override
public void run() {
for (int i = 0; i < 2000000000; i++) {
System.out.println("threadA " + i);
}
}
}
测试类
public class ThreadExercise
public static void main (String[] args){
CustomThread customThread = new CustomThread();
//启动线程
customThread.start();
}
实现接口Runnable
public static void createThread2(){
System.out.println("main thread start");
Thread ThreadA = new Thread(new Runnable(){
@overwrite
public void run(){
for*int i=0; i<1000;i++){
System.out.println("threadA"+i);
}
}
});
Thread threadB = new Thread(()->{
for(int i = 0; i<1000;i++){
System.out.println("threadB"+i);
}
});
threadA.start();
threadB.start();
System.out.println("main thread end");
}
3.s实现接口Callable,线程执行完任务之后会返回一个结果,使用到FutureTask
public static void createThread3(){
//Callable接口是线程执行完之后 返回一个结果
FutureTask<Object>futureTask = new FutureTask<>(()->{
for(int i = 0;i<10; i++){
System.out.println("threadA"+i);
}
return "abc";
});
Thread ThreadA = new Thread(futureTask);
ThreadA.start();
try{
//获取线程的返回结果 get是阻塞式的方法
Object o = futureTask.get();
System.out.println("return"+o)
}
}
1.3线程的方法
public static void threadMethods(){
Thread thread = new Thread(()->{
for(int i= 0 ; in <1000;i++){
//获取当前线程的名字
System.out.println(Thread.currentThread().getName()+i);
}
});
//设置线程名字
thread.setName("tome");
//查看线程状态
System.out.println(thread.getState());
//表示线程准备好了 可以被os调度了
thread.start();
System.out.println(thread.getState());
//获取线程的优先级 OS调度器会优先调度优先级高的线程
System.out.println("priority"+thread.getPriority);
//获取线程id
System.out.println("id"+thread.getId());
//获取当前线程
Thread thread1 = Thread.currentThread();
System.out.println(thread1.getName());
try {
// 当前线程需要等待 thread执行完之后才能继续往下走
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" main thread end");
}
}
1.4线程池
- 频繁的创建和销毁线程是在浪费资源 线程池(很多个线程)是对象池思想当有任务来的时候 从线程池中取出一个线程来执行任务 任务结束后回归池子继续接下一个任务
- 线程池是为了节省系统资源 提供性能
public static void threadPoolMethod(){
//频繁的创建和销毁线程 在浪费资源 线程池(很多个线程) 节省资源
//对象池思想 当有任务来的时候,取出一个线程来执行完任务,回归到池子接下一个任务
// 创建了一个固定数量的线程池
ExecutorService exevcutorService - Excutors.newFixedThreadPool(2);//创建了一个含有两个线程的线程池
//带缓冲区的线程
ExecutorService executorService1 = Executors.newCachedThreadPool(); //默认100
//提交任务给线程池
executorService.submit(()->{
for (int i = 0; i < 100; i++) {
System.out.println("threadA " + i);
}
System.out.println(Thread.currentThread().getId());
});
try {
//单位是 毫秒 让线程休眠
// Thread.sleep(1000L);
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.submit(()->{
for (int i = 0; i < 100; i++) {
System.out.println("threadB " + i);
}
System.out.println(Thread.currentThread().getId());
return "";
});
//关闭线程池
executorService.shutdown();
}
1.5线程安全,线程同步
- 多线程环境下,同时对内存/共享变量进行操作时,出现数据不一致的情况
银行账号
public class BankAccount {
private double balance;
/**
* 原子性
* @param money
*/
public void saveMoney(double money) {
//1,取出balance
//2,add
//3,重新赋值
balance = balance + money;
}
public void withDraw(double money) {
balance -= money;
}
public double getBalance() {
return balance;
}
}
测试类
public static void main(String[] args) {
BankAccount bankAccount = new BankAccount();
Thread threadA = new Thread(() -> {
for (int i = 0; i < 20000; i++) {
bankAccount.saveMoney(10.0);
}
System.out.println("A end");
});
Thread threadB = new Thread(() -> {
for (int i = 0; i < 20000; i++) {
bankAccount.withDraw(10.0);
}
System.out.println("B end");
});
threadA.start();
threadB.start();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("余额 " + bankAccount.getBalance());
}
如何解决?
1.synchronized表示线程同步
- 可以用在方法声明处,方法内部(当前对象的锁),用在静态方法中需要类是锁
- 每个对象都有一把对象锁,如果方法使用了synchronized进行修饰,那么线程在执行这个方法的时候必须获取对象锁才能够去执行方法
- synchronized保证原子性(要么都做,要么都不做)
- synchronized是一把排它锁/互斥锁,并且是可重入锁(不需要重复获取)
- synchronized在使用的时候如果出现异常会自动释放对象锁
/**
* 原子性
*
* @param money
*/
public void saveMoney(double money) {
//1,取出balance
//2,add
//3,重新赋值
// 小括号 里面表示需要获取 哪个对象的对象锁
synchronized (this) {// 需要获取当前对象的对象锁
balance = balance + money;
}
}
public synchronized void withDraw(double money) {
balance -= money;
}
/**
* 静态方法需要是类锁 字节码对象的锁
*/
public static void test() {
synchronized (BankAccount.class) {
System.out.println(" =");
}
}
2.使用Lock,上锁和释放锁的时机由使用者把控
释放锁放在finally中做,防止出现死锁
public class BankAccount {
private double balance;
//创建了一把可重入锁
private ReentrantLock lock = new ReentrantLock();
/**
* @param money
*/
public void saveMoney(double money) {
//上锁
lock.lock();
try {
balance = balance + money;
System.out.println(1 / 0);
} catch (Exception e) {
} finally {
//释放锁
lock.unlock();
}
}
}
1.6线程安全的类
1.类内部做了线程保护,例如集合中类Vector Hahstable
public static void main(String[] args) {
Vector<Integer> arrayList = new Vector<>();
Thread threadA = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
arrayList.add(i);
}
});
Thread threadB = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
arrayList.add(i);
}
});
threadA.start();
threadB.start();
try {
TimeUnit.SECONDS.sleep(5L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("size " + arrayList.size());
}
2.原子类Atomic内部使用CAS(compare And Swap)保证数据一致性
public class DeadLockExercise{
//private static int count = 0;
private static AtomicInteger count = new AtomicInteger(0);
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args){
Thread threadA = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
//自增
count.incrementAndGet();
}
System.out.println("A end");
});
Thread threadB = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
//自减
count.decrementAndGet();
}
System.out.println("B end");
});
threadA.start();
threadB.start();
try {
//休眠
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" count " + count);
}
}
1.7死锁
- 当多个线程之间出现互相等待的情况 会产生死锁
如何解决
- synchronized嵌套锁的时候 上锁顺序保持一致
- 使用Lock进行获取锁的时候用trylock,拿不到直接返回 或者加一个超时等待
//死锁
public static void deadLockMethod(){
Object o1 = new Object();
Object o2 = new Object();
Thread threadA = new Thread(() -> {
synchronized (o1) {
System.out.println("threadA 获取了object1的对象锁");
synchronized (o2) {
System.out.println("threadA 获取了object2的对象锁");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (o2) {//o1
System.out.println("threadB 获取了object2的对象锁");
synchronized (o1) {//o2
System.out.println("threadB 获取了object1的对象锁");
}
}
});
threadA.start();
threadB.start();
}
//synchronized嵌套锁的时候 上锁顺序保持一致即可
使用lock 的tryLock解决
public static void deadLockMethod1() {
Object o1 = new Object();
Object o2 = new Object();
Thread threadA = new Thread(() -> {
// tryLock()不会无限等待 获取不到锁 直接返回false
try {
//让他稍微等一会再关闭锁
if (lock1.tryLock(3, TimeUnit.SECONDS)) {
try {
System.out.println("threadA 获取object1的锁");
try {
TimeUnit.MILLISECONDS.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (lock2.tryLock()) {
try {
System.out.println("threadA 获取了object2的对象锁");
System.out.println("===");
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread threadB = new Thread(() -> {
if (lock2.tryLock()) {
try {
System.out.println("threadB 获取object2的锁");
try {
TimeUnit.MILLISECONDS.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//
if (lock1.tryLock()) {
try {
System.out.println("threadB 获取了object1的对象锁");
System.out.println("===");
} finally {
lock1.unlock();
}
}
} finally {
lock2.unlock();
}
}
});
threadA.start();
threadB.start();
}
1.8生产者消费者
wait()方法和notify()notifyAll()必须在同步代码块中使用
wait()方法应该始终出现在循环中,唤醒之后继续判断条件;导致当前线程进入到当前对象的等待池里面
notifyALL通知当前对象等待池中的所有线程,notify()通知当前对象等待池中某个线程
仓库类
public class Store {
//商品数据量
private int count;
//仓库最大容量
private final int MAX_COUNT = 200;
/**
* 生成者生成商品进去
*/
public void put() {
synchronized (this) {
while (count == MAX_COUNT) {
System.out.println("仓库已经满了 停止生产");
try {
//需要等待 消费者来消费了 导致当前线程进入到当前对象的等待池里面
// wait()方法 线程会释放锁 sleep()是不释放锁的
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println("生产者生产了一件商品 " + count);
//notifyAll 通知当前对象等待池中的所有线程,notify()通知当前对象等待池中某个线程
this.notifyAll();
}
}
/**
* 消费者去商品
*/
public void get() {
synchronized (this) {
while (count == 0) {
System.out.println("仓库已经空了 停止消费 需要等待");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println("消费者消费了一件商品 " + count);
//通知 等待池中的生产者线程
this.notifyAll();
}
}
}
面试题
sleep()与wait()的区别
1.sleep是Thread的静态方法,wait是Object的方法
2.使用不同,sleep可以在任意地方使用,wait只能在同步代码块中调用
3.关于锁的释放,sleep不会释放锁,wait会释放锁
1.9多线程的三个核心概念
-
原子性
要么全都执行,要么全都不执行
synchronized保证原子性
-
可见性,线程之间的可见性
线程A对共享变量的操作,其他线程可见(必须从主存获取最新的值)
synchronized可以保证可见性
volatile也可以保证可见性
-
顺序性
指令执行的顺序
JVM内部有指令重排序,是为了性能;在单线程情况下,不会有任何问题;
多线程情况下,有可能有问题
volatile可以禁止JVM指令重排序
public class VisibleExercise {
// volatile可以保证可见性
private volatile static boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
while (flag) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
System.out.println("main end");
}
}
1.10.设计模式
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
1.10.1.单例模式
只有一个实例,后期Servlet都是单例模式,以及SpringMVC中的controller等;管理器或者控制器都是单例的;
核心思路
- 构造器私有化
- 提供一个和本类类型一样的 静态的成员变量
- 提供一个静态的公有方法,返回类型是本类类型
1.10.1.1.饿汉式单例
用反射调用class文件或序列化会影响单例模式所以一般用枚举类实现单例模式
缺点是有可能存在空间 浪费
优点 在类加载期间实例化 线程安全
使用场景:单例占用内存小的时候
public class Singleton {
//1.构造器私有化
//2。声明唯一的对象
//3.声明方法返回对象用static修饰 调用
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
1.10.1.2.懒汉式单例
优点:空间合理使用
缺点:需要外部加线程保护
使用场景:单例对象是个 大对象
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {
}
/**
* 第一次请求的时候 实例化
* JVM指令重排序会导致对象状态出错,
* @return
*/
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
//double check双重检测
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
防止指令重排序
public class LazySingleton {
//volatile防JVM指令重排序
private volatile static LazySingleton instance;
private LazySingleton() {
}
/**
* 第一次请求的时候 实例化
* JVM指令重排序会导致 对象状态出错,
*
* @return
*/
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
//double check双重检测
if (instance == null) {
/**
* 1,开辟空间 10ms
* 2,初始化实例 100ms
* 3,引用赋值 5ms 115ms 13 2 15ms
*/
instance = new LazySingleton();
}
}
}
return instance;
}
}
21.泛型&注解
- 常用于集合中,用来做类型检查
- 使用到泛型的地方,如果没有指定都是Object类型
- 是一个占位符,符号个数不限,名称不限
- 泛型符号可以用在类的声明,用在接口处,用在方法声明处
1.泛型的使用
泛型类
T type
E element
K key
V value
在实例化的时候确定类型
如果不指定,就当成Object来看
静态方法不能使用类的泛型占位符
public class CustomGeneric<T, A, B, C> {
private String name;
private T t;
private A a;
private B b;
private C c;
public C test() {
return c;
}
// 静态方法不能使用 类的泛型占位符
public static void method(A a) {
}
}
public static void main(String[] args) {
CustomGeneric customGeneric = new CustomGeneric();
customGeneric.test()
}
泛型接口
public interface GenericInter<A, B, C> {
A method1();
B method2();
C method3();
}
1.在实现类时确定泛型借口类型 不指定时默认Object类
型
public class GenericImpl1 implements GenericInter<String, Integer, Double> {
@Override
public String method1() {
return null;
}
@Override
public Integer method2() {
return null;
}
@Override
public Double method3() {
return null;
}
}
2.实现类时不指定泛型借口类型,在实现类中应带上借口中的泛型,实现类中前表示接口中的泛型,本类的泛型在接口泛型后边
public class GenericImpl1 implements GenericInter {
@Override
public Object method1() {
return null;
}
@Override
public Object method2() {
return null;
}
@Override
public Object method3() {
return null;
}
}
public class GenericImpl2<A, B, C,D> implements GenericInter<A, B, C> {
@Override
public A method1() {
return null;
}
@Override
public B method2() {
return null;
}
@Override
public C method3() {
return null;
}
}
测试类
public static void main(String[] args) {
GenericImpl2<Integer, Double, String, Float> genericImpl2 = new GenericImpl2<>();
Integer integer = genericImple2.method1();
ArrayList<String> objects = new ArrayList<>();
}
泛型方法
泛型方法只能在本类中使用
泛型类型由实际传参时决定
静态方法可以使用泛型
public class GenericMethod {
//只在本方法中使用
public static void main(String[] args) {
GenericMethod genericMethod = new GenericMethod();
//类型由调用方法的时候传参决定
Integer integer = genericMethod.method1(1);
String s = genericMethod.method1("qwer");
}
public <E> E method1(E e){
System.out.println("====");
return e;
}
//静态方法中可以使用泛型 静态类中不可以
public static <A> void method2(A a){
}
}
测试类
public static void genericMethod() {
GenericMethod genericMethod = new GenericMethod();
String aaa = genericMethod.method1("aaa");
Integer integer = genericMethod.method1(1);
Double aDouble = genericMethod.method1(6.6);
GenericMethod.method2("aa");
}
2.泛型通配符及上下限
为了体现出多态,在用泛型的地方需要实现泛型上限
//动物类
public class Animal {
public void eat(){
}
}
//狗类 继承动物类
public class Dog extends Animal {
@Override
public void eat(){
System.out.println("狗喜欢吃骨头");
}
}
//猫类 继承动物类
public class Cat extends Animal {
public void eat(){
System.out.println("猫喜欢吃鱼");
}
}
//喂养者类
public class Feeder {
//? 泛型通配符 泛型的上限 继承animal的子类或animal
public void feed(ArrayList<? extends Animal> animals){
for (Animal animal : animals){
animal.eat();
}
}
//下限:<? super Animal>只能传Animal的父类
public void feed2(ArrayList<?super Animal> animals){
for (Object animal:animals){
}
}
//测试类
public class FeederTest {
@Test
public void feed() {
ArrayList<Animal> animals = new ArrayList<>() ;
animals.add(new Dog());
animals.add(new Cat());
Feeder feeder = new Feeder();
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
feeder.feed(dogs);
}
}
3.泛型擦除
@Test
public void method1() {
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
//泛型擦除
List list = dogs;
list.add(new Cat());
}
4.注解
- XML配置文件的格式,配置信息越来越多,维护较为麻烦;解耦性最高
- 注解的出现可以取代XML配置,耦合度高,但是维护比较方便简单 清晰
- 注解只是一个标记而已,一定要有个解析程序 注解才有意义
1.自定义注解
其实注解本质上都是Annotation接口的子接口
如果注解声明体内部没内容,只是一个标记注解
注解的注解是元注解
Target:表示此注解可以使用在哪些成员(类,成员变量,方法)上面
Retention:
Documented,
inherited
5.枚举类型
22.Mysql
1.安装
解压压缩包到所想放置的目录
配置环境变量path
放置ini文件,修改文件内安装路径和数据存储路径
bin目录下管理员打开cmd
//初始化
mysqld -initialize --console
//安装
mysqld install
//启动服务
net start mysql
//进入mysql
mysql -uroot-p+密码
//修改密码
alter user 'root'@localhost' identified with mysql_native_password by '666';
2.数据库概述
- 是长期存储在计算机内有组织,有共享,统一管理的数据集合
- DB(database):存储数据的仓库,其中的数据是有组织有关联的
- DBMS(database management system)数据库管理系统,管理DB的
- SQL (structure query language) 结构化查询语言,专门与DB通信的语言,所有DBMS(MySQL,Oracle, SQLserver等)都支持;
存储位置的不同进行分类:
1,基于磁盘的存储,MySQL,Oracle,SQLServer
2,基于内存的存储,redis非常适合做缓存,
从数据间是否存在关系进行分类:
- 关系型数据库:MySQL,Oracle,SQLServer
- 非关系型数据库:redis,mongodb nosql(not only sql)
数据库中每个表有不同的列和行组成,每一行表示一个单位 类似于java中的对象 每一列表示一个属性
3.字段的数据类型
1,整型
- tinyint(2) 等同于byte的取值范围 -128-127
- tinyint(1) 0 1 等同于java语言 boolean
- int(n) n: 查询的时候单元格宽度 int(11) 显示宽度和数据类型的取值范围是无关的。如果插入了大于显示宽度的值,只要该值不超过该类型整数的取值范围,数值依然可以插入,而且能够显示出来。
- bigint(n): long id 时间:毫秒数
2,小数
- float(M,D),M称为精度,表示总共 的位数;D表达标度,表示小数的位数 12.345
- double(m,d):
- decimal(m,n): BigDecimal DECIMAL 的存储空间并不是固定的,而由精度值 M 决定,占用 M+2 个字节
3,字符型
- char(n) 固定长度,char(4)不管是存入几个字符,都将占用4个字节
- VARCHAR 和 TEXT 类型是变长类型,其存储需求取决于列值的实际长度,一个 VARCHAR(10) 列能保存一个最大长度为 10 个字符的字符串
- MySQL不区分字符和字符串,单引号和双引号类似;
4,日期型
- date: 年月日
- datetime: 年月日 时分秒
- timestamp: 年月日 时分秒 时间戳
4.SQL语句
结构化查询语言,sql语句不区分大小写
DDL(Data Definition Language)
:数据定义语言 create drop alter用来定义数据库对象:库,表,列
DML(Data Manipulation Language)
: insert delete update 用来定义数据库记录(数据)
DQL(Data Query Language)
: select 数据库查询语言 用来查询记录
DCL(Data Control Language)
:commit rollback 定义访问权限和安全级别
4.1DDL语句
1.创建表 多个单词使用下划线分割
CREATE TABLE student(
sid INT,
sname VARCHAR(10),
sgender CHAR(1),
age INT,
score FLOAT(4,2),
birthday TIMESTAMP,
createtime date
)
2,alter修改表结构
-- 新增一个字段
ALTER TABLE student ADD updatetime DATE;
-- 删除一个字段
ALTER TABLE student DROP updatetime;
-- 修改表字段的数据类型
ALTER TABLE student MODIFY sgender VARCHAR(1);
-- 修改表名
ALTER TABLE student RENAME to stu;
3,drop
-- 删除数据库的命令
DROP DATABASE db_test;
-- 删除数据表
DROP TABLE tb_test;
4.2DML语句
1.insert插入一行记录
--对所有字段依次赋值 VALUES和VALUE一样
insert INTO stu VALUES(1,'anne','m',16,'60','2000-10-09','2000-10-22 11:33:20');
--一次插入多条记录
insert into stu values(1,'anna','m',16,'60','2000-10-09','2000-10-22 11:33:20'),(2,'lisa','n',15,'50','1999-10-2','2020-04-04');
--指定字段插入
insert into stu(sid,sname)values(3,'tom');
2.删除行记录
--指定条件删记录
DELETE FROM stu where sid = 3;
3.更新记录内容
--修改表记录内容
UPDATE stu SET age = 19,score = 99 WHERE sid = 1;
4.3DQL语句
1.基本语法
select selection_list(查询的字段)
from table(表名称)
where condition(筛选的条件)
group by grouping_cloumns(分组条件)
having condition(分组后的记录进行条件筛选)
order by cloumns(对结果进行排序)
limit (对记录总数进行限定);
- select 后面可以是表中的字段 常量值 表达式 函数 ;查询的结果是一个虚拟的表格
2.基础查询
--*查询所有的行和列
select * from stu;
-- 指定字段进行查询
SELECT sid,sname FROM t_stu;
3.按条件查询
按条件表达式筛选,> < = != <= >= <>
逻辑表达式 && || and or not
模糊查询 like between and (not between and ) in (列表中的值不支持通配符) is null(not is null)
--指定某一个人条件进行查询
SELECT * FROM stu WHERE sid=2;
--AND是两个条件都要满足
SELECT * FROM stu WHERE sid=2 AND sage=18;
--OR是两个条件满足一个就行
SELECT * FROM stu WHERE sid=2 or score>60;
--查询sid为1,6,8的记录 属于某个集合
SELECT * FROM stu WHERE sid in (1,6,8);
--查询sid不属于1,5,7的记录
SELECT * FROM stu WHERE sid not in (1,5,7);
--查询记录某个字段为null
SELECT * FROM stu WHERE sgender is null;
SELECT * FROM stu WHERE updatetime is NOT null;
--查询成绩在70-80之间的记录
SELECT * FROM stu WHERE score BETWEEN 70 AND 80;
--性别非男的记录
SELECT * FROM stu WHERE sgender <>'m';
SELECT * FROM stu WHERE sgender !='m';
4.模糊查询,处理字符类型
%:表示0或多个字符
_:任意一个字符
--名字由三个字母组成
select * from stu where sname like '___';
--名字由j开头
select * from stu where sname like 'j%';
--第二个字母为a的学生记录
select * from stu where sname like'_a%';
--查询名字中包含字母a的记录
select * from stu where sname like '%a%';
5.去重查询&起别名
--查询学生表中的所有性别 DISTINCT去重
select distinct sgender from stu;
--给查询出来的字段起别名 as可以省略 使用空格
select age as 年龄,sname 姓名 from stu;
6.排序order by子句可以跟单个字段 多个字段 表达式 函数 别名
--查询所有学生记录 按成绩进行降序排序
--缺省是ASC升序(默认)
select * from stu order by score desc;
select * from stu order by score;
--查询所有学生记录,首先按照成绩排序,如果成绩相同。按照姓名进行排序
select * from stu orer by score desc,sname asc;
7.组函数/聚合函数/分组函数
用作统计使用 又称为聚合函数或者统计函数或者组函数
- 聚合函数是用来做纵向运算的函数
- count:统计指定列不为null的记录行数;一般使用count(*)统计行数
- max:计算指定列的最大值,如果指定列是字符串类型那么使用字符串排序运算
- min:计算指定列的最小值 如果指定列是字符串类型 那么使用字符串排序运算
- sum:计算指定列的数值和,如果指定列类型不是数值类型 那计算结果为0
- avg:计算指定列的平均值,如果指定列类型不是数值类型,那么计算结果为0
- sum , avg一般处理数值型
- max,min,count可以处理任意数据库类型
- 分组函数都忽略了null值,可以和distinct搭配使用
组函数可以出现多个,但是不能嵌套;如果没有group by 子句,结果集中所有行数作为一组
--查询stu表中有生日的人数,统计指定列不为null的记录行数
select count(birthday) a from stu;
--select 200+null
select count(sid)总人数 from stu;
select count(*) 总人数 from stu;
select count(1) 总人数 from stu;
--查询表中成绩大于60的人数
select count(score>60) from stu;
select * from stu where score>60;
--统计月薪与佣金之和大于2500的人数 ifnull(表达式1,表达式2) 如果表达式1为null 那么取表达式2的值否则取表达式1的值
select* from emp where sal+ifnull(comm,0)>2500;
--查询有佣金且有领导的人数;
select count(comm) from emp where mgr is not null;
--查询所有学生的成绩和
select sum(score) 总成绩 from stu;
--查询所有学生的成绩和,年龄和
select sum(score) 总成绩,sum(sage) 年龄和 from stu;
--查询所有员工月薪+佣金和
select sum(sal+ifnull(comm,0)) 总工资 from emp;
select sum(sal)+sum(ifnull(comm,0)) 总佣金 from emp;
--统计所有学生的平均成绩
select avg(score) 平均成绩 from emp;
--查询最高成绩和最低成绩
select max(score),min(score) from emp;
8.group by 分组查询
查询出来的字段要求是group by 后的字段,查询字段中可以出现组函数
group by 后面可以跟聚合函数 可以起别名
--查询每个年龄段的总成绩和成绩
select sum(score) ,age from stu group by age;
--查询每个年龄段的年龄和人数
select count(*),age from stu group by age;
--查询每个年龄段的年龄以及每个年龄段成绩超过60分的人数
select age count(1) from stu where score>60 group by age;
按多个字段分组,后面字段一致的为一组
--按成绩分组
select count(*),score from stu group by score;
--按成绩和年龄分组
select count(*) ,score,age from stu group by score,age;
9.having 子句
where是对分组前进行过滤;having是对分组后进行过滤
where中不能出现分组/聚合函数,having中可以出现
where是比分组先执行的, having是在分组之后执行的;
having后面可以跟别名
--查询工资总和大于8000的部门编号以及工资总和
select deptno , sum(sal)from emp group by deptno having sum(sal)>8000;
--having 中使用别名
selec t deptno,sum(sal) 总薪资 from emp group by deptno having 总薪资>8000;
--查询部门员工个数大于3的,having中使用了别名
select count(1) cc, deptno from emp group by deptno having cc>3;
10.limit
--第一位表示起始索引位置,第二位表示总的长度;分页中会使用
select * feom emp limit 1,5;
select * from emp limit 5 ; 等价于select * from emp limit 0,5;
select 完整语法使用
--查询部门编号 工资总数allsum 工资大于1500 按部门分组 总工资大于7000,按总工资降序排序
select deptno,coount(1),sum(sal) allsum from emp where sal>1500 group by deptno having sum(sal)>7000 order by allsum desc limit 2;
11.多表查询/关联查询
内连接
- 多表等值连接的结果是多表的交集部分,N表连接,至少需要N-1个连接条件,没有顺序要求,一般起别名
- 非等值连接,只要不是等号连接的都是非等值连接
外连接,有主表有从表,主表肯定会显示完整的内容
- 左外连接,以左表为主
- 右外连接,以右表为主
--查询员工信息,要求显示员工号,姓名,月薪,部门名称
--笛卡儿积(a,b)(123) --(a,1)(a,2)(a,3)(b,1)(b,2)(b,3)-->会生成一个中间表
--多表查询,关联条件使用的是等号
--查询员工信息,要求显示员工号,姓名,月薪,部门名称 等值连接
select empno,ename,sal,emp,deptno,dmame from emp,dept where emp.deptno=dept,deptno;
--给表起别名
select empno,ename,sal,a.deptno,dname from emp a, dept b where a.deptno=b.deptno;
--非等值连接
--查询员工信息,要求显示:员工号,姓名,月薪,薪水的级别
select empno,ename,sal,grade from emp e,salgrade sa where e.sal between sa.losal and sa.hisal;
--外连接
--查询员工信息,要求显示员工号,姓名,月薪,部门名称 使用内连接和等值连接等同
select empno,ename,sal,emp.deptno,dname from emp inner join dept on emp.deptno= dept.deptno;
--左外连接 左边的表内容全部显示 ,右边的表没有的以null进行填充
select empno,ename,sal,emp.deptno,dname from emp left join dept on emp.deptno =dept.deptno;
--右外连接,右表内容全部显示,左表没有的以null进行填充
select empno,ename,sal,emp.deptno,dname from from emp right join dept on emp.deptno=dept.deptno;
on后面的条件和where条件的区别:
ON条件:是过滤两个连接表笛卡儿积形成中间表的约束条件
where条件:在没有on 的单表查询中,是限制物理表或者中间查询结果返回记录的约束。在两表或者多表连接中是限制链接形成最终中间表的返回结果的约束
建议:
ON只进行连接操作,where只过滤中间表的记录
12.自连接
通过别名,将同一张表视为多张表;同一张表中某个字段要去关联另一个字段
--查询员工姓名和员工的老板的名称
select e1.empno,e1.ename,e2.empno,e2.ename from emp e1,emp e2 where e1.mgr=e2.empno;
13.子查询
--查询工资为20号部门平均工资的员工信息
select * from emp where sal>(select avg(sal) from emp group by deptno having deptno =20);
5.约束
为了数据的正确性
1.非空约束 一定要给值才能插入
create table a (
id int not null,
sname varchar(20)
)
--提示错误filed 'id' doesn't have a default value
--未给值
2.唯一性约束
create table b(
id int not null unique
)
3.默认约束 给一个默认值
create table c(
id int not null unique default 6,
sname varchar(3)
)
4.主键约束,主键列自动增长,无需赋值,表格必须要有一个主键
create teble stu(
--主键自增
sid int primary key auto_increment,
sname varchar(10) not null,
sgender char(1),
score float(4,1) not null,
birthday timestamp,
stuud vachar(30) unique
)
5.外键约束
外键代表着另外一张表的主键
alter table stu add constraint fk_cid foreign key(courseid) references course(id);
标识列:自增长列
一个表只能有一个标识列
set auto_increment_ increment=2;
--查看标识列的起始和步长
show varitables like '%auto_increment%';
4.常用函数
注意:MySQL中的+就只有运算符的功能;会试图将字符型数值转换为数值型的在进行操作,转换失败则转换为0,若其中有null则结果为null;字符串可以使用concat函数拼接;
1.字符函数:
- length(str)得到的是字节个数 utf8中中文是三个字节
- concat()拼接字符串
- upper/lower(str)
- substr,substring截取字符串MySQL的索引是从1开始的,截取的是字符长度
- instr(str,substr)返回上字串第一次出现的索引,找不到则为0
- trim()
- replace替换全部符合的
sql中的索引你是从1开始的
--字符串连接
select concat('java','sun','aa');
--length()
selec t length(sname),sname from stu;
--指定位置插入
select insert ('javasun',5,3,'oracle');
--字符长度
select char_length('java');
--左填充
select ipad('java',9,'bb');
--右填充
select rpad ('java',9,'bb');
--去除前后空格
select trim (' ja va ');
--重复指定次数
seled=ct repeat('ja',4);
--字符串替换
select replace('javaoror','or','sun');
--截取字串
select substring(’javasun',5,3);
2.数学函数
- round() 四舍五入
- ceil()向下取整
- floor()向下取整
- truncate()截断
- mod()取余
--去绝对整
select abs(-32);
--向上取最小整数
select ceil(3.2);
--向下取最大整数
select floor(3.2);
select floor(score),score from stu;
--取余数
select mod(21.3);
--得到0-1之间的随机值
select ranbd();
--有两位小数的四舍五入值
select round(5.7888.,2);
--截断,小数位保持2位
select truncate(5.679888,2);
3.日期函数
- now()返回日期+时间
- curdate()返回系统日期不包含时间
- curtime()返回当前时间,不包含日期
- year()年 获取指定的年 month() mothname()
- str_to_date 将字符通过指定的格式转换为日期
- date_format将日期转换为字符
- datediff返回两个日期相差天数
--当前日期,当前时间,日期和时间
select curdate(),curtime(),now();
--指定日期是一年中的第几周
select week(now());
--返回指定日期的年份
select year(now());
--返回指定时间的小时
select hour(now(),hour(curtime()));
--返回date的月份名
select monthname(now());
--计算时间相隔的天数
select datediff('2021-1-15','2021-1-12');
select datediff(now(),'2021-1-10');
--把日期按指定的格式进行转换
select date_format(now(),'%y-%m-%d %h:%i:%s');
--把字符串转日期
select str_to_date('2021-1-12','%y-%m-%d');
--将毫秒数转换为时间格式
select from_unixtime(646545353);
4.其他函数
- version()版本号
- user()当前账号
- if(exp1,exp2,exp3)如果exp1为true,取exp2的值 否则取exp3的值
select version();
select user();
select database();
select if(10>2,'10','2')a;
5.数据库范式
- 第一范式(1NF)用来确保每列的原子性,要求每列(或者每个属性值)都是不可再分的最小数据单元(也称为最小的原子单元)。
- 第二范式(2NF)在第一范式的基础上再进一层,要求表中的每列都与主键相关,即要求实体的唯一性,如果第一个表满足第一范式,并且除了逐渐以外的其他列全部都依赖于该主键,那么该表满足第二范式。
- 第三范式(3NF)在第二范式的基础上再进一层,第三范式是确保每列都和主键列直接相关,而不是间接相关,即限制列的冗余性。如果一个关系满足第一范式,并且除了主键以外的其他列都依赖于主键列,列于列之间不存在相互依赖关系则满足第三范式。
数据库存储引擎:在MySQL中的数据使用不同的存储技术存储在文件或内存中
--查看MySQL中所用到的存储引擎
show engines;
innodb是支持事物的
而myisam,memory不支持事务
6.数据库事务
1.什么是事务
-
Transaction
-
事务:一个最小的不可再分的工作单元;通常一个事物对应着一个完整的业务(例如银行账户转账业务,该业务员就是一个最小的工作单元)
-
一个完整的业务需要批量的DML(insert,update,delete)语句共同联合完成
-
事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同。
2.事务四大特性ACID
-
原子性(Atomicity):事务不可分割的最小工作单元,事务内的操作要么全做,要么全不做。事物具有要么成功,要么全部失败,折就是原子性。
-
一致性(Consistency):在事务执行前数据库的数据处于正确的状态,需事务执行完之后数据库的书库依然处于正确的状态,及数据完整性约束没有被破坏;如A和B转账,不管转账是否成功,转账之后A和B的帐户总额和转账之前是相同的
-
隔离性(Isolation):当多个事务处于并发访问同一个数据库资源时,事务之间相互影响,不同的隔离级别决定了各个事务对数据资源访问的不同行为
-
持久性(Durability):事务一旦执行成功,他对数据库的数据的改变是不可逆的
在业务逻辑层,一个service层可能需要执行一次或多次增删改操作,如果这期间发生了异常,数据库事务不回滚的话数据库的数据就会不完整,举个例子,订单信息中包含订单明细,现在在保存订单的业务逻辑方法中,先保存订单成功了,再保存订单明细,如果保存订单明细的过程中失败了,肯定希望之前的保存订单数据回滚
3.和事务相关的语句
- 开启事务 start transaction
- commit:提交
- rollback:回滚
4.事务何时开启何时结束
开始标志:任何一条DML语句(insert,update,delete)执行,标志着事务的开启
结束标志:
提交:成功的结束,将所有DML语句操作历史和底层硬盘数据来一次同步
回滚:失败的结束,将所有DML语句操作历史记录全部清空
5.事务和底层数据库的关系
在事务进行过程中,未结束之前,DML语句是不会更改底层数据,只是奖励是操作记录一下,在内存中完成记录。只有在事务结束的时候,而且是成功的结束的时候,才会修改底层硬盘文件中的数据。如果执行失败,则清空所有的历史操作记录,不会对底层硬盘文件中数据做出任何修改
--开启事务
START TRANSACTION;
INSERT INTO stu(sid,sname) VALUES(5,'jerry');
--此命令执行会出错?
INSERT into stu(sid,sname) VALUES(6,'TOM','aa');
--提交 出错之后会rollback
COMMIT;
6.在MySQL中事务的提交和回滚
在MySQL中,默认情况下,事务是自动提交的,也就是说,只要执行一条DML语句就开启了事务,并且提交了事务,自动提交机制是可以关闭的,可以由程序控制何时提交。
7.事务的隔离级别
不同事务之间具有隔离性,隔离级别分为四个:
读未提交:read uncommitted(读到未提交的数据)
事务A和事务B,事务A未提交的数据,事务B可以读到
这里读到的数据叫做“脏数据”(已经被改动的数据)
这种隔离级别最低,这种级别一般是在理论上存在,数据库隔离级别一般都高于该级别
读已提交:read committed(读到已提交的数据)
事务A和事务B,事务A提交的数据,事务B才可以读取到
这种隔离级别高于读未提交
这种级别可以避免“脏数据”
这种隔离级别会导致“不可重复读取”,(事务B两次读取到的数据不一致)
这是Oracle默认隔离级别
可重复读:repeatable read
事务A和事务B,事物A提交之后的数据,事务B读取不到
事物B是可重复读取数据
这种隔离级别高于读已提交
换句话说,对方提交之后的数据我还是读取不到
这种隔离级别可以避免”不可重复读取“,达到可重复读取
这是MySQL默认级别
虽然可以达到可重复读取,但是会导致”幻读“(A把所有数据都清空了,B在这个时候修改了其中一条数据,当A结束后发现还存在一条数据,就好像产生了幻觉一样,这就是幻读)
串行化:serializable
事务A和事务B,事务A在操作数据库的时候,事务B只能排队等待
这种隔离级别很少使用,吞吐量太低,用户据体验差(特别的慢)
这种级别可以避免”幻读“,每一次读取的都是数据库中真实存在的数据,事务A与事务B串行而不并发
show variables like 'transaction_isolation';