JavaSE
2023-04-21 15:07:16 0 举报
AI智能生成
自学内容
作者其他创作
大纲/内容
入门
概述
Java是一门高级编程语言
Java语言由Sun公司研发,后期被Oracle公司收购
Java之父:詹姆斯·高斯林
Java技术平台
JavaSE(标准版):Java技术的核心和基础
JavaEE(企业版):企业级应用开发的一套解决方案
JavaME(小型版):针对移动设备应用的解决方案
Java程序的执行原理
机器语言:0和1组成
汇编语言
高级语言
Java使用
JDK
开发工具
javac
将源代码文件(*.java)编译成字节码文件(*.class)
java
在JVM中运行字节码文件(*.class)
JRE
JVM
运行Java程序
核心类库(API)
Java调用程序
Java跨平台
一次编译,处处可用
path环境变量
JAVA_HOME环境变量
开发工具IDEA
project(项目、工程)
module(模块)
package(包)
class(类)
Java基础
基础语法
数据类型
注释note
//~~~注释一行(Ctrl+/)
/*~~~*/注释多行(Ctrl+Shift+/)
/**~~~~~*/文档注释
字面量
常用数据
整数
写法一样
小数
写法一样
字符
单引号括起来,只能写一个字符
' '空字符
'\n'换行的意思
'\t'相当于TAB操作
'\n'换行的意思
'\t'相当于TAB操作
字符串
双引号括起来,里面内容随意
布尔值
true
flase
null值
变量
数据类型 变量名称 = 初始值
int a = 1
int a = 1
使用注意事项
要先声明再使用
变量声明后,不能存储其它类型的数据
变量的有效范围是从定义开始到“}”截止,且在同一范围内部不能定义2个同名变量
变量定义的时候可以没有初始值,但使用的时候必须给初始值
变量在计算机中的底层原理
二进制:0和1
最小保存单元:8个二进制位(bit,简称b)组成一个字节(byte,简称B)
1B=8b
1KB=1024B
1MB=1024KB
1GB=1024MB
1TB=1024GB
1KB=1024B
1MB=1024KB
1GB=1024MB
1TB=1024GB
字符存储方式
ASCII字符代码表
'0'-->48-->0101...
'A'-->65-->01.....
'a'-->97-->01100001
图片存储方式
图片由无数个像素点组成,每个像素点的数据用0~255*255*255*255表示颜色
声音存储方式
声音由声波的形式表示,声波可以用二进制数据表示
二进制、八进制、十六进制
数据类型
基本数据类型
整型
byte字节整形——1字节
short短整型——2字节
int整形(默认)——4字节
long长整型——8字节
随便写一个整数字面量默认是int类型,需要在整数字面量后加L/l来表示long类型
浮点型(小数)
float单精度——4字节
随便写一个小数字面量默认是double类型,需要在小数字面量后加F/f变成float类型
double双精度(默认)——8字节
字符型
char——2字节
布尔型
boolean——1字节
引用数据类型
字符串
String
数组
int[ ] b = new int[7]
类
当建立对象时,类就是引用类型Car a = new Car()
内存原理
基本数据类型
基本数据类型的存储原理:所有的简单数据类型不存在“引用”的概念,基本数据类型都是直接存储在内存中的内存栈上的,数据本身的值就是存储在栈空间里面,Java语言里面八种数据类型是这种存储模型;
引用数据类型
引用类型的存储原理:引用类型继承于Object类(也是引用类型)都是按照Java里面存储对象的内存模型来进行数据存储的,使用Java内存堆和内存栈来进行这种类型的数据存储,简单地讲,“引用”(存储对象在内存堆上的地址)是存储在有序的内存栈上的,而对象本身的值存储在内存堆上的 ;
1、基本数据类型和引用类型的区别主要在于基本数据类型是分配在栈上的,而引用类型是分配在堆上的
2、不论是基本数据类型还是引用类型,他们都会先在栈中分配一块内存,对于基本类型来说,这块区域包含的是基本类型的内容;而对于引用类型来说,这块区域包含的是指向真正内容的指针,真正的内容被手动的分配在堆上。
2、不论是基本数据类型还是引用类型,他们都会先在栈中分配一块内存,对于基本类型来说,这块区域包含的是基本类型的内容;而对于引用类型来说,这块区域包含的是指向真正内容的指针,真正的内容被手动的分配在堆上。
堆和栈
堆(heap)
Java内存的一种,作用是用于存储Java中的对象和数组,当new一个对象或创建一个数组时,就会在堆内存中开辟一段空间,用于存放
栈(stack)
Java内存的另一种,主要是用来执行程序用的,比如基本类型的变量和对象的引用变量
优缺点
堆
优势:可以动态地分配内存空间,需要多少内存空间不必事先告诉编译器,因为它是在运行时动态分配的
缺点:由于需要在运行时动态分配内存,所以存取速度较慢
堆的物理地址分配对象是不连续的,因此性能慢些
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定,一般堆大小远远大于栈
栈
优势:存取速度比堆快,栈数据可以共享
缺点:存放在栈中的数据占用多少内存空间需要在编译时确定下来,缺乏灵活性
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的,所以性能快
栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的
堆和栈异同
差异
1、堆内存用来存放由new创建的对象和数组
2、栈内存用来存放方法或者局部变量等
3、堆是先进先出,后进后出
4、栈是后进先出,先进后出
相同
1、都是Java内存的一种
2、系统都会自动去回收它,但是对于堆内存一般开发人员会自动回收它
关键字
不能用来作为类名或是变量名称,否则报错
标识符
基本要求:由数字、字母、下划线_和美元符$等组成
强制要求:不能以数字开头、不能是关键字、区分大小写
命名指导规范
变量名称:满足标识符规则,建议全英文、有意义、首字母小写,满足“驼峰模式”。int studyNumber = 29
类名称:满足标识符规则,建议全英文、有意义、首字母大写,满足“驼峰模式”。HelloWorld.java
类型转换
自动类型转换
类型范围小的变量,可以直接赋值给类型范围大的变量
byte->short(char)->int->long->float->double
byte->short(char)->int->long->float->double
表达式的自动类型转换
在表达式中,小范围类型的变量会自动转换成当前较大范围的类型再运算
byte\char\short->int->long->float->double
byte\char\short->int->long->float->double
表达式的最终结果类型是由表达式中最高类型决定的
表达式中,byte、short、char是直接转换成int类型参与运算
强制类型转换
类型范围大的数据或变量,不能直接赋值给类型范围小的变量
数据类型 变量2 = (数据类型)变量1或数据
数据类型 变量2 = (数据类型)变量1或数据
强制类型转换可能造成数据(丢失)溢出
浮点型强转成整型,直接丢掉小数部分,保留整数部分返回
运算符
基本算数运算符
加+
减-
乘*
除/
两个整数型相除会得到整数型
如果想得到准确数值,则前面*1.0,结果为double类型
如果想得到准确数值,则前面*1.0,结果为double类型
10 / 3 == 3
1.0 * 10 / 3 ==3.3333
取余%
+符号做连接符
+符号与字符串运算是用作连接符,结果依然是一个字符串
能算则算,不能算则连在一起
自增自减运算符
++变量自身的值加1
--变量自身的值减1
++和--既可放变量前也可放变量后
单独使用时得数一样
在表达式中,放变量前表示先对变量+1、-1再运算;放变量后表示先运算再对变量+1、-1
拓展面试
int a=3;int b=5;int rs=a++ + ++a - --b + b-- - a-- + ++b +2 9
++和--只能操作变量,不能操作字面量
赋值运算符
基本赋值=
+=加后赋值
实现数据累加,把右边的数据加给自己
a += b —> a = a + b
自带强制类型转换
byte a = 1; byte b = 3; a += b; 结果等同于byte a = (byte) (a + b)
-=减后赋值
*=乘后赋值
/=除后赋值
%=取余后赋值
关系运算符
==相等
!=不相等
>
>=
<
<=
逻辑运算符
& 逻辑与
| 逻辑或
! 逻辑非
^ 逻辑异或
两个条件都是flase(或true)则结果为flase,两个条件不同结果是true
&& 短路与(双与)
判断结果与"&"一样,过程是左边为flase,右边则不执行
|| 短路或(双或)
判断结果与"|"一样,过程是左边为true,右边则不执行
三元运算符
条件表达式 ? 值1:值2;
首先计算关系表达式的值,如果值为true,返回值1,如果值为flase,返回值2
首先计算关系表达式的值,如果值为true,返回值1,如果值为flase,返回值2
求三个整数的最大值
int temp = num1 > num2 ? num1 : num2;用临时变量保存起来
int max = temp > num3 ? temp : num3;输出max
int temp = num1 > num2 ? num1 : num2;用临时变量保存起来
int max = temp > num3 ? temp : num3;输出max
求三个整数最大值(拓展)
int max = num1 > num2 ? num1 >num3 ? num1 : num3 : num2 > num3 ? num2 : num3
int max = num1 > num2 ? num1 >num3 ? num1 : num3 : num2 > num3 ? num2 : num3
运算符优先级
(由高到低)
(由高到低)
( )
!、-、++、--
*、/、%
+、-
<<、>>、>>>
<、<=、>、>=
==、!=
&
^
|
&&
||
?
=、+=、-+、*=、/=、%=、&=
案例键盘录入技术
API
应用程序编程接口,技术说明书,告诉我们怎么用Java程序
键盘录入技术
导包:告诉程去JDK的哪个包中找扫描器技术
import java.util.Scanner;
写一行代码代表得到键盘扫描器对象
Scanner sc = new Scanner(System.in);
等待接收用户输入数据
int age = sc.nextlnt();
String name = sc.next();
程序流程控制
顺序结构
程序默认流程
分支结构
if
根据判断结果(真或假)去执行分支,可做只匹配,也可做区间匹配
子主题
switch
匹配条件去执行分支,适合做值匹配的分支选择
switch的注意事项
表达式类型只能式byte、short、int、char,不支持double、float、long
case给出的值不允许重复,只能是字面量,不能是变量
不要忘记break,否则会出现穿透现象
switch的穿透性
当执行case块中的语句没有break时,执行语句之后不会跳出,而是继续往下执行下一个case的语句(此时不会执行任何匹配),直到出现break才跳出分支
案例:月份天数查看器
循环结构
for
一般知道需要循环多少次时使用for循环
while
一般不知道循环多少次时使用while循环
do...while
先执行再判断循环条件
死循环
案例:输入登录密码,密码输入错误时一直循环,直到密码正确
循环嵌套
循环中有循环
跳转关键字
break
跳出并结束当前所在循环的执行
只能用于结束所在循环,或者结束所在switch分支的执行
continue
用于跳出当前循环的当次执行,进入下一次循环
只能在循环中进行使用
break out
在循环之前写out: , 如果想在循环内直接结束直接用break out;即可,嵌套中的循环也可以直接结束,非常好用.
案例技术:随机数Random类
导包:告诉程序去JDK的哪个包找随机数技术
import java.util.Random
写一行代码代表得到随机数对象
Random r = new Random()
调用随机数的功能获取0~9的随机数
int number = r.nextInt(10) 结果得到0~9的随机数(加减法确定区间)
数组
数组定义
静态初始化数组
基本原理(注意:数组变量名中存储的是数据在内存中的地址,数组是引用类型
格式
动态初始化数组
基本原理(与静态初始化数组原理相似,但是每个元素为默认值)
元素默认值规则
格式
注意事项
"数据类型[] 数组名"也可以写成"数据类型 数组名[]"
int[ ] a=... ——> int a[ ]=...
什么类型的数组存放什么类型的数据,否则报错
数组一旦定义出来,程序执行的过程中,长度、类型就固定了
两种格式的写法是独立的,不可以混用
数组的访问
数组的访问(注意:数组的最大索引表示为:数组名.length-1 //前提元素个数大于0)
数组的遍历
格式
简捷操作:数组名.fori
数组的案例
数组遍历-求和
数组求最值
猜数字游戏
随机排名
数组排序
冒泡排序
思想:
从头开始两两比较,把较大的元素与较小的元素进行交换
每轮把当前最大的一个元素存入到数组当前的末尾
从头开始两两比较,把较大的元素与较小的元素进行交换
每轮把当前最大的一个元素存入到数组当前的末尾
选择排序
快速排序
插入排序
。。。
数组搜索
二分搜索
分块查找
哈希表查找
。。。
数组的内存图
Java内存分配、数组内存图
方法区——字节码文件加载时进入的内存,放class文件
栈——方法运行时所进入的内存变量也是在这里,有运行的方法,main方法,定义的变量
堆——new出来的对象会在这块内存中开辟空间并产生地址
两个数组变量指向同一数组对象
数组使用的常见问题
问题1:如果访问的元素位置超过最大索引,执行时会出现ArrayIndexOutOfBoundsException(数组索引越界异常)
问题2:如果数组变量中没有存储数据的地址,而是null,在访问数组信息时会出现NullPointerException(空指针异常)
Debug工具的使用
IDEA自带的断点调试(排错)工具,可以控制代码从断点开始一行一行的执行,然后详细观看程序执行情况
方法
方法定义、调用
完整的定义形式、调用
1、方法的修饰符暂时都使用public static修饰
2、方法申明了具体的返回值类型,内部必须使用return返回对应类型的数据
3、形参列表可以有多个,甚至可以没有;如果有多个形参,多个形参必须用","隔开,且不能给初始化值
2、方法申明了具体的返回值类型,内部必须使用return返回对应类型的数据
3、形参列表可以有多个,甚至可以没有;如果有多个形参,多个形参必须用","隔开,且不能给初始化值
示例
调用格式
其他定义形式、调用
1、如果方法不需要返回结果,返回值类型必须申明成void(无返回值),此时方法内部不可以使用return返回数据
2、方法如果没有参数,或者返回值类型申明为void可以称为无参数、无返回值的方法,依次类推
2、方法如果没有参数,或者返回值类型申明为void可以称为无参数、无返回值的方法,依次类推
示例
调用格式
方法使用的常见问题
1、方法的编写顺序无所谓
2、方法与方法之间是平级关系,不能嵌套定义
3、方法的返回值类型为void(无返回值),方法内则不能使用return返回数据,如果方法的返回值类型写了具体的类型,方法内部则必须使用return返回对应类型的数据
4、return语句下面,不能编写代码,因为永远执行不到,属于无效代码
5、方法不调用就不执行,调用时必须严格匹配方法的参数情况
6、有返回值的方法调用时可以选择定义变量接受结果,或者直接输出调用,甚至直接调用;无返回值方法的调用只能直接调用一下
方法案例
计算1-n的和返回
判断整数是奇数还是偶数
数组求最值案例改方法形式
方法调用的内存图
注:当栈内存中的方法都调用运行完之后,内存释放
栈内存是方法运行的区域
栈内存是方法运行的区域
方法的参数传递机制
基本类型的参数传递
值传递(数据):在传输实参(方法内部定义的变量)给方法的形参(定义方法时"()"中声明的参数)时,并不是传输实参变量本身,而是传输实参变量中存储的数据
引用类型的参数传递
值传递(地址值):与基本类型传递实参的值不同的是,引用类型传输的是实参的地址值,而不是数据
方法的参数传递案例
打印整形数组内容
从数组中查询元素的索引返回
比较两个数组内容是否一致
方法重载
形式
同一个类中,出现多个方法名称相同,但是形参列表是不同的,那个这些方法就是重载方法
调用方法
调用方法时会通过参数的不同来区别调用的是哪个方法
功能
对于相似功能的业务场景,可读性好,方法名称相同提示是同一类型的功能,通过形参不同实现功能差异化的选择,这是一种专业的代码设计
识别技巧
只要是同一个类中,方法名称相同(Java区分大小写)、形参列表不同(指:个数、类型、顺序不同,不关心名称),那么他们就是重载方法,其他不管(如:修饰符、返回值类型都无所谓)
补充知识:单独使用return关键字
可以立即跳出并结束当前方法的执行
return关键字单独使用可以放在任何方法中
专题(综合案例)
买飞机票
键盘录入机票原价、月份和机舱类型
使用if判断月份是淡季或旺季,使用switch分支判断是头等舱还是经济舱
选择对应的折扣进行计算并返回计算的结果
找素数
101-200之间的数据可以采用循环依次拿到,每拿到一个数,判断该数是否是素数
判断规则:从2开始遍历到该数的一半的数据,看是否有数据可以整除它,没有就为素数,否则不是
补充知识:信号位(标记)
开发验证码
定义一个方法,生成验证码返回:方法参数是位数、方法的返回值类型是String
在方法内部使用for循环生成指定位数的随机字符,并连接起来
把连接好的随机字符作为一组验证码进行返回
数组元素的复制
需要动态初始化一个数组,长度和原数组一样
遍历原数组的每个元素,依次赋值给新数组
输出两个数组的内容
注意int[] arr1 =arr2并不是数组的复制,而是两个地址同时指向一个数组
评委打分
把6位评委的分数录入动态初始化数组中
遍历数组的每个数据,进行累加求和,并找出最高分、最低分
按照分数的计算规则算出平均分
数字加密
将每位数据存入数组中,遍历数组每位数据按照规则进行更改,把更改后的数据重新存入到新数组中
将数组的前后元素进行交换,数组中的最终元素就是加密后的结果
模拟双色球
业务分析、随机生成一组中奖号码
中奖号码由6个红球和1个蓝球组成(6个红球不能重复)
每次随机一个红球号码后去做数组中判断是否重复
存在需要更新随机一个数字直到不重复为止(死循环)
可以定义方法用于返回一组中奖号码(7个数据),返回形式是一个整型数组
用户输入一组红球号码
判断中奖情况
面向对象基础
设计对象并使用
设计类、创建对象并使用
先定义类
public class 类名 {
1、成员变量(代表属性,一般是名词)
2、成员方法(代表行为,一般是动词)
3、构造器(初始化一个类的对象,返回对象地址)
4、代码块
5、内部类
}
1、成员变量(代表属性,一般是名词)
2、成员方法(代表行为,一般是动词)
3、构造器(初始化一个类的对象,返回对象地址)
4、代码块
5、内部类
}
得到类的对象
类名 对象名 = new 类名( );
Car c = new Car( );
Car c = new Car( );
使用对象
访问属性:对象名.成员变量
访问行为:对象名.方法名(...)
定义类的几个补充注意事项
成员变量的完整定义格式:修饰符 数据类型 变量名称 = 初始化值;一般无需指定初始化值,存在默认值
类名首字母建议大写,且有意义,满足“驼峰模式”
一个Java文件中可以定义多个class类,且只能一个类是public修饰,而且public修饰的类名必须成为代码文件名
(实际开发中建议还是一个文件定义一个class类)
(实际开发中建议还是一个文件定义一个class类)
对象内存图
多个对象的内存图
成员变量(name、price)的数据放在对象中,存在于堆内存中
Car c=new Car();中变量c存储的是对象在堆内存中的地址
对象放在堆内存中
两个变量指向同一对象的内存图
垃圾回收
在堆内存中的类对象或数组对象,没有被任何变量引用(指向)时,就会被判定为内存中的“垃圾”
Java存在自动垃圾回收器,会定期进行清理
面向对象编程案例
模拟购物车模块
需求分析、架构搭建
购物车中的每个商品都是一个对象,需要定义一个商品类
购物车本身也是一个对象,可以使用数组对象代表它
完成界面架构,让用户选择操作的功能
添加商品到购物车、查看购物车信息
需要让用户录入商品信息,创建商品对象封装商品信息
并把商品对象加入到购物车数组中去
查询购物车信息,就是遍历购物车数组中的每个商品对象
修改购买数量
定义方法能够根据用户输入的id去购物车数组中查看是否存在该商品对象
存在返回该商品对象的地址,不存在返回null
判断返回的对象地址是否存在,存在修改其购买数量,不存在就继续
结算金额
定义求和变量,遍历购物车数组中的全部商品,累加其单价*购买数量
构造器
用于初始化一个类的对象,并返回对象的地址
构造器的定义格式
分类
无参数构造器(默认存在的):初始化对象时,成员变量的数据均采用默认值
有参数构造器:在初始化对象时,同时可以为对象进行赋值
初始化对象的格式
创建对象初始化的过程
面试题
1、先将class文件加载到方法区中
2、在栈中为变量开辟空间
3、在堆内存中创建对象
4、系统给成员变量进行默认赋值
5、给成员变量进行显示赋值
6、构造方法给成员变量进行赋值
7、将堆内存中的地址赋值给栈内存中的变量
注意事项
任何类定义出来,默认都自带了无参数构造器,写不写都有
一旦定义了有参数构造器,无参数构造器就没有了,此时就需要自己写一个无参数构造器了
this关键字
出现在成员方法、构造器中代表当前对象的地址,用于访问当前对象的成员变量、成员方法
用法
在构造器中的用法
构造器中调用方法时this.可以省略
在成员方法中的用法
面向对象三大特征之一:封装
隐藏实现细节,暴露出合适的访问方式(合理隐藏、合理暴露)
实现步骤
先合理隐藏(private):一般对成员变量使用private(私有)关键字修饰进行隐藏,private修饰后该成员变量就只能在当前类中访问
再合理暴露(getter、setter):提供public修饰的公开的getter、setter方法暴露其取值和赋值
好处
加强了程序代码的安全性
适当的封装可以提升开发效率,同时可以让程序更容易理解与维护
JavaBean
可以理解成实体类,其对象可以用于在程序中封装数据
标准JavaBean需要满足的要求
成员变量使用private修饰
提供每一个成员变量对象的setXxx()/getXxx()
必须提供一个无参构造器
补充知识:成员变量、局部变量区别
常用API基础
String
String类概述
Java.lang.String类代表字符串,String类定义的变量可以用于指向字符串对象,然后操作该字符串
Java程序中的所有字符串文字(例如“abc”)都为此类的对象
String类的特点
String其实常被称为不可变字符串类型,它的对象在创建后不能被更改???
String类创建对象的2种方式
直接使用“”定义(推荐方式)
通过String类的构造器创建对象
String类常见面试题
String类创建对象的2种方式的区别
以“”方式给出的字符串对象,在字符串常量池中存储,而且相同内容只会在其中存储一份
通过构造器new对象,每new一次都会产生一个新对象,并放在堆内存中
String类常用API-字符串内容比较equals
字符串内容比较不适合用“==”比较(字符串常量池和堆内存)
推荐使用String类提供的“equals”方法来进行字符串内容比较,只关心内容一项
String类常用API-遍历、替换、截取、分割操作
public boolean contains(CharSequence s)——判断是否包含s字符串
public boolean startWiths(String prefix)——判断是否是以prefix作为开始
public String[ ] split(String s)——按照某个内容s把字符串分割成字符串数组返回
public boolean startWiths(String prefix)——判断是否是以prefix作为开始
public String[ ] split(String s)——按照某个内容s把字符串分割成字符串数组返回
String类案例实战
String类开发验证码功能
定义一个String类型的变量存储a-zA-Z0-9之间的全部字符
循环5次,随机一个范围内的索引,获取对应字符连接起来即可
模拟用户登录功能
系统后台定义好正确的登录名称、密码
使用循环控制三次,让用户输入正确的登录名称和密码,判断是否登录成功,登陆成功则不再进行登录;登陆失败给出提示,并让用户继续登录
手机号码屏蔽
键盘录入一个字符串,用Scanner实现
截取字符串前三位,截取字符串后四位
将截取后的两个字符串,中间加上****进行拼接,输出结果即可
集合基础
ArrayList
集合概述
集合是与数组类似,也是一种容器,用于装数据的
数组
数组定义完成并启动后,类型确定、长度固定
问题:在个数不能确定,且要进行增删数据操作的时候,数组是不太合适的
集合
集合的大小不固定,启动后可以动态变化,类型也可以选择不固定
集合非常适合做元素个数不确定, 且要进行增删操作的业务场景
ArrayList集合快速入门
ArrayList是集合中的一种,它支持索引
ArrayList集合的对象获取
ArrayList集合添加元素的方法
ArrayList对泛型的支持
ArrayList<E>:其实就是一个泛型类,可以在编译阶段约束集合对象只能操作某种类型
如:ArrayList<String>:此集合只能操作字符串类型的元素
ArrayList<integer>:此集合只能操作整数类型的元素
ArrayList<integer>:此集合只能操作整数类型的元素
注意:集合和泛型中只能存储引用类型,不支持基本数据类型
ArrayList常用API-遍历
常用方法
注:第四个remove删除指定元素,当出现两个或多个一样的元素,删除第一个
ArrayList集合案例:遍历并删除元素
定义ArrayList集合存储多名学员的成绩
遍历集合每个元素,如果元素值低于80分,去掉
注意:从集合后面遍历然后删除,可以避免漏掉元素
注意:每删掉元素,i--
ArrayList集合案例:存储自定义类型的对象
定义一个电影类,定义一个集合存储电影对象
创建3个电影对象,封装相关数据,把3个对象存入到集合中去
遍历集合中的3个对象,输出相关信息
ArrayList集合案例:元素搜索
定义Student类,定义ArrayList集合存储如上学生对象信息,并遍历展示出来
提供一个方法,可以接受ArrayList集合,和要搜索的学号,返回搜索到的学生对象信息,并展示
使用死循环,让用户可以不停的搜索
案例ATM系统
系统准备
定义账户类,同于后期创建账户对象封装用户的账户信息public class Account{}
账户类中信息至少需要包含(卡号cardID、姓名userName、密码passWord、余额money、取现额度quotaMoney)
需要准备一个ArrayList集合,用于存储系统用户的账户对象
需要展示欢迎页包含2个功能:开户功能、登录账户
用户开户功能实现
1、开户功能其实就是往系统的集合容器中存入一个新的账户对象的信息
2、开户应该定义一个方法,并传入账户集合
3、创建一个Account账户类的对象用于封装账户信息(姓名、密码、卡号)
4、键盘录入姓名、密码、确认密码
5、生成账户卡号,卡号必须有系统自动生成8位数字(保证卡号的唯一)
用户登录功能实现
1、登录功能应该定义成一个方法,并传入账户集合;public static void login(ArrayList<Account> accounts){...}
让用户输入卡号,根据卡号去账户集合中查询账户对象
如果没有找到账户对象,说明登录卡号不存在,提示继续输入卡号
如果找到了账户对象,说明卡号存在,继续输入密码
如果密码不正确,提示继续输入密码
如果密码也正确,登陆成功
用户登陆后
操作页、查询账户、退出账户
用户登陆成功后,需要进入用户操作页
查询就是直接展示当前登录成功的账户对象信息
退出账户是需要回到首页
存款
存款就是拿到当前账户对象
然后让用户输入存款的金额
调用账户对象的setMoney方法将账户余额修改成存钱后的余额
取款
取款需要先判断账户是否有钱
有钱则拿到自己账户对象
然后让用户输入取款金额
判断取款金额是否超过了当次限额,以及余额是否足够
满足要求则调用账户对象的setMoney方法完成金额的修改
转账
转账功能需要判断系统中是否有2个账户对象及以上
同时还要判断自己账户是否有钱
接下来需要输入对方卡号,判断对方账户是否存在
对方账户存在还需要认证对方户主的姓氏
满足要求则可以把自己账户对象的金额修改到对方账户对象中去
密码修改
修改密码就是把当前对象的密码属性使用se方法进行更新
销户
销户是从集合对象中删除当前对象,回到首页
JavaSE进阶
面向对象进阶
static
static静态关键字
static是什么
static是静态的意思,可以修饰成员变量和成员方法
static修饰成员变量表示该成员变量只在内存中只存储一份,可以共享访问、修改
static修饰成员变量的用法
静态成员变量(有static修饰,属于类,内存中加载一次):常表示如在线人数信息、等需要被共享的信息,可以被共享访问
类名.静态成员变量(推荐)
对象.静态成员变量(不推荐)
实例成员变量(无static修饰,存在于每个对象中):常表示姓名name、年龄age、等属于每个对象的信息
对象.实例成员变量
static修饰成员变量的内存原理
class类会放在方法区,同时在堆内存中会生成类的静态变量区,此区会加载静态成员变量。
当用new创建对象时,地址存放栈内存,对象存放堆内存,堆内存中存放对象的实例成员变量
当用new创建对象时,地址存放栈内存,对象存放堆内存,堆内存中存放对象的实例成员变量
static修饰成员方法的基本用法
静态成员方法(有static修饰,归属于类),建议用类名访问,也可以用对象访问
如果该方法是以执行一个共用功能为目的,则可以申明成静态方法
实例成员方法(无static修饰,归属于对象),只能用对象触发访问
表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法
static修饰成员方法的内存原理
static访问的注意事项
静态方法只能访问静态的成员,不可以直接访问实例成员
实例方法可以访问静态的成员,也可以访问实例成员
静态方法中是不可以出现this(当前对象)关键字的
static应用知识:工具类
类中都是一些静态方法,每个方法都是以完成一个共用功能为目的,这个类用来给系统开发人员共同使用
好处:一是调用方便,二是提高代码的复用(一次编码,处处可用)
为什么工具类中的方法不是实例方法做?
1、实例方法需要创建对象调用
2、此时用对象只是为了调用方法,只会浪费内存
工具类定义时的其他要求
1、由于工具类里面都是静态方法,直接用类名即可访问,因此,工具类无需创建对象,建议将工具类的构造器进行私有private
案例
需求:在实际开发中,经常会遇到一些数组使用的工具类,请按照如下要求编写一个数组的工具类:ArrayUtils
1、我们知道数组对象直接输出的时候是输出对象的地址的,而项目中很多地方都需要返回数组的内容,请在ArrayUtils中提供一个工具类toString,用于返回整数数组的内容,返回的字符串格式如:[10,20,50,34,100](只考虑整数数组,且只考虑一维数组)
2、经常需要统计平均值,平均值为去掉最低分和最高分后的分值,请提供一个这样的工具getAerage,用于返回平均分(只考虑浮点型数组,且只考虑一维数组)
3、定义一个测试类TestDemo,调用该工具类的工具方法,并返回结果
static应用知识:代码块
概述
代码块是类的5大成分之一(成员变量、构造器、方法、代码块、内部类),定义在类中方法外
在Java类下,使用{}括起来的代码被称为代码块
分类
静态代码块
格式:static{ }
通过static关键字修饰,属于类,与类一起优先加载,自动触发执行,只执行一次
作用:可以用于初始化静态资源/静态数据
构造代码块/实例代码块(了解,见得少)
格式:{ }
每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
作用:初始化实例资源
静态代码块的应用案例—斗地主
需求:在启动斗地主游戏房间的时候,应该提前准备好54张牌,后续才可以直接使用这些牌数据
1、该房间只需要一副牌
2、定义一个静态的ArrayList集合存储54张牌对象,静态的集合只会加载一次
3、在启动游戏房间前,应该将54张牌初始化好
4、当系统启动的同时需要准备好54张牌数据,此时可以用静态代码块完成
static应用知识:单例设计模式
设计模式
开发中经常遇到一些问题,一个问题通常有n种解法的,但其中肯定有一种解法是最优的,这个最优的解法被人总结出来了,称之为设计模式
设计模式有20多种,对应20多种软件开发中会遇到的问题
学设计模式主要是学2点:
1、这种模式用来解决什么问题
2、遇到这种问题,该模式怎么写,如何解决这个问题
1、这种模式用来解决什么问题
2、遇到这种问题,该模式怎么写,如何解决这个问题
单例模式
可以保证系统中,应用该模式的这个类永远只有一个实例,即一个类永远只能创建一个对象
例如任务管理器对象我们只需要一个就可以解决问题了,这样可以节省内存空间
实现方式
饿汉单例模式
在用类获取对象的时候,对象已经提前为你创建好了
设计步骤:
调用方式:SingleInstance s1 = SingleInstance.instance; //拿到类的对象
懒汉单例模式
在真正需要该对象的时候,才去创建一个对象(延迟加载对象)
设计步骤
public static SingleInstance getInstance(){
if (instance == null) {
instance = new SingleInstance(); //保证只能创建一个对象
}
return instance;
}
public static SingleInstance instance;这一句最好是私有化,防止调用生成多个默认值的对象,即public变为private
调用方法:SingleInstance s1 = SingleInstance.getInstance( );
if (instance == null) {
instance = new SingleInstance(); //保证只能创建一个对象
}
return instance;
}
public static SingleInstance instance;这一句最好是私有化,防止调用生成多个默认值的对象,即public变为private
调用方法:SingleInstance s1 = SingleInstance.getInstance( );
.......
面向对象三大特征之二:继承extends
概述
Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起父子关系
public class Student extends People { }
Student称为子类(派生类),People称为父类(基类或超类)
Student称为子类(派生类),People称为父类(基类或超类)
作用:当子类继承父类后,就可以直接使用父类公共的属性和方法了,Java中子类更强大
好处:可以提高代码的复用性,减少代码冗余,增强类的功能扩展性
设计规范
子类们相同特征(共性属性,共性方法)放在父类中定义,子类独有的属性和行为应该定义在子类自己里面
为什么?如果子类的独有属性、行为定义在父类中,会导致其它子类也会得到这些属性和行为,这不符合面向对象逻辑
内存运行原理
此时创建Student子类对象时,在堆内存中会生成两个空间:一个子类空间和一个父类空间,两个空间都属于一个对象中,所以变量参数可以相互调用
特点
1、子类可以继承父类的属性和行为,但是子类不能继承父类的构造器
子类是否可以继承父类的构造器?子类有自己的构造器,父类构造器用于初始化父类对象
子类是否可以继承父类的私有成员?可以的,只是不能直接访问
子类是否可以继承父类的静态成员?(有争议的知识点)子类可以直接使用父类的静态成员(共享),不能继承父类的静态成员(共享并非继承),因为静态成员只有一个
2、Java是单继承模式:一个类只能继承一个直接父类
3、Java不支持多继承、但是支持多层继承
就近原则:当多层继承出现相同方法,子类会就近继承
4、Java中所有的类都是Object类的子类
Object类特点
Java中所有类,要么直接继承了Object,要么默认继承了Object,要么间接继承了Object,Object是祖宗类
继承后:
成员变量、成员方法的访问特点
满足就近原则
先子类局部范围找
然后子类成员范围找
然后父类成员范围找,如果父类范围还没找到则报错
如果子父类中,出现重名的成员,会优先使用子类的,此时如果一定要在子类中使用父类的怎么办?
可以通过super关键字,指定访问父类的成员
格式:super.父类成员变量/父类成员方法
方法重写
在继承体系中,子类出现了和父类中一摸一样的方法声明,我们就称子类这个方法是重写的方法
应用场景:当子类需要父类的功能,但父类的该功能不完全满足自己的需求时,子类可以重写父类中的方法
案例
旧手机的功能只能是基本的打电话、发短信
新手机的功能需要能够:基本的打电话下支持视频通话,基本的发短息下支持发送语音和图片
@Override重写注解
@Override是放在重写后的方法上,作为重写是否正确的校验注解
加上该注解后,如果重写错误,编译阶段会出现错误提示
提高程序的可读性,代码优雅
方法重写的注意事项和要求
重写方法的名称、形参列表必须与被重写方法的名称和形参列表一致
私有方法不能被重写
子类重写父类方法时,访问权限必须大于或等于父类(缺省<protected<public)
子类不能重写父类的静态方法,如果重写会报错的
子类构造器的特点
子类中所有的构造器默认都会先访问父类的无参数构造器
1、子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据
2、子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化
调用方法:子类构造器的第一行语句默认都是:super( ),不写也存在
子类构造器访问父类有参数构造器
作用:初始化继承自父类的数据
如果父类中没有无参数构造器,只有有参数构造器,会报错。因为子类默认是调用父类的无参数构造器。子类构造器中可以通过书写super( ... ),手动调用父类的有参数构造器
this、super的总结
案例
需求
在学员信息登记系统中,后台创建对象封装数据的时候如果用户没有输入学校,则默认使用“黑马”
如果用户输入了学校则使用用户输入的学习信息
this(...)和super(...)使用注意点
1、子类通过this(...)去调用本类的其他构造器,本类其他构造器会通过super去手动调用父类的构造器,最终还是会调用父类构造器的
2、注意:this(...)super(...)都只能放在构造器的第一行,所以二者不能共存在同一个构造器中
语法
包package
包是用来分门别类的管理各种不同类的,类似于文件夹、建包利于程序的管理和维护
建包的语法格式:package 公司域名倒写.技术名称。包名建议全部英文小写,且具备意义
建包语句必须在第一行,一般IDEA工具会帮助创建
导包:
使用相同包下的类可以直接访问,不同包下的类必须导包,才可以使用。
导报格式:import 包名.类名
如果类中使用不同包下的相同类名,此时默认只能导入一个类的包,另一个需要用包的全名访问
权限修饰符
用来控制一个成员能够被访问的范围
可以修饰成员你变量、方法、构造器、内部类,不同权限修饰符修饰的成员能够被访问的范围将受到控制
权限范围:private -> 缺省 -> protected -> public
自己定义成员(方法、成员变量、构造器等)一般需要满足的要求
成员变量一般私有
方法一般公开
如果该成员只希望本类访问,使用private修饰
如果该成员只希望本类,同一个包下的其他类和子类访问,使用protected修饰
final
作用
final关键字是最终的意思,可以修饰(类、方法、变量)
修饰类:表明该类是最终类,不能被继承
修饰方法:表明该方法是最终方法,不能被重写
修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)public static final修饰的也称为常量
注意事项
final修饰的变量是基本类型,那么变量存储的数据值不能发生变化
final修饰的变量是引用类型,那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生改变的
常量public static final
常量是使用了public static final修饰 的成员变量,必须有初始化值,而且执行的过程中其值不能被改变
常量的作用和好处:可以用于做系统的配置信息,方便程序的维护,同时也能提高可读性
常量命名规范::英文单词全部大写,多个单词下划线连接起来
常量执行原理
在编译阶段会进行“宏替换”,把使用常量的地方全部替换成真实的字面量
这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的
常量做信息标志和分类
好处:代码可读性好,实现了软编码形式
枚举enum
枚举是Java中的一种特殊类型
枚举的作用:是为了做信息的标志和信息的分类
格式
反编译后观察枚举的特征
枚举做信息标志和分类
入参约束严谨,代码优雅,是最好的信息分类技术!建议使用
选择常量做信息标志和分类
虽然可以实现可读性,但是入参值不受约束,代码相对不够严谨
抽象类abstract
概述
在Java中abstract是抽象的意思,可以修饰类、成员方法
abstract修饰类,这个类就是抽象类;修饰方法,这个方法就是抽象方法
格式
注意事项
抽象方法只有方法签名,不能声明方法体
一个类中如果定义了抽象方法,这个类必须声明成抽象类,否则报错
使用场景
抽象类可以理解成不完整的设计图,一般作为父类,让子类来继承
一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类
当父类知道子类一定要完成某些行为,但是每个子类该行为的实现又不同,于是该父类就把该行为定义成抽象方法的形式,具体实现交给子类去完成,此时这个类就可以声明成抽象类
案例
需求
某加油站推出了2中支付卡,一种是预存10000的金卡,后续加油享受8折优惠,另一种是预存5000的银卡,后续加油享受8.5折优惠
请分别实现2种卡片进入收银系统后的逻辑,卡片需要包含主人名称、余额、支付功能
分析
创建一张卡片父类:定义属性包括主人名称、余额、支付功能(具体实现交给子类)
创建一张白金卡类:重写支付功能,按照原价的8折计算输出
创建一张银卡类:重写支付功能,按照原价的8.5折计算输出
特征和注意事项
普通类有的成员(成员变量、成员方法、构造方法),抽象类一样都有,但是抽象类不能实例化。构造方法只能用于初始化数据
抽象类中不一定有抽象方法,但是有抽象方法的必须是抽象类
一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类
不能用abstract修饰变量、代码块、构造器
最重要的特征:抽象类得到了抽象方法,但失去了创建对象的能力(有得有失)
抽象类的应用知识:模板方法模式
使用场景说明:当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只是其中部分可能不同的时候
实现步骤
把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码
模板方法中不能决定的功能定义成抽象方法让具体子类去实现
案例
需求
现在有两类学生,一类是中学生,一类是小学生,他们都要写《我的爸爸》这篇作文
要求每种类型的学生,标题第一段和最后一段,内容必须一样,正文部分自己发挥
请选择最优的面向对象方法进行设计
模板方法建议使用final修饰:因为模板方法是给予子类直接使用的,不是让子类重写的,一旦子类重写了模板方法,则模板方法就失效了。因此,加上final后可以防止子类重写了模板方法,这样更安全、专业
final和abstract是什么关系
互斥关系
final定义的类不能被继承,但abstract定义的抽象类作为模板必须让子类继承
抽象方法定义的通用功能让子类重写,但final定义的方法子类不能重写
接口interface
接口也是一种规范,规范默认都是公开的,从代码层面,public abstract可以省略不写
格式(JDK 8之前的版本):
基本使用:被实现
接口是用来被类实现(implements)的,实现接口的类称为实现类。实现类可以理解成所谓的子类
从上面可以看出,类可以实现多个接口
一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类
接口的成员特点
成员变量
默认用public abstract final修饰,只能是常量
成员方法
默认用public abstract修饰,只能是抽象方法
在JDK 8 和JDK 9 增加了一些新特性
构造方法
不存在构造方法
一个类如果没有父类,默认继承Object类
基本小结
类和类的关系:单继承
类和接口的关系:多实现
接口和接口的关系:多继承,一个接口可以同时继承多个接口
接口与接口的关系:多继承
规范合并,整合多个接口为同一个接口,便于子类实现
JDK8开始接口新增的方法
项目需要对Inter接口丰富,加入10个新的抽象方法,此时改了接口就要所有实现类实现这些方法
如何能在丰富接口功能的同时又不对子类代码进行更改呢?——允许接口中直接定义带有方法体的方法
第一种:默认方法
类似之前写的普通实例方法:必须用default修饰
默认会public修饰,需要用接口的实现类的对象来调用
第二种:静态方法
默认会public修饰,必须static修饰
注意:接口的静态方法必须要本身的接口名来调用
第三种:私有方法
就是私有的实例方法,必须使用private修饰,从JDK1.9才开始有的
只能在本类中被其他的默认方法或者私有方法访问
使用接口的注意事项
1、接口不能创建对象
(接口是更加彻底的抽象)
2、一个类实现多个接口,多个接口中有同样的静态方法不冲突
因为接口的静态方法必须由本身的接口名来调用
3、一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的
class Cat extends Animal implements Food{ }先继承后实现
4、一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可
重写后会导致永远不会实现接口的方法
一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承
面向对象三大特征之三:多态polymorphic
含义:同类型的对象,执行同一个行为,会表现出不同的行为规范
常见形式
父类类型 对象名称 = new 子类构造器
接口 对象名称 = new 实现类构造器
访问特点**.**
方法调用:编译看左边,运行看右边
变量调用:编译看左边,运行也看左边(多态侧重行为多态)
使用前提
有继承/实现关系;
有父类引用指向子类对象;
有方法重写
优势
1、在多态形式下,右边对象可以实现解耦合,便于扩展和维护
2、定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利
多态下会产生一个问题
多态下不能使用子类的独有功能
多态下引用数据类型的类型转换
自动类型转换(从子到父)
子类对象赋值给父类类型的变量指向
强制类型转换(从父到子
此时必须进行强制类型转换:子类 对象变量 = (子类)父类类型的变量
作用:可以解决多态下的劣势,可以实现调用子类独有的功能
注意:如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException
强制类型转换,编译阶段不报错的(注意:有继承或者实现关系编译阶段可以强制)运行时可能出错的
Java建议强转转换前使用instanceof判断当前对象的真实类型,再进行强制转换
多态的案例
需求
1、使用面向对象编程模拟:设计一个电脑对象,可以安装2个USB设备
2、鼠标:被安装时可以完成接入、调用点击功能、拔出功能
3、键盘:被安装时可以完成接入、调用点击功能、拔出功能
分析
1、定义一个USB的接口(申明USB设备的规范必须是:可以接入和拔出)
2、提供2个USB实现类代表鼠标和键盘,让其实现USB接口,并分别定义独有功能
3、创建电脑对象,创建2个USB实现类对象,分别安装到电脑中并触发功能的执行
内部类
内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外面的类可以理解成(宿主)
使用场景、作用
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计
内部类通常可以方便访问外部类的成员,包括私有的成员
内部类提供了更好的封装性,内部类本身就可以用private protected等修饰,封装性可以做更多控制
分类
静态内部类(了解)
有static修饰,属于外部类本身
它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已
静态内部类创建对象的格式
拓展(访问)
1、静态内部类可以直接访问外部类的静态成员,外部类的静态成员只有一份可以被共享访问
2、静态内部类不可以直接访问外部类的实例成员,外部类的实例成员必须用外部类对象访问
成员内部类(非静态内部类)(了解)
无static修饰,属于外部类的对象
JDK16之前,成员内部类中不能定义静态成员, JDK16开始也可以定义静态成员了
成员内部类创建对象的格式
拓展(访问)
1、成员内部类可以直接访问外部类的静态成员,外部类的静态成员只有一份可以被共享访问
2、成员内部类的实例方法中可以直接访问外部类的实例成员,因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类对象的实例成员
面试题
局部内部类(鸡肋语法,了解)
局部内部类放在方法、代码块、构造器等执行体中
局部内部类的类文件名为:外部类$N内部类.class
局部内部类不能用权限修饰符修饰
匿名内部类(重点)
本质上是一个没有名字的局部内部类,定义在方法、代码块中。是一个继承了该类或者实现了该接口的子类匿名对象
匿名内部类是将(继承\实现)(方法重写)(创建对象)三个步骤,放在了一步进行
作用:方便创建子类对象,最终目的为了简化代码编写
格式
实例
总结
1、匿名内部类是一个没有名字的内部类
2、匿名内部类写出来就会产生一个匿名内部类的对象
3、匿名内部类的对象类型相当于是当前new的那个的类型的子类类型
在开发中的使用形式
匿名内部类可以作为方法的实际参数进行传输
在开发中的真实使用场景演示
开发中不是我们主动去定义匿名内部类,而是别人需要我们写或者我们可以写的时候才会用
Lambda表达式
Lambda表达式是JDK8开始后的一种新的语法形式
作用:简化匿名内部类的代码写法
简化格式
三要素
( )
匿名内部类被重写方法的形参列表
->
{ }
代码块,具体要做的事情
注意:Lambda表达式只能简化函数式接口的匿名内部类的写法形式
1、首先必须是接口,其次接口中有且仅有一个抽象方法的形式
2、通常我们会在接口上加上一个@FunctionalInterface注解,标记该接口必须是满足函数式接口
好处:Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码,它可以写出更简洁、更灵活的代码,作为一种更紧凑的代码风格,使Java语言表达能力得到了提升
省略写法(进一步在Lambda表达式的基础上继续简化)
1、参数类型可以省略不写
2、如果只有一个参数,参数类型可以省略,同时()也可以省略
3、如果Lambda表达式的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号
4、如果Lambda表达式的方法体代码只有一行代码,可以省略大括号不写,此时,如果这行代码是return语句,必须省略return不写,同时也必须省略分号不写
应用场景
当某个方法的参数类型是一个接口或抽象类类型,并且接口或抽象类中只有一个抽象方法
Lambda表达式和匿名内部类的关系
能使用Lambda表达式简化的,一定可以使用匿名内部类
能使用匿名内部类的,不一定能使用Lambda表达式
Lambda表达式和匿名内部类的区别
所需类型不同
Lambda表达式只能针对函数式接口使用
匿名内部类的使用可以是接口,也可以是抽象类,还可以是具体类
使用限制不同
如果一个接口有且只有一个抽象方法,可以使用匿名内部类,也可以使用Lambda表达式
如果一个接口多余一个抽象方法,只能使用匿名内部类,不能使用Lambda表达式
实现原理不同
匿名内部类在编译后,会单独生成一个独立的.class字节码文件
Lambda表达式在编译后,不会生成.class字节码文件,对应的字节码会在运行时动态生成
包装类
其实就是8中基本数据类型对应的引用类
为什么提供包装类?
1、Java为了实现一切皆对象,为8中基本类型提供了对应的引用类型
2、后面的集合和泛型其实也只能支持包装类型,不支持基本数据类型
自动装箱/自动拆箱
自动装箱:基本类型的数据和变量可以直接赋值给包装类型的变量
自动拆箱:包装类型的变量可以直接赋值给基本数据类型的变量
包装类的特有功能
1、包装类的变量的默认值可以是null,容错率更高
2、可以把基本类型的数据转换成字符串类型(用处不大)
调用toString()方法得到字符串结果
调用Integer.toString(基本类型的数据)
3、可以把字符串类型的数值转换成真是的数据类型(真的很有用)
Integer.parseInt("字符串类型的整数")
Double.parseDouble("字符串类型的小数")
常用API
正则表达式
正则表达式可以用一些规定的字符来指定规则,并用来校验数据格式的合法性
匹配规则matches
案例
需求
1、请编写程序模拟用户输入手机号码、验证格式正确,并给出提示,直到格式输入正确为止
2、请编写程序模拟用户输入邮箱号码、验证格式正确,并给出提示,直到格式输入正确为止
3、请编写程序模拟用户输入电话号码、验证格式正确,并给出提示,直到格式输入正确为止
分析
定义方法,接收用户输入的数据,使用正则表达式完成校验,并给出提示
额外
判断金额格式是否正确0.0.3
正则表达式在字符串方法中的使用
正则表达式爬取信息
Arrays类
数组操作工具类,专门用于操作数组元素的
常用API(注意,第一个toString是将数组打印)
binarySearch二分搜索技术
(前提是数组必须排好序才支持,否则出bug)
sort数组排序
Arrays类的排序方法
自定义排序规则(Comparator比较器)
图中的规则是升序排序,反过来就是降序排序
图中的规则是升序排序,反过来就是降序排序
常见算法
选择排序
思想
每轮选择当前位置,开始找出后面的较小值与该位置交换
关键
1、确定总共需要选择几轮:数组的长度-1
2、控制每轮从以前位置为基准,与后面元素选择几次
二分查找binarySearch
二分查找性能好,二分查找的前提是必须是排好序的数据
二分查找相当于每次去掉一半的查找范围
二分查找正常的检索条件应该是开始位置min ≤ 结束位置max
实现步骤
1、定义变量记录左边和右边位置的索引
2、使用while循环控制查询(条件是左边位置的元素≤右边位置的元素)
3、循环内部获取中间元素索引
4、判断当前要找的元素如果大于中间元素,左边位置索引 = 中间索引 + 1
5、判断当前要找的元素如果小于中间元素,右边位置索引 = 中间索引 - 1
6、判断当前要找的元素如果等于中间元素,返回当前中间元素索引
Object
作用
1、一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类
2、Object类的方法是一切子类都可以直接使用的,所以我们要学习Object类的方法
常用方法
toString方法
直接输出对象变量,默认可以省略toString调用不写的
存在意义:父类toString()方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息
equals方法
直接比较两个对象的地址是否相同完全可以用“==”代替equals
父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则
Objects
Objects类与Object还是继承关系,Objects类是从JDK1.7开始之后才有的
Objects的equals方法比较的结果是一样的,但是更安全
常用方法
StringBuilder
StringBuilder是一个可变的字符串类,我们可以把它看成一个对象容器
作用:提高字符串的操作效率,如拼接、修改等
StringBuilder构造器
常用方法
String拼接字符串:内容不可变,拼接字符串性能差(定义字符串使用String)
StringBuilder拼接字符串:内容可变,拼接字符串性能好、代码优雅(拼接、修改等操作字符串使用StringBuilder)
案例
需求
设计一个方法用于输出任意整型数组的内容,要求输出如下格式:
“该数组内容为:[11 , 22, 33, 44, 55]”
“该数组内容为:[11 , 22, 33, 44, 55]”
分析
1、定义一个方法,要求该方法能够接收数组,并输出内容。
2、定义一个静态初始化数组,调用该方法,并传入该数组
Math
包含执行基本数字运算的方法,Math类没有提供公开的构造器
类中的成员都是静态成员,可以通过类名直接调用
常用方法
System
System的功能是通用的,都是直接用类名调用,所以不能被实例化
常用方法
时间毫秒值
BigDecimal
作用
使用步骤
常用方法
日期与时间
Date系统日期时间
Date类的对象在Java中代表的是当前所在系统的此刻日期时间
Date类记录时间的2种方式
SimpleDateFormat日期格式化
作用:
1、可以对Date对象或时间毫秒值格式化成我们喜欢的时间形式
2、也可以把字符串的时间形式解析成日期对象
1、可以对Date对象或时间毫秒值格式化成我们喜欢的时间形式
2、也可以把字符串的时间形式解析成日期对象
时间形式一:日期类表示
时间形式二:时间毫秒值表示
开发中常见的时间形式
SimpleDateFormat的日期格式化
格式化的时间形式的常用的模式对应关系如上:
yyyy年MM月dd日 HH:mm:ss EEE a——EEE代表星期几,a代表上午
yyyy年MM月dd日 HH:mm:ss EEE a——EEE代表星期几,a代表上午
SimpleDateFormat解析字符串时间成为日期对象
解析字符串时间
案例:请计算出2021年08月06日11点11分11秒,往后走2天14小时48分06秒后的时间是多少
案例:秒杀活动
Calendar日历
Calender代表了系统此刻日期对应的日历对象
Calender是一个抽象类,不能直接创建对象
Calender日历类创建日历对象的方法
常用方法
注意:Calender的是可变日期对象,一旦修改后其对象本身表示的时间将产生变化
JDK8新增日期类
LocalDateTime/LocalDate/LocalTime日期时间
LocalDate:不包含具体时间的日期
LocalTime:不包含日期的时间
LocalDateTime:包含了日期及时间
LocalTime:不包含日期的时间
LocalDateTime:包含了日期及时间
创建对象的方式
LocalDateTime转换成LocalDate、LocalTime对象
日期/时间的运算
instant时间戳
JDK8获取时间戳特别简单,且功能丰富。Instant类由一个静态的工厂方法now()可以返回当前时间戳
时间戳是包含日期和时间的,与java.util.Date很类似,事实上Instant就是类似JDK8以前的Date
Instant和Date这两个类可以进行转换
instant返回的是世界标准时间,与北京时间相差8个小时
获取北京时间的方法:instant.atZone(ZoneId.systemDefault())
获取北京时间的方法:instant.atZone(ZoneId.systemDefault())
DateTimeFormatter日期/时间格式器
在JDK8中,引入了一个全新的日期与时间格式器DateTimeFormatter
正反都能调用format方法
Duration/Period时间/日期间隔
Period
Duration
ChronoUnit测量时间
集合
数据结构
数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的
通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率
常见数据结构
栈
后进先出,先进后出
队列
先进先出,后进后出
数据从后端进入队列模型的过程称为:入队列
数据从前端离开队列模型的过程称为:出队列
数据从前端离开队列模型的过程称为:出队列
数组
查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同(元素在内存中是连续存储的)
删除效率低:要将原始数据删除,同时后面每个数据前移
添加效率极低:添加位置后的每个数据后移,再添加元素
链表
链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址
链表查询慢:无论查询哪个数据都要从头开始找
链表增删相对快
链表的种类
二叉树
二叉查找树
二叉查找树又称二叉排序树或者二叉搜索树
特点:
1、每一个节点上最多有两个子节点
2、左子树上所有节点的值都小于根节点的值
3、右子树上所有节点的值都大于根节点的值
目的:
提高检索数据的性能
存入规则
平衡二叉树
问题
平衡二叉树的要求
在任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树
平衡二叉树的识别
平衡二叉树的策略
左左:当根节点左子树的左子树有节点插入,导致二叉树不平衡
右旋
左右:当根节点左子树的右子树有节点插入,导致二叉树不平衡
右旋,发现不行,则放弃右旋
以出问题的节点作为支点
先左旋
再进行整体右旋
右右:当根节点右子树的右子树有节点插入,导致二叉树不平衡
左旋
右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡
左旋,发现不行,则放弃左旋
以出问题的节点作为支点
先右旋
再整体左旋
红黑树
红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构
1972年出现,当时被称之为平衡二叉B树。1978年被修改为如今的“红黑树”
每一个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的
红黑规则
1、每一个节点或是红色的,或者是黑色的,根节点必须是黑色
2、如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,叶节点是黑色的
3、如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
4、对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数据的黑色节点
红黑树增删改查的性能都很好
添加节点
添加节点的颜色,可以是红色,也可以是黑色,默认用红色效率高
如果默认为黑色的话,添加三个元素,一共需要调整三次
如果默认为红色的话,添加三个元素,一共需要调整一次
默认元素都为红色
.........
集合和数组都是容器
数组
数组定义完成并启动后,类型确定、长度确定
在进行增删数据操作的时候,数组是不太适合的,增删数据都需要放弃原有数组或者移位
数组可以存储基本类型和引用类型的数据
适合场景:当业务数据的个数是固定的,且都是同一批数据类型的时候,可以使用数组类型存储
集合
集合是Java中存储对象数据的一种容器
集合的大小不固定,启动后可以动态变化,类型也可以选择不固定
集合非常适合做元素的增删操作
集合中只能存储引用类型数据,如果要存储基本类型数据可以选用包装类
适合场景:数据的个数不确定,需要进行增删元素的时候
体系结构
Collection单列集合
Collection单列集合的祖宗接口
List系列集合特点:添加的元素是有序、可重复、有索引
ArrayList、LinkedList:有序、可重复、有索引
Set系列集合特点:添加的元素是无序、不重复、无索引
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:按照大小默认升序排序、不重复、无索引
Collection集合
集合对泛型的支持
集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型
注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象的地址
常用API
遍历方式
方式一:迭代器Iterator
迭代器在Java中的代表是Iterator,迭代器是集合的专用遍历方式
Collection集合获取迭代器Iterator的方法
Iterator常用方法
方式二:foreach/增强for循环
增强for循环:既可以遍历集合也可以遍历数组
它是JDK5之后出现的,其内部原理是一个Iterator迭代器,遍历集合相当于是迭代器的简化写法
实现Iterable接口的类才可以使用迭代器和增强for,Collection接口已经实现了Iterable接口
格式
遍历格式
举例说明
方式三:Lambda表达式
得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式
Collection结合Lambda遍历API
方法
案例
需求
某影院系统需要在后台存储上述三部电影,然后依次展示出来
分析
1、定义一个电影类,定义一个集合存储电影对象
2、创建3个电影对象,封装相关数据,把3个对象存入到集合中去
3、遍历集合中的3个对象,输出信息
内存原理
List系列集合
List系列集合特点:添加的元素是有序、可重复、有索引
ArrayList、LinkedList:有序、可重复、有索引
特有方法
(因为支持索引,所以多了很多索引操作的独特API,其他Collection的功能List也都继承了)
遍历方式
迭代器Iterator
增强for循环
Lambda表达式
for循环(因为List集合存在索引)
实现类底层原理
ArrayList底层
ArrayList底层是基于数组实现的,根据索引定位元素快,增删需要做元素的移位操作,相对慢
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组,如果数组满了,再添加元素时会扩容1.5倍
LinkedList底层
LinkedList底层基于双链表(队列、栈)实现的,查询元素慢,增删首尾元素是非常快的,所以多了很多首尾操作特有APII
特有功能
补充知识:集合的并发修改异常问题
问题:当我们从集合中遍历找出某个元素并删除的时候可能出现一种并发修改异常问题
哪些遍历存在问题?-->
1、迭代器遍历集合且直接用集合删除元素的时候可能出现
2、增强for循环遍历集合且直接用集合删除元素的时候可能出现
解决方案1
解决方案2
哪些遍历且删除元素不出问题?-->
1、迭代器遍历集合但是用迭代器自己的删除方法操作可以解决
2、使用for循环遍历并删除元素不会存在这个问题
补充知识:泛型深入
泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
泛型的格式:<数据类型>; 注意:泛型只能支持引用数据类型
集合体系的全部接口和实现类都是支持泛型的使用的
好处:
1、统一数据类型
2、把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来
泛型可以在很多地方进行定义
类后面--->泛型类
定义类时同时定义了泛型的类就是泛型类
泛型类的格式:修饰符 class 类名<泛型变量>{ }
此处泛型变量可以随便写为任意标识,常见的如E、T、K、V等
作用:编译阶段可以指定数据类型,类似于集合的作用
案例:模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可
泛型类的原理:把出现泛型变量的地方全部替换成传输的真实数据类型
方法申明上--->泛型方法
定义方法时同时定义了泛型的方法就是泛型方法
泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){ }
范例:public <T> void show(T t) { }
作用:方法中可以使用泛型接受一切实际类型的参数,方法更具备通用性
案例:给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能
泛型方法的原理:把出现泛型变量的地方全部替换成传输的真实数据类型
接口后面--->泛型接口
使用了泛型定义的接口就是泛型接口
泛型接口的格式:修饰符 interface 接口名称<泛型变量>{ }
作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
案例:教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作
泛型接口的原理:实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作
泛型通配符(?)
? 可以在“使用泛型”的时候代表一切类型
E T K V 是定义泛型的时候使用的
案例:开发一个极品飞车的游戏,所有的汽车都能一起参与比赛
注意:虽然BMW和BENZ都继承了Car,但是ArrayList<BMW>和ArrayList<BENZ>与ArrayList<Car>没有关系
注意:虽然BMW和BENZ都继承了Car,但是ArrayList<BMW>和ArrayList<BENZ>与ArrayList<Car>没有关系
泛型的上下限
? extends Car: ?必须是Car或者其子类 泛型上限
? super Car:?必须是Car或者其父类 泛型下限
Set系列集合
Set系列集合特点:添加的元素是无序、不重复、无索引
HashSet:无序、不重复、无索引
LinkedHashSet:有序、不重复、无索引
TreeSet:按照大小默认升序排序、不重复、无索引
Set集合的功能上基本上与Collection的API一致
HashSet元素无序的底层原理:哈希表
HashSet集合底层采取哈希表存储的数据
哈希表是一种对于增删改查数据性能都较好的结构
哈希值
是JDK根据对象的地址,按照某种规则算出来的int类型的数值
Object类的API
public int hashCode( ) : 返回对象的哈希值
对象的哈希值特点
同一个对象对此调用hashCode()方法返回的哈希值是相同的
默认情况下,不同对象的哈希值是不同的
哈希表的组成
JDK8之前的,底层使用数组+链表+(结合哈希算法)组成 —— table[ ]
1、首先创建一个默认长度16的数组,默认加载因子0.75的数组名table
2、根据元素的哈希值跟数组的长度求余计算应存入的位置(哈希算法)
3、判断当前位置是否为null,如果是null直接存入
4、如果位置不为null,表示有元素,则调用equals方法比较
5、如果一样,则不存,如果不一样,则存入数组
JDK7新元素占老元素位置,指向老元素
JDK8中新元素挂在老元素下面
当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
JDK8开始后,底层采用数组+链表+红黑树组成
当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树
HashSet元素去重复的底层原理
1、首先创建一个默认长度16的数组,默认加载因子0.75的数组名table
2、根据元素的哈希值跟数组的长度求余计算应存入的位置(哈希算法)
3、判断当前位置是否为null,如果是null直接存入
4、如果位置不为null,表示有元素,则调用equals方法比较
5、如果一样,则不存,如果不一样,则存入数组
结论:如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法
案例
需求
创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象
分析
1、定义学生类,创建HashSet集合对象,创建学生对象
2、把学生添加到集合中
3、在学生类中重写两个方法,hashCode()和equals(),自动生成即可
4、遍历集合(增强for)
LinkedHashSet
有序,不重复,无索引
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构依然是哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序
TreeSet
不重复、无索引、可排序
可排序:按照元素的大小默认升序(由小到大)排序
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好
注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序
默认的排序规则
对于数值类型:Integer,Double,官方默认按照大小进行升序排序
对于字符串类型:默认按照首字母的编号升序排序
对于自定义类型如Student对象,TreeSet无法直接排序
结论:想要使用TreeSet存储自定义类型,需要指定排序规则
自定义排序规则
方式一:让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则
方式二:TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则
两种方式中,关于返回值的规则
如果认为第一个元素大于第二个元素返回正整数即可
如果认为第一个元素小于第二个元素返回负整数即可
如果认为第一个元素等于第二个元素返回0即可,此时TreeSet集合只会保留一个元素,认为两者重复
注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序
补充知识:可变参数
可变参数用在形参中可以接受多个数据
可变参数的格式:数据类型...参数名称
作用:传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组。可变参数在方法内部本质上就是一个数组
注意事项
1、一个形参列表中可变参数只能有一个
2、可变参数必须放在形参列表的最后面
补充知识:集合工具类Collections
java.utils.Collections是集合工具类
作用:Collections并不属于集合,是用来操作集合的工具类
常用API
排序相关API:只能对于List集合的排序
排序方式1:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口
排序方式2
综合案例:斗地主
Map双列集合
概述
Map集合是一种双列集合,每个元素包含两个数据
Map集合的每个元素的格式:key=value(键值对元素)
Map集合也被称为“键值对集合”
整体格式
Collection集合的格式:[元素1 , 元素2 , 元素3 , ...]
Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , ...}
使用场景
购物车系统
购物车提供的四个商品和购买数量在后台需要容器存储
每个商品对象都一一对应一个购买数量
把商品对象看成是Map集合的键,购买数量看成Map集合的值
{商品1=2 , 商品2=3 , 商品3=2 , 商品4=3}
体系特点
Map集合体系特点
Map集合的特点都是由键决定的
Map集合的键:无序、不重复的、无索引。Map集合的值:值不做要求,可以重复
Map集合后面重复的键对应的值会覆盖前面重复键的值
Map集合的键值对都可以为null
Map集合实现类特点
HashMap:元素按照键是无序、不重复、无索引,值不做要求。(与Map体系一致)(使用最多)
LinkedHashMap:元素按照键是有序、不重复、无索引,值不做要求
TreeMap:元素按照键是排序、不重复、无索引的,值不做要求
常用API
Map集合
V get(Object key)----根据键返回对应的值
Set<K> keySet( )----返回全部键的集合
Collection<V> values( )----返回全部值的集合
M putall(M map)-----合并Map集合
Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的
Set<K> keySet( )----返回全部键的集合
Collection<V> values( )----返回全部值的集合
M putall(M map)-----合并Map集合
Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的
遍历
方式一:键找值
先获取Map集合的全部键的Set集合
遍历键的Set集合,然后通过键提取对应值
键找值涉及的API
方式二:键值对entrySet
先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了
Set<Map.Entry<String,Integer>> entries = maps.entrySet( )
Set<Map.Entry<String,Integer>> entries = maps.entrySet( )
遍历Set结合,然后提取键以及提取值
键值对涉及的API
方式三:Lambda表达式
得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式
Map结合Lambda遍历的API
流程:maps = { huawei=1000, 手表=10, 生活用品=10, iphoneX=100}
maps.forEach((k , v) ->{ System.out.println( k + "---->" + v); })
maps.forEach((k , v) ->{ System.out.println( k + "---->" + v); })
Map集合案例--统计投票人数
需求
某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A,B,C,D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多
分析
将80个学生选择的数据拿到程序中去
定义Map集合用于存储最终统计的结果
遍历80个学生选择的数据,看Map集合中是否存在,不存在存入“数据=1”,存在则其对应值+1
实现类
HashMap
HashMap是Map里面的一个实现类。特点都是由键决定的:无序、不重复、无索引
没有额外需要学习的特有方法,直接使用Map里面的方法就可以
HashMap跟HashSet的底层原理是一模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已
依赖hashCode方法和equals方法保证键的唯一
如果键要存储的是自定义对象,需要重写hashCode和equals方法
实际上:Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已
LinkeHashMap
由键决定:有序、不重复、无索引
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构依然是哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序
TreeMap
由键决定:不重复、无索引、可排序
可排序:按照键数据的大小默认升序(由小到大)排序。只能对键排序
注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
TreeMap跟TreeSet的底层原理是一样的
自定义排序规则
类实现Comparable接口,重写比较规则
集合自定义Comparator比较器对象,重写比较规则
补充知识:集合的嵌套
Map集合案例--统计投票人数
需求
某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A,B,C,D),每个学生可以选择多个景点,请统计出最终哪个景点想去的人数最多
分析
将80个学生选择的数据拿到程序中去,需要记住每个学生选择的情况
定义Map集合用于存储最终统计的结果
不可变集合
概述
不可变集合,就是不可被修改的集合
集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错
目的
如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践
或者当集合对象被不可信的库调用时,不可变形式是安全的
创建方式
在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合
例:List<Double> lists = List.of(423, 234, 634);
不可变集合不能添加、不能删除、不能修改
Stream流
概述
在java8中,得益于Lambda表达式所带来的函数式编程,引入了一个全新的Stream流概念
目的:用于简化集合和数组操作的API
Stream流的思想核心
1、先得到集合或者数组的Stream流(就是一根传送带)
2、把元素放上去
3、然后就用这个Stream流简化的API来方便的操作元素
Stream流的三类方法
1、获取Stream流
集合获取Stream流的方式
可以使用Collection接口中的默认方法stream( )生成流
数组获取Stream流的方式
2、中间方法
注意:中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程
注意:在Stream流中无法直接修改集合、数组中的数据
注意:在Stream流中无法直接修改集合、数组中的数据
map加工方法
3、终结方法
注意:终结操作方法,调用完成后就无法继续使用了,原因是不会返回Stream了
Stream流的综合应用
Stream流的收集操作
收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去
异常
概述
异常是程序在“编译”或者“执行”的过程中可能出现的问题,注意:语法错误不算在异常体系中
比如:数组索引越界、空指针异常、日期格式化异常,等等
异常一旦出现,如果没有提前处理,程序就会退出JVM虚拟机而终止
研究异常并且避免异常,然后提前处理异常,体现的是程序的安全、健壮性
异常体系
Throwable
Error
系统级别问题,JVM退出等,代码无法控制
Exception
java.lang包下,成为异常类,它表示程序本身可以处理的问题
RuntimeException及其子类
运行时异常
直接继承RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误
常见示例:
数组索引越界异常:ArrayIndexOutOfBoundsException
空指针异常:NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错
数学操作异常:ArithmeticException
类型转换异常:ClassCastException
数字转换异常:NumberFormatException
运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误
除RuntimeException之外所有的异常
编译时异常
不是RuntimeException或者其子类的异常,编译阶段报错,必须处理,否则代码不通过
常见示例
parse---日期解析异常:ParseException
异常的处理机制
异常的默认处理流程
1、默认会在出现异常的代码那里自动的创建一个异常对象:例如ArithmeticException
2、异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机
3、虚拟机接受到异常对象后,先在控制台直接输出异常栈信息数据
4、直接从当前执行的异常点干掉当前程序
5、后续代码没有机会执行了,因为程序已经死亡了
编译时异常的处理机制
编译阶段就出错,必须处理,否则代码无法通过
处理形式
throws---出现异常直接抛出去给调用者,调用者也继续抛出去
throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理
这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡
抛出异常格式
规范做法
try...catch...---出现异常自己捕获处理,不麻烦别人
try...catch...:监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理
这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行
格式
前两者结合,出现异常直接抛出去给调用者,调用者捕获处理
方法直接将异常通过throws抛出去给调用者
调用者收到异常后直接捕获处理
运行时异常的处理机制
运行时异常编译阶段不会出错,是运行时才可能出错的,所以编译阶段不处理也可以
按照规范建议还是处理:建议在最外层调用处集中捕获处理(try...catch...)即可
自定义异常
必要性
Java无法为这个世界上全部的问题提供异常类
如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了
好处
可以使用异常的机制管理业务问题 ,如提醒程序员注意
一旦同时出现bug,可以用异常的形式清晰的指出出错的地方
分类
自定义编译时异常
定义一个异常类继承Exception
重写构造器
在出现异常的地方用throw new自定义对象抛出
作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理
自定义运行时异常
定义一个异常类继承RuntimeException
重写构造器
在出现异常的地方用throw new自定义对象抛出
作用:提醒不强烈,编译阶段不报错!运行时才可能出现
日志框架
程序中的日志可以用来记录程序运行过程中的信息,并可以进行永久存储
日志技术的优势
可以将系统执行的信息选择性的记录到指定的位置(控制台、文件中、数据库中)
可以随时以开关的形式控制是否记录日志,无需修改源代码
体系结构
日志规范:一些接口,提供给日志的实现框架设计的标准
日志框架:牛人或者第三方公司已经做好的日志记录实现代码,后来者直接可以拿来使用
因为对Commons Logging的接口不满意,有人就搞了SLF4J。因为对Log4j的性能不满意,有人就搞了Logback
Logback日志框架
Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好
官方网站:http://logback.qos.ch/index.html
Logback是基于slf4j的日志规范实现的框架
三个技术模块
logback-core:基础模块,该模块为其它两个模块奠定了基础,必须有
logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API
logback-access:该模块与Tomcat和Jetty等Servlet容器集成,以提供HTTP访问日志功能
Logback快速入门
Logback的配置
Logback日志系统的特性都是通过核心配置文件logback.xml控制的
Logback日志输出位置、格式设置
通过logback.xml中的<appender>标签可以设置输出位置和日志信息的详细格式
通常可以设置2个日志输出位置:一个是控制台CONSOLE、一个是系统文件中FILE
Logback日志级别设置
级别程度依次是:TRACE < DEBUG < INFO < WARN < ERROR;默认级别是DEBUG(忽略大小写),对应其方法
作用:用于控制系统中哪些日志级别是可以输出的,只输出级别不低于设定级别的日志信息
ALL和OFF分别是打开全部日志信息,及关闭全部日志信息
综合案例:电影院系统
技术点分析
面向对象编程:系统包含了电影对象、商家对象、客户对象,需要用到继承、多态等语法知识
使用集合容器:系统需要提供不同的容器分别存储系统注册的用户,以及当前商家发布的电影信息
程序流程控制:需要结合分支、循环、跳转关键字等相关操作控制程序的业务逻辑
使用常见API:登录信息的内容比较,业务数据的分析、处理、日期时间的处理等
1、日志框架搭建
集成日志框架、用于后期记录日志信息
2、系统角色分析
1、定义一个电影类Movie类,Movie类包含:片名、主演、评分、时长、票价、余票
2、系统包含2个用户角色:客户、商家。存在大量相同属性信息
3、定义User类作为父类,属性:登录名称、密码、真实名称、性别、电话、账户余额
4、定义Business类代表商家角色,属性:店铺名称、地址
5、定义Customer类代表客户角色,属性:(可继承)
6、定义集合List<User>用户存放系统注册的用户对象信息
7、定义集合Map<Business, List<Movie>>存放商家和其排片信息
8、准备一些测试数据
3、首页、登录、商家界面、用户界面设计
1、首页需要包含登录,商家入驻,客户注册功能
2、商家和客户可以共用一个登录功能
3、判断登录成功的用户的真实类型,根据用户类型完成对应的操作界面设计
4、商家功能-展示详情、影片上架、退出
1、展示本商家的信息和其排片情况
2、提供影片上架功能:就是创建一个影片对象,存入到商家的集合中去
3、退出,需要回到登录的首页
5、商家功能-影片下架、影片修改
1、提供影片下架功能:其实就是从商家的集合中删除影片对象
2、影片修改功能:拿到需要修改的影片对象,修改里面的数据
File类、IO流
File类
可以定位文件:进行删除、获取文本本身信息等操作,但是不能读写文件内容
概述
File类在包java.io.File下、代表操作系统的文件对象(文件、文件夹)
File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能
File类创建对象
File对象可以定位文件和文件夹
File封装的对象仅仅是一个路径名,这个路径名可以是存在的,也可以是不存在的
File封装的对象仅仅是一个路径名,这个路径名可以是存在的,也可以是不存在的
绝对路径和相对路径
绝对路径:从盘符开始
File file1 = new File("D:\\itheima\\a.txt");
相对路径:不带盘符,默认直接到当前工程下的目录寻找文件
File file2 = new File("模块名\\a.txt");
File类的判断文件类型、获取文件信息功能
File类的创建文件、删除文件的功能
File类的遍历功能
方法递归算法
概述
方法直接调用自己或者间接调用自己的形式称为方法递归(recursion),递归作为一种算法在程序设计语言中广泛应用
形式
直接递归
方法自己调用自己
间接递归
方法调用其他方法,其他方法又回调方法自己
方法递归存在的问题
递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出现象
算法流程
递归解决问题的思路
把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
递归算法三要素
递归的公式:f(n) = f(n-1) * n;
递归的终结点:f(1)
递归的方向必须走向终结点
执行流程
递归的经典问题
非规律化递归案例
文件搜索
啤酒问题
IO流前导:字符集
计算机底层不可以直接存储字符的,计算机中底层只能存储二进制(0、1)。二进制是可以转换成十进制的
计算机底层可以表示十进制编号。计算机可以给人类字符进行编号存储,这套编号规则就是字符集
常用字符集
ASCII字符集
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字、英文、符号
ASCII使用1个字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的
GBK
window系统默认的码表。兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字
注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字
Unicode码表
Unicode(又统称一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准
容纳世界上大多数国家的所有常见文字和符号
由于Unicode会先通过UTF-8,UTF-16,以及UTF-32的编码成二进制后再存储到计算机,其中最为常见的就是UTF-8
注意:
1、Unicode是万国码,以UTF-8编码后一个中文一般以三个字节的形式存储
2、UTF-8也要兼容ASCII编码表
3、技术人员都应该使用UTF-8的字符集编码
4、编码前和编码后的字符集需要一致,否则会出现中文乱码
1、Unicode是万国码,以UTF-8编码后一个中文一般以三个字节的形式存储
2、UTF-8也要兼容ASCII编码表
3、技术人员都应该使用UTF-8的字符集编码
4、编码前和编码后的字符集需要一致,否则会出现中文乱码
汉字存储和展示过程解析
总结
字符串常见的字符底层组成
英文和数字等在任何国家的字符集中都占1个字节
GBK字符中一个中文字符占2个字节
Unicode的UTF-8编码中一个中文一般占3个字节
编码前的字符集和编码好的字符集必须一致,否则会出现中文字符乱码,而英文和数字在任何国家的编码中都不会乱码
编码、解码
IO流
IO流也称为输入、输出流,IO流技术可以对硬盘中的文件进行读写
I 表示input,是数据从硬盘文件读入到内存的过程,称为输入,负责读
O 表示output,是内存程序的数据从内存写出到硬盘文件的过程,称之输出,负责写
I 表示input,是数据从硬盘文件读入到内存的过程,称为输入,负责读
O 表示output,是内存程序的数据从内存写出到硬盘文件的过程,称之输出,负责写
分类
按流的方向
输入流
输出流
按流中的数据最小单位
字节流
操作所有类型的文件,包括音频视频图片等
字符流
只能操作纯文本文件,包括Java文件,txt文件等
IO流体系
字节输入流
以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流
字节输出流
以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流
字符输入流
以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中的流称为字符输入流
字符输出流
以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流
字节流
文件字节输入流:FileInputStream
作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去
读取中文乱码的解决方式
文件字节输出流:FileOutputStream
构造器
方法
注意:换行:os.write( "\r\n".getBytes());
流的关闭与刷新(让写出去的数据成功生效)
文件拷贝
1、根据数据源创建字节输入流对象
2、根据目的地创建字节输出流对象
3、读写数据,复制数据
4、释放资源
字符流
文件字符输入流:FileReader
文件字符输出流:FileWriter
构造器
方法
流的关闭与刷新
资源释放
方式一:try-catch-finally
finally:在异常处理时提供finally块来执行所有清除操作,比如说IO流中的释放资源
特点:被finally控制的语句最终一定会执行,除非JVM退出
异常处理标准格式:try...catch...finally
释放资源的代码可以放在finally控制语句中
方式二:try...catch...resourse
作用:自动释放资源、代码简洁
JDK 7以及JDK 9的()中只能放置资源对象,否则报错
资源:资源都是实现了Closeable/AutoCloseable接口的类对象
public abstract class InputStream implements Closeable{}
public abstract class OutputStream implements Closeable,Flushable{}
资源:资源都是实现了Closeable/AutoCloseable接口的类对象
public abstract class InputStream implements Closeable{}
public abstract class OutputStream implements Closeable,Flushable{}
字节流、字符流的选择使用
1、字节流适合做一切文件数据的拷贝(音视频、文本)
2、字节流不适合读取中文内容输出
3、字符流适合做文本文件的操作(读、写)
缓冲流
概述
缓冲流也称为高效流、或者高级流,之前学习的字节流可以称为原始流
作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能
字节缓冲流
字节缓冲流自带8KB缓冲区,可以提高原始字节流、字符流读写数据的性能
性能分析
1、使用低级的字节流按照一个一个字节的形式复制文件
2、使用低级的字节流按照一个一个字节数组的形式复制文件
3、使用高级的缓冲字节流按照一个一个字节的形式复制文件
4、使用高级的缓冲字节流按照一个一个字节数组的形式复制文件(一般性能最优)
2、使用低级的字节流按照一个一个字节数组的形式复制文件
3、使用高级的缓冲字节流按照一个一个字节的形式复制文件
4、使用高级的缓冲字节流按照一个一个字节数组的形式复制文件(一般性能最优)
字符缓冲流
字符缓冲流自带8KB缓冲区,可以提高原始字符流读写数据的性能
案例:复制出师表,恢复顺序
1、定义一个缓存字符输入流管道与源文件接通
2、定义一个List集合存储读取的每行数据
3、定义一个循环按照行读取数据,存入到List集合中去
4、对List集合中的每行数据按照首字符编号升序排序
5、定义一个缓存字符输出管道与目标文件接通
6、遍历List集合中的每个元素,用缓冲输出管道写出并换行
转换流
字符输入转换流:InputStreamReader
代码编码和文件编码不一致,使用字符流直接读取会乱码。
解决方法
1、使用字符输入转换流
2、可以提取文件(GBK)的原始字节流,原始字节不会存在问题
3、然后把字节流以指定编码转换成字符输入流,这样字符输入流中的字符就不乱码了
字符输出转换流:OutputStreamWriter
当需要控制写出去的字符使用的编码,可以把字符以指定编码获取字节后再使用字节输出流写出去,也可以使用字符输出转换流实现
序列化对象
对象序列化:ObjectOutputStream
作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化
使用到的流是对象字节输出流:ObjectOutputStream
注意:对象必须实现序列化接口(implements Serializable)
对象反序列化:ObjectInputStream
作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化
使用到的流是对象字节输入流:ObjectInputStream
transient修饰的成员变量不参与序列化
序列化的版本号与反序列化的版本号必须一致才不会出错,如果更改版本号,序号重新生成序列化文件,再反序列化
序列化的版本号与反序列化的版本号必须一致才不会出错,如果更改版本号,序号重新生成序列化文件,再反序列化
打印流
作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指:PrintStream、PrintWriter两个类
可以实现打印什么数据就是什么数据,例如打印整数97写出去就是97,打印boolean的true,写出去就是true
可以实现打印什么数据就是什么数据,例如打印整数97写出去就是97,打印boolean的true,写出去就是true
PrintStream
追加操作需要在低级管道中进行操作
PrintWriter
PrintStream和PrintWriter的区别
1、打印数据功能上是一模一样,都是使用方便,性能高效(核心优势)
2、PrintStream继承自字节输出流OutputStream,支持写字节数据的方法
3、PrintWriter继承自字符输出流Writer,支持写字符数据出去
应用知识:输出语句重定向
属于打印流的一种应用,可以把输出语句的打印位置改到文件
PrintStream ps = new PrintStream("文件地址")
System.setOut(ps);
System.setOut(ps);
补充知识:Properties
Properties属性集对象:其实是一个Map集合,但是我们一般不会当集合使用,因为HashMap更好用
Properties的核心作用:Properties代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去
属性文件:后缀是.Properties结尾的文件,里面的内容都是key=value,后续做系统配置信息的
Properties的核心作用:Properties代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去
属性文件:后缀是.Properties结尾的文件,里面的内容都是key=value,后续做系统配置信息的
可以存储Properties属性集的键值对数据到属性文件中去:void store(Writer writer, String comments)
可以加载属性文件中的数据到Properties对象中来:void load(Reader reader)
可以加载属性文件中的数据到Properties对象中来:void load(Reader reader)
补充知识:IO框架
commons-io
概述
commons-io是apache开源基金组织提供的一组有关IO操作的类库,可以提高IO功能开发的效率
commons-io工具包提供了很多有关io操作的类,有两个主要的类FileUtils,IOUtils
FileUtils
多线程
概述
线程(thread)是一个程序内部的一条执行路径
之前启动程序执行后,main方法的执行其实就是一条单独的执行路径
程序中如果只有一条执行路径,那么这个程序就是单线程的程序
多线程是指从软硬件上实现多条执行流程的技术
多线程的创建
方式一:继承Thread类
概述
Java是通过java.lang.Thread类来代表线程的
按照面向对象的思想,Thread类应该提供了实现多线程的方式
实现方案
1、定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
2、创建MyThread类的对象
3、调用线程对象的start()方法启动线程(启动后还是执行run方法的)
优缺点
优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于扩展
问题:为什么不直接调用run方法,而是调用start启动线程
直接调用run方法会当成普通方法执行,此时相当于还是单线程执行
只有调用start方法才是启动一个新的线程执行
问题:为什么不要把主线程任务放在子线程之前
主线程任务放前面,主线程一直是先跑完的,相当于是一个单线程的效果
方式二:实现Runnable接口
实现方案
1、定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
2、创建MyRunnable任务对象
3、把MyRunnable任务对象交给Thread处理
4、调用线程对象的start()方法启动线程
Thread构造器
方式三:JDK 5.0新增:实现Callable接口
Thread的常用方法
Thread常用方法:获取线程名称getName()、设置名称setName()、获取当前线程对象currentThread()。
Thread类的线程休眠方法
至于Thread类提供的诸如:yield、join、interrupt、不推荐的方法stop、守护线程、线程优先级等线程的控制方法,在开发中很少使用,这些方法会在高级篇以及后续需要用到的时候再次讲解
线程安全
多个线程同时操作(修改)同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题
线程同步
为了解决线程安全问题
线程同步的核心思想
加锁,把共享资源进行上锁,每次只能一个线程进入访问,完毕以后解锁,然后其他线程才能进来
加锁方式一:同步代码块
作用:把出现线程安全问题的核心代码给上锁
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行
锁对象要求:理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可
锁对象用任意唯一的对象不好,会影响其他无关线程的执行
锁对象的规范要求
规范上:建议使用共享资源作为锁对象
对于实例对象建议使用this作为锁对象
对于静态方法建议使用字节码(类名.class)对象作为锁对象
加锁方式二:同步方法
作用:把出现线程安全问题的核心方法给上锁
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行
同步方法的底层原理
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码
如果方法是实例方法:同步方法默认用this作为锁对象,但是代码要高度面向对象
如果是静态方法:同步方法默认用类名.class作为锁的对象
同步方法和同步代码块哪个好???
同步代码块锁的范围小,同步方法锁的范围大
加锁方式三:Lock锁
为了更清晰的表达如何加锁和释放锁,JDK5之后提供了一个新的锁对象Lock,更加灵活、方便
Lock实现提供比使用synchronized方法和语句可以获得更广法的锁定操作
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象
Lock的API
线程通信
什么是线程通信:所谓线程通信就是线程间相互发送数据,线程通信通常通过共享一个数据的方式实现
如何实现线程通信:线程间会根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做
线程通信常用模型
生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费数据
要求:生产者线程生产完数据后,唤醒消费者,然后等待自己:消费者消费完数据后,唤醒生产者,然后等待自己
线程通信的前提:线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全
注意:上述方法应该使用当前同步锁对象进行调用
线程池
线程池就是一个可以复用线程的技术
不适用线程池的问题:
如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样就会严重影响系统的性能
线程池实现的API
JDK 5.0起提供了代表线程池的接口:ExecutorService
创建线程池对象
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
常见面试题
临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
什么时候会开始拒绝任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
线程池处理Runnable任务
使用ExecutorService的方法:
void execute(Runnable target)----执行任务/命令,没有返回值,一般用来执行Runnable任务
线程池处理Callable任务
使用ExecutorService的方法:
Future<T> submit(Callable<T> command)
补充知识:定时器
定时器是一种控制任务延时调用,或者周期调用的技术
作用:闹钟、定时邮件发送
实现方式:
方式一:Timer
方式二:ScheduledExecutorService
补充知识:并发、并行
正在运行的程序(软件)就是一个独立的进程,线程是属于进程的,多个线程其实是并发与并行同时进行的
并发的理解:
CPU同时处理线程的数量有限
CPU会分时轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发
并行的理解:
在同一个时刻上,同时有多个线程在被CPU处理并执行
补充知识:线程的生命周期
线程的状态
线程的状态也就是线程从生到死的过程,以及中间经历的各种状态及状态转换
理解线程的状态有利于提升并发编程的理解能力
Java线程的状态
Java总共定义了6种状态
6种状态都定义在Thread类的内部枚举类中
网络通信
网络编程可以让程序与网络上的其他设备中的程序进行数据交互
网络通信基本模式
常见的通信模式有如下2种形式:Client-Server(CS)、Browser/Server(BS)
网络通信三要素
IP地址:设备在网络中的地址,是唯一的标识
IP(Internet Protocol):全称”互联网协议地址“,是分配给上网设备的唯一标志
常见的IP分类为:IPv4和IPv6
IP地址基本寻路
IP地址形式
公网地址、和私有地址(局域网使用)
192.168.开头的就是常见的局域网地址,范围即为192.168.0.0--192.168.255.255,专门为组织机构内部使用
IP常见命令:
ipconfig(查看本机IP地址)
ping IP地址(检查网络是否连通)
特殊IP地址
本机IP:127.0.0.1或者localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本机
IP地址操作类---InetAddress
InetAddress(此类表示Internet协议(IP)地址)
端口号:应用程序在设备中唯一的标识
端口号:标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0~65535
端口类型
周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
注册端口:1024~49151,分配给用户进程或某些应用程序。(如:Tomcat占用8080,MySQL占用3306)
动态端口:49152~65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配
协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议
连接和通信数据的规则被称为网络通信协议
网络通信协议有两套参考模型
OSI参考模型:世界互联协议标准,全球通信规范,由于此模型过于理想化,未能在因特网上进行广泛推广
TCP/IP参考模型(或TCP/IP协议):事实上的国际标准
传输层的2个常见协议
UDP(User Datagram Protocol):用户数据报协议
UDP协议特点
UDP是一种无连接、不可靠传输的协议
将数据源IP、目的地IP和端口封装成数据包,不需要建立连接
每个数据包的大小限制在64KB内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送,发送数据结束时无需释放资源,开销小,速度快
UDP协议通信场景:语音通话,视频会话等
UDP通信:快速入门(一发一收)
DatagramPacket:数据包对象
DatagramSocket:发送端和接收端对象
UDP通信:多发多收
UDP的接收端可以接受很多发送端的消息,接收端只负责接收数据包,无所谓是哪个发送端的数据包
UDP通信--广播、组播
UDP的三种通信方式
单播:单台主机与单台主机之间的通信
广播:当前主机与所在网络中的所有主机通信
如何实现广播
使用广播地址:255.255.255.255
1、发送端发送的数据包的目的地写的是广播地址、且指定端口(255.255.255.255 ,9999)
2、本机所在网段的其他主机的程序只能匹配端口成功即就可以收到消息了(9999)
组播:当前主机与选定的一组主机的通信
如何实现组播
使用组播地址:224.0.0.0~239.255.255.255
1、发送端的数据包的目的地是组播IP(例如:224.0.1.1,端口:9999)
2、接收端必须绑定该组播IP(224.0.1.1),端口还要对应发送端的目的端口9999,这样即可接受该组播的消息
3、DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP
TCP(Transmission Control Protocol):传输控制协议
TCP协议特点
使用TCP协议,必须双方先建立连接,它是一种面向连接的可靠通信协议
传输前,采用”三次握手“方式建立连接,所以是可靠的
我请求与你连接(客户端->服务器)
可以(服务器->客户端)
好的,我马上与你连接(客户端->服务器)
可以(服务器->客户端)
好的,我马上与你连接(客户端->服务器)
在连接中可进行大数据量的传输
连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,采用”四次挥手“的方式断开连接。通信效率相对较低
我请求与你断开连接(客户端->服务器)
可以(服务器->客户端)
数据传输完毕,可以断开连接(服务器->客户端)
好的,我马上断开连接(客户端->服务器)
可以(服务器->客户端)
数据传输完毕,可以断开连接(服务器->客户端)
好的,我马上断开连接(客户端->服务器)
TCP协议通信场景:对信息安全要求较高的场景:例如:文件下载、金融等数据通信
TCP通信模式
注意:在Java中只要是使用java.net.Socket类实现通信,底层即是使用了TCP协议
TCP通信的基本原理
客户端怎么发,服务端就应该怎么收
客户端如果没有消息,服务端会进入阻塞等待
Socket一方关闭或者出现异常,对方Socket也会失效或者出错
TCP通信:快速入门(一发一收)
客户端发送消息
一般把低级的字节流(低级)包装成打印流(高级)
不建议关闭Socket管道,除非客户端离线
不建议关闭Socket管道,除非客户端离线
服务端接收消息
TCP通信:多发多收
TCP通信:同时接受多个客户端的消息
多发多收不可以接受多个客户端的消息
因为服务端只有一个线程,只能与一个客户端进行通信
因为服务端只有一个线程,只能与一个客户端进行通信
引入多线程,可以处理多个客户端的通信请求
TCP通信:使用线程池优化
引入线程池处理多个客户端消息
适合客户端通信时长较短的场景
TCP通信实战案例--即时通信
即时通信
是指一个客户端的消息发出去,其他客户端可以接收到
之前我们的消息都是发给服务端的
即时通信需要进行端口转发的设计思想
客户端发送消息——服务端接收消息——服务端转发端口给客户端
服务端需要把在线的Socket管道存储起来
一旦收到一个消息要推送给其他管道
TCP通信实战案例--模拟BS系统
浏览器访问服务端,不需要开发客户端
如何实现
1、客户端使用浏览器发起请求(不需要开发客户端)
2、服务端必须按照浏览器的协议规则相应数据
单元测试
概述
单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对Java方法的测试,进而检查方法的正确性
目前测试方法存在什么问题
1、只有main方法,如果一个方法的测试失效了,其他方法测试会受到影响
2、无法得到测试的结果报告,需要程序员自己去观察测试是否成功
3、无法实现自动化测试
JUnit单元测试框架
概述
JUnit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应当学习并使用JUnit编写单元测试
此外,几乎所有的IDE工具都继承了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试,JUnit目前最新版本是5
优点
1、JUnit可以灵活的选择执行哪些测试方法,可以一键执行全部测试方法
2、JUnit可以生成全部方法的测试报告
3、单元测试中的某个方法测试失败了,不会影响其他测试方法的测试
快速入门
编写测试方法时测试方法必须是公开的、无参数、无返回值的非静态方法
常用注解
反射
概述
反射是指对于任何一个Class类,在“运行的时候”都可以直接得到这个类的全部成分
在运行时,可以直接得到这个类的构造器对象:Constructor
在运行时,可以直接得到这个类的成员变量对象:Field
在运行时,可以直接得到这个类的成员方法对象:Method
这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制
关键:反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分
反射获取类对象
反射的第一步是先获取Class类对象,如此才可以解析类的全部成分
反射获取构造器对象
反射的第一步是先得到Class类对象,然后从Class类对象中获取类的成分对象
作用:获取构造器的作用依然是初始化一个对象返回
如果某个构造器是非public的,需要打开权限(暴力反射setAccessible(boolean)),然后再执行
反射可以破坏封装性,私有的也可以通过暴力反射执行
反射可以破坏封装性,私有的也可以通过暴力反射执行
反射获取成员变量对象
反射的第一步是先得到Class类对象,然后从类对象中获取类的成分对象
作用:获取成员变量对象的作用依然是对成员变量赋值和取值
如果某成员变量是非public的,需要打开权限(暴力反射setAccessible(boolean)),然后再取值、赋值
反射获取方法对象
反射的第一步是先得到Class类对象,然后从类对象中获取类的成分对象
作用:获取成员方法的作用依然是在某个对象中进行执行此方法
如果某个成员方法是非public的,需要打开权限(暴力反射setAccessible(boolean)),然后再触发执行
反射的作用--绕过编译阶段为集合添加数据
反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的
泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,泛型相当于被擦除了
反射的作用--通用框架的底层原理
注解
概述
Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制
Java语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注
作用
对Java中类、方法、成员变量做标记,然后进行特殊处理,至于到底作何种处理由业务来决定
例如:JUnit框架中,标记了注解@Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行
自定义注解
格式
Java支持的数据类型基本上都支持
特殊属性
value属性:如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写!!
但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的
元注解
就是给注解注解的注解
常见的元注解
@Target:约束自定义注解只能在哪些地方使用
@Retention:申明注解的生命周期
注解解析
概述
注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容
与注解解析相关的接口
Annotation:注解的顶级接口,注解都是Annotation类型的对象
AnnotatedElement:该接口定义了与注解解析相关的解析方法
所有的类成分Class,Method,Field,Constructor,都实现了AnnotatedELement接口,他们都拥有解析注解的能力
解析注解的技巧
1、注解在哪个成分上,我们就先拿哪个成分对象
2、比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解
3、比如注解作用在类上,则要该类的Class对象,再来拿上面的注解
4、比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解
注解解析的案例
注解的应用场景:Junit框架
设计模式:动态代理
概述
代理:指某些场景下对象会找一个代理对象,来辅助自己完成一些工作
作用:主要是对对象的行为额外做一些辅助操作
如何创建代理对象
Java中代理的代表类是:java.lang.reflect.Proxy
Proxy提供了一个静态方法,用于为对象产生一个代理对象返回
Java实现动态代理
Java中实现动态代理:
1、必须存在接口
2、被代理对象需要实现接口
3、使用Proxy类提供的方法,得到对象的代理对象
1、必须存在接口
2、被代理对象需要实现接口
3、使用Proxy类提供的方法,得到对象的代理对象
通过代理对象调用方法时:
1、先走向代理
2、代理可以为方法额外做一些辅助工作
3、开发真正触发对象的方法的执行
4、回到代理中,由代理负责返回结果给方法的调用者
1、先走向代理
2、代理可以为方法额外做一些辅助工作
3、开发真正触发对象的方法的执行
4、回到代理中,由代理负责返回结果给方法的调用者
动态代理的应用案例:做性能分析
动态代理的优点
1、可以在不改变方法源码的情况下,实现对方法功能的增强,提高了代码的复用
2、简化了编程工作、提高了开发效率,同时提高了软件系统的可扩展性
3、可以为被代理对象的所有方法做代理
4、非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接本身做代理
XML
概述
XML是可扩展标记语言(extensible Markup Language)的缩写,它是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据
作用:一是用于进行存储数据和传输数据;二是作为软件的配置文件
特点:一是纯文本,默认使用UTF-8编码;二是可嵌套。如果把XML内容存为文件,那么它就是一个XML文件
使用场景:XML内容经常被当成消息进行网络传输,或者作为配置文件用于存储系统的信息
创建XML
XML的创建:就是创建一个XML类型的文件,要求文件的后缀必须使用xml,如hello_world.xml
XML语法规则
XML文件的后缀名为:xml
文档声明必须是第一行
XML标签(元素)规则
标签由一对尖括号和合法标识符组成:<name></name>,必须存在一个根标签,有且只有一个
标签必须成对出现,有开始,有结束:<name></name>
特殊的标签可以不成对,但是必须有结束标记,如<br/>
标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来<student id = "1" ></student>
标签需要正确的嵌套
XML的其他组成
XML文件中可以定义注释信息:<!-- 注释内容 -->
XML文件中可以存在以下特殊字符:
XML文件中可以存在CDATA区:<![CDATA[ ...内容... ]]>
XML文档约束
问题:由于XML文件可以自定义标签,导致XML文件可以随意定义,程序在解析的时候可能出现问题
文档约束:是用来限定xml文件中的标签以及属性应该怎么写,以此强制约束程序员必须按照文档约束的规定来编写xml文件
分类
DTD约束
(不能约束具体的数据类型)
schema约束
schema可以约束具体的数据类型,约束能力上更强大
schema本身也是一个xml文件,本身也受到其他约束文件的要求,所以编写的更加严谨
XML解析技术
概述
XML解析就是使用程序读取XML中的数据
作用:存储数据、做配置信息、进行数据传输。最终需要被程序进行读取,解析里面的信息
解析方式
SAX解析
DOM解析
DOM常见解析工具
DOM解析解析文档对象的模型
Dom4j解析XML
得到文档对象Document,从中获取元素对象和内容
案例
XML检索技术-XPath
XPath在解析XML文档方面提供了一独树一帜的路径思想
XPath使用路径表达式来定位XML文档中的元素节点或属性节点,例如:/根元素/子元素/孙元素
XPath的四大检索方案
绝对路径
/根元素/子元素/孙元素(从根元素开始,一级一级向下查找,不能跨级)
子主题
相对路径
./子元素/子元素(.代表当前元素)
全文检索
//元素(在全文检索这个元素)
//元素1/元素2(在全文检索元素1下面的一级元素2)
//元素1//元素2(在全文检索元素1下面的全部元素2)
//元素1/元素2(在全文检索元素1下面的一级元素2)
//元素1//元素2(在全文检索元素1下面的全部元素2)
属性查找
//@属性名称(在全文检索属性对象)
//元素[@属性名称](在全文检索包含该属性的元素对象)
//元素[@属性名称=值](在全文检索包含该属性的元素且属性值为该值的元素对象)
//元素[@属性名称](在全文检索包含该属性的元素对象)
//元素[@属性名称=值](在全文检索包含该属性的元素且属性值为该值的元素对象)
设计模式:工厂模式
工厂模式(Factory Pattern)是Java中最常用的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种获取对象的方式
作用:
1、工厂的方法可以封装对象的创建细节,比如:为该对象进行加工和数据注入
2、可以实现类与类之间的解耦操作(核心思想)
设计模式:装饰模式
创建一个新类,包装原始类,从而在新类中提升原来类的功能
作用:
1、装饰模式指的是在不改变原类的基础上,动态地扩展一个类的功能
0 条评论
下一页