目录
Chp1—Java基础
java发展史
1995年 sun发布了Java
java的优势
-
简单
-
纯面向对象
-
开源
-
跨平台
配置环境变量
-
安装JDK(最起码安装8.0版本)
-
配置JDK:环境变量
-
JAVA_HOME:jdk的安装目录
-
作用:告知计算机JDK的位置
-
-
Path:jdk的bin目录
-
作用:告知计算机其他软件JDK命令的位置
-
-
CLASSPATH:.
-
作用:告知计算机编译生成的字节码文件的位置
-
.代表当前目录
-
-
-
名词解释
1.JDK: Java工具包
JRE+类库+调试内容+编译器
2.JRE:Java运行环境
JVM+解释器
3.JVM:Java虚拟机
作用为屏蔽各个操作系统的之间的底层差异 ,为跨平台提供支持。
java的运行机制
java源代码->
编译器
->字节码文件->(.clsss文件)->解释器->对字节码文件逐行解释逐行执行。
先编译,后解释执行
第一个Java程序
目标:输出helloword
新建一个后缀名为.java的程序
-
class:类
-
是盛放代码的容器
-
是程序的基本组成部分
-
-
类名:随便起(在语法要求范围内)
-
作用为区分各个类
-
同一源文件下的类不可重名
-
-
{}:划分类的边界
-
主函数:程序的入口
-
写法固定
-
一个类中只能存在0-1个主函数
-
所有想要运行的内容都必须写进主函数,自上而下。
-
-
输出语句:将某个内容展示到控制台查看
-
写法固定
-
加ln:输出内容独占一行,可以输出空值
-
不加ln:输出内容不会独占一行,无法输出空值
-
-
运行:
编译:javac 源文件名.java 执行:java 类名
-
每个类都会在编译完成后生成一个对应的字节码文件
-
字节码文件的文件名必须与对应的类保持一致
-
执行时执行的就是字节码文件
-
class Hello1{ public static void main(String[] args){ System.out.println("helloworld"); System.out.println("helloworld"); System.out.println(); //System.out.print(); System.out.print("helloworld"); System.out.print("helloworld"); } }
public class-公开类
public ckass 类名{ }
公开类的使用
1.公开类名需与源文件名保持一致
2.一个源文件中只能存在0-1个公开类
package-包
作用为整理归纳字节码文件
package 包名1.包名2.包名3....;
使用
1.必须写在有效代码的第一行
2.一个源文件至多声明一个包
3.建议包的书写三次起
package com.名字缩写.xxx;
4.带包编译和运行
编译:javac -d.源文件名.java 运行:java 包名.类名
声明package语句之后把必须带包编译和运行,否则该语句无效
注释
作用为给代码添加备注说明,特点为不参与编译运行
单行注释
只能注释一行内容
//这是单行注释
多行注释
/* 这是多行注释 这是多行注释 这是多行注释 */
文档注释
/** 这是文档注释 */
-
可以生成说明文档
-
需要写在类或方法的上方
-
只有公开类可以生成说明文档
-
生成说明文档 _代表空格
javadoc -d_._源文件名.java
命名规范
1.硬性规范
-
必须遵守
-
严格区分大小写
-
都不能以数字开头
-
符号只接收_和$(空格也属于符号)
-
无法使用关键字和保留字
-
2.软性规范
-
建议遵守
-
不以中文命名
-
类名采用大驼峰命名法(所有组成部分首字母都大写)(StudentClass)
-
方法名和变量名采用小驼峰命名法(从第二个组成部分开始首字母大写)(studentsAge)
-
包名全小写
-
常量全大写,多部分组成时_连接(常量-学生姓名:STUDENGT_AGE)
-
做到望名知意
-
黑窗口常用命令
命令 | 作用 |
---|---|
盘符名: | 进入指定盘符 |
cd 文件夹名 | 进入指定文件夹 |
cd .. | 返回上一层 |
dir | 查看直属子目录 |
cls | 清屏 |
今日掌握
-
环境变量的配置步骤
-
JDK及内部内容名词解释
-
java的运行机制
-
公开类的使用特点
-
包的使用特点
Chp2-变量和运算符
-
什么是程序
利用逻辑操作数据
变量的概念
是JVM内存中的一块存储空间,内部可以存放一个数据,是存储数据的基本单元
变量的组成
-
变量是由数据类型+变量名+数据组成
变量的创建
-
先声明后赋值
数据类型 变量名; 变量名 = 值;
//先声明 int num; //后赋值 num =100;
-
变量必须在赋值完成之后才能使用
-
-
声明的同时直接赋值 (比较常用)
数据类型 变量名=值; int num2 =200;
-
同时声明多个变量,其后一一赋值
数据类型 变量名1,变量名2,变量3....; 变量名1=值; 变量名2=值; ...
-
同时声明多个变量并直接赋值
数据类型 变量名1 = 值 1,变量名2 = 值2...;
-
同时声明的变量数据类型都要一致
-
数据类型
java是强类型的编码语言,意味着所有数据都有对应的数据类型
基本类型
整型:byte short int long
浮点型:float double
字符:char
布尔型:Boolean
整型
位(b):计算机中最小的单位 字节(B):计算机中最小单元 8b=1B 一字节(B)等于8比特(bit)
-
当long类型的取值超出了int的范围,则必须在末尾添加l或L
-
int为整数默认类型
浮点型
-
double是小数类型的默认类型
-
float类型赋值必须在末尾加F
布尔类型
-
无法参与数学运算,只能返回true或false
字符
-
单引号赋值:内部只能有一个字符
-
数字赋值:每个数字都有一个对应的字符形态,数字必须在取值范围内
-
Unicode编码赋值
-
可以参与数学运算,会自动转换为数字
A-Z:65-90 a-z:97-122 0-9:48-57
引用类型
-
只要不是基本类型就一定属于引用类型
String:字符串
String 变量名 = "值";
字符串内容须写进双引号
String对字符串内容以及数据不做要求
类型转换
从小到大
byte->short|char->int->long->float->double
boolean不参与类型排序与转换
自动类型转换
发生在小类型给大类型赋值时
大类型 变量名 =小类型; byte b =10; int n =b;//byte的值赋值给Int
-
所有数据类型都无法直接给char赋值,因为其取值无负数
强制类型转换
发生在大类型给小类型赋值时
小类型 变量名 = (小类型)大类型; byte b =(byte)int;
-
大类型的值在小类型的范围内,强转无异常
int n1 =127; byte b1 =(byte)n1; //127
-
大类型的值不在小类型的取值范围内,会出现数据紊乱
int n1 =128; byte b1 =(byte)n1 //-128
-
小数类型给整数赋值,会直接舍弃小数位,有可能导致数据丢失
double d =10.9; int n = (int)d; //10
-
任意内容加上双引号或者拼接双引号内容都会变成String类型(从左至右执行)
String s2 ="true"; String s3 = true +""; String s4 = "abc"+true+10+5.5; System.out.println(s2);//true System.out.println(s3);//true System.out.println(s4);//abctrue105.5 System.out.println("abc"+10+10);//abc1010 System.out.println(10+"abc"+10);//10abc10 System.out.println(10+10+"abc");//20abc
-
自动类型提升
当进行数学运算时,数据类型不同时,结果会自动提升为大类型,最低提升为int,最高提升为double
short s4 =1; byte b2 =10; short s5 =(short)(s4 + b2);//short+byte 提升为int
运算符
表达式:通过运算符将值或变量连接起来,最终可以得到一个结果,连接起来的式子称之为表达式
算数运算符
+,-,*,/,%
赋值运算符
= | 直接赋值 |
---|---|
+= | 先相加,在赋值 |
-= | 先相减,后赋值 |
*= | 先相乘,后赋值 |
/= | 先相除,后赋值 |
%= | 先取余,后赋值 |
赋值运算符在运算时不会发生自动类型提升
int n =10; n = n+0.5;//会发生自动类型提升double n+=0.5//不会发生自动类型提升,自动将double强转为int类型,结果为10
比较运算符
== | 等于 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
!= | 不等于 |
-
比较运算符结果一定为boolean类型
逻辑运算符
&& | 逻辑与,表示并且,双方都满足才为true,都为真才为真 |
---|---|
|| | 逻辑或,表示或者,只要有一方满足则为true,一个为真则为真 |
! | 逻辑非,表示取反,结果为原本结果的相反值 |
-
&& || 连接双方必须为布尔结果的表达式,对连接数量不做要求
布尔表达式1 && 布尔表达式2 布尔表达式1 || 布尔表达式2
-
!需要写在布尔表达式之前
!布尔表达式
-
结果都为布尔类型
-
&&和||内部存在短路机制
短路机制:当执行到能奠定最终结果的表达式时,后续表达式将不再执行
-
&&:执行到结果为false的表达式时短路
-
||:执行到结果为true的表达式时短路
&和|:非短路运算符,不存在短路机制
-
-
//短路机制 System.out.println(1==2 && 5/0==1);//f System.out.println(1==1 || 5/0==1);//t //非短路 System.out.println(1==2 & 5/0==1);//报错 因为都执行,不进行短路机制 System.out.println(1==2 | 5/0==1);
一元运算符
++ | 值加1,n++,相当于n=n+1; |
---|---|
-- | 值减1,n--,相当于n=n-1; |
++和--在前 | 变量值+1或-1,表达式的结果为运算之后的值 |
---|---|
++和--在后 | 变量值+1或-1,表达式的结果为运算之前的值 |
三元运算符
布尔表达式 ?结果1:结果2;
-
执行规则:布尔表达式结果为true执行结果1,否则执行结果2
-
一定且只会执行其中一个结果
转义字符
将特殊字符转为普通字符或将普通字符转为特殊字符
\字符
\n:用来换行,效果与System.out.println();一致 \t:用来生成一段制表符距离,效果与键盘TAB键一致
-
必须写进双引号
//特殊字符转普通字符 System.out.println(" \" ");//" //普通字符转特殊字符 // \n换行 \t一个制表符距离 System.out.println("举头望明月,\n低头思故乡"); //举头望明月, //低头思故乡 System.out.println("\t举头望明月,低头思故乡"); // 举头望明月,低头思故乡
Scanner-扫描器
作用为接收用户在控制台的输入
-
创建扫描器
java.util.Scanner sc = new java.util.Scanner(System.in);
-
给用户提示
System.out.println("提示")
-
接收用户输入
String : sc.next() int : sc.nextInt() double : sc.nextDouble() char : sc.next().chartAt(0)
空格和回车都代表用户输入结束
导包
作用为源文件一次性指明某个或某些类的来源
import 包名.类名;
使用
-
源文件第一个类的上方,package语句的的下方
-
该语句可以存在多个
-
必须截止至类
-
可以通过包名.*的方式直接引入某个包下的所有内容
-
只能引入包下直属内容,无法引入子包中的内容
-
-
一个导包语句至多使用一个*通配符
-
每个类中都默认导入了java.lang包
今日掌握
-
八大基本数据类型及其字节数
-
强制数据类型转换的时机和语法
-
什么是自动类型提升
-
逻辑运算符的作用
-
什么是短路机制
-
一元运算符和三元运算符的使用
-
Scanner的使用
-
导包的使用
Chp-3分支和循环
if分支
语法
-
当布尔表达式判断结果为true,则执行操作语句
if(布尔表达式){ //操作语句 }
if-else分支
语法
-
判断布尔表达式,如果为true则执行if的操作语句,否则执行else的操作语句
if(布尔表达式){ //操作语句1 }else{ //操作语句2 }
-
该分支结构一定并且只会执行其中一个分支操作语句
多重if分支
if(布尔表达式1){ //操作语句1 }else if(布尔表达式2){ //操作语句2 }else if(布尔表达式3){ //操作语句3 }else{ //else的操作语句 }
-
多重if分支是一个结构只会执行其中一个操作语句
-
自上而下的判断布尔表达式
-
else必须写在最下方
-
只要逻辑完整else-if可以存在多个
-
else部分可以省略
嵌套if分支
if(布尔表达式){ if(布尔表达式){ } }
-
嵌套分支对嵌套位置不做要求,if、else、else-if大括号内都可嵌套其他分支
-
对嵌套层数不做要求
switch分支
用于对变量或者表达式进行等值分支判断
switch(变量|拥有具体值结果的表达式){ case 值1: //操作语句1 break; case 值2: //操作语句2 break; ... default: //最终的操作语句 }
使用
-
case从上往下判断
-
所有case都不满足,执行default
-
break用于跳出switch分支,结束分支执行
-
当匹配到满足的case之后,剩余的case将不再判断
-
多个case可以共用同一个操作语句
-
default可以省略
-
default可以不写在最下方,default需要添加break
-
只有最后一个语句可以省略break
-
default永远在所有case都不满足的时候执行,与他的书写位置无关
-
-
只能判断byte,short,char,int,String(JDK7.0)
局部变量
位置:定义在方法以内的变量
作用范围:从定义行开始,到直属代码块大括号结束
命名冲突:同一作用范围内,不可重名
循环
作用为在满足某个条件的前提下使某些语句多次执行,解决代码冗余问题
for循环
适用于循环次数或循环范围明确的情况
for(循环初始值,循环条件,迭代语句){ //操作语句 }
循环初始值:循环范围或循环次数的起始值
循环条件:循环可以继续执行的判断条件
迭代语句:循环初始值的变化规律
执行流程
循环初始值->判断循环条件->为true->执行操作语句->迭代语句->判断循环条件->直到为false->循环结束
-
初始值只会执行一次
-
操作语句执行次数为0~n次
死循环
循环永远满足循环条件,操作语句一直执行,程序无法继续向下
for循环不适合结合死循环
while循环
适用于循环次数和范围不确定的情况
while(循环条件){//循环条件:布尔表达式 }
执行流程
判断循环条件→为true→执行操作语句→判断循环条件→直到为false→循环结束
while循环与for循环可以完全互等
流程控制
break
-
跳出当前所有循环,使循环停止
continue
-
跳出本次循环,直接开始下一次
区别
-
break是跳出所有循环,可以作用于switch分支
-
continue是跳过本次循环,不能作用于分支
(⁄ ⁄•⁄ω⁄•⁄ ⁄)
do-while循环
do{ }while(循环条件);
-
特点:先执行,再判断。执行次数为1~n次
执行流程
操作语句->判断循环条件->为true->操作语句->直到为false->循环结束
嵌套循环
一个循环中嵌套另一个循环
使用
-
对嵌套的循环类型与层数不做要求,不建议超过两层
-
执行流程:外层循环执行一次,内层循环执行一遍
-
图形输出:外层循环代表行,内层循环代表列
-
break和continue只能作用于直属的循环层次
今日掌握
-
if分支系列的使用
-
switch能作用的数据类型
-
局部变量的特点
-
三个循环的语法结构
-
break和continue的区别
-
while和do...while的区别
-
嵌套循环的执行流程
Chp4-函数
概念
可以存放操作语句,使内容在不同位置多次执行
通常一个函数代表一个功能
语法
访问修饰符 static 返回值类型 函数名(形参列表){ //操作语句 }
public static void 函数名(){ }
位置
类以内,其他函数以外,与主函数平级
调用
函数想要执行,必须经过调用
函数名(实参列表); 函数名();
位置
写进某个代码块,想在哪执行,就在哪调用。
执行流程
只有当调用的函数执行结束回到调用位置,程序才能继续向下执行
参数
形参
形式上的参数,特点为没有具体值只有声明部分,写在函数声明处的小括号内
实参
实际上的参数,特点为是一个具体值或拥有具体值结果的表达式。写在函数调用处的小括号内
作用为给形参赋值
一个参数
public static void 函数名(数据类型 参数名){ //操作语句 } 调用: 函数名(值或拥有结果的表达式)
执行流程
使用
-
函数定义形参之后,调用时必须传入实参给形参赋值
-
实参类型必须与形参类型保持一致
多个参数
public static void 函数名(数据类型 参数名1,数据类型 参数名2...){ //操作语句 } 调用: 函数名(值1或拥有结果的表达式1,值2或拥有结果的表达式2...)
使用
-
实参的参数列表必须与形参的参数列表保持一致
参数列表:数据类型+个数+顺序
返回值
函数的执行结果
public static 返回值类型 函数名(参数列表){ //操作语句 retrun 值; }
retrun语句
-
函数无返回值时,声明为void,此时内部无需书写retrun语句
-
retrun的作用为向上返回返回值
向上返回:向调用者返回
-
若函数声明了返回值类型,表示需要向上返回数据,则函数内部必须书写retrun进行值的向上返回
-
当函数要将值向上返回时,自身就不再建议处理该返回值
数据的处理者和数据的传递这两个身份不可兼得
-
当调用了拥有返回值的函数时,必须对返回值做出后续处理,否则会导致返回值无效。
-
先接收再处理
//先定义变量接收返回值 数据类型 变量名 = 函数名(实参列表); //后续直接操作该变量
-
变量类型应与函数返回值类型保持一致
-
-
直接处理
-
6.执行流程
无返回值时:被调用函数内容全部执行结束才会回到调用位置
有返回值时:执行到retrun语句就会回到调用位置
-
当函数需要返回值时,假设函数内部存在分支,则必须保证每种分支结构都有对应的retrun语句执行
-
推荐的语法结构(三部曲) (⁄ ⁄•⁄ω⁄•⁄ ⁄)
-
在函数最上方定义用来返回的变量,并赋予初始值
-
变量类型与函数声明保持一致
-
-
在操作语句中更改变量的值
-
在函数最下方retrun该变量
-
-
一个函数至多执行一个return语句
-
retrun语句直属范围内下方不可存在其他有效语句
-
无法执行
-
函数高级
函数的嵌套调用
-
函数嵌套调用时,调用顺序与执行结束顺序相反
-
嵌套调用时,被调用函数执行结束,调用者才能继续向下执行
-
只要逻辑完整,嵌套层数不做要求
JVM内存结构
-
栈的特点:先进后出(FILO)
-
栈只能操作栈顶元素
-
-
栈帧:栈的基本组成部分
-
每调用一次函数,都会生成一条对应的栈帧,栈帧的特点是先进后出,所以函数的嵌套调用特点与之一致
-
栈中存放的栈帧拥有上限,所以函数的调用不可一直进行,否则栈帧会将栈的内存撑满,导致栈溢出错误
-
今日掌握
-
函数的完整语法
-
形参和实参的区别
-
return语句的使用特点
Chp5-数组
概念
是一个容器,特点为内存空间连续,可以存放多个相同数据类型的值
创建
-
先声明,后指明长度
数据类型[] 数组名; 数组名=new 数据类型[长度]
数据类型[] 数组名;
数据类型 []数组名;
数据类型 数组名[] ;
int [] arr; arr = new int[3];
-
长度:方便内存分配空间,长度为整型
-
new:开辟空间,逢new必开,只要执行到该关键字,就一定会开辟新的内存空间
-
声明的同时直接指明长度
数据类型[] 数组名=new 数据类型[长度];
-
创建的同时直接存放数据
数据类型[] 数组名 = new 数据类型[]{值1,值2,值3...};
-
数组长度由值的个数决定
-
中括号指明长度与大括号直接赋值只能存在一个
-
-
创建的同时直接存放数据(简化版)
数据类型 [] 数组名 ={值1,值2,值3...};
-
无法先声明后大括号直接赋值
int arr[]; // arr = {10,20,30};//错误 arr = new int[]{10, 20, 30};//正确
-
下标
-
操作数组元素必须借助数组的下标
-
从零开始,到数组长度前一位结束
-
使用语法
取值:数组名[下标]; 赋值:数组名[下标]=值;
-
下标的使用不可超出界限,否则会在运行时报出下标越界异常
java.lang.ArrayIndexOutOfBoundsException //数组下标越界异常
-
可以通过数组名.length动态获取数组长度
-
数组存在默认值,作用为进行空间占位,保证内存分配空间
默认值:虚拟机分配的值
初始值:手动第一次附的值
常用类型的默认值:
int:0
double:0.0
boolean:false
String(引用类型):null
遍历
-
利用for循环遍历数组元素
遍历数组元素的过程就是遍历数组下标的过程
for(int i =0;i<数组名.length;i++){ //i代表数组下标 //通过数组名[i]获取当前元素 }
数组高级
深入数组底层
-
数组是引用类型
-
基本类型之间相互赋值传递的是值,引用类型之间相互赋值传递的是堆地址
-
数组的内存存放:栈中存放引用名,引用名下存放着对应的堆地址,堆空间存放着数组的具体信息和数据
数组扩容
步骤
-
创建一个更大的数组(推荐为原数组的2倍)
-
将原数组的数据复制到新数组中
-
将引用的地址转向新数组
实现
-
利用for循环
-
arraycopy
-
没有返回值
-
System.arraycopy(原数组名,原数组复制起始下标,新数组名,新数组存放起始下标,复制长度)
-
-
-
copyOf
-
有返回值 是数组
-
新数组地址 java.util.Arrays.copyOf(原数组名,预期长度)
int[] a = {10, 20, 30, 40, 50}; //用a接收返回的数组的地址 a=java.util.Arrays.copyOf(a,a.length*2); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } System.out.println();
-
排序
冒泡排序
-
原理:让相邻的两个位置进行比较,根据比较结果决定是否换位
-
特点:每轮比较之后,都会从后往前确定一个位置的元素
-
.实现:外层循环表示比较轮数内层循环表示比较次数和下标
int a[] = {55, 11, 66, 44}; for (int i = 1; i < a.length; i++) {//轮数 for (int j = 0; j < a.length - i; j++) {//次数 //让下一元素和当前元素进行比较 前一位比当前位置交换位置 //从小到大:小于号 从大到小:大于号 if (a[j + 1] < a[j]) { //交换值 int temp = a[j + 1]; a[j + 1] = a[j]; a[j] = temp; } } } /* *轮数=数组长度前一位 *次数=数组长度-当前轮数 * 从小到大 * 55, 11, 66, 44 * *第1轮: * 第0次:11 55 66 44 * 第1次:11 55 66 44 * 第2次:11 55 44 66 * 第2轮: * 第0次:11 55 44 66 * 第1次:11 44 55 66 * 第3轮: * 第0次:11 44 55 66 * * 1-3 * 2-2 * 3-1 * * * * * 99 55 77 1 * 第1轮: * 第1次:55 99 77 1 * 第2次:55 77 99 1 * 第3次:55 77 1 99 * 第2轮: * 第1次:55 77 1 99 * 第2次:55 1 77 99 * 第3轮: * 第1次:1 55 77 99 * * * * */
选择排序
-
原理:固定一个下标位置,使其他下标位置与固定下标位置的值进行比较,根据比较结果决定是否换位
-
特点:每轮比较之后,固定下标位置的值可以被确定
-
实现:外层循环表示固定下标,内层循环表示与其比较的下标
int[] a = {99, 55, 77, 1}; for (int i = 0; i < a.length - 1; i++) {//固定的下标范围 for (int j = i + 1; j < a.length; j++) {//比较的下标 //判断比较位置的值是否小于固定位置的值 //从小到大:小于号 从大到小:大于号 if (a[j] < a[i]) { //值换位 int temp = a[j]; a[j] = a[i]; a[i] = temp; } } }
JDk排序
-
java.util.Arrays.sort(数组名):对数组内容进行从小到大的排序
int[] a = {88,26,71,12,1}; java.util.Arrays.sort(a);
今日掌握
-
数组的创建
-
下标的使用
-
数组的遍历
-
数组的数组类型
-
基本类型与引用类型之间相互赋值的区别
-
数组扩容的步骤
-
冒泡排序和选择排序的原理及特点
Chp6-面向对象
"面向过程":关注实现功能的操作步骤
”面向对象“:关注实现功能的方式方法
对象
-
在Java思想中:万物皆对象
-
在代码中:是一种数据,是计算机内存中的一块存储空间,是对现实生活中内容的实例化体现
对象的组成
-
特征:都有什么
-
行为:会做什么
对象间的关系
-
is a :一个对象继承自另一个对象
-
狗是一个动物 dog is a animal 狗对象继承自动物
-
猫是一个动物 cat is a animal 猫对象继承自动物
-
-
has a:一个对象是另一个对象的组成部分(包含)
-
硬盘是一个对象
-
键盘是一个对象
-
电脑是一个对象
-
电脑包含(has a)硬盘和键盘
-
-
use a:一个对象使用另一个对象
-
电脑是一个对象
-
程序员是一个对象
-
程序员使用(use a)电脑
-
重新认识类
是一个容器,存放对同一批具有相同特征和行为的描述
类和对象的关系
-
类是对象的模板
-
对象是类的实例
-
一个模板可以创建多个相同相似的实例
类的组成
描述型的类:com.xxx.entity
描述对象内容
测试型的类:com.xxx.test
包含主函数,可以直接运行
-
属性:用来描述对象特征。也称为成员属性、成员变量、全局变量等。
1.访问修饰符 数据类型 属性名;//只声明 2.访问修饰符 数据类型 属性名=值;//声明的同时之间赋值
-
位置:类以内,其他内容以外,通常写在类的最上方
-
属性存在默认值,作用为做空间占位,保证内存会给对象的属性分配空间
-
属性和局部变量的区别
局部变量 属性 位置 方法内部 方法外部 作用范围 从定义行开始到直属代码块结束 至少整个类 命名冲突 同一作用范围内不可重名 属性可以和局部变量重名,局部变量优先级更高 默认值 没有 有
-
-
方法:用来描述对象行为.也称为成员方法、实例方法等
函数就是加了static的方法
访问修饰符 返回值类型 方法名(形参列表){ //操作语句 }
-
位置:类以内、其他内容以外,与属性平级
public class Student { private String name; private int age; private char sex; private double score; public void study() { System.out.println(name + "在学习"); } public void eat() { System.out.println(name + "在吃饭"); } public void sleep() { System.out.println(name + "在睡觉"); } }
创建对象
类名 对象 = new 类名();
Student stu1 = new Student();
对象访问
-
访问属性
取值:对象名.属性名 赋值:对象名.属性名=值;
-
访问方法
对象名.方法名(实参列表);
构造方法
-
无参构造
访问修饰符 类名(){}
-
有参构造
访问修饰符 类名(数据类型 参数1,数据类型 参数2){ 属性1 = 参数1; 属性2 = 参数2; ... }
特点
语法特点
-
没有声明返回值类型部分
-
方法名必须与当前类名一致
使用特点
-
作用为构建对象内容
-
只能通过new关键字调用
类名 对象名 = new 类名();
-
第一个类名:指明创建的是哪个类的对象
-
第二个类名:通过new关键字调用对应的构造
-
-
创建对象必须借助构造,调用构造就会创建一个对象
-
语法补充:利用有参构造创建对象
类名 对象名 = new 类名(实参列表);
-
无参构造和有参构造的区别:
-
无参:只是单纯的创建对象,至多存在一个
-
有参:可以在创建对象的同时直接给对象属性赋予初始值,可以存在多个
-
-
通过参数列表决定执行的是哪个构造
-
每个类中都会存在一个默认的无参构造,当类中显示定义构造之后,该默认给予的无参构造将会失效
方法重载
作用为方便程序员书写和调用功能相同相似的不同方法,提高开发效率
规则
-
在同一类中方法名相同,参数列表不同
-
与访问修饰符、返回值类型、异常没有关系
使用
从上往下进行匹配,根据参数列表决定执行的是哪个方法
对象的创建过程
-
又名属性的三个赋值时期
-
给属性开辟空间,赋默认值
-
给属性赋初始值
-
执行构造,再次赋值
this关键字
代表当前对象
this.
指明调用当前对象的属性或方法
this.属性名 this.方法名(实参) 不常用
-
更改有参构造的语法要求
访问修饰符 类名(数据类型 属性名1,数据类型 属性名2,数据类型 属性名3...){ this.属性名1=属性名1; this.属性名2=属性名2; this.属性名3=属性名3; }
this()
调用本类其他构造的内容
使用
-
根据参数列表决定调用哪个构造的内容
-
只能写在构造方法有效代码第一行
-
不能自己调用自己
-
不能循环调用
今日掌握
-
类和对象间的关系
-
属性和局部变量的区别
-
创建对象的语法
-
对象的访问语法
-
构造的语法特点
-
方法重载的规则
-
对象的创建过程
-
this关键字代表什么及其使用
Chp7-封装
面向对象的三大核心思想:封装、继承、多态
概念
是一种屏障,防止对象属性被外界任意访问,提高对象属性的安全性
步骤
-
属性私有化
private 数据类型 属性名;
访问修饰符 含义 作用范围 public 公共的,公开的 任意位置都可以访问 private 私有的 本类内部可以访问 -
可以根据需求选择性的对属性进行私有化,但是通常情况下所有属性都应该私有化
-
-
提供取值赋值所需的getter\setter方法
-
getter:取值,有返回值,无参数
public 返回值类型 getXxx(){ return 属性名; } //Xxx 对应属性名,首字母大写 //如果属性是boolean类型,该方法名为“isXxx”
-
返回值类型与对应属性保持一致
-
-
setter:赋值,没有返回值,有参数
public void setXxx(数据类型 属性名){ this.属性名= 属性名; } //Xxx 对应属性名,首字母大写
-
可以根据需求为私有属性添加getter或setter方法,但是通常情况下所有私有属性都应该有配套的getter和setter方法
-
一个getter或setter方法只能操作一个属性
-
使用
给属性加上封装之后,必须通过getter和setter方法才能访问属性
取值:对象名.getXxx()|对象名.isXxx() 赋值:对象名.SetXxx(实参)
今日掌握
-
封装的步骤
-
getter、setter的区别
-
封装后的使用
Chp8-继承
概念
将子类之间的共性进行抽取,生成父类
在继承关系下,子类就可以默认拥有父类可被继承的内容
语法
class 子类类名 extends 父类类名{ }
规则
-
必须建立在is a关系之上
-
一个子类只能有一个直接父类 (单继承)
-
一个父类可以有多个直接子类
-
一个子类同时也可以是其他类的父类
-
子类中可以定义独有内容
-
子类可以继承拥有直接父类和间接父类中所有可被继承的内容
-
父类无法访问子类的独有内容
-
父类的私有内容子类无法直接继承访问
-
父类的构造子类无法继承
子类的内存结构
-
子类的对象空间由父类内容+独有内容构成
父类封装
父类也是类,也应该按照要求进行属性的封装操作
使用
父类封装之后,子类将无法直接访问父类属性,必须通过getter()|setter()的方式对父类属性进行访问
方法重写
-
又名方法覆盖
-
对从父类继承过来的方法进行方法体的重新书写,简称方法重写
规则
-
返回值类型、方法名、参数列表必须与父类保持一致
-
访问修饰符必须与父类相同或者更宽
-
不能抛出比父类更大或更多的异常
使用
子类进行方法重写之后,优先使用自身重写内容
父类的作用:
作为子类的共性抽取,解决子类之间的冗余问题
强制约束子类必须拥有某些特征和行为
有继承关系的对象创建过程
-
子类对象的构建
-
给父子类属性分配空间,赋默认值
-
给父类属性赋初始值
-
执行父类构造
-
给子类属性赋初始值
-
执行子类构造
super关键字
-
代表父类对象
super.
-
指明调用父类对象的属性或方法
super.属性名 super.方法名();
-
无法调用父类的私有属性
-
super()
-
调用父类构造
使用
-
必须写在子类构造有效代码第一行
-
根据参数列表决定执行的是哪个父类构造
-
this()和super()不可同时显式存在
-
执行子类构造内容之前必定先执行父类构造内容
-
当子类构造中未显式调用父类构造时,默认存在一个无参的super()
-
可以利用有参的super()直接在子类有参构造中给父类属性赋值
-
优化后的子类的有参构造:
访问修饰符 子类类名(父类的属性,独有的属性){ super(父类属性); this.独有属性名=独有属性名; }
访问修饰符
-
控制内容可被访问的范围
本类 同包 非同包子类 非同包非子类 private(私有的) √ default(默认的) √ √ protected(受保护的) √ √ √ public(公开的) √ √ √ √ -
都可以修饰属性,方法(普通方法,函数,构造)
-
都不可以修饰局部变量
-
只有public和default可以修饰类
-
default无法显示声明
-
今日掌握
-
继承的语法
-
继承的规则
-
方法重写的规则
-
有继承关系的对象创建过程
-
super关键字和this关键字的区别
-
四个访问修饰及其作用范围
Chp9-多态
概念
父类类型引用可以指向不同的子类对象,一个父类引用不同的表现形态
语法
父类类名 引用名 = new 子类类名(); 父类引用=子类对象;
使用
-
建立在继承关系之上的
-
实际创建的是子类对象
-
优先执行子类内容
-
父类引用无法访问子类的独有内容
-
编译失败
-
编译器关注的是引用类型,解释器关注的是实际对象类型
-
左边决定都能做什么,右边决定谁能做
-
-
引用类型间的类型转换
自动类型转换
发生在小类型给大类型赋值
大类型 引用名= 小类型引用名|小类型对象;
-
父类是大类型,子类是小类型
强制类型转换
发生在大类型给小类型赋值的时候
小类型 引用名 = (小类型)大类型引用名
Animal a = new Dog(); ((Dog)a).lookDoor();
-
只能转向父类引用原本指向的子类类型
// Cat cat = (Cat)a; //运行报错
-
编译不报错,运行报错
-
出现java.lang.ClassCastException 对象转换异常
-
-
无父子类关系的子类之间不可进行强转
Dog d =new Dog(); Cat cat = (Cat)d;//编译报错
多态的使用场景
-
用于容器:将容器类型声明为大类型,则容器内部可以存放不同的小类型对象
-
用于参数:将方法形参声明为大类型,则实参可以传入不同的小类型对象
public static void main(String[] args) { Dog d1 = new Dog(); Cat c1 = new Cat(); method(d1);//狗吃大骨头 method(c1);//猫吃小鱼干 } //定义一个函数, 传入参数,要求可以执行出"狗吃大骨头"或者"猫吃小鱼干" public static void method(Animal a) {//Animal a=new Dog(); Animal a=new Cat(); a.eat(); }
-
用于返回值:将方法的返回值声明为大类型,则可以实际return不同的小类型对象
public static void main(String[] args) { Animal a= method3(11);//a=new Dog() a.eat(); } //定义一个函数, 传入一个整型参数n,要求n为偶数返回Dog对象,否则返回Cat对象 public static Animal method3(int n) { if (n % 2 == 0) { return new Dog(); } return new Cat(); }
instanceof关键字
-
判断当前对象是否与指定类型兼容
引用名 instanceof 类名
public static void main(String[] args) { Animal []as = {new Dog(),new Cat(),new Cat()}; method(as); } public static void method(Animal [] as){ for (int i = 0; i <as.length ; i++) { if (as[i] instanceof Dog ){ ((Dog)as[i]).lookDoor(); }else { as[i].eat(); } } }
使用
-
子类对象可以被父类类型兼容(包括间接父类)
-
父类引用只能被所指向的子类类型兼容
-
子类类型无法兼容父类对象
Dog jm = new JinMao(); System.out.println(jm instanceof JinMao);//t System.out.println(jm instanceof Dog);//t System.out.println(jm instanceof Animal);//t //System.out.println(jm instanceof Cat); Animal a = new Dog(); System.out.println(a instanceof Dog);//t子类对象可以被父类类型兼容 System.out.println(a instanceof Animal);//t子类对象可以被父类类型兼容 System.out.println(a instanceof JinMao);//f 子类类型无法兼容父类对象 System.out.println(a instanceof Cat);//f 父类引用只能被所指向的子类类型兼容
多态的好处
-
减少代码冗余
-
将代码解耦合,提升代码的扩展性
今日掌握
-
多态的概念
-
多态的使用
-
引用类型之间的强转
-
多态的三个使用场景(用法)
-
instanceof关键字的作用和语法
Chp10-三大修饰符
abstract-抽象
-
抽象的,不是真实存在的
抽象类
public abstract class 类名{} abstract public class 类名{}
-
修饰符之间的书写顺序不做要求
使用
-
抽象类无法实例化对象
-
抽象父类中仍然存在构造,为了让子类使用
-
抽象类中可以存在非抽象内容
-
通常情况下,父类会被设计为抽象类
-
非父类的类,不可声明为抽象类,会导致类中内容无效
-
-
抽象父类仍然可以参与多态
抽象方法
访问修饰符 abstract 返回值类型 方法名(形参列表);
使用
-
没有方法体部分
-
必须写在抽象类中
-
子类必须重写父类中的抽象方法,除非子类自身也是抽象类
-
如果子类本身不是父类,则不能将其声明为抽象类
-
-
父类中的抽象方法的作用
-
子类共性的抽取
-
约束子类拥有该方法
-
约束子类必须重写该方法
-
抽象的好处
-
更贴合现实逻辑
-
优化程序设计
final-最终
-
最终的,不可更改的
修饰属性
-
会变成常量属性,值不可改
使用
-
没有默认值
-
在创建的同时必须直接赋值
-
声明的同时直接赋值
-
在构造中赋值
-
必须保证所有构造中都有对常量属性的赋值操作
-
-
修饰方法
-
可以被继承,不可被重写
修饰类
-
断子绝孙类,不可被继承
修饰局部变量
-
会变成常量,值不可改
只需保证不二次赋值即可
修饰引用
-
对象内容可改,地址不可改
static-静态
修饰属性
静态属性、也称为静态变量、类变量
使用
-
不被对象独有,被所有对象共享
-
静态属性和非静态属性的区别
-
非静态属性:存放在堆中的某个对象空间中,每个对象都有一份
-
静态属性:存放在方法区中,一个类只有一份
-
-
静态属性的内存存放:
-
静态内容区中的内容以类名为存放区分标志
-
静态内容是在类加载的时候出现在内存中的
-
早于对象
-
-
可以通过类名.静态属性名直接访问静态属性
-
当静态属性封装之后,无法通过类名.属性名进行访问,必须通过getter|setter方法访问
-
getter|setter是静态的:存放在方法区,可以直接通过类名.getter()|setter()访问
-
getter|setter不是静态的:存放在各个空间之中
-
修饰方法
-
静态方法,也就是函数
使用
-
不能访问使用非静态内容
-
静态内容早于非静态内容出现在内存中
-
-
静态方法可以直接通过类名.方法名(实参)的方式直接访问
-
无法使用this和super关键字
-
this和super表示的都是对象,静态方法执行时对象有可能未被创建
-
-
static无法修饰局部变量
-
局部变量的作用范围限制其无法成为类变量
-
-
无法修饰构造方法
-
构造方法只能通过new关键字调用,无法通过类名.方法名()直接访问
-
-
父类的静态方法子类可以继承
-
静态内容的执行只关注引用类型,不关注实际对象类型
-
在多态的前提下,父类中的静态方法并不会优先执行子类内容
-
修饰初始代码块
初始代码块
class 类名{ { //初始代码块 } }
位置
类以内,通常写在属性下方,其他内容上方
作用
-
提炼所有构造的共有内容,通常用于给属性赋值
-
在创建对象时执行
-
可以执行多次
静态初始代码块
class 类名{ static{ //静态初始代码块 } }
-
位置与初始代码块一致
使用
-
在类加载时执行
-
只会执行一次
-
无法访问非静态内容,通常给静态属性赋值
类加载
什么是类加载?
第一次使用类内容时,通过CLASSPATH类路径找到字节码文件,将字节码文件中的内容加载到虚拟机中的过程,称之为类加载,通常情况下,类加载只会发生一次
类加载的触发时机
-
第一次创建类的对象的时侯
-
第一次访问静态内容
-
通过Class.forName(“全限定名”)的方式强制触发类加载
-
全限定名:包名.类名 类的完整路径
-
-
子类的类加载也会触发父类的类加载
-
只声明引用不会触发类加载
-
当访问一个类的静态成员时,如果该类尚未被加载,Java虚拟机(JVM)将触发该类的加载
-
今日掌握
-
抽象类和抽象方法的关系
-
抽象类中可以存在非抽象方法
-
抽象方法必须存在于抽象类
-
-
父类抽象方法对子类的约束
-
final都能修饰什么及修饰后的特点
-
静态属性的特点
-
静态方法的使用
-
什么是类加载
-
类加载的时机
chp-11 接口
概念
-
从功能上看,实现接口就意味着扩展了某些功能
-
从规则上看,是接口定义者和接口实现者都必须遵守的某些规则
-
从抽象上看,接口是特殊的抽象父类
语法
【访问修饰符】 interface 接口名{ }//访问修饰符可写可不写
包:com.xxx.dao
接口的规则
-
属性都是公开静态常量(public static final)
由于修饰符固定,所以书写上可以省略或者缺失,编译器可以自动填充
-
方法都是公开抽象方法(public abstract)
-
没有构造方法
-
无法实例化对象
实现类
访问修饰符 class 类名 implements 接口名1,接口名2...{}
包:com.xxx.dao.impl
使用规则
-
实现类必须对接口中的抽象方法提供方法实现
-
除非实现类是抽象类(不推荐)
-
-
实现类实现多个接口时,必须对所有接口的所有抽象方法全部提供实现
-
一个接口可以有多个实现类,一个实现类也可以有多个接口(多实现)
-
一个类可以同时继承父类和实现接口,继承优先(先继承后实现)
访问修饰符 class 类名 extends 父类类名 implements 接口名{}
-
接口可以参与实现类对象的多态创建
-
接口是大类型,相当于父类
-
实现类是小类型,相当于子类
接口名 引用名 = new 实现类名();
-
-
接口与实现类之间的多态使用与父子类之间的一致
接口间的继承
一个接口可以直接继承多个父接口(多继承)
访问修饰符 interface 子接口名 extends 父接口名1,父接口名2{ }
-
子接口可以继承拥有父接口的所有内容
接口和抽象类的区别
抽象类 | 接口 | |
---|---|---|
关键字 | abstract class | interface |
属性 | 不做要求 | 公开静态常量 |
方法 | 可以存在非抽象方法 | 公开抽象方法 |
构造 | 有 | 没有 |
继承性 | 单继承(一个子类只能有一个直接父类) | 多继承(一个接口可以直接继承多个父接口) |
接口高级
高版本接口
JDk8.0
-
默认方法
public default 返回值类型 方法名(形参列表){ //操作语句 }
-
当接口中的方法体与父类中的方法体出现冲突时,优先执行父类内容(类优先原则)
-
当接口之间的方法出现冲突时,实现类或子接口必须对该方法进行重写,使用自身重写内容
-
-
静态方法
public static 返回值类型 方法名(形参列表){ //操作语句 }
-
可以通过接口名.方法名(实参)的方式访问
-
JDk9.0
-
私有方法
private 返回值类型 方法名(形参列表){ }
接口回调
概念
先有接口的使用者,再有接口的实现者
实现
将方法的形参声明为接口,根据需求为接口提供实现类,调用方法时传入实现类对象
优势
满足开闭原则
“开闭原则”:扩展开放,修改关闭 允许代码中扩展新的功能,但不能更改原有的代码
比较器案例
-
创建Comparator比较器的实现类,重写compare方法
public class 实现类名 implements Comparator <被排序的类名> { /** * 存放比较规则 * @param o1 the object to be compared. * @param o2 the second object to be compared. * @return */ @Override public int compare(被排序的类名 o1,被排序的类名 o2) { return 0; } }
-
书写排序规则
-
从小到大:
-
o1的值>o2的值,返回正数
-
o1的值<o2的值,返回负数
-
相等返回0
-
-
从大到小:
-
o1的值>o2的值,返回负数
-
o1的值<o2的值,返回正数
-
相等返回0
-
public class lalal implements Comparator<Student> { /** * * @param o1 the first object to be compared. * @param o2 the second object to be compared. * @return */ @Override public int compare(Student o1, Student o2) { if (o1.getScore()>o2.getScore()){ return 1; } else if (o1.getScore()<o2.getScore()) { return -1; }else { return 0; } } }
-
-
在方法中传入比较器实现类对象
-
java.util.Arrays.sort(被排序的数组,实现类对象)
Student[] ss = {new Student("zhangsan", 12, 55.5), new Student("lis", 132, 99), new Student("wangemaz", 122, 78)}; Arrays.sort(ss, new lalal()); for (int i = 0; i < ss.length; i++) { System.out.println(ss[i].getName() + " " + ss[i].getAge() + " " + ss[i].getScore()); }
-
接口的好处
-
提高代码的扩展性
-
提高代码的复用度
-
优化代码结构
今日掌握
-
接口的语法
-
接口与实现类的的使用规则
-
接口和抽象类的区别
-
类优先原则
-
什么是接口回调
-
比较器的使用
Chp12-内部类
概念
在类的内部再次声明定义类
优势
打破封装又不破坏封装
分类
-
成员内部类
-
静态内部类
-
局部内部类
-
匿名内部类
成员内部类
位置
在类内部,方法外部,与外部类属性和方法平级
语法
访问修饰符 class 外部类类名{ 访问修饰符 class 内部类类名{ } }
使用
-
无法定义静态内容,但是可以访问外部类静态内容
高版本的的JDk中,成员内部类中也可以定义静态内容
-
当内部类局部变量、局部类属性、外部类属性重名时:
-
内部类局部变量:变量名
-
内部类属性:this.属性名
-
外部类属性:外部类类名.this.属性名
-
-
成员内部类对象的创建必须基于外部类对象
外部类类名.内部类类名 对象名 = 外部类对象名.new.内部类类名
静态内部类
位置
与成员内部类一致
语法
访问修饰符 class 外部类类名{ 访问修饰符 static class 内部类类名{ } }
使用
-
可以定义静态内容,无法访问外部类外部非静态内容
-
当在内部类静态方法里出现重名时:
-
局部变量:变量名
-
内部类静态属性:内部类类名.属性名
-
外部静态属性:外部类类名.属性名
-
-
静态内部类对象的创建需要基于外部类类名
外部类类名.内部类类名 对象名 = new.外部类类名.内部类类名();
-
可以直接通过外部类类名.内部类类名.静态内容的方式直接访问内部类静态内容
局部内部类
位置
外部类方法中,与外部类局部变量平级
语法
访问修饰符 class 外部类类名{ 访问修饰符 返回值类型 方法名(形参列表){ class 内部类类名{ } } }
使用
-
作用范围:从定义行开始,到直属代码块结束
-
只能访问外部类的局部常量,不可访问变量
-
JDK7.0之前,必须为通过final修饰的常量
-
JDK7.0之后,事实上的常量即可(未二次赋值)
-
-
对象创建与内容访问只能在所属方法内进行
匿名内部类
位置
在需要创建实现类|子类对象的位置书写
语法
接口名|父类类名 引用名 = new 接口名|父类类名 (){ //实现类|子类内容 };
使用
-
必须使用多态创建对象
-
一个匿名内部类只能创建一个对象
-
作用:创建生成一个实现类对象或者子类对象
-
特点:将类的声明、方法的定义、对象的创建三合一
-
拥有一个默认的无参构造,用来创建唯一的对象
-
无法显式定义构造
-
可以定义独有内容,但是只能在本类内部使用
-
(和局部内部类限制一样)
-
lambda表达式
接口的分类
标记式接口:无任何内容
常量式接口:只定义常量属性,未定义方法
函数式接口:只定义了一个需要重写的方法
普通接口:定义了多个需要重写的方法
作用
简化部分匿名内部类的书写
匿名内部类:可以给接口提供一个实现类对象
↓
lambda表达式可以简化匿名内部类的书写
↓
lambda表达式:可以给接口提供一个实现类对象
语法
(形参列表)->{方法内容}; 结合引用: 接口名 引用名 = (形参列表)->{方法内容};
使用
-
JDK8.0
-
只能作用于函数式接口,可以给接口生成一个实现类对象
-
简化标准:
-
参数的数据类型可省(要省则都省)
-
当参数只有一个时,小括号可省
-
当操作语句只要一条时,大括号可省
-
当操作语句只有一条并且为return语句时,大括号和return都可省(要省则都省)
-
今日掌握
-
内部类的分类
-
匿名内部类的使用
-
lambda表达式的语法和简化标准
Chp13-常用类
Object
最大父类,所有类一定直接或间接的继承自Object
内部存放了所有对象都需要拥有的内容,可以根据需求对其中的部分内容进行重写
常用方法
-
类对象 getClass(): 获取引用的实际对象类型
-
int hashCode(): 获取对象的哈希码值
-
重新原因:该方法默认根据对象地址获取哈希码值,但是开发过程中,某些场景下需要关注的是对象内容,内容相同则哈希码值理应相同,所以需要重写
-
重写规则:
-
整型:直接相加(long类型需要强转int)
-
浮点型:强转为int相加
-
引用类型:属性名.hashCode()相加
-
类库声明的引用类(String):已经重写过,直接调用
-
自定义的引用类:自己进行重写,然后调用
-
-
-
-
boolean equals(Object o):判断当前对象与参数对象是否相同(默认判断地址是否相同)
-
重写原因:该方法默认比较地址,但是实际开发中,某些场景下需要比较内容,所以需要重写
-
重写规则
@Override public boolean equals(Object obj) { //自反性 if (this==obj){ return true;//自己和自己比一定相同 } //类型判断和非空判断 if (this.getClass()!= obj.getClass()|| obj==null){ return false;//实际对象类型不同或参数为null,一定不同 } //类型强转 ;//当前类名 引用名 = (当前类名)o; //基本类型比较:== //引用类型比较:对象1.equals(对象2) return this.name.equals(p.name) && this.age==p.age && this.score==p.score && this.stu.equals(p.stu); //return this对象的属性值与引用名的属性值比较的结果 }
-
类库中的引用类:已经重写,直接调用
-
自定义的引用类:自行重写equals
-
-
-
String toString():获取对象信息
-
直接操作引用名可以默认调用该方法
-
重写原因:该方法默认获取对象地址信息,实际开发,更需要对象的内容信息,所以需要重写
-
重写规则:根据需求对属性进行字符串拼接即可
-
-
void finalize():进行垃圾对象回收
-
垃圾回收机制:当虚拟机内存满到不足以支撑新对象的创建时,虚拟机会调用垃圾对象的finalize方法对其进行回收销毁,以此来释放空间
-
垃圾对象的判断标准:没有任何引用指向的对象(零引用机制)
-
手动垃圾回收:利用垃圾回收器GC,在代码中通过 System.gc()进行手动垃圾对象回收
-
包装类
byte | short | int | long | float | double | char | boolean |
---|---|---|---|---|---|---|---|
Byte | Short | Integer | Long | Float | Double | Character | Boolean |
包装类和基本类型
基转包
-
利用构造
包装类型 引用名 =new 包装类型(引用类型);
-
利用valueOf
包装类型 引用名 = 包装类名.valueOf(基本类型);
包转基
-
利用XXXValue
基本类型 变量名 = 包装对象名.XXXValue(); //XXX对应基本类型名
int n1 = 10; //转包 Integer i1 = new Integer(n1); Integer i2 = Integer.valueOf(n1); //转基 int n2 = i1.intValue();
自动转换
-
JDk5.0之后,提供了自动封箱和拆箱,两者之间可以直接进行转换
-
封箱:基转包
-
拆箱:包转基
int n1 = 10; //转包 Integer i1 = new Integer(n1); Integer i2 = Integer.valueOf(n1); //转基 int n2 = i1.intValue(); //自动转换 Integer i3 = n2; int n3 = i3;
-
基本类型和String
基转String
-
字符串拼接
String 引用名 = 基本类型+""; String 引用名 = ""+基本类型;
-
valueOf
String 引用名=String.valueOf(基本类型);
String转基(重点)
-
parseXxx
基本类型 变量名 = 对应的包装类型.parseXxx(String类型); Xxx是对应基本类型名,首字母大写
-
String中的数据必须为基本类型能接收的数据,否则会在执行时报出java.lang.NumberFormatException数据类型转换异常
//基转String int n = 10; String s = n+""; String s1 = String.valueOf(n); //String转基 String s2 = "10"; int n1 = Integer.parseInt(s2);//正常运行 String s3 = "abc"; //int n2 = Integer.parseInt(s3); //编译通过,运行报错,报出java.lang.NumberFormatException数据类型转换异常
包装类型和String
包转String
-
字符串拼接
String 引用名=包装类型+"";
-
toString
String 引用名= 包装类型引用名.toString
String转包
-
与基转包的两种方式一致
-
String的数据必须为包装类可以存放的数据,否则运行异常
//String转包装类型 String s ="123"; Integer i = new Integer(s); Integer i1 = Integer.valueOf(s); //包装类型转String String s1 = i.toString(); String s2 = i+"";
整数缓冲区
官方认定-128至127是包装类型中最常用的256个数字,所以在方法去中设立了整数缓冲区存放这些数字,当使用的数字在此区间之内,将不会开辟堆空间,使用缓冲区中的内容,目的为减少空间浪费,节约空间资源
==:基本类型比较值,引用类型比较堆地址
比较地址时堆地址优先
Integer i1 = 100; Integer i2 = 100; System.out.println(i1 == i2);//t Integer i3 = 200; Integer i4 = 200; System.out.println(i3 == i4);//f Integer i5 = new Integer(127); Integer i6 = new Integer(127); System.out.println(i5 == i6);//f Integer i7 = 127; System.out.println(i5 == i7);//f
String类型
内存中常量,值一旦创建,则不可更改
-
引用地址可改,地址中的值不可改
String s1= "abc"; String s2 = s1; s2="edf"; System.out.println(s1);//abc System.out.println(s2);//edf
创建
-
双引号直接赋值
String 引用名="值";
-
构造赋值
String 引用名 = new String("值");
什么是串池?
全称字符串常量池,实际开发中,String是使用频率最高的数据类型,且复用频率也很高,为了减少重复字符串反复创建导致空间浪费,在方法区中设立了串池
区别
-
第一种创建方式:直接使用串池,先从串池中查找对应内容是否存在,存在,则直接指向串池地址,不存在则先在串池中创建对应内容,再指向该地址
-
第二种创建方式:无论如何都会开辟堆地址,如果字符串内容在串池中存在,则存放相应串池地址,如果不存在,则先在串池中创建内容,再引用存放对应串池地址
String s1 = "abc"; String s2 = "abc"; System.out.println(s1==s2);//t String str1 = new String("edf"); String str2 = new String("edf"); System.out.println(str1==str2);//f String s3 = "edf"; System.out.println(str1==s3);//f
可变长字符串
-
StringBuffer:JDk1.0 线程安全,效率低
-
StringBuilder:JDK5.0 线程不安全,效率高
特点
-
必须通过构造创建
-
对内容的操作必须通过方法完成
StringBuilder sb = new StringBuilder(); //拼接26个大写字母输出 for(char c = 'A';c<="Z";c++){ sb.append(c); } System.out.println(sb);
实际开发中对字符串的复用频率远远高于更改频率,所以可以使用串池并且操作更简单的String使用频率要高于可变长字符串
和String的区别
-
String内存中常量,可以使用串池
-
可变长字符串:值可改,不能使用串池
-
StringBuffer:JDk1.0 线程安全,效率低
-
StringBuilder:JDK5.0 线程不安全,效率高
-
StringBuilder常用方法
-
StringBuilder delete(int startIndex,int endIndex); 删除指定区间字符串,包头不包尾
-
StringBuilder deleteCharAt(int index); 删除指定索引的字符
-
StringBuilder insert(int index,String str); 在指定索引位置 插入字符串
-
StringBuilder append(String str); 在末尾追加指定字符串
-
StringBuilder reverse(); 翻转字符串数据
-
StringBuilder setCharAt(int index, char newChar); 修改指定索引字符数据
-
StringBuilder replace(int start, int end, String newStr) 使用newStr替换指定区间字符串
常用方法
String引用名.方法(实参)
-
char charAt(下标):获取指定下标位置的字符
-
下标使用不可超出范围,否则报出java.lang.StringIndexOutOfBundsException异常
-
-
boolean contains(“值”):判断字符串中是否包含指定内容
-
boolean startsWith(“值”):判断是否以指定内容开头
-
boolean endsWith(“值”):判断是否以指定内容结尾
-
boolean equals(“值”):判断是否与指定内容相同,区分大小写
-
boolean equalsIgnoreCase(“值”):判断是否与指定内容相同,不区分大小写
-
下标 indexOf(“值”):获取指定内容第一次出现的下标,不存在返回-1
-
下标 lastIndexOf(“值”):获取指定内容最后一次出现的下标,不存在返回-1
-
boolean isEmpty():判断字符串是否为空串,不能判比null值
使用null值调用内容、参与运算、参与判断都会导致java.lang.NullPointerException空指针异常
-
int length():获取字符串长度
-
新字符串 replace(“旧内容”,“新内容”):将字符串中的旧内容替换为新内容,替换所有旧内容
-
String replaceAll(String regex,String str):使用str替换原字符串满足/匹配正则模式的所有的字符串数据
-
String[] split(“分隔符”):根据分隔符对字符串内容进行分割,返回分割后的数组,不保留分割符
-
String[] split(String str,int limit):根据分隔符对字符串内容进行分割,limit 是限制分割的元素个数
-
String substring(起始下标):将字符串从起始下标截取至末尾
-
String substring(起始下标,结束下标):将字符串从起始下标截取至结束下标前一位
-
String toLowerCase():转全小写
-
String toUpperCase():转全大写
-
String trim():去除字符串的前后空格
-
String stripLeading():去除前面的空格
-
String stripTrailing():去除后面的空格
-
byte[] getBytes():以byte数组的形式返回字符串的所以内容
-
char[] toCharArray():以char数组的形式返回字符串的所以内容
-
static String join(String 分隔符,String...data):使用指定的分隔符连接多个字符串数据
-
String intern(): 获取源字符串的副本(获得常量池数据引用)
-
boolean compareToIgnoreCase(String str)
-
IntStream chars()
String s = "hello"; //char charAt[下标] 获取指定下标的内容 System.out.println(s.charAt(4));//o //System.out.println(s.charAt(5));//java.lang.StringIndexOutOfBoundsException //boolean contains 判断字符串中是否包含指定内容 System.out.println(s.contains("lo"));//t //boolean startsWith("值") 判断是否以指定内容开头 //boolean endsWith("值") 判断是否以指定内容结尾 System.out.println(s.startsWith("hell"));//t System.out.println(s.endsWith("llo"));//t //boolean equlas("值") 判断是否与指定内容相同,区分大小写 System.out.println(s.equals("Hello"));//f System.out.println(s.equals("hello"));//t //boolean equalsIgnoreCase("值")判断是否与指定内容相同,不区分大小写 System.out.println(s.equalsIgnoreCase("hEllo"));//true //下标 indexOf("值") 获取指定内容第一次出现的下标,不存在返回-1 System.out.println(s.indexOf("e"));//1 //下标 lastIndexOf("值") 获取指定内容最后一次出现的下标,不存在返回-1 System.out.println(s.lastIndexOf("l"));//3 //boolean isEmpty() 判断是否为空,不能判比null 否则执行时会报出空指针异常java.lang.NullPointerException System.out.println(s.isEmpty());//false String s1 = ""; System.out.println(s1.isEmpty()); String s2 = null; //System.out.println(s2.isEmpty());空指针异常java.lang.NullPointerException //使用null值调用内容、参与运算、参与判断都会导致==java.lang.NullPointerException==空指针异常 //int length() 获取字符串长度 System.out.println(s.length());//5 //新字符串 replace("旧内容","新内容") 将字符串中的旧内容替换为新内容,替换的是所有旧内容 System.out.println(s.replace("o","1")); //String [] split("分隔符") 根据分隔符对字符串进行分割,返回分割后的数组,不保留分隔符 String s3 = "1988/05/02"; String [] str = s3.split("/"); for (int i = 0; i <str.length ; i++) { System.out.print(str[i]+" "); }//1988 05 02 System.out.println(); //String substring(起始下标) 将字符串从起始下标截取到末尾 System.out.println(s.substring(1));//ello //String substring(起始下标,结束下标) 将字符串从起始下标截取到结束下标前一位 System.out.println(s.substring(1,4));//ell //String toLowerCase() 全小写 String s4 = "HEllo"; System.out.println(s4.toLowerCase());//hello //String toUpperCase() 全大写 System.out.println(s.toUpperCase());//HELLO //String trim() 移除字符串的前后空格 String s5 = " a v f "; System.out.println(s5.trim());//a v f //byte[] getBytes() 以byte数组的形式返回字符串的全部内容 byte [] b = s.getBytes(); for (int i = 0; i <b.length ; i++) { System.out.print(b[i]+" "); } System.out.println();//104 101 108 108 111 //char[] toCharArray() 以char数组的形式返回字符串的全部内容 char[] c = s.toCharArray(); for (int i = 0; i <c.length ; i++) { System.out.print(c[i]+" "); } System.out.println();//h e l l o
今日掌握
-
getClass()和instanceof的区别
-
hashCode和equals的重写规则
-
垃圾回收机制和判断标准
-
自动封箱和拆箱的的概念
-
String转基本类型的语法
-
String两种创建方式及区别
-
什么是串池?
-
String的常用方法
-
可变长字符串和String的区别
Chp14-List集合
集合的概念
是一个容器,用来存放多个数据,通常用来替代数组
集合的特点
-
只能存放引用类型
-
所有的集合都来自于java.util包
List 、Set、Map 都是接口
List的存储特点
有序、有下标、元素可以重复
常用实现类
-
ArrayList(常用)
-
JDK1.2 底层数组实现 查询快,增删慢 线程不安全,效率高
-
-
LinkedList (一般不用)
-
JDk1.2 底层链表实现 查询慢,增删快 线程不安全,效率高
-
-
Vector (不用)
-
JDK1.0 底层数组实现 查询慢,增删慢 线程安全,效率低
-
List的创建
-
推荐使用多态
List 集合名 = new 实现类名();
常用方法
-
boolean add(元素):添加元素至集合末尾,成功返回true
-
void add(下标,元素): 将元素添加到指定下标位置
-
boolean addAll(集合):将指定集合元素添加到当前集合末尾
-
boolean addAll(下标,集合):将指定集合元素添加到当前集合指定下标位置
-
boolean contains(元素):判断集合是否包含指定元素
-
boolean containsAll(集合):判断集合是否包含指定集合的所有元素
-
元素 get(下标):获取指定下标的元素
-
int indexOf(元素):获取指定元素第一次出现的下标,不存在返回-1
-
int lastIndexOf(元素):获取指定元素最后一次出现的下标,不存在返回-1
-
boolean isEmpy():判断集合长度是否为0(判断当前集合是否为空), 不能判比null值
-
被删掉的元素 remove(下标):删除指定下标位置的元素,返回的是被删掉的元素
-
boolean remove(元素):删除指定元素,只删除第一个
-
当List集合存放内容为整型数值时,无法使用该传参方式进行删除,默认会优先认定为下标,所以删除整数值时只能通过下标删除
-
-
int size():获取集合元素个数(长度)
-
Object[] toArray():将集合转换为数组
泛型
作用
用于集合,可以约束集合中存放的数据类型
语法
List<泛型> 集合名 = new 实现类名<泛型>();
使用
-
如果存放基本类型,则泛型必须声明为其对应的包装类
-
声明泛型之后,集合中只能存放泛型数据
-
一个集合容器只能声明一种泛型
-
由左侧泛型决定泛型约束,右侧泛型可以只书写<>做语法占位
List<泛型> 集合名 = new 实现类名<>();
遍历
-
for循环下标遍历
for(int i=0;i<集合名.size();i++){ //通过集合名.get(i)获取当前元素 System.out.print(集合名.get(i)+" "); }
-
迭代器遍历
-
利用集合中的Iterator()方法 ,返回的是迭代器
-
boolean hasNext():判断是否存在下一元素
-
元素 next():使指针后移一位,获取下一元素
//获取集合的迭代器 Iterator<集合泛型> it = 集合名.iterator(); //操作迭代器,获取集合元素 while(it.hasNext()){//条件:存在下一元素 //通过it.next();指针后移,获取下一元素 Object o = it.next(); if(o!=null){ System.out.print(o+" "); } }
-
由于指针走向固定,所有循环操作迭代器的过程中不可增删集合元素
-
一次循环只能调用一次next()方法
-
-
外遍历forEach
for(泛型 元素名:集合名){ //元素名就代表正在被遍历的元素 }
-
JDk5.0
-
本质上仍为迭代器遍历,所以过程中不可增删元素
-
-
自遍历forEach
集合名.forEach(Consummer接口实现类对象())
集合名.forEach(new Consummer<泛型>(){ public void accept(泛型 元素名){ //元素名就代表正在被操作的集合元素 } });
集合名.forEach(参数名->{操作语句});
-
JDk8.0
-
不可使用外部的累加器和标识变量
List<Integer> list = new ArrayList<>(); list.add(10); list.add(20); list.add(30); list.add(40); //下标遍历 for (int i = 0; i <list.size() ; i++) { System.out.print(list.get(i)+" "); } System.out.println(); //迭代器遍历 Iterator<Integer> it = list.iterator(); while (it.hasNext()){//判断集合是否存在下一元素 Object o = it.next();//指针后移,获取下一元素 if (o!=null){ System.out.print(o+" "); } } System.out.println(); //外遍历forEach for (Object o : list) { System.out.print(o+" "); } System.out.println(); //内遍历forEach list.forEach(new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.print(integer+" "); } }); //lambda表达式简化forEach System.out.println(); list.forEach(o -> System.out.print(o+" "));
今日掌握
-
集合的特点
-
List的存储特点
-
List的常用实现类和特点
-
泛型在集合中的作用
-
List的遍历方式
Chp15-Set集合
Collection
-
所有集合都有Collection或者Map派生
-
是List和Set的父接口
特点
-
存放List和set的共性内容
-
没有直接实现类
-
没有明确的存储特点的要求
Set的存储特点
无序,无下标,元素不可重复
常用实现类
-
HashSet
-
JDK1.2 底层哈希表(数组+链表)实现 线程不安全,效率高
-
-
LinkedHashSet
-
JDK1.2 是HashSet的子类,底层哈希表实现 线程不安全,效率高
-
-
TreeSet
-
JDK1.2 是SortedSet的实现类,底层是红黑树是实现 线程不安全,效率高
-
创建
建议使用多态
Set<泛型> 集合名 = new 实现类名<>();
常用方法
没有独有方法,所有方法都继承自父接口Collection
与下标相关的方法是List集合的独有方法
遍历
-
Iterator迭代器遍历
-
外遍历forEach
-
自遍历forEach
哈希表的去重原理
调用元素的hashCode()方法获取哈希码值
根据哈希码值%数组长度(底层数组长度为16)得到存放下标
-
若下标位置未存有元素,则直接存放
-
若存有元素,则调用元素的equals方法与下标位置元素进行值的比较
-
都不相同,则链表存放
-
有相同,则舍弃添加的元素
-
使用
-
哈希表存放自定义类型时,必须重写hashCode方法和equals方法才能实现去重
-
LinkedHashSet可以保证元素的存入和取出的顺序一致
-
TreeSet可以对元素进行默认的升序排序(从小到大)
-
如果存放的为自定义类型,则必须提供排序规则
-
方式1:实现Comparable接口,重写compareTo(类名 o)方法
-
原理:让当前对象和参数对象进行比较
-
实现:对谁排序,让谁实现
public class 类名 implements Comparable<类名>{ public int compareTo(类名 o){ /* 从小到大:this的值>o的值 返回正数 this的值<o的值 返回负数 从大到小:this的值>o的值 返回负数 this的值>o的值 返回正数 */ } }
-
-
方式2:实现Comparator接口,重写compare方法
-
原理:让两个参数进行比较
-
实现:在集合创建处的小括号内传入实现类对象
Set<自定义类名> 集合名=new TreeSet<泛型>(Comparator实现类对象);
Set<自定义类名> 集合名=new TreeSet<泛型>(new Comparator<自定义类型名>(){ public int compare(自定义类型名1 o1,自定义类型名2 o2){ //书写排序规则 //让两个参数进行比较 } });
-
-
-
默认识别Comparable,但是比较器Comparator优先级更高
-
更推荐使用比较器书写排序规则
Comparable会破坏自定义类型的单一职责,并且无法拓展不同的排序规则
-
-
TreeSet是当排序规则返回值为0时去重
今日掌握
-
Collection的特点
-
Set的存储特点
-
Set的常用实现类
-
哈希表的去重原理
-
TreeSet自定义排序规则的方法
-
TreeSet的去重规则
Chp16-Map集合
Map的存储特点
-
以键值对的形式存放
-
键(Key-K):无序,无下标,元素不可重复
-
值(Value-V):无序,无下标,元素可以重复
常用实现类
-
HashMap
-
JDK1.2 底层哈希表实现 线程不安全,效率高
-
-
LinkedHashMap
-
JDK1.2 是Hash Map的子类。底层哈希表实现 线程不安全,效率高
-
-
TreeMap
-
JDK1.2 是SortedMap的实现类,底层红黑树实现 线程不安全,效率高
-
-
Hashtable
-
JDk1.0 底层哈希表实现 线程安全,效率低
-
-
Properties
-
JDk1.0 是Hashtable的子类,底层哈希表实现 线程安全,效率低
-
创建
建议使用多态
Map<键的泛型,值的泛型> 集合名 = new 实现类名<>();
常用方法
-
V put(K,V):往集合中添加一个键值对,返回的是被添加的值
-
如果键已经存在,则进行值的覆盖
-
-
int size():获取集合长度
-
boolean cotainsKey(K):判断是否包含指定键
-
boolean containsValue(V):判断是否包含指定值
-
V get(K):根据键获取值,如果键不存在,返回null
-
V remove(K):根据键删除整个键值对,返回的是被删除的值
遍历
-
键遍历:获取所有键,遍历键,根据键获取值
-
Set<K的泛型> keySet():获取所有的键存入Set集合返回,因为存储特点一样
Map<String,Integer> map = new HashMap<>(); map.put("1",20); map.put("2",30); map.put("3",40); map.put("4",50); map.put("5",60); Set<String> set = map.keySet();//keySet()将map中的所有键存入set集合里 //迭代器遍历 Iterator<String> it = set.iterator(); while (it.hasNext()){ Object o = it.next(); System.out.println(o+"-"+map.get(o)); } //外遍历 for (String s : set) { System.out.println(s+"-"+map.get(s)); } //自遍历 set.forEach((s -> System.out.println(s+"-"+map.get(s))));
-
-
值遍历:获取所有值,遍历值
-
Collection<V的泛型> values():获取所有的值存入Collection集合返回
//值遍历:获取所有值,遍历所有值,因为值可重复,所以只能输出所有值 //values()方法:获取集合所有值,返回到Collection集合里,泛型声明为v的泛型 //迭代器遍历 Collection<Integer> coll = map.values();//用Collection集合接收map.values()的返回值 Iterator<Integer> it = coll.iterator();//创建迭代器 System.out.println("这是迭代器遍历"); while (it.hasNext()){//迭代器遍历 Object o = it.next(); System.out.println(o);// } //外遍历 System.out.println("这是外遍历"); for (Integer i : coll){ System.out.println(i); } //自遍历 System.out.println("自遍历"); coll.forEach(new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println(integer); } }); //lambda System.out.println("lambda"); coll.forEach(i-> System.out.println(i));
-
-
键值对遍历:获取所有的键值对对象,遍历键值对
-
Set<Entry对象> entrySet():将集合键值对存入Entry对象,将所有Entry对象放入Set集合返回
-
Entry对象的表现形式:Map.Entry<K.V>
-
获取键:Entry对象.getKey();
-
获取值:Entry对象.getValue();
Set<Map.Entry<K的泛型,V的泛型>> set = map.enterySet();
//迭代器遍历 Set<Map.Entry<String, Integer>> set = map.entrySet(); Iterator<Map.Entry<String, Integer>> it = set.iterator(); System.out.println("迭代器遍历"); while (it.hasNext()) { Map.Entry<String, Integer> entry = it.next(); System.out.println(entry.getKey() + "-" + entry.getValue()); } //外遍历 System.out.println("外遍历"); for (Map.Entry<String, Integer> entry : set) { System.out.println(entry.getKey() + "-" + entry.getValue()); } //自遍历 System.out.println("自遍历"); set.forEach(entry -> System.out.println(entry.getKey() + "- " + entry.getValue())); }
-
-
自遍历forEach:
-
forEach(BiConsumer接口实现类对象)
//自遍历forEach map.forEach(new BiConsumer<String, Integer>() { @Override public void accept(String s, Integer integer) { System.out.println(s+"-"+integer); } }); //lambda map.forEach((s,i)-> System.out.println(s+"-"+i));
-
使用
-
当哈希表的键为自定义类型时,必须重写类的hashCode和equals方法才能实现去重
-
HashMap允许null值和null键
-
最多存在一个null键,可以存在多个null值
-
-
LinkedHashMap在HashMap的基础上可以保证元素的存入和取出的顺序一致
-
TreeMap可以根据键对元素进行默认的升序排序
-
如果键为自定义类型,则必须书写排序规则,方式与TreeSet一致(Conparable和Conparator)
-
无法对null值进行排序(键不可为null)
-
-
Hashtable不允许null值和null键
-
Properties只能存放String类型
-
不推荐使用多态
-
不可添加泛型
-
推荐优先使用独有内容
-
String v getProperty(String k):根据键获取值
-
setProperty(String k ,String v):添加键值对
-
void load(输入流):根据流将配置文件内容读取到集合中
-
-
今日掌握
-
Map的存储特点
-
Map的常用实现类和特点
-
Map的创建语法
-
Map的遍历方式
-
Map的使用
Chp17-异常
概念
程序执行中不正常的情况
分类
-
Throwable:总父类,表示所有不正常的情况
-
Error:错误
-
无法解决也无法提前避免
-
通常由硬件设备、JVM问题、内存问题等导致
-
-
Exception:异常
-
可以解决或可以提前避免
-
通常由代码导致
-
RuntimException:运行时异常,也称未检查异常|未检异常
-
编译不报错,运行报错
-
可以处理,也可以不处理
-
java.lang.ArrayIndexOutOfBoundsException 数组下标越界
-
java.lang.StringIndexOutOfBoundsException 字符串下标越界异常
-
java.lang.NullPointerException:空指针异常
-
java.lang.Arithm eticException:数学运算异常
-
java.lang.NumberFormatException:数据类型转换异常
-
java.lang.ClassCastException:类型转换异常
-
...
-
普遍来自于java.lang包
-
-
非RuntimeException:非运行时异常,已检查异常|已检异常
-
编译就会报错
-
必须处理
-
只要不是运行时,就一定是非运行时
-
-
异常的产生
-
自然(自动)产生:当虚拟机运行或编译到存在问题的代码时,可以自动产生相应异常
-
手动产生:
throw 异常对象;
-
思路:在代码中通过throw关键字抛出一个异常对象,当虚拟机编译或运行到该语句时,就会触发相应异常
-
位置:方法内部
-
使用:
-
同一直属作用范围内,下方不可存在其他有效语句,因为无法执行
-
当执行到该语句时,会导致程序终止
程序终止:
-
内容运行结束
-
在非递归的环境下执行return;
-
执行到异常
-
-
-
异常处理
-
上抛异常(消极处理)
访问修饰 返回值类型 方法名(形参) throws 异常类名{}
-
原理:方法自身不解决问题,将自身异常向上抛至调用者,由调用者解决该异常,若所有调用者都选则上抛,则最终会抛至虚拟机,仍然会导致程序终止
-
无法根治问题,只能暂时使当前方法编译正常
-
使用:
-
可以同时上抛多个异常,异常类名之间,隔开即可
-
父类异常声明可以解决子类异常问题
-
调用者需要处理的异常类型由方法throws上抛的类型决定
throw和throws的区别:
-
位置:throw写在方法内部,throws写在方法声明处
-
作用:throw是抛出制造异常,throws是上抛解决异常
-
-
-
try-catch块(积极处理):
try{ //有可能发生异常的代码 }catch(有可能匹配的异常类型1 引用名1){ //处理的代码 }catch(有可能匹配的异常类型2 引用名2){ //处理的代码 }
-
使用:
-
对catch块的数量不做要求
-
原理:当try块中的内容发生异常时,则开始自上而下的进行catch块的捕捉匹配,如果匹配成功,则执行相应的catch块内容,程序方可继续向下执行。如果匹配失败,则仍然会导致程序终止。
-
一个try-catch至多执行一个catch内容
-
父类异常可以匹配子类异常
-
父类异常catch块必须写在子类异常catch下方
-
为了防止catch失败,通常会在最下方catch一个最大的Exception
-
catch非运行时异常时只能捕捉try中存在的异常
异常对象的操作方法:
-
返回String getMessage():获取异常的详细信息
-
void printStackTrace():输出异常的追栈(追踪)信息(异常类型、异常的详细信息、异常的触发位置)
-
-
finally块
try{ }catch(){ }finally{ //关闭链接、释放资源的代码 }
使用
-
写在try-catch块的最下方
-
无论如何都会执行
-
可以与try单独结合,无法与catch单独结合
-
当try、catch、finally中有retrun语句冲突时,优先执行finally
自定义异常
-
运行时异常:继承RuntimeException
-
构造:无参构造+有参构造
-
有参构造目的为给父类中声明的详细信息属性赋值
-
public class MyRuntimeException extends RuntimeException { public MyRuntimeException(){} public MyRuntimeException(String message){ super(message); } }
-
-
非运行时异常:继承Exception
-
内容:构造要求与运行时一致
public class MyException extends Exception { public MyException(){} public MyException(String message){ super(message); } }
-
今日掌握
-
异常的完整分类
-
运行时异常和非运行时异常的区别
报错时机、处理要求
-
throw和throws的区别
位置:throw写在方法内部,throws写在方法声明处小括号和大括号之间
作用:throw是抛出制造异常,throws是上抛解决异常
语法:throw后跟的是异常对象,throws后跟的是异常类名
-
try-catch的使用
-
finally的特点
-
自定义异常的特点
chp18-IO1-字节流
概念
将数据在JVM和本地磁盘之间传输
I:input 输入
O:output 输出
流
相当于管道,用来完成JVM和本地磁盘之间的数据传输
分类
-
从传输方向上看
-
输入流:本地磁盘向JVM传输
-
输出流:JVM向本地磁盘传输
-
-
从传输单位上看
-
字节流:以字节为单位进行传输,可以传输任意类型的数据,如文本、图片、视频、音频等
-
字符流:以字符为单位进行传输,只能传输文本类型的数据,如.txt .java .html等
-
-
从功能上看
-
节点流:具有实际传输意义的流
-
过滤流:没有传输功能,作用给节点增加附加功能或增强传输能力
-
输入流
-
抽象父类:inputStream
-
节点流:FileInputStream 是抽象父类的子类
创建
FileInputStream fis = new FileInputStream("被读取的文件夹文件路径")
-
绝对路径:以电脑磁盘为基点的完整路径
-
相对路径:以当前项目路径为基点的路径
-
文件必须存在项目下,实际开发不推荐
//相对路径 FileInputStream fis = new FileInputStream("file/a.txt"); //绝对路径 FileInputStream fis1 = new FileInputStream("C:\\Users\\62727\\IdeaProjects\\Chp18-input\\file/a.txt");
-
/和\是路径分隔符,由于\本身是转义字符的语法标志,所以需要写成\\,意为转义字符
-
-
路径必须截止至文件
-
文件必须已经存在
常用方法
-
void close():关闭流链接,释放相关资源(该方法是所有流中都具备的方法)
-
int read():读取一个字节并返回,读取到达末尾返回-1
-
int read(byte[]):尝试读取数组长度的数据至数组中,返回实际读取个数,读取到达末尾返回-1
//read()方法 while (true){ int n = fis.read();//接收读取的字节 if (n==-1){//判断是否返回-1 break; } System.out.println((char) n); } //read(byte[])方法,读取数组长度的数据至数组中 while (true){ byte[] bs = new byte[3];//一次读取3个长度的数据 int n = fis.read(bs);//接收返回的实际读取个数 if (n==-1){//判断是否到达末尾,返回-1 break; } for (byte b : bs) { System.out.println((char) b); } }
输出流
-
抽象父类:OutputStream
-
节点流:FileOutputStream
创建
FileOutputStream fos = new FileOutputStream("被写入的文件路径",true|false);
-
true:数据追加
-
false:数据覆盖,默认为false
-
文件不存在则自动创建
-
文件夹不存在,则报错
常用方法
-
void flush():刷新数据缓冲区(所有的输出流都拥有该方法)
-
void wirte(int ):向目标文件写入一个字节
-
void wirte(byte[] ):向目标文件写入一个数组的数据
FileOutputStream fos = new FileOutputStream("file/b.txt");//默认覆盖false fos.write(65); fos.write(66); fos.write(67);//b.txt = A B C String s = "ASDASDSAD"; byte[] bs = s.getBytes(); fos.write(bs);//b.txt = A B C A S D A S D S A D fos.close();
标准异常处理
-
JDK7.0之后提供了可以自动关流的异常处理结构
try( //需要关流的对象创建语句 ){ //其他操作语句 }catch(){//异常捕捉处理 }
-
原理:所有流都默认实现了AutoCloseable接口,该接口中提供了自动关流所需的close方法
try ( FileOutputStream fos = new FileOutputStream("file/c.txt"); ) { fos.write(65); fos.write(66); fos.write(67);//c.txt : A B C String s = "123456"; byte[] bs = s.getBytes(); fos.write(bs);//c.txt : A B C 1 2 3 4 5 6 for (byte b : bs) { System.out.print((char) b + " "); } } catch (FileNotFoundException e) { System.out.println("文件路径错误"); } catch (IOException e) { System.out.println("读写错误"); } catch (Exception e) { System.out.println("未知错误"); e.printStackTrace(); }
文件复制
-
原理:从文件A中读取内容到JVM,再从JVM将读取的内容写入到文件B。以此实现AB文件之间的数据复制
-
先读后写
-
public static void main(String[] args) { copy1(); copy2(); } public static void copy1() { try ( FileOutputStream fos = new FileOutputStream("E:\\byjava\\test/B.jpg"); FileInputStream fis = new FileInputStream("E:\\byjava\\test/A.jpg") ) {//一次复制一个字节 while (true){ int n = fis.read();//读取字节并返回 if (n==-1){ break; } //将接收到的字节写入C.jpg fos.write(n); } System.out.println("成功"); } catch (FileNotFoundException e) { System.out.println("文件路径错误"); } catch (IOException e) { System.out.println("读写错误"); } catch (Exception e) { System.out.println("未知错误"); e.printStackTrace(); } } public static void copy2() { try ( FileOutputStream fos = new FileOutputStream("E:\\byjava\\test/C.jpg"); FileInputStream fis = new FileInputStream("E:\\byjava\\test/A.jpg") ) {//一次复制一个数组 while (true){ byte [] bs = new byte[1024];//定义数组接收字节 int n = fis.read(bs);//按数组长度读取字节并存到原数组中 if (n==-1){ break; } fos.write(bs); } System.out.println("成功"); } catch (FileNotFoundException e) { System.out.println("文件路径错误"); } catch (IOException e) { System.out.println("读写错误"); } catch (Exception e) { System.out.println("未知错误"); e.printStackTrace(); } }
缓冲过滤流
-
原理:内置数据缓冲区,在进行数据读写时,数据会先对接给缓冲区,当缓冲区存满或手动刷新时,再将缓冲区中的数据对接给目标文件,以此减少JVM读写的次数,提高数据传输效率
-
BufferedInputStream:字节缓冲输入过滤流
-
BufferedOutputStream:字节缓冲输出过滤流
创建
-
基于节点流对象
输入: BufferedInputStream bis = new BufferedInputStream(fis对象) 输出: BufferedOutputStream bos = new BufferedOutputStream(fos对象)
public static void copy3() { long l1 = System.nanoTime(); try ( FileOutputStream fos = new FileOutputStream("E:\\byjava\\test/D.jpg"); FileInputStream fis = new FileInputStream("E:\\byjava\\test/A.jpg"); BufferedOutputStream bos = new BufferedOutputStream(fos); BufferedInputStream bis = new BufferedInputStream(fis) ) {//一次复制一个字节+缓冲过滤流 while (true){ int n = bis.read();//读取字节并返回 if (n==-1){ break; } //将接收到的字节写入D.jpg bos.write(n); } long l2 = System.nanoTime(); System.out.println("一次复制一个字节+缓冲过滤流,花费了"+(l2-l1)/1E09+"秒"); } catch (FileNotFoundException e) { System.out.println("文件路径错误"); } catch (IOException e) { System.out.println("读写错误"); } catch (Exception e) { System.out.println("未知错误"); e.printStackTrace(); } }
使用
当缓冲过滤流先写后读操作同一文件时,必须在写入完成之后强刷缓冲区,将内容提前写入目标文件,才能正常读取
-
强刷缓冲区:
-
关流:执行输出流的close()方法之前,会自动执行flush()
-
直接调用输出流的flush()方法(推荐)
-
对象过滤流
-
附件功能1:读写基本类型
-
附加功能2:读写引用类型
-
输入:ObjectInputStream
-
输出:ObjectOutputStream
读写基本类型
输入 - 读取:xxx readxxx() 输出 - 写入:void writeXxx(xxx 值) 注:xxx表示为对应的基本类型名
使用
-
对象过滤流底层嵌套了数据缓冲区,所以先写后读操作同一文件时,仍然需要在写入完成后刷新缓冲区
-
由于对象过滤流读写数据存在一定的复杂性,所以为了保证数据的安全,在写入数据时会通过魔数机制对其进行加密,读取时再进行解密。
try ( //先写 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file/c.txt")); //再读 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file/c.txt")) ) { oos.writeObject(6.6); oos.flush(); System.out.println(ois.readObject()); } catch (FileNotFoundException e) { System.out.println("文件路径错误"); } catch (IOException e) { System.out.println("读写错误"); } catch (Exception e) { System.out.println("未知错误"); e.printStackTrace(); }
读写引用类型
输入-读取:Object readObject() 输出-写入:void write(Object o)
使用
-
readObject()方法读取到达末尾,会抛出EOFException异常
-
writeObject()自带缓冲区刷新,先写后读操作同一文件时,无需刷新缓冲区
读写String
try( ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file/c.txt")); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file/c.txt")); ) { oos.writeObject("ali"); oos.writeObject("alibaba"); oos.writeObject("alibaba是个快乐的男孩"); oos.writeObject("ali"); oos.writeObject("alibaba"); while (true){ try { String s = (String) ois.readObject(); System.out.println(s); } catch (EOFException e) { break; } } } catch...
读写自定义类型
-
自定义类型必须实现java.io.Serializable接口,意为该类允许被序列化
序列化:拆分对象信息的过程
反序列化:根据信息组装对象的过程
Serializable接口:是序列化接口,是IO流能否读写引用对象的标志
-
可以通过再属性前添加transient修饰符的方式防止某个属性参与序列化
public class Student1 implements Serializable { private String name; private int age; ...
try( ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file/c.txt")); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file/c.txt")); ) { //一个对象 oos.writeObject(new Student1("张三",22)); System.out.println(ois.readObject()); //多个对象 oos.writeObject(new Student1("张三",22)); oos.writeObject(new Student1("张二",20)); oos.writeObject(new Student1("张一",21)); while (true){ try { Student1 s1 = (Student1) ois.readObject(); System.out.println(s1); } catch (EOFException e) { break; } } } catch ... }
今日掌握
-
流的分类
-
标准异常处理的写法
-
文件复制的源码(字节复制+缓冲过滤流)
-
对象过滤流读写自定义类型的方法
Chp19-IO2-字符流
作用
只能操作char或String类型的数据
输入流
-
抽象父类:Reader
-
节点流:FileReader
创建与字节流一致
常用方法
-
int read():读取一个字符,读取到末尾返回-1
-
int read(Char[]):尝试读取数组长度的数据至数组中,返回实际读取个数,读取到达末尾返回-1
try ( FileReader fr = new FileReader("file/a.txt"); ) {//一次读取一个字符 /* while (true){ int n = fr.read(); if (n==-1){ break; } System.out.println((char) n); }*/ //一次读取数组长度的数据至数组中 while (true){ char[] c = new char[2]; int n =fr.read(c); if (n==-1){ break; } for (char c1 : c) { System.out.println(c1); } } } catch ...
输出流
-
抽象父类:Writer
-
节点流:FileWriter
创建与字节流一致
常用方法
-
void write(int):写入一个字符
-
void write(char[]):写入一个字符串
//一次写入一个字符 fw.write(65); fw.write(66); fw.write(67); //一次写入一个字符串 fw.write("阿里巴巴"); fw.flush(); while (true){ int n = fr.read(); if (n==-1){ break; } System.out.print((char) n); }
缓冲过滤流
-
输入:BufferedReader
-
输出:PrintWriter
-
BufferedWriter中的方法不及PrintWriter的实用
创建与字节流一致
-
输入缓冲过滤流
常用方法
String readline():读取一行数据,读取到达末尾返回null
try (BufferedReader fr = new BufferedReader(new FileReader("file/a.txt")); ) { while (true){ //字符串接收 String s = fr.readLine(); if (s==null){//判断是否读取到达末尾 break; } //操作字符串 System.out.println(s); } } catch ...
输出缓冲过滤流
常用方法
-
print(值):向目标文件写入一个数据,默认不换行
-
println(值):向目标文件写入一个数据,默认换行
-
println():向目标文件写入一个空行
try ( PrintWriter pw = new PrintWriter(new FileWriter("file/a.txt")) ) { pw.println(10); pw.println(20); pw.println("asdsad"); pw.println(new Student("zhangsan",22)); System.out.println("成功"); } catch... }
printWriter中的方法与字节流中的writeObject方法都可以传入一个对象,区别是什么?
printWriter中的方法:写的是对象的toString方法,并不是对象本身
writerObject方法:写的是对象本身,是对对象信息的完整序列化,所以可在读取时进行反序列化
什么是输出语句?
System是来自于Java.lang包的工具类,out是该类的一个静态属性,属性类型为标准输出流类型(PrintStream),print或println是该流中的方法
PrintWriter和PrintStream中的print或println方法有什么区别?
PW:是将数据写入到任意文件
PS:是将数据写入到控制台
桥转换流
字符编码集
字符编码集:一整套编码和解码的流程就称之为编码集
编码:原内容->编码->数字
解码:数字->解码->原内容
常见编码集:
GBK:简体中文
Big5:繁体中文
ASC||:美国
ISO-8859-1:西欧
Unicode:
UTF-8:行业标准,内容所占字节由内容大小而定,通常为1-3个字节
UTF-16:java默认编码集,内容固定占用两个字节
编解码集之间的编解码方式互不相通
作用
将字节流转为字符流,并将转换过程中设置编解码集
创建
-
输入:InputStreamReader
-
输出:OutputStreamWriter
InputStreamReader isr = new InputStreamReader(fis对象,”编码集“);
OutputStreamWriter osw = new OutputStreamWriter(fos对象,”编码集“);
-
创建字节节点流,并设置文件路径
-
创建桥转换流,将字节节点流转为字符流并设置编码集
-
为了方便使用,给桥转换流添加缓冲过滤流
使用
操作同一文件时,读写使用的编码集必须保持一致,否则乱码
今日掌握
-
字符流和缓冲过滤流的使用
-
字符流和对象过滤流写对象的区别
-
标准输出流和字符输出缓冲过滤流方法的区别
-
桥转换流的创建和使用
Chp20-线程
什么是进程?
操作系统(OS)中并发(同时)执行的多个程序任务
进程的特点
-
宏观并行,微观串行
CPU会切割时间段,在一个时间段内,会划分成若干个时间片,拥有时间片的程序才能执行自身内容,一个时间片又只能被一个程序拥有,所以程序之间注定是串行的,但是由于时间片的划分足够细小,交替频率足够快,所以就会形成并行的假象
什么是线程?
是进程中并发执行的多个任务,是程序的基本组成部分
线程的特点
-
宏观并行,微观串行
一个时间片只能被一个程序拥有,一个程序一次又只能执行一条线程,时间片之间交替执行,所以线程执行也是如此
多线程的概念
-
只存在多线程,不存在多进程
拿到时间片正在执行中的程序才叫进程
只要被创建出来的线程任务都叫线程,线程任务可以同时存在多个
线程的组成
-
时间片:由CPU调度分配给程序,线程之间争抢获取时间片
-
数据:一个线程对应某个或某个功能操作,数据是功能执行的基础
-
代码:书写逻辑,操作数据
线程的创建
-
继承Thread类,重写run方法(线程对象与任务内容结合)
public class MyThread extends Thread{ @Override public void run() { for (int i = 1; i <=50 ; i++) { System.out.println(i); } } } public class Test1 { public static void main(String[] args) { MyThread t1 = new MyThread(); } }
-
实现Runnable接口,重写run方法,将实现类传入Thread对象(线程对象与任务内容分离)
public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 1; i <=50 ; i++) { System.out.println(i); } } } public class Test1 { public static void main(String[] args) { Thread t2 = new Thread(new MyRunnable()); } } //若任务内容只会执行一次,则可以直接借助匿名内部类或lambda表达式: //匿名内部类 Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <100 ; i++) { System.out.println(i); } } }); //lambda Thread thread1 = new Thread(()->{ for (int i = 0; i <50 ; i++) { System.out.println(i); } });
第一种创建方式会将线程对象与任务内容绑定,不满足单一职责,也无法灵活更改线程对象的任务内容,所以推荐使用第二种创建方式
使用
-
开启线程需要调用线程的start()方法
-
主函数也称为主线程,是JVM中默认存在的一条线程,也是第一个拿到时间片的线程
-
当开启多条线程,则JVM执行结束的时机会从主函数结束变为所有开启的线程都执行结束
-
执行流程:多线程之间会同时争抢时间片,那到时间片的线程才能执行自身内容,其他线程只能等抢到时间片或拥有时间片的线程执行结束释放时间片之后才有可能执行自身内容。当线程自身内容执行结束,则线程不会再争抢时间片
线程的状态
基础状态
等待状态
-
sleep()
-
Thread.sleep(long 毫秒数):使当前线程休眠指定时间
-
1秒=1000毫秒
-
-
作用:当线程进入休眠状态会释放自身的时间片,等到休眠结束才能回到就绪状态
-
有限期等待
-
该方法需要处理非运行时异常,由于run方法无法上抛异常,所以必须try-catch处理
//匿名内部类 Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("休眠异常"); } for (int i = 0; i <51 ; i++) { System.out.println("thread:"+i); } } }); //lambda Thread thread1 = new Thread(()->{ for (int i = 51; i <101 ; i++) { System.out.println("thread1》"+i); } }); thread.start(); thread1.start(); System.out.println("主线程结束");
-
-
join()
-
线程对象.join()
-
作用:使调用者线程优先于当前线程执行,只有当调用者线程执行完毕进入死亡状态,当前线程才有可能进入就绪状态
-
该方法需要处理非运行时异常,由于run方法无法上抛异常,所以必须try-catch处理
-
无限期等待状态
//匿名内部类 Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <51 ; i++) { System.out.println("t1:"+i); } } }); //lambda Thread t2 = new Thread(()->{ try { t1.join(); } catch (InterruptedException e) { System.out.println(); } for (int i = 51; i <101 ; i++) { System.out.println("t2》"+i); } }); Thread t3 = new Thread(()->{ try { t2.join(); } catch (InterruptedException e) { System.out.println(); } for (int i = 101; i <151 ; i++) { System.out.println("t3》>>"+i); } }); t1.start(); t2.start(); t3.start(); System.out.println("主线程结束");
-
sleep和join的区别:
sleep进入的是有限期等待状态,join进入的是无限期等待状态
sleep是静态方法,通过Thread调用,join是实例方法,必须通过线程对象调用
sleep不会干扰其他线程,join方法一定会干扰其他线程
线程池
概念
管理线程任务。
作用
线程任务在线程池中执行结束之后并不会立即销毁,而是回到池中等待下次执行。直至线程池关闭,任务内容才会统一销毁。在线程任务需要反复执行时,可以有效的减少空间浪费、提高执行效率
API
-
ExecutorService:线程池接口
-
submit(线程任务对象):提交线程任务使其执行
-
shutdown:关闭线程池
-
-
Executors:线程池工具类
-
newFixedThreadPool(int ):获取一个固定并发数量的线程池对象
-
newCachedThreadPool():获取一个不固定并发数量的线程池对象
-
区别:
-
固定并发数量:同时只能有固定数量的任务执行,其他任务只能等执行中的任务运行结束让位之后才能候补执行
-
不固定并发数量:所有提交的任务都会同时并发执行
-
-
//创建一个固定并发数量的线程池对象 // ExecutorService es = Executors.newFixedThreadPool(2); //创建一个不固定并发数量的线程池对象 ExecutorService es = Executors.newCachedThreadPool(); //创建多个任务 Runnable r1 = new Runnable() { @Override public void run() { for (int i = 0; i <=50 ; i++) { System.out.println("r1>"+i); } } }; Runnable r2 = new Runnable() { @Override public void run() { for (int i = 51; i <=100 ; i++) { System.out.println("r2><"+i); } } }; Runnable r3 = new Runnable() { @Override public void run() { for (int i = 101; i <=150 ; i++) { System.out.println("r3>><<"+i); } } }; //提交线程任务 es.submit(r1); es.submit(r2); es.submit(r3); //固定并发数量为2的话r1,r2交替执行,一方先结束,另一个和r3交替执行 //关闭线程池 es.shutdown();
线程任务
-
Runnable():run()
-
特点:无返回值,不可上抛异常
Runnable r1 = new Runnable() { @Override public void run() { for (int i = 0; i <=50 ; i++) { System.out.println("r1>"+i); } } };
-
-
Callable:call()
-
特点:有返回值,可以上抛异常,默认上抛Exception
-
语法:
Callable<返回值泛型> c = new Callable<返回值泛型>() { @Override public Integer call() throws Exception { //操作语句 } };
-
使用:执行结束返回值会封装进Future对象返回
-
可以通过Future<返回值泛型> 引用名 = es对象.submit(Callable任务对象)的方式接收返回值
-
可以通过Future对象.get()获取内部封装的返回值,需要处理非运行时异常
//获取一个线程池对象 ExecutorService es = Executors.newCachedThreadPool(); //线程任务1:计算1-100的和并返回 Callable<Integer> c = new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 1; i <=100 ; i++) { sum+=i; } return sum; } }; //线程任务2:拼接大写字母并返回 Callable<StringBuilder> c1 = new Callable<StringBuilder>() { @Override public StringBuilder call() throws Exception { StringBuilder sb = new StringBuilder(); for (int i = 'A'; i <'Z' ; i++) { sb.append((char) i); } return sb; } }; //提交任务执行并接收返回值 Future<Integer> f1 = es.submit(c); System.out.println("1~100的和为:"+f1.get()); Future<StringBuilder> f2 = es.submit(c1); System.out.println(f2.get()); //关闭线程池 es.shutdown();
-
-
线程安全问题(重点)
概念
多个线程同时访问同一个临界资源对象时,有可能破坏其原子操作,从而导致数据丢失,引发线程安全问题
-
临界资源:被多个线程同时访问的对象
-
原子操作:线程在访问临界资源的过程中不可更改或缺失的操作
互斥锁
-
所有对象都默认拥有互斥锁,该锁默认处于关闭状态
-
开启互斥锁:synchronized
-
作用:开启锁之后,线程会处于同步状态,只有同时拥有时间片和锁标记的线程才能执行自身内容,在此期间不会被其他线程打断和破坏,其他线程只能等拥有时间片和锁标记的线程执行结束释放资源之后,才有可能进入就绪状态
同步方法
-
原理:给被同时访问的方法加锁
-
语法:
访问修饰符 synchronized 返回值类型 方法名(形参列表){ }
private List<Integer> list = new ArrayList<>(); /** * 添加元素 */ public synchronized void insert(int n){ list.add(n); } /** * 查看元素 */ public void look(){ System.out.println("长度为"+list.size()); list.forEach(i-> System.out.print(i+" ")); }
同步代码块
-
原理:在拥有时间片的线程内部完成加锁
-
语法:
synchronized(临界资源对象){ }
-
使用:所有同时访问临界资源的线程都需要添加同步代码块
-
Method m = new Method(); Thread t1 = new Thread(()->{ synchronized (m) {//包裹整个for循环,保证不被其他线程争抢时间片,循环结束之后,继续执行其他线程 for (int i = 1; i <=5 ; i++) { m.insert(i); } } }); Thread t2 = new Thread(()->{ synchronized (m) { for (int i = 6; i <=10 ; i++) { m.insert(i); } } }); t1.start(); t2.start(); t1.join(); t2.join(); m.look();
区别
-
同步方法:在临界资源中加锁。线程需要同时争抢时间片和锁标记,效率较慢但是书写简单
-
同步代码块:在线程中加锁。线程只需争抢时间片,拥有时间片的线程默认拥有锁标记,效率较快但是书写繁琐
线程安全的集合类
-
悲观锁:悲观的认为集合一定会出现线程安全问题,所以直接加锁
-
乐观锁:乐观的认为集合不会出现线程安全问题,所以不加锁,直到真正出现问题时,再结合算法解决问题
JDK5.0--java.util.concurrent
-
CopyOnWriteArrayList:
-
原理:在进行写操作时(增删改),会先复制出一个副本,在副本中完成写操作,如果过程中出现安全问题,则舍弃当前副本,重写复制新的副本重复操作,直至副本中未出现安全问题,再将集合地址转换为副本地址,以此保证集合中不会存在线程安全问题
-
特点:舍弃写的效率,提高读的效率。适用于读操作远高于写操作时
-
-
CopyOnWriteArraySet:
-
原理和CopyOnWriteArrayList一致,在写入时可以去重
-
特点与CopyOnWriteArrayList一致
-
-
ConcurrentHashMap:
-
原理:CAS算法
compare and swap:比较并交换
原有值、预期值、结果值:只有当原有值与预期值相同时才会将结果值放入内存,否则重写计算
例:
int i = 1;
i++;
原有值:1 预期值:1 结果值:2
-
今日掌握
-
进程线程的特点及原理
-
线程的两种创建方式
-
线程的基础状态
-
sleep和join的区别
-
线程池的使用
-
Runnable和Callable的区别
-
什么是线程安全问题
-
如何解决线程安全问题
-
同步方法和同步代码块的区别
-
线程安全的集合类都有哪些?
Chp21-反射和设计模式
反射
是一种底层技术,通常用于底层框架的编写
类对象-Class
-
类对象:是类加载的产物,存放着类的所有信息(如:属性、方法、构造、父类信息、接口信息等)。通常只有一个
-
类的对象:是类实例化的产物,存放着对象的具体信息,可以存在多个
获取类对象
-
引用名.getClass()
-
唯一一个需要创建对象的
Student stu = new Student; Class c1 = stu.getClass;
-
-
类名.class
-
唯一一个可以添加泛型的
Class<Student> c2 = Student.class;
-
-
Class.forName(“全限定名”)
-
forName()需要处理非运行时异常
-
唯一一个可以把类的信息和类对象的获取分离的
Class c3 = Class.forName("com.qc.entity.Student") //分离 String s = "com.qc.entity.Student"; Class c4 = Class.forName(s);
-
构建类的对象
-
类的对象 newInstance():通过无参构造构建类的实例
//通过无参构造类的实例 //无泛型,需要强转 Student stu1 =(Student) c1.newInstance(); //有泛型,不需要强转 Student stu2 = c2.newInstance();
-
利用有参构造获取类的实例
-
Constructor<?> getDeclaredConstructor(参数列表的类对象):获取类对象中的有参构造器对象
-
构造器对象.newInstance(实参列表):构建类的实例
//利用有参构造类的实例 Constructor<Student> con = c2.getDeclaredConstructor(String.class,int.class,double.class); Student student= con.newInstance("张三",88,79);
-
-
设计模式
是一种编码套路
单例模式
一类只能创建一个实例
饿汉式
直接创建唯一实例
//单例模式-饿汉式 public class ClassA { //构造私有化-防止外界调用构造创建对象 private ClassA(){} //提供渠道 外界获取唯一实例的渠道 //static:使外界直接通过类名访问 public static ClassA newClassA(){ return ca; } //static:1.newClassA可以返回访问 2.静态属性内存中只会存在一个 //private:防止外界直接访问属性 private static ClassA ca = new ClassA(); } //测试类 public class Test1 { public static void main(String[] args) { ClassA ca1 = ClassA.newClassA(); ClassA ca2 = ClassA.newClassA(); System.out.println(ca1);//com.qc.entity.ClassA@1b6d3586 System.out.println(ca2);//com.qc.entity.ClassA@1b6d3586 } }
缺点:有可能浪费空间
懒汉式
在获取实例时创建唯一对象
//懒汉式 public class ClassB { //构造私有化 private ClassB(){} //提供渠道 外界获取唯一实例的渠道 //static:使外界直接通过类名访问 //synchronized:同步方法 预防线程安全问题 public static synchronized ClassB newClassB(){ if (cb==null){//第一次获取时再进行实例化 cb = new ClassB(); } return cb; } //static:1.newClassA可以返回访问 2.静态属性内存中只会存在一个 //private:防止外界直接访问属性 private static ClassB cb = null; } //测试类 public class Test2 { public static void main(String[] args) { Thread t1 = new Thread(()->{ ClassB cb = ClassB.newClassB(); System.out.println(cb); }); Thread t2 = new Thread(()->{ ClassB cb = ClassB.newClassB(); System.out.println(cb); }); t1.start(); t2.start(); } }
缺点:线程效率慢
懒汉式-进阶版
在懒汉式的基础上利用同步代码块结合二次校验提高代码执行效率
//懒汉式-进阶版 public class ClassC { //构造私有化 private ClassC() { } //提供渠道 外界获取唯一实例的渠道 //static:使外界直接通过类名访问 public static ClassC newClassB() { if (cc == null) {//二次校验,决定是否开启互斥锁 synchronized (ClassC.class) {//临界资源对象:当前类的类对象 if (cc == null) {//当第一次获取时再进行实例化 cc = new ClassC(); } } } return cc; } //static:1.newClassA可以返回访问 2.静态属性内存中只会存在一个 //private:防止外界直接访问属性 private static ClassC cc = null; } //测试类 package com.qc.test; import com.qc.entity.ClassC; public class Test3 { public static void main(String[] args) { Thread t1 = new Thread(()->{ ClassC cc = ClassC.newClassC(); System.out.println(cc); }); Thread t2 = new Thread(()->{ ClassC cc = ClassC.newClassC(); System.out.println(cc); }); t1.start(); t2.start(); } }
工厂模式
-
是一种底层技术,通常用于底层框架的编写
-
思路:将数据的创建和维护交由工厂完成
案例
-
需求:构建一个工厂类,获取学生对象
-
提供学生类
package com.qc.entity; public class Student { private String name; private int age; private double score; @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", score=" + score + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getScore() { return score; } public void setScore(double score) { this.score = score; } public Student() { } public Student(String name, int age, double score) { this.name = name; this.age = age; this.score = score; } }
-
书写配置文件
-
在项目下创建后缀名为.properties的配置文件
-
作用:存放被管理类的全限定名
-
以键=值的形式存放数据
-
键值不可添加双引号、末尾不可添加分号、中间不可存在多个符号(如空格)
-
一行只能有一个键值对
StudentClassName=com.qc.entity.Student
-
-
书写工厂类
package com.qc.entity; import java.io.FileInputStream; import java.nio.file.FileSystem; import java.util.Properties; public class MyFactory { /** * 获取学生类的实例对象 * @return 学生对象实例 */ public static Student newStudent(){ Student student = null; try ( //创建字节输入流 FileInputStream fis = new FileInputStream("factory.properties") ){ //将配置文件中的内容读取存放至Properties集合 Properties properties = new Properties(); properties.load(fis); //获取学生类的全限定名 String studentClassName = properties.getProperty("StudentClassName"); //通过Class.forName获取类对象 Class c = Class.forName(studentClassName); //通过类对象构建学生实例 student = (Student) c.newInstance(); }catch (Exception e){ System.out.println("未知异常"); } return student; } }
-
测试
package com.qc.test; import com.qc.entity.MyFactory; import com.qc.entity.Student; public class TestFactory { public static void main(String[] args) { Student s2 = MyFactory.newStudent(); Student s1 = MyFactory.newStudent(); System.out.println(s1); System.out.println(s2); } }
今日掌握
-
类对象和类的对象的区别
-
获取类对象的方式
-
通过类对象构建实例的方式
-
书写peoperties配置文件的要求及读取至集合中的代码