JAVAEE基础
2021-07-25 21:52:03 17 举报
AI智能生成
JAVAEE基础
作者其他创作
大纲/内容
Java概述
什么是程序
程序为了模拟现实世界,解决现实问题的一系列的有序的指令的集合
Java的历史
1996年推出JDK1.0
2004年推出1.5,更名为5.0,添加了一系列的功能
2014年推出了8.0,添加了一系列的功能
Java语言的特点
面向对象
简单性
跨平台
Java的执行原理
计算机的执行原理
编译执行:效率高,不能跨平台
解释执行:效率低,可以跨平台
Java语言的执行原理
先编译(将.java源文件编译成.class字节码文件),再解释执行
名词解释
JVM
Java虚拟机,跨平台的关键
JDK
Java开发工具包,包含开发环境和运行环境
JRE
Java运行环境
第一个程序
第一个程序
javac命令用来将.java文件编译成.class文件
java命令用来运行.class文件
注意:使用java命令运行.class文件时,不能写后缀名,例如:java HelloWorld
带包编译
编译时需要带包编译,命令为:javac -d . HelloWorld.java
运行时需要带包运行,命令为:java first.HelloWorld
编码规范
规定
标识符(类名、函数名、变量名等)由字母、数字、下划线、$组成,而且不能以数字开头
不能使用关键字和保留字
约定俗成的规范
标识符命名尽量有意义
类名使用帕斯卡(Pascal)模式,每个单词首字母大写
函数名、变量名使用驼峰式(camel)式,第一个单词首字母小写,后面每个单词首字母大写
包名全小写
常量全大写,用下划线_隔开
注释
单行注释
注释只能用在一行
多行注释
注释可以使用多行
文档注释
注释可以生成文档,并且在其他位置调用时可以有文档提示
DOS命令
更换盘符: 盘符:
查看目录内容:dir
进入文件夹: cd
进入上一层文件夹:cd ..
清空屏幕:cls
删除文件:del 文件名
删除文件夹:rd 文件夹名
退出:exit
语言基础
变量
变量的概念
内存是存储数据的空间。
变量是内存中保存数据的一个单位。
三要素
数据类型:表示该变量能够存储的数据的范围
变量名:该变量的名称
值:该变量最终存储的内容
隐形要素:地址,表示该变量在内存存放的位置。
注意:变量之所以称为变量,是因为保存的数据是可以发生变化的。
数据类型
基本数据类型
整数
byte:字节类型,大小为1个字节,范围为-2^7 ~ 2^7-1
short:短整数类型,大小为2个字节,范围为-2^15 ~ 2^15-1
int: 整型,大小为4个字节,范围为-2^31 ~ 2^31-1
long: 长整型,大小为8个字节,范围为-2^63 ~ 2^63-1
注意:直接写的数字默认是int类型,如果要给long赋值,并且值超出了int类型,需要在后面加个字母L(大小写都可以)
小数
float:单精度浮点数(小数),4个字节
负数部分:-3.4 * 10^38 ~ -1.4 * 10^-45
正数部分:1.4 * 10^-45 ~ 3.4 * 10^38
double:双精度浮点数(小数),8个字节
注意:小数默认是double,如果要定义一个float,需要在后面加个字母F。
注意:1、float只有4个字节,long有8个字节,float是以指数的形式表示,而且精度低,所以范围实际上比long大
注意:如果数字在项目中需要精度,或者整数是一个很大的数字,可以使用BigDecimal类型
布尔
布尔类型,只有一个字节,范围只有两个值,true和false
字符
char
引用数据类型
字符串
String
数组
其他自定义类型
运算符
算术运算符
int n5 = n1 * n2; // 乘法
int n3 = n1 / n2; // 整数除以整数结果还是整数,结果是2
int n4 = n1 % n2; // 求模,即余数,结果是1
一元运算符
m1++; // 自增1
m2--; // 自减1
注意:int m4 = m3++ + 5; // 当++、--在后时,先参与运算,再自操作。当++、--在前时,先自操作,将结果参与运算
赋值运算符
n1 = n1 + 3; // 简写为 n1 += 3; 自增3
n2 -= 3; // n2 = n2 -3;
n3 *= 3; // n3 = n3 * 5;
n4 /= 3; // n4 = n4 / 3;
n5 %= 3; // n5 = n5 % 3;
关系运算符
> 大于
< 小于
>= 大于等于
<= 小于等于
== 等于
!= 不等于
逻辑运算符
&&
并且(与),同时满足true才会为true,短路与
||
或者(或),满足其一就会是true,短路或
!
非,对结果取反
注意
代码中&&和&结果是一样的,||和|结果也是一样的,区别在于,当只有一个符号时,会计算两边表达式的值,不会短路
短路是指如果前面一个表达式能够决定整个表达式的值,那么后面一个表达式不运算
三元运算符
String s = m > 3 ? "成立" : "不成立";
选择和分支结构
选择结构
基本if结构
语法
if(条件-布尔表达式){
// 代码块
}
// 代码块
}
流程
如果布尔表达式返回true,则执行代码块
if-else 结构
语法
if(条件-布尔表达式){
// 代码块1
}else{
// 代码块2
}
// 代码块1
}else{
// 代码块2
}
流程
如果条件成立,则执行代码块1,否则则执行代码2
if-else if -else 结构
语法
if(条件1-布尔表达式){
// 代码块1
}else if(条件2){
// 代码块2
}else if(条件3){
// 代码块3
}else{
// 代码块4
}
// 代码块1
}else if(条件2){
// 代码块2
}else if(条件3){
// 代码块3
}else{
// 代码块4
}
流程
如果条件1成立,则执行代码块1,否则再来判断条件2,成立则执行代码块2,否则再判断条件3,成立则执行代码块3,否则执行代码块4
注意:代码自上而下执行,只会执行一个分支
嵌套if结构
语法
if(条件1){
// 代码块1
if(条件2){
}else{
}
}else{
// 代码块2
}
// 代码块1
if(条件2){
}else{
}
}else{
// 代码块2
}
流程
如果条件1成立,则执行语句块1,否则执行代码块2,在执行代码块1时,发现又有选择,继续判断条件2,按照if的流程继续往下执行
switch分支结构
语法
switch(变量或者表达式){
case 值1:
// 代码块1
[break;]
case 值2:
// 代码块2
[break;]
case 值n:
// 代码块n
[break;]
default:
// 默认代码块
[break;]
}
case 值1:
// 代码块1
[break;]
case 值2:
// 代码块2
[break;]
case 值n:
// 代码块n
[break;]
default:
// 默认代码块
[break;]
}
注意
在case时,值不能相同
在if中的条件可以写大于小于等于等多种条件,但是case中只能判断等于
在case时,如果不满足条件,继续向下判断,如果条件满足,会执行里面的内容,如果里面的内容没有break关键字,会继续向下执行,不会判断
switch中变量的类型只能是byte、short、int、char、String[JDK7+]
局部变量
在方法中定义的变量称为局部变量
局部变量需要先赋值后使用
局部变量的作用域范围是在它所对应的大括号里,并且要定义后才能使用
局部变量在有重复的作用范围时,不能重复定义(重名)
循环
while循环
语法
while(条件){
// 代码块
}
// 代码块
}
流程
先判断条件,如果条件成立,则执行代码块,执行完毕继续判断条件,条件成立时继续执行代码块,直到条件不成立则结束
循环过程的四要素
初始变量
循环条件
循环内容(循环体)
条件的变更
do-while循环
语法
do{
// 代码块
}while(条件);
// 代码块
}while(条件);
流程
先执行一次代码块,然后判断条件,如果条件成立则继续执行代码块,直到条件不成立为止
while与do-while区别
while先判断条件,再执行代码块,可能一次都不执行
do-while先执行一次代码块,再判断条件,至少执行一次
for循环
语法
for(初始化变量;条件;迭代变量变更){
// 代码块
}
// 代码块
}
流程
1、初始化变量(执行一次)
2、判断条件
3、循环内容
4、条件变更
5、再判断条件...(循环2、3、4直到条件不成立)
2、判断条件
3、循环内容
4、条件变更
5、再判断条件...(循环2、3、4直到条件不成立)
流程控制
break
直接终止当前循环,整个循环结束
continue
跳过当次循环(continue后面的代码不执行),继续进行下一次循环
嵌套循环
循环中包含其他的循环
流程
外层循环执行一次,里层循环全部执行后,外层再执行第二次,里层循环继续全部执行。
简单来说,双重嵌套循环,总循环次数等于外层循环次数乘以里层循环次数。
简单来说,双重嵌套循环,总循环次数等于外层循环次数乘以里层循环次数。
注意
在嵌套循环时,使用break或continue只对当前循环有效。
方法
方法的基本使用
概念
实现特定功能的一段代码,可以反复使用
方法的定义
语法
public static void 方法名(){
// 需要重复执行代码块
}
// 需要重复执行代码块
}
注意
方法名的定义需要遵循标识符的定义规则。
作用
当程序中有重复的代码块,可以放到一个方法中,以方便扩展和维护。
简化流程的理解过程。
简化流程的理解过程。
方法的返回值
语法
public static 返回值类型 m1(){
return 值;
}
return 值;
}
注意
在一个方法中,return后面不应该有代码,因为return就表示方法结束
在一个方法中,不能仅用有条件的方式来返回,必须在所有的情况下都能返回
当返回值类型为void时,在方法中可以没有return,也可以使用return作为方法的结束,用来终止嵌套循环。此时return后面直接加分号。
方法的多级调用
概念
方法的多级调用是指,在方法中调用方法,通常用来流程简化。
递归
概念
在方法中再次调用当前方法,称为递归调用
注意
循环只需要使用一个方法栈空间,所以消耗空间小,执行速度快,递归需要使用多次方法栈空间,要谨慎使用。
进制
进制概念
计算机中二进制表示数字对于人类来说太长了,一般情况下,通常使用8进制或者16进制来简短的描述2进制
进制的转换
十进制转换成二进制(倒除法)
52采用倒除法计算除以2的余数,反向统计为110100
二进制转换成十进制
99== 9 * 10 + 9
任何进制转换成十进制规则
110100 = 1 * 2的二次方 + 1 * 2的四次方 + 1 * 2的5次方 = 4 + 16 + 32 = 52
注意
在Java中,书写2进制使用0b开头,8进制用0开头,十六进制用0x开头
二进制的运算
与运算
计算规则:同为1则为1,否则为0
3 & 5
0000 0011
0000 0101
-----------
0000 0001
0000 0011
0000 0101
-----------
0000 0001
或运算
计算规则:只要有一个为1,结果为1
3 | 5
0000 0011
0000 0101
-----------
0000 0111
0000 0011
0000 0101
-----------
0000 0111
非运算
使用~作为取反(非)运算符
计算规则:表示各位取反,0变1,1变0
异或运算
^作为异或运算符。
计算规则,相异为1,相同为0。
任何数与另外一个数字异或两次得到该数字本身。
小技巧:可以不用第三个变量实现两个整数的交换。
位运算(位移)
>>向右位移。移动n位相当于除以2的n次方
<<向左位移。移动n位相当于乘以2的n次方。低位补0
注意
性能比乘除高得多。
注意
如果在逻辑判断中使用&或者|,在判断结果上与&&或者||是一样的,但是&&或者||有短路的特征。而&或者|一定要计算两边的结果。
原码、反码、补码
原码
直接通过10进制转换成二进制之后的编码。
例如:3 ,原码为0000 0011
注意
负数的表示方式,在最高位使用符号位1来表示,其他位与正数方式一致。
例如:-3,原码表示为:1000 0011
反码
将负数的反码设计为:符号位不变,其他各位取反。对于正数来说,反码与原码一致。
-3 原码表示为:1000 0011,使用反码1111 1100
补码
正数原码反码补码一致,负数补码等于反码+1
-3 原码表示为:1000 0011,使用反码1111 1100,使用补码1111 1101
浮点数十进制转换成二进制
整数部分按照整数的方式转换。小数部分乘以2,减1
3.2
----------------------------------------------------------
11.0011001100110011001100110011001100110011
----------------------------------------------------------
11.0011001100110011001100110011001100110011
这就是导致浮点数不精确的原因
数组
数组的概念
一组连续的存储空间,存储数据类型相同的一组数据
特征:连续空间、相同类型、长度固定
创建
int[] a = new int[5]; // 创建5个元素大小的int型数组
数组的默认值
数字,默认值都是0
整数都是0
小数都是0.0
char是0对应的ascii码
布尔默认值是false
字符串的默认值是null
整数都是0
小数都是0.0
char是0对应的ascii码
布尔默认值是false
字符串的默认值是null
创建方式
先声明,再分配空间,再赋值,再使用
int [] a; // 声明
a = new int[5]; // 再分配空间
a[0] = 10; // 赋值
a = new int[5]; // 再分配空间
a[0] = 10; // 赋值
声明的同时分配空间
int [] a1 = new int[10];
声明并赋值复杂方案
int [] a2 = new int[] {3,5,8,9,10};
声明并赋值简单方案
int [] a3 = {2,56,8,9,10};
数组的扩容
概念
1、创建一个超过原来数组大小的新数组
2、将原数组中的元素复制到新数组中
3、将要添加的新元素添加到新数组中
系统提供的方法
System.arraycopy(src, 0, dest, 0, src.length);
参数说明:源数组,从那里开始复制,目标数组,从那里开始存储,复制长度
int[] newArr = Arrays.copyOf(arr1, arr1.length + arr2.length);
参数说明:源数组,新数组的长度,返回一个新数组
值传递和引用传递
值传递,将变量中的值传递了其他变量,通常来说基本数据类型都是值传递
引用传递,在引用数据类型中,存储是对应的元素的位置,在传递时,只传递了位置信息
可变长参数数组
语法:类型后面加... 相当于数组,是一种特殊的数组(例:String... arr)
优点:是在调用时,可以不传参,也可以传多个参,不能构建成数组
缺点:1、只能作为方法的参数
2、只能作为方法的最后一个参数
3、在方法中只能使用一个可变长参数
2、只能作为方法的最后一个参数
3、在方法中只能使用一个可变长参数
数组排序
冒泡排序
int [] arr = {12, 35, 9, 55, 34, 4, 11, 8, 38};
// 外层循环控制循环轮次,特点是每一轮可以将最值交换到数组的最后,应该循环的次数为长度-1
for (int i = 0; i < arr.length - 1; i++) {
// 里层循环两两比较,根据条件决定是否交换,循环完毕会将最值交换到最后,
// 里层循环次数越往后越少,所以第0轮循环长度-1次,第1轮循环长度-2次,所以特点应该:长度-外层轮数-1
for (int j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j+1]) { // 由小到大排序
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
}
}
// 外层循环控制循环轮次,特点是每一轮可以将最值交换到数组的最后,应该循环的次数为长度-1
for (int i = 0; i < arr.length - 1; i++) {
// 里层循环两两比较,根据条件决定是否交换,循环完毕会将最值交换到最后,
// 里层循环次数越往后越少,所以第0轮循环长度-1次,第1轮循环长度-2次,所以特点应该:长度-外层轮数-1
for (int j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j+1]) { // 由小到大排序
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
}
}
选择排序
int [] arr = {12, 35, 9, 55, 34, 4, 11, 8, 38};
// 外层循环循环轮次,此时应该是长度-1
for (int i = 0; i < arr.length - 1; i++) {
// 定义一个变量记住当前下标(轮次)
int index = i;
// 里层循环每次选择出最值对应的下标,循环从下标的后一位开始比较,记住该下标
for (int j = i + 1; j < arr.length; j++) {
if(arr[j] < arr[index]) {
index = j;
}
}
// 在里层循环结束后会得到一个下标,判断该下标是否轮次的值,如果不是,则将轮次对应下标元素与最值下标对应的元素交换
if(index != i) {
arr[index] = arr[index] ^ arr[i];
arr[i] = arr[index] ^ arr[i];
arr[index] = arr[index] ^ arr[i];
}
}
// 外层循环循环轮次,此时应该是长度-1
for (int i = 0; i < arr.length - 1; i++) {
// 定义一个变量记住当前下标(轮次)
int index = i;
// 里层循环每次选择出最值对应的下标,循环从下标的后一位开始比较,记住该下标
for (int j = i + 1; j < arr.length; j++) {
if(arr[j] < arr[index]) {
index = j;
}
}
// 在里层循环结束后会得到一个下标,判断该下标是否轮次的值,如果不是,则将轮次对应下标元素与最值下标对应的元素交换
if(index != i) {
arr[index] = arr[index] ^ arr[i];
arr[i] = arr[index] ^ arr[i];
arr[index] = arr[index] ^ arr[i];
}
}
插入排序
int [] arr = {5, 12, 35, 9, 55, 34, 4, 11, 8, 38, 6};
// 循环轮次,由于每轮解决一个数字,所以一共需要数组长度-1轮
for (int i = 0; i < arr.length - 1; i++) {
// 从当前轮次后面一个数字开始判断,定义一个变量记住当前数字的值,定义一个变量记住空出来的位置
int n = arr[i+1];
int index = -1;
// 从该数字之前一个数字开始循环比较,依次向前循环,
for (int j = i; j >= 0; j--) {
// 如果发现该数字比记住的数字小(假定由小到大排序),该数字向后移动,并且记住该数字空出来的位置
if(n < arr[j]) {
arr[j+1] = arr[j];
index = j;
}
}
// 里层循环完毕后,检查是否有空位置被记住,如果有,将记住的数字插入到该空位置处
if(index != -1) {
arr[index] = n;
}
}
// 循环轮次,由于每轮解决一个数字,所以一共需要数组长度-1轮
for (int i = 0; i < arr.length - 1; i++) {
// 从当前轮次后面一个数字开始判断,定义一个变量记住当前数字的值,定义一个变量记住空出来的位置
int n = arr[i+1];
int index = -1;
// 从该数字之前一个数字开始循环比较,依次向前循环,
for (int j = i; j >= 0; j--) {
// 如果发现该数字比记住的数字小(假定由小到大排序),该数字向后移动,并且记住该数字空出来的位置
if(n < arr[j]) {
arr[j+1] = arr[j];
index = j;
}
}
// 里层循环完毕后,检查是否有空位置被记住,如果有,将记住的数字插入到该空位置处
if(index != -1) {
arr[index] = n;
}
}
JDK自带排序
int [] arr = {5, 12, 35, 9, 55, 34, 4, 11, 8, 38, 6};
Arrays.sort(arr);
Arrays.sort(arr);
二维数组[了解]
面向对象
面向对象的概念
万事万物皆为对象
对于程序来说,主要研究对象的属性和方法
属性:也叫特征,一般是名词,例如:颜色、大小等
方法:也叫行为,一般是动词,例如:走,跑,吃等
面向对象的编程和面向过程的编程
面向对象的编程
简称为OOP,面向对象的思想一般指在研究项目时,采用研究相应的业务有哪些对象参与,以及对象有哪些属性和方法,在执行方法时与其他对象之间的关联。面向对象的编程是指用面向对象的思想去思考,并且写对应代码。可以解决复杂的项目业务问题。
面向过程的编程
是指在研究项目时,考虑项目执行步骤,每一步如何完成,以此写出的代码就是面向过程的编程。一般应用底层开发(系统),以及业务较为简单的项目。
类的概念
类是指从多个对象中抽取出相同的特征和行为的一个抽象的概念。
类和对象的关系
类是对象的模板(抽象)
对象是依据类创建的实例(可以有多个)
属性又叫做实例变量
在创建了对象后会有默认值,与前面学习的数组默认值一样
实例变量(属性)与局部变量的区别
定义位置:实例变量定义在类的内部,与方法并列的。局部变量定义在方法的内部。
默认值:实例变量有默认值,与数组类似。局部变量需要先赋值后使用。
使用范围:实例变量在整个类中基本都能使用。局部变量只能在定义的方法中使用。
命名冲突:局部变量可以与实例变量重名,在方法中使用时默认使用局部变量。
实例方法
类中没有static关键字的方法是实例方法。
方法的重载
概念
方法的重载是指在类中,可以定义相同名词的方法
规定
1、方法名词相同
2、参数列表不同(类型,个数,顺序)
重载与访问修饰符(public等),返回值类型、参数的变量名称、异常的声明无关。
好处
在方法调用时,只需要传入相应类型的参数即可,无需考虑方法名称上的差异。
注意
在方法调用时,优先考虑传入参数类型相符的方法,如果没有,会考虑自动转换类型能够匹配的方法。
构造方法
构造方法基本定义
构造方法是通过new关键字调用的方法,用来创建对象。
构造方法与类名相同,没有返回值,不是void,所以构造方法一般以大写字母开头。
当类没有定义构造方法时,会默认有一个无参的构造方法。
构造方法的重载
在一个类中,可以写多个重载的构造方法。
有参构造方法的作用是用来强制用户在创建对象时必须给关键属性赋值。
有参构造方法的作用是用来强制用户在创建对象时必须给关键属性赋值。
注意
类中默认有个无参的构造方法,如果显示的定义了任何构造方法,那么原来默认的无参构造方法就会失效。
this关键字的用法
概念
this一般指代当前对象
用法
1、在实例方法或构造方法中,如果定义了局部变量名称与属性相同,可以通过this.属性来调用属性。
2、在一个实例方法中,如果想要调用其他的实例方法,可以使用this.方法名(),此处this可以省略。
3、在构造方法中,如果想调用类中定义其他构造方法,可以使用this关键字。
面向对象的三大特征
三大特征
封装、继承、多态
封装
概念
封装分为属性封装和方法封装。
属性封装
将属性设置为私有,提供公有的访问方法(getter和setter),在访问方法中,对属性的赋值或取值进行限制。
方法的封装
封装的作用是将需要被外部访问的方法暴露(公有),将不需要外部关注和使用的方法私有。
继承
继承的概念
继承在Java中使用extends关键字来描述,表示扩展。
Java中的继承一般指is a关系,例如:狗 is a 动物,所以狗继承自动物,具备用动物的所有的特征和行为,那么动物是父类,狗是子类。
继承的特点
继承只能单继承(继承一个直接父类),但是可以多级继承(父类继承其他父类,那么子类就间接继承)
不可继承
构造方法, 因为需要与类名相同,所以不可继承。
无法访问的(private,默认的在不同包的子类中)视为不可继承。
访问修饰符
本类 本包 非本包子类 其他
private √
默认的 √ √
protected √ √ √
public √ √ √ √
private √
默认的 √ √
protected √ √ √
public √ √ √ √
方法的重写
概念
在子类中,可以定义一个与父类中的方法完全一样的方法,称为方法的重写(override)
原则
1、方法名称和参数必须与父类完全一样。
2、返回值类型必须与父类完全一样。
3、异常声明必须与父类完全一样。
4、访问修饰符可大不可小。
注意
为了防止在方法重写时单词拼写错误导致重写失败,可以使用@Override注解来检查。
经典面试题
重载(overload)和重写(override)的区别
1、重载可以发生在一个类中,而重写至少需要两个类。
2、重载要求方法名称相同,但是参数列表不同,其他的不关注。但是重写严格得多,要求除了访问修饰符以外的其他都要相同。
3、重载实际是两个方法,而且都可以调用。但是重写属于一个方法的覆盖,子类的对象只能调用重写后的方法。
4、构造方法只能重载,不能重写。
2、重载要求方法名称相同,但是参数列表不同,其他的不关注。但是重写严格得多,要求除了访问修饰符以外的其他都要相同。
3、重载实际是两个方法,而且都可以调用。但是重写属于一个方法的覆盖,子类的对象只能调用重写后的方法。
4、构造方法只能重载,不能重写。
super关键字
概念
super关键字表示父类的对象
使用方式
super.属性,来调用父类的属性
一般情况下,是子类有与父类相同的属性,此时,直接调用属性,会调用子类的属性,使用super可以调用父类的属性。
super.方法,来调用父类的方法
一般情况下,是子类重写父类的方法后,只是需要添加一些子类方法的代码,还需要接着使用父类的方法中的代码,此时可以在子类的方法中使用super调用父类的方法。
super(),来调用父类的构造方法
调用构造方法
子类在调用构造方法时,会自动调用父类的默认的无参构造方法。
当父类没有无参构造方法时,子类没有办法调用到默认的无参构造方法,此时会报错,需要手动调用有参构造方法。
无论子类构造方法中有没有调用父类的构造方法,都会调用父类的构造方法。如果使用super()调用父类的构造方法,一定要放到代码的第一行。
继承过程中的对象创建流程
当创建子类对象时,会先创建父类的对象。
1、分配相应的空间
2、创建父类的对象
a、初始化父类的属性
b、调用父类的构造方法
3、创建子类对象
a、初始化子类的属性
b、调用子类的构造方法
1、分配相应的空间
2、创建父类的对象
a、初始化父类的属性
b、调用父类的构造方法
3、创建子类对象
a、初始化子类的属性
b、调用子类的构造方法
所有的类最顶层的父类是Object,默认继承(隐式)自Object。
多态
程序中的多态是指可以使用父类的引用指向子类的对象。
注意
由于采用父类的引用去指向子类的对象,所以父类的引用只能调用父类的属性和方法。
如果想要调用子类的特有属性和方法,需要强制转换类型(向下转型,拆箱),如果转换的类型不对,会出现ClassCastException异常(类型转换异常)
此时应该使用instanceof关键字来判断类型。
如果想要调用子类的特有属性和方法,需要强制转换类型(向下转型,拆箱),如果转换的类型不对,会出现ClassCastException异常(类型转换异常)
此时应该使用instanceof关键字来判断类型。
栈和堆
栈空间很小,由系统管理,一般的局部变量,方法调用等都在栈中完成,所以递归层次较深时,会出现栈溢出。
堆空间比较大,一般存放对象,new关键字创建出来的对象和数组一般都存在堆里。
三个修饰符
abstract
指抽象的
修饰类
表示该类不能创建对象。
有一些抽象的概念所对应的类,例如动物类,本不应该创建对象,但是可能会由于失误创建了该对象,
为了限制此问题的出现,那么可以给类加上此关键字,作用是让该类不能被创建对象
有一些抽象的概念所对应的类,例如动物类,本不应该创建对象,但是可能会由于失误创建了该对象,
为了限制此问题的出现,那么可以给类加上此关键字,作用是让该类不能被创建对象
修饰方法
表示该方法不能直接实现,需要在子类中重写实现。
因为有些方法在父类中实现没有意义,只有在各个不同的子类中去实现,那么此时可以给该方法添加此关键字
因为有些方法在父类中实现没有意义,只有在各个不同的子类中去实现,那么此时可以给该方法添加此关键字
注意
抽象类中可以没有抽象方法,但是抽象方法所在的类必须是抽象类
static
指静态的(类的),可以修饰属性或方法
修饰属性
表示该属性为静态属性(类属性),该属性在类中只有一份,所有的对象共享一份
注意: 由于类属性整个类只有一份,所有的对象共享,所以一般推荐使用类名来直接操作,不需要创建对象。
修饰方法
表示该方法为类方法,应该通过类名直接调用,不需要创建对象。
注意:
1、static方法中可以访问其他的static方法,也可以使用static属性,
但是不能调用实例方法,也不能访问实例属性, 也不可以使用this或super关键字,原因是此时没有创建对象。
2、实例方法中可以访问实例属性,类属性,this或super关键字,也可以调用实例方法,类方法。
1、static方法中可以访问其他的static方法,也可以使用static属性,
但是不能调用实例方法,也不能访问实例属性, 也不可以使用this或super关键字,原因是此时没有创建对象。
2、实例方法中可以访问实例属性,类属性,this或super关键字,也可以调用实例方法,类方法。
修饰语句块
static修饰的语句块叫做静态语块,没有static修饰的叫做动态语句块
作用
语句块的作用一般用来初始化属性值或者完成一些对象创建时就需要完成的操作。
静态语句块是类加载时执行,动态语句块是创建对象时执行,所以静态语句块中只能操作静态属性和静态方法。
代码创建过程的执行顺序
先执行静态的内容:
- 父类静态属性初始化
- 父类的静态代码块
- 子类的静态属性初始化
- 子类的静态代码块
后执行非静态内容,先执行父类的非静态内容,再执行子类。
- 父类实例变量初始化
- 父类的动态代码块
- 父类的构造方法
- 子类的实例变量初始化
- 子类的动态代码块
- 子类的构造方法
- 父类静态属性初始化
- 父类的静态代码块
- 子类的静态属性初始化
- 子类的静态代码块
后执行非静态内容,先执行父类的非静态内容,再执行子类。
- 父类实例变量初始化
- 父类的动态代码块
- 父类的构造方法
- 子类的实例变量初始化
- 子类的动态代码块
- 子类的构造方法
final
final关键字表示最终的
修饰类
该类不可以被继承
修饰方法
表示该方法不能被重写
修饰变量
表示该变量赋值后不能被重新赋值,由变量变成了常量
接口
接口的定义规则
接口里面定义的所有属性都是public static final的。
接口里面只能定义抽象方法。
接口中不能定义构造方法,代码块。
接口中可以定义默认方法
接口的基本使用
在Java中类只能单继承,但是可以实现多个接口
注意
1、如果同时继承类并实现接口,那么继承类要放在前面
2、实现多个接口使用逗号隔开
3、当继承类并同时实现接口时,如果接口中有一个抽象方法与父类中某个方法声明完全一致,
在子类中可以不实现该方法。
2、实现多个接口使用逗号隔开
3、当继承类并同时实现接口时,如果接口中有一个抽象方法与父类中某个方法声明完全一致,
在子类中可以不实现该方法。
接口的其他用法[了解]
接口中也可以定义静态方法和默认方法(JDK的高版本添加)
不能定义实例方法,可以定义静态方法,使用接口名调用
常用类
内部类
概念
内部类是指在一个类的内部定义的类。
分类
成员内部类[了解]
概念:在类里面直接定义的内部类,使用方式与成员变量相似。
使用
在成员内部中,如果属性与外部类属性同名,优先调用内部类属性,可以使用外部类的名称.this去调用外部内的属性。
在成员内部中,不可以定义静态属性,在外部内中,也不能直接调用内部类的属性和方法。需要创建对象才能调用。
静态内部类
概念:在类中定义一个static的类,用法与静态属性或静态方法相似
局部内部类[了解]
概念:在类的方法中定义的类
使用
与该方法的使用方式一致。而且只能在该方法中使用,所以该内部类不能使用public来修饰。
匿名内部类
直接使用接口或者抽象类来直接创建对象,在创建对象的过程中实现其未实现的方法
使用
本质与局部内部类用法相似。
Object类
概念
Object类是所有类的父类,是最顶层的父类,所有的类直接或间接继承了Object类
如果类继承了一个类,则间接继承了Object,如果没有继承任何类,则默认直接继承了Object。
所有类都具备有Object中定义的方法。Object类中没有定义属性
方法
getClass方法
getClass方法是final的,所以不能重写,该方法表示返回对象所对应的实际类型。
作用
显示当前对象所对应的类型
hashCode方法
根据对象的地址或字符串或数字计算出来的10进制结果。
要保证相同对象返回相同的hashCode,尽量保证不同对象返回不同的hashCode。
系统默认是采用对象地址作为hashCode值,如果项目中需要判断比较对象是否相同,
应该重写hashCode方法,以保证使用对象唯一标识来判断对象是否相同,
而不是采用地址直接比较。
应该重写hashCode方法,以保证使用对象唯一标识来判断对象是否相同,
而不是采用地址直接比较。
toString方法
当对象使用println输出时或将对象当作字符串拼接时,会自动调用toString方法,
此时toString默认会显示该类型(getClass)加上地址(hashCode),
此内容一般没有意义,可以自己重写toString方法,将对象信息返回。
此时toString默认会显示该类型(getClass)加上地址(hashCode),
此内容一般没有意义,可以自己重写toString方法,将对象信息返回。
finalize方法
主要用于垃圾回收。
1、由系统管理,不需要手动调用。
2、垃圾回收是定时回收。
3、System.gc()方法是手动调用垃圾回收,
但是只是通知系统进行垃圾回收,并非直接回收。
2、垃圾回收是定时回收。
3、System.gc()方法是手动调用垃圾回收,
但是只是通知系统进行垃圾回收,并非直接回收。
equals方法
equals方法是用来判断两个对象是否相同。
默认是判断地址是否相同,应该重写该方法,以达到根据id标识判断的逻辑结果。
默认是判断地址是否相同,应该重写该方法,以达到根据id标识判断的逻辑结果。
重写的步骤
1、判断是否同一个地址。
2、判断obj是否为空。
3、判断是否同一个类型。
4、强转类型。
5、判断id标识是否相同。
2、判断obj是否为空。
3、判断是否同一个类型。
4、强转类型。
5、判断id标识是否相同。
包装类
包装类是指基本数据类型所对应类,有八种
分类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
在JDK1.5之后实现了自动装箱和拆箱
包装类常用的一些内容
Integer.MAX_VALUE; // 最大值
Integer.toHexString(n4); // 显示十六进制字符串
Double.parseDouble(s);使用对应的包装类的parseXXX方法
整数缓冲池(常量池)
对于Integer,系统预先创建了256个对象,范围是-128~127之间,作用是可以共享对象,节约内存。
字符串
字符串是一个final的类,底层使用一个private final char[]来保存字符串值
字符串常量池
与整数常量池相同的是,都可以共享使用,以节省空间
与之不同的,整数常量池在IntegerCache类加载后,就创建了256个对象,以共享使用,
而字符串值太多,无法直接一开始就创建,
所以是在第一次使用该字符串的字面量值时在常量池创建,并且提供共享引用。
而字符串值太多,无法直接一开始就创建,
所以是在第一次使用该字符串的字面量值时在常量池创建,并且提供共享引用。
常用方法
charAt()
String s = "Hello";
// 得到某个下标位置的字符
char ch = s.charAt(0); // h
// 得到某个下标位置的字符
char ch = s.charAt(0); // h
contains()
// 判断字符串是否包含另一个字符串
boolean b = s.contains("ell");
boolean b = s.contains("ell");
toCharArray()
// 将字符串转换成数组
char [] arr = s.toCharArray();
char [] arr = s.toCharArray();
indexOf()
indexOf(String)指在一个字符串中查找另一个字符串首次出现位置,如果没有,返回-1
indexOf(String,int)指在一个字符串中从指定位置开始查找另一个字符串首次出现位置,如果没有,返回-1
length()
得到字符串的长度
注意与数组区分,数组是length属性,而字符串是length()方法
trim()
去掉字符串两边的空格,中间的空格不会去掉,原字符串是不变的,需要接收该方法的返回值
toUpperCase()
字符串转换成大写
toLowerCase()
字符串转换成小写
endsWith()
// 判断字符串是否以另一个字符串结尾
String s3 = "photo.png";
boolean b1 = s3.endsWith(".png");
String s3 = "photo.png";
boolean b1 = s3.endsWith(".png");
replace()
// 将字符串中的一个字符串全部替换成另一个字符串
String s4 = "hellohello";
s4 = s4.replace("ell", "AAA");
String s4 = "hellohello";
s4 = s4.replace("ell", "AAA");
split()
// 将字符串根据另一个字符串切割成数组
String s5 = "武汉-黄石-宜昌-孝感";
String [] arr1 = s5.split("-");
String s5 = "武汉-黄石-宜昌-孝感";
String [] arr1 = s5.split("-");
substring()
截取字符串中的一部分
String s6 = s5.substring(3); // 一个参数表示截取后面所有
String s7 = s5.substring(3, 5); // 两个参数表示从哪里开始(包含),截取哪里为止(不包含)
concat()
拼接字符串
String s8 = "hello,";
String s9 = "world";
String s10 = s8.concat(s9);
String s9 = "world";
String s10 = s8.concat(s9);
可变字符串
概念
String类由于不可变,每次对其操作,必须接收返回值,操作需要更多的空间。
如果项目中需要大量操作字符串的,为了节约空间,提升效率,推荐使用可变字符串。
可变字符串是直接在字符数组中操作的。除非需要扩容,否则不会浪费额外的空间。
如果项目中需要大量操作字符串的,为了节约空间,提升效率,推荐使用可变字符串。
可变字符串是直接在字符数组中操作的。除非需要扩容,否则不会浪费额外的空间。
分类
StringBuilder
JDK5.0添加的,线程不安全,但效率高
StringBuffer
JDK1.0就有的,线程安全(几乎所有的方法上都直接使用synchronized关键字),但效率低
方法
append()
添加(拼接)字符串
delete()
删除
sb.delete(3, 5); // 包含3,不包含5,一共删除两个字符
replace()
修改
sb.replace(3, 5, "AAAAAA");
insert()
插入
sb.insert(3, "bbbbb"); // 从下标3的位置开始插入后面的字符串
toString()
将可变字符串变成普通字符串
BigDecimal
两种用法
浮点数精确运算
大数字运算
方法
subtract()
减法
add()
加法
multiply()
乘法
divide()
除法
日期相关类
方法
new Date()
得到当前时间
getTime()
得到当前时间的毫秒数
new Date(1620715603615L)
根据时间毫秒数创建一个时间对象
before()
比较两个时间
boolean b =now.before(before); // 是否早
compareTo()
比较两个时间
int n =now.compareTo(before); // now比before早返回-1,晚返回1,同返回0
currentTimeMillis()
得到当前系统的毫秒数
long mill = System.currentTimeMillis();
getInstance()
不通过new,而通过下面的方式创建对象,当前时间的日历
Calendar c = Calendar.getInstance()
add()
c1.add(Calendar.DATE, 3); // 得到3天后的日历,负数表示之前
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
日期格式化
yy表示两位年,yyyy四位年
MM月,mm分
dd日 ss秒
hh 12小时制小时,HH24小时制的小时
SSS毫秒 a上午下午
MM月,mm分
dd日 ss秒
hh 12小时制小时,HH24小时制的小时
SSS毫秒 a上午下午
format()
将日期格式化成指定的字符串格式
Date d1 = new Date();
String s = sdf.format(d1);
String s = sdf.format(d1);
parse()
将规定格式的字符串转换成日期
String s1 = "2020-12-15 16:14:56";
Date d2 = sdf.parse(s1);
Date d2 = sdf.parse(s1);
System
System是系统类
方法
arrayCopy
数组复制
currentTimeMillis
得到当前时间的毫秒数
exit(0)
退出当前程序
gc()
请求垃圾回收
System.in
系统输入属性
System.out
系统输出属性
集合
集合的概念
集合是类似于数组,存储对象的容器,定义了对容器中对象的增删改查遍历等一系列方法的类。位于java.util包中。
集合与数组的区别
1、数组长度固定,集合长度不固定。
2、数组可以存储基本数据类型和引用数据类型,集合只能存储引用数据类型。
注意
由于JDK5.0后有自动装箱拆箱,所以向集合中添加基本数据类型也是正确的,本质上是将其自动装箱,变成了包装类的对象。
Collection集合
特点
Collection是一个集合的顶层接口。定义了存储多个对象的集合,具备有基本的增删改查等方法。
List和Set是Collection的子接口。
List中元素是有下标,可重复的。而Set中的元素无下标,不可重复。
Collection父接口中的方法
add、addAll、clear、remove、contains、size、isEmpty、toArray
Collections类的使用
概念
Collections类是集合的工具类,提供了集合的一些操作,例如:排序,反序,打乱等。
方法
// 将数组转换成集合
List list = Arrays.asList(1,2,3,4,5,6,7,8,9);
List list = Arrays.asList(1,2,3,4,5,6,7,8,9);
// 随机打乱元素顺序
Collections.shuffle(list);
Collections.shuffle(list);
// 排序
Collections.sort(list);
Collections.sort(list);
// 反序
Collections.reverse(list);
Collections.reverse(list);
List子接口
特点
List子接口继承自Collection接口,具备有Collection接口的特点。
而且自身具备有序、有下标、元素可重复等特点。
而且自身具备有序、有下标、元素可重复等特点。
方法
get(index)、add(index, object)
List接口的常用实现类
ArrayList
特点
插入、删除速度比较慢。遍历比较快。
方法
// 根据下标删除元素
list.remove(0);
list.remove(0);
// 根据对象删除元素
list.remove(Integer.valueOf(3));
list.remove(Integer.valueOf(3));
获取元素的个数
list.size()
list.size()
// 通过下标获取元素
list.get(1)
list.get(1)
// 添加元素
list.add(3);
list.add(3);
LinkedList
特点
插入、删除速度比较快,遍历比较慢。链表有单向和双向之分。添加元素时往首尾添加性能更优。
方法
和ArrayList方法一样
list.removeFirst();
list.removeLast();
Vector
Vector与ArrayList基本实现方式一致,只是大部分方法前面加上了synchronized关键字,
表示加锁,实现线程安全,性能极低,基本不使用。
表示加锁,实现线程安全,性能极低,基本不使用。
Set接口
特点
特点是无序、无下标,元素不可重复。
HashSet
底层使用HashMap,HashSet中添加的元素直接放到HashMap的key上,
所以元素不能重复(map的key不能重复),无序,无下标。
所以元素不能重复(map的key不能重复),无序,无下标。
方法
add()
添加元素
remove()
删除元素
LinkedHashSet
使用了链表的HashSet,是有序的
LinkedHashSet底层是继承了HashSet,使用了HashSet中创建了LinkedHashMap的构造方法。
LinkedHashMap相对于HashMap仅仅是多添加了一个链表,用来记录添加元素的顺序。
LinkedHashMap相对于HashMap仅仅是多添加了一个链表,用来记录添加元素的顺序。
TreeSet
TreeSet实现了排序功能
Comparable接口
要求添加的元素必须使用Comparable接口,
通过比较返回负数、正数、零来进行排序,
负数排左边,正数排右边,零当作重复的元素去掉
通过比较返回负数、正数、零来进行排序,
负数排左边,正数排右边,零当作重复的元素去掉
Map接口
特点
Map接口采用键值对(key-value)的方式存储数据
key不能重复,value可以重复,如果key重复,value会覆盖。
方法
put(key, value); // 设置值
get(key); // 获取值
keySet(); // 获得所有的key
values(); // 获得所有的value
entrySet(); // 遍历时使用的entry(键值对)的集合
Hashtable
Hashtable代码与HashMap基本一样,只是大部分方法前面加上了synchronized关键字,
表示加锁,线程安全,但是性能很低,基本不使用。
表示加锁,线程安全,但是性能很低,基本不使用。
Properties
继承了Hashtable,添加了对资源文件操作的内容,一般用来在系统初始化时加载资源文件。
方法
// 通过文件路径读取文件内容
InputStream inStream = TestMain1.class.getResourceAsStream("/db.properties");
InputStream inStream = TestMain1.class.getResourceAsStream("/db.properties");
// 直接加载文件中的内容到集合中
prop.load(inStream);
prop.load(inStream);
TreeMap
特点
有序
有序是指,保留添加顺序。
无序
无序是指,不保留添加顺序。
排序
排序是指,添加后按照指定的规则进行排序。
Comparable接口
TreeMap会将添加的元素按照key排序,所以key对应的类型应该实现Comparable接口。否则会报错。
异常
异常的概念
异常就是指程序运行过程中,出现的不正常的情况,一般会导致程序运行终止。例如:除数为0。
异常处理的必要性
出现异常的原因并非是bug,大部分情况是由用户造成的(例如:文件格式不正确,路径不存在等)
如果程序中不处理,会导致程序终止,所以应该在程序中对异常出现的情况进行提醒或处理。
如果程序中不处理,会导致程序终止,所以应该在程序中对异常出现的情况进行提醒或处理。
异常的分类
Throwable
Throwable是所有的异常的顶级父类。
Error
Error是指系统错误,例如内存不足,硬件问题等,此类问题程序无法解决,所以不做异常处理
Exception
Exception才是需要处理的异常
运行时异常(RuntimeException)
运行时异常一般是程序员写的bug,只需要检查一下,加个判断就能解决。
所以又称为未检查异常(非受检异常),此类异常在程序中不强制要求处理。
所以又称为未检查异常(非受检异常),此类异常在程序中不强制要求处理。
已检查异常(CheckedException)
通常是用户操作可能引发的,程序员没办法使用代码避免此类异常的出现,
所以在程序中强制必须处理。所以称为已检查异常,又称为非运行时异常。
所以在程序中强制必须处理。所以称为已检查异常,又称为非运行时异常。
异常处理
在运行时异常出现时,可以不处理异常。但是如果代码中包含有已检查异常,必须进行异常处理。
使用throws和throw处理
throws
throws表示抛出异常,会将异常抛给调用者,自己不需要处理。
语法
语法是在方法声明时使用throws关键字声明要抛出的异常,如果有多种异常,可以用逗号隔开。
throw
如果在处理业务的过程中,需要以异常的形式提醒调用者,那么此时可以手动抛出异常。
语法
throw 异常对象
注意
throws抛出异常是最简单异常处理方式,但是该方式要根据实际情况选择,当异常产生的原因是由调用者造成的(参数格式问题等),那么该方法本身是无法解决此问题的,应该抛给调用者解决,但是如果原因不是调用者造成的,而且调用根本不能解决此问题,则没必要抛出。
try-catch-finally
try
try是尝试运行可能出现异常的代码
catch
catch是出现异常后,执行此处的代码,catch可以出现多次,Exception类型只能在catch最后一个
finally
finally表示一定会执行,如果有return,在返回之前执行。。尽量保证逻辑不相关的代码不要在一个try中。
自定义异常
通常是在项目中要抛出一个业务异常时,需要一个异常类来描述该业务。
需继承自Exception即可
多线程
进程和线程
概念
一般情况下,一个应用程序在运行的时候会有一个进程。
线程是轻量级的进程,一个进程中可以有多个线程,同一个进程的线程之间可以共享数据。
线程的组成
1、抢占的CPU的时间片。
2、运行过程中的数据,堆的数据共享,每一个线程都有独立的栈空间。
3、线程执行的代码(任务)
线程的基本创建
继承Thread类
方法
// 得到当前线程的名称
String name = Thread.currentThread().getName();
String name = Thread.currentThread().getName();
start()
启动线程
实现Runnable接口
经典面试题
start方法和run方法的区别
start方法表示启动线程,包括执行线程中任务。
run方法是定义线程中需要执行的任务,不能直接调用
继承Thread类和实现Runnable接口的区别
继承Thread类,
优点是使用方便,创建对象后可以直接调用start。
缺点是不能再继承其他的类。
优点是使用方便,创建对象后可以直接调用start。
缺点是不能再继承其他的类。
实现Runnable接口,缺点是创建对象后,还需要创建Thread类的对象,
才能调用start方法,比较麻烦。优点是还可以继承其他的类。
才能调用start方法,比较麻烦。优点是还可以继承其他的类。
线程的状态
基础状态
new(创建)
线程被创建后,进入此状态。
start(就绪)
线程调用start方法后,进入就绪状态,此时并不意味着就开始执行了,还需要等待抢占CPU时间片。
running(运行)
当线程抢占到CPU的时间片时,就会执行任务(run方法中的业务代码),
CPU的时间片到期后,会继续转入就绪状态。
CPU的时间片到期后,会继续转入就绪状态。
terminate(终止)
当线程中的任务执行完毕后,进入终止状态。
线程执行过程中的方法
sleep方法
表示当前正在执行的线程进入休眠状态,等待休眠状态时间结束后,会进入就绪状态。
yield方法
此方法表示在线程运行过程中,放弃当前执行的时间片,进入就绪状态。
join方法
此方法表示将A线程join(合并)到B线程中,此时B线程会暂停执行,
直到A线程执行完毕后,B线程才会进入就绪状态。
直到A线程执行完毕后,B线程才会进入就绪状态。
线程状态
等待状态:可能都会造成线程的阻塞
sleep:让当前线程进入休眠状态,需要满足休眠时间结束才会继续进入就绪状态。
wait:让当前线程进入等待状态,需要等待被其他线程唤醒才会进入就绪状态。
yield:让当前线程从运行状态进入到就绪状态。
join:让当前线程暂停,让其他线程先行。
阻塞状态:线程同步时,没有抢到锁,被迫进入了阻塞状态。
线程安全
导致线程不安全的原因
1、多线程并发操作。
2、共同操作(写操作)同一个共享的临界资源。
同步锁机制
将要操作的临界值加锁,让其他线程等待(阻塞)当前线程对临界值的操作完毕后,才可以继续执行。
使用synchronized加锁
语法
synchronized(锁){} // 注意,锁应该唯一,而且需要是Object,不能是基本数据类型。
public synchronized void(){} 对方法进行加锁,锁的是调用者。
线程间的通信
wait
表示当前线程进入等待状态,等待notify唤醒,有参数的表示超时时间,超过时间还没被唤醒会自动醒来。
notify
表示唤醒使用wait等待的线程,一次只能随机唤醒一个
notifyAll
一次性唤醒所有的线程
注意
使用wait和notify、notifyAll时,必须在同步(synchronized)代码块中执行。
否则会出现java.lang.IllegalMonitorStateException异常。
否则会出现java.lang.IllegalMonitorStateException异常。
经典面试题
sleep和wait的区别
1、sleep是休眠,需要指定时间,时间结束后进入就绪状态。而wait是等待,需要被notify唤醒,设置时间也只是超时时间。
2、sleep休眠时持有(不释放)锁,wait等待时释放锁。
死锁
当多个线程在处理共享资源,使用锁时,如果出现同时都持有一把锁,
并且需要另一把锁才能继续执行,没有另一把锁,绝不会释放当前锁时,
就会出现死锁。
并且需要另一把锁才能继续执行,没有另一把锁,绝不会释放当前锁时,
就会出现死锁。
设计模式
23种设计模式,共分为3大类
1、创建型模式,用来创建对象的(5种)。
例如:单例模式、原型模式、工厂模式
例如:单例模式、原型模式、工厂模式
2、结构型模式,针对于多个对象之间组成一种新的结构(7种)。
例如:桥接模式、门面模式、适配器模式
例如:桥接模式、门面模式、适配器模式
3、行为型模式,多个对象之间产生行为处理的方式(11种)。
例如:调停者模式、命令模式、监听者模式、迭代模式
例如:调停者模式、命令模式、监听者模式、迭代模式
线程的终止
使用方法
stop()[已废弃,不推荐]
Interrupt()
使用系统的interrupt标识来停止线程。
线程的优先级
优先级分为10级,最高10,最低1,默认5。
线程的优先级,优先级越高的线程将优先抢到时间片
线程饿死
当一个线程一直抢不到时间片来执行,称为线程饿死。
守护线程
setDaemon(true)
守护线程是一个特殊的线程,当其他的线程都执行完毕后,守护线程也会终止。
线程池
指管理大量线程的容器。需要创建任务(Runnable、Task)对象,来使用池中线程对象来执行任务,如果池中线程已经用完,会进行等待。
线程池的基本使用
创建线程池
创建一个无上限的线程池
ExecutorService service = Executors.newCachedThreadPool();
ExecutorService service = Executors.newCachedThreadPool();
创建一个单线程的线程池
ExecutorService service = Executors.newSingleThreadExecutor();
ExecutorService service = Executors.newSingleThreadExecutor();
创建一个4个线程的线程池
ExecutorService service = Executors.newFixedThreadPool(4);
ExecutorService service = Executors.newFixedThreadPool(4);
submit()
将任务交给线程池去执行
shutdown()
关闭线程池
Callable接口和Future接口
Callable接口的返回值并不是立即就能得到的,需要线程任务执行完毕,
所以会返回一个Future接口的对象,通过该对象的get方法才能获取结果
所以会返回一个Future接口的对象,通过该对象的get方法才能获取结果
Callable接口与Runnable接口作用相似,都可以作为线程池的任务,
但是Runnable接口没有返回值,
当需要获取返回值时就需要使用Callable接口。
但是Runnable接口没有返回值,
当需要获取返回值时就需要使用Callable接口。
Lock和ReentrantLock
Lock接口
Lock接口是jdk5添加的功能
对比synchronized
synchronized是由jvm来完成加锁和解锁的过程,Lock可以自由的加锁解锁,更加灵活
lock()加锁
unlock()解锁
tryLock()尝试获取锁
ReentrantLock重入锁(公平锁)
private static ReentrantLock lock = new ReentrantLock();
ReentrantReadWriteLock读写锁
当有一个线程在写时,会互斥,当所有的线程都在读时,不互斥,共享读取,以在保障线程安全的前提下提升性能。
用于读多写少的任务
// 使用读写锁
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ReadLock readLock = lock.readLock();
private WriteLock writeLock = lock.writeLock();
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ReadLock readLock = lock.readLock();
private WriteLock writeLock = lock.writeLock();
volatile关键字用法
synchronized关键字,可实现线程安全。
1、可见性。多线程访问共享变量时,如果一个线程对其进行修改,其他的线程能够立即发现改变后的值。
2、互斥性。多线程访问共享变量时,如果一个线程对其进行操作(读写),其他线程都会等待其操作结束。
volatile关键字只能保证线程的可见性,不能保证互斥性,所以并不能保证线程安全。
volatile使用在属性前,表示该属性具备线程的可见性。
private volatile String name1 = "a";
线程安全的集合
CopyOnWriteArrayList
加强版的读写锁实现,可以读写分离。
写有锁,读无锁,每次写的时候都会复制一个副本,写入后替换原来的地址。
CopyOnWriteArraySet
底层以CopyOnWriteArrayList实现,就是添加时使用的addIfAbsent()会遍历集合,
如果已经存在该元素,则抛弃该副本,不存在则添加。
如果已经存在该元素,则抛弃该副本,不存在则添加。
ConcurrentHashMap[面试的一个重点]
JDK1.8之前,默认将其分为16段(segment),对每一段进行加锁,
理论上来说,如果有16个元素,分别放到16段里,那么基本没锁。
理论上来说,如果有16个元素,分别放到16段里,那么基本没锁。
JDK1.8之后,采用的CAS机制,即只对当前添加的对象加锁,通过CAS交换算法来修改数据。
Queue
队列,先进先出,FIFO原则,First In First Out
ConcurrentLinkedQueue
性能最好的线程安全的队列。采用CAS交换原则。
BlockingQueue
增加了两个无限期等待的方法。
put():添加,如果没有空间则等待。
take():获取,如果没有元素则等待。
主要用来实现生产消费模式。
ArrayBlockingQueue
有界队列,使用数组实现,事先在构造时先确定队列空间大小。
LinkedBlockingQueue
无界队列,使用链表实现。
0 条评论
下一页